Skip to content

Commit

Permalink
[TRELLO-2781] Implement reassign report feature (#1026)
Browse files Browse the repository at this point in the history
* [TRELLO-2781] Implement reassign report feature

* [TRELLO-2781] Change 'reassign' to 'reattribute'

* [TRELLO-2781] i18n
  • Loading branch information
charlescd authored Mar 4, 2025
1 parent 0325f62 commit e7bbae0
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 47 deletions.
17 changes: 17 additions & 0 deletions website/src/app/[lang]/reattribuer/[reportId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {buildGenerateMetadataForNoIndexPage, PageComponentProps, PathParams} from '@/core/metadatas'
import {ReattributeCompany} from '@/components_feature/reportFlow/Company/ReattributeCompany'
import {LimitedWidthPageContainer} from '@/components_simple/PageContainers'

type LocalPathParams = PathParams<{
reportId: string
}>

export const generateMetadata = buildGenerateMetadataForNoIndexPage('reattribuer')

export default function Page(props: PageComponentProps<LocalPathParams>) {
return (
<LimitedWidthPageContainer>
<ReattributeCompany reportId={props.params.reportId} isWebView={false} />
</LimitedWidthPageContainer>
)
}
18 changes: 18 additions & 0 deletions website/src/app/[lang]/webview/reassigner/[reportId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {buildGenerateMetadataForWebviews, PageComponentProps, PathParams} from '@/core/metadatas'
import {ReattributeCompany} from '@/components_feature/reportFlow/Company/ReattributeCompany'
import {WebviewEnvMarker} from '@/utils/WebviewEnvMarker'

type LocalPathParams = PathParams<{
reportId: string
}>

export const generateMetadata = buildGenerateMetadataForWebviews()

export default function Page(props: PageComponentProps<LocalPathParams>) {
return (
<div className="max-w-[624px] px-4 mx-auto pb-4">
<WebviewEnvMarker />
<ReattributeCompany reportId={props.params.reportId} isWebView={true} />
</div>
)
}
2 changes: 1 addition & 1 deletion website/src/clients/AdresseApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class AdresseApiClient {
type,
//Some PostalCode does not appear when we have more than one city linkend to same postal code

limit: 100,
limit: 20,
// autocomplete: 1
},
})
Expand Down
19 changes: 17 additions & 2 deletions website/src/clients/SignalConsoApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {ConsumerEmailResult} from '@/model/ConsumerEmailValidation'
import {Report} from '@/model/Report'
import {ApiCreatedReport, ApiReport} from '@/model/reportsFromApi'
import {GenericAbortSignal} from 'axios'
import {SocialNetwork, Subcategory} from 'shared/anomalies/Anomaly'
import {ReportTag, SocialNetwork, Subcategory} from 'shared/anomalies/Anomaly'
import {ResponseConsumerReview, ResponseConsumerReviewExists} from '../core/Events'
import {AppLang} from '../i18n/localization/AppLangs'
import {BarcodeProduct} from '../model/BarcodeProduct'
import {WebsiteCompanySearchResult} from '../model/Company'
import {CompanySearchResult, WebsiteCompanySearchResult} from '../model/Company'
import {Country} from '../model/Country'
import {CreatedReport} from '../model/CreatedReport'
import {FileOrigin, UploadedFile} from '../model/UploadedFile'
Expand Down Expand Up @@ -144,4 +144,19 @@ export class SignalConsoApiClient {
engagementReviewExists = (reportId: string) => {
return this.client.get<ResponseConsumerReviewExists>(`/reports/${reportId}/engagement/review/exists`)
}

isReportReattributable = (reportId: string) => {
return this.client.get<ReattributableReport>(`/reports/${reportId}/reattribute`)
}

reattributeReport = (reportId: string, company: CompanySearchResult, metadata: ApiReport['metadata']) => {
return this.client.post<CreatedReport>(`/reports/${reportId}/reattribute`, {body: {company, metadata}})
}
}

export interface ReattributableReport {
tags: ReportTag[]
creationDate: string
companyName?: string
daysToAnswer: number
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@ import {useToastError} from '../../../hooks/useToastError'
import {CompanySearchResult, isGovernmentCompany} from '../../../model/Company'
import {PartialReport} from '../ReportFlowContext'
import {NoSearchResult} from './lib/NoSearchResult'
import {ReportTag} from 'shared/anomalies/Anomaly'

interface Props {
companies: CompanySearchResult[]
report: PartialReport & Pick<Report, 'step0' | 'step1'>
onSubmit: (selected: CompanySearchResult, vendor?: string) => void
}

interface CompanySearchResultComponentWithTagsProps {
companies: CompanySearchResult[]
tags: ReportTag[]
onSubmit: (selected: CompanySearchResult, vendor?: string) => void
}

interface Form {
siret: string
vendor?: string
}

export const CompanySearchResultComponent = ({companies, report, onSubmit}: Props) => {
export const CompanySearchResultComponentWithTags = ({companies, tags, onSubmit}: CompanySearchResultComponentWithTagsProps) => {
const {m} = useI18n()
const {
control,
Expand Down Expand Up @@ -98,7 +105,7 @@ export const CompanySearchResultComponent = ({companies, report, onSubmit}: Prop
options={companies.map(company => {
const closed = !company.isOpen
return {
label: <CompanyRecapFromSearchResult company={company} tags={getTags(report)} />,
label: <CompanyRecapFromSearchResult company={company} tags={tags} />,
value: company.siret!,
disabled: closed,
}
Expand Down Expand Up @@ -151,6 +158,10 @@ export const CompanySearchResultComponent = ({companies, report, onSubmit}: Prop
)
}

export const CompanySearchResultComponent = ({companies, report, onSubmit}: Props) => {
return <CompanySearchResultComponentWithTags companies={companies} onSubmit={onSubmit} tags={getTags(report)} />
}

function NextBtn() {
return (
<div className="flex justify-end">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
'use client'
import {useI18n} from '@/i18n/I18n'
import {useState} from 'react'
import {Animate} from '@/components_simple/Animate'
import {ScRadioButtons} from '@/components_simple/formInputs/ScRadioButtons'
import {CompanySearchByNameAndGeoArea} from '@/components_feature/reportFlow/Company/CompanySearchByNameAndGeoArea'
import {CompanySearchResultComponentWithTags} from '@/components_feature/reportFlow/Company/CompanySearchResultComponent'
import {CompanySearchByIdentifier} from '@/components_feature/reportFlow/Company/CompanySearchByIdentifier'
import {ReportTag} from 'shared/anomalies/Anomaly'
import {useMutation, useQuery} from '@tanstack/react-query'
import {useApiClients} from '@/context/ApiClientsContext'
import {CompanySearchResult} from '@/model/Company'
import {buildReportMetadata} from '@/utils/buildReportMetadata'
import {isoToHumanReadableText} from '@/utils/utils'
import {Loader} from '@/feature/Loader'
import {CreatedReport} from '@/model/CreatedReport'
import {AcknowledgementInner} from '@/components_feature/reportFlow/Acknowledgement/Acknowledgement'
import {useToastError} from '@/hooks/useToastError'

interface ReattributeCompanyProps {
reportId: string
isWebView: boolean
}

export const ReattributeCompany = ({reportId, isWebView}: ReattributeCompanyProps) => {
const {m, currentLang} = useI18n()
const [method, setMethod] = useState<'byNameAndGeoArea' | 'byIdentifier' | undefined>()
const {signalConsoApiClient} = useApiClients()
const toastError = useToastError()

const _isReportReattributable = useQuery({
queryKey: ['isReportReattributable', reportId],
queryFn: () => signalConsoApiClient.isReportReattributable(reportId),
retry: false,
})

const [isDone, setDone] = useState<CreatedReport | undefined>()

const _reattributeReport = useMutation({
mutationFn: (company: CompanySearchResult) =>
signalConsoApiClient.reattributeReport(reportId, company, buildReportMetadata({isWebView})),
onSuccess: report => setDone(report),
onError: error => {
toastError(error.message)
},
})

const optionByNameAndGeoArea = {
label: m.identifyBy_name_geoarea,
value: 'byNameAndGeoArea' as const,
}
const optionByIdentifier = {
label: m.identifyBy_identity,
description: m.identifyBy_identityDesc,
value: 'byIdentifier' as const,
}

const options = [optionByNameAndGeoArea, optionByIdentifier]

if (_isReportReattributable.isLoading) {
return <Loader />
} else if (_isReportReattributable.data) {
const {companyName, daysToAnswer, tags, creationDate} = _isReportReattributable.data

return isDone ? (
<AcknowledgementInner createdReport={isDone} country={isDone.companyAddress.country} isWebView={isWebView} />
) : (
<>
<div>
<h3>{m.reattribute.title}</h3>
<p>
{m.step_company} <strong>{companyName}</strong> {m.reattribute.companyIndicated}{' '}
{isoToHumanReadableText(creationDate, currentLang)} {m.reattribute.badAttribution}
</p>
<p>
{m.reattribute.youHave}{' '}
<strong>
{daysToAnswer} {m.days}
</strong>{' '}
{m.reattribute.toReattribute}
</p>
</div>
<Animate>
<div id="CompanyIdentifyBy">
<ScRadioButtons
required
value={method}
onChange={setMethod}
options={options}
title={m.reattribute.identify}
description={<span dangerouslySetInnerHTML={{__html: m.canYouIdentifyCompanyDesc}} />}
/>
</div>
</Animate>
{method && dispatch(method, tags, _reattributeReport.mutate)}
</>
)
} else {
return (
<>
<h1>{m.reattribute.notReattributable}</h1>
<div className="text-center">
<i className="ri-emotion-normal-line fr-icon--lg" />
</div>
</>
)
}
}

const dispatch = (
method: 'byNameAndGeoArea' | 'byIdentifier',
tags: ReportTag[],
onIdentification: (_: CompanySearchResult) => void,
) => {
switch (method) {
case 'byNameAndGeoArea':
return (
<CompanySearchByNameAndGeoArea>
{companies => (
<CompanySearchResultComponentWithTags
companies={companies ?? []}
tags={tags}
onSubmit={company => {
onIdentification(company)
}}
/>
)}
</CompanySearchByNameAndGeoArea>
)
case 'byIdentifier':
return (
<CompanySearchByIdentifier>
{companies => (
<CompanySearchResultComponentWithTags
companies={companies ?? []}
tags={tags}
onSubmit={company => {
onIdentification(company)
}}
/>
)}
</CompanySearchByIdentifier>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {getReportInputs} from '../Details/draftReportInputs'
import {useReportCreateContext} from '../ReportCreateContext'
import {useReportFlowContext} from '../ReportFlowContext'
import {ConfirmationStep, ConfirmationStepper} from './ConfirmationStepper'
import {buildReportMetadata} from '@/utils/buildReportMetadata'

export const Confirmation = ({stepNavigation, isWebView}: {stepNavigation: StepNavigation; isWebView: boolean}) => {
const _reportFlow = useReportFlowContext()
Expand Down Expand Up @@ -236,30 +237,3 @@ function RenderEachStep({
)
}
}

function buildReportMetadata({isWebView}: {isWebView: boolean}): ApiReport['metadata'] {
if (isWebView) {
return {
isMobileApp: true,
os: detectMobileOs(),
}
}
return {isMobileApp: false}
}

function detectMobileOs(): 'Ios' | 'Android' | undefined {
if (/android/i.test(navigator.userAgent)) {
return 'Android'
}

if (
['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) ||
// iPad on iOS 13 detection
(navigator.userAgent.includes('Mac') && 'ontouchend' in document)
) {
return 'Ios'
}
// could happen in case of weird settings, browser extensions
// or if our user agent detection isn't perfect
return undefined
}
13 changes: 13 additions & 0 deletions website/src/i18n/localization/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ export const en = {
title: 'Share your review - SignalConso',
description: '',
},
reattribuer: {
title: 'Reattribute your report - SignalConso',
description: '',
},
avisEngagement: {
title: 'Share your review on commitment - SignalConso',
description: '',
Expand Down Expand Up @@ -1352,6 +1356,15 @@ export const en = {
chargeBack: `Through the charge-back procedure, you may be eligible for a refund following an online purchase:`,
emailForErrorInReport: `In case of an error in your report, send an email to `,
},
reattribute: {
title: 'Reassign your report',
identify: 'Identify the new company',
notReattributable: 'This report does not exist or is not reassignable',
youHave: 'You have',
toReattribute: 'to reassign your report.',
companyIndicated: 'has indicated that your report from',
badAttribution: 'was incorrectly assigned.',
},
externalLink: 'External link',
specialLegislation: {
SHRINKFLATION: `Only stores with a sales area of <strong>over 400m²</strong> are subject to shrinkflation regulations. This store is probably <strong>not subject</strong> to these regulations.`,
Expand Down
13 changes: 13 additions & 0 deletions website/src/i18n/localization/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ export const fr = {
title: 'Donnez votre avis - SignalConso',
description: '',
},
reattribuer: {
title: 'Réattribuer votre signalement - SignalConso',
description: '',
},
avisEngagement: {
title: "Donnez votre avis sur l'engagement - SignalConso",
description: '',
Expand Down Expand Up @@ -1381,6 +1385,15 @@ contacter directement.`,
chargeBack: `Grâce à la procédure de charge-back vous pouvez être remboursé gratuitement suite à un achat effectué en ligne :`,
emailForErrorInReport: `En cas d’erreur sur votre signalement, envoyez un email à `,
},
reattribute: {
title: 'Réattribuer votre signalement',
identify: 'Identifier la nouvelle entreprise',
notReattributable: "Ce signalement n'existe pas ou n'est pas réattribuable",
youHave: 'Vous avez',
toReattribute: 'pour réattribuer votre signalement.',
companyIndicated: 'a indiqué que votre signalement du',
badAttribution: 'était mal attribué.',
},
externalLink: 'Lien externe',
specialLegislation: {
SHRINKFLATION: `Seuls les magasins dont la surface de vente est <strong>supérieure à 400 m²</strong> sont concernés par la règlementation relative à la réduflation (ou shrinkflation). Il est probable que cet établissement n’y soit <strong>pas soumis.</strong>`,
Expand Down
16 changes: 1 addition & 15 deletions website/src/reusablePages/faireUnSignalementPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {LimitedWidthPageContainer} from '@/components_simple/PageContainers'
import {appConfig} from '@/core/appConfig'
import {ReactNode} from 'react'
import {Anomaly} from 'shared/anomalies/Anomaly'
import {ReportFlowStepper} from '@/components_feature/reportFlow/reportFlowStepper/ReportFlowStepper'
import {WebviewEnvMarker} from '@/utils/WebviewEnvMarker'

export const FaireUnSignalementPage = ({anomaly, isWebView}: {anomaly: Anomaly; isWebView: boolean}) => {
return (
Expand All @@ -22,17 +22,3 @@ function Container({isWebView, children}: {isWebView: boolean; children: ReactNo
<LimitedWidthPageContainer>{children}</LimitedWidthPageContainer>
)
}

function WebviewEnvMarker() {
const marker = appConfig.envMarker ?? (appConfig.isDev ? 'dév' : null)
if (marker) {
return (
<div className="absolute z-[999] pointer-events-none top-0 left-0 w-full flex items-center justify-center">
<div className="text-green-900 border-green-900 border border-solid w-fit p-1 text-sm bg-white bg-opacity-80">
webview {marker}
</div>
</div>
)
}
return null
}
Loading

0 comments on commit e7bbae0

Please sign in to comment.