diff --git a/.changeset/itchy-dryers-turn.md b/.changeset/itchy-dryers-turn.md new file mode 100644 index 000000000..81c7f1fb3 --- /dev/null +++ b/.changeset/itchy-dryers-turn.md @@ -0,0 +1,5 @@ +--- +"go-web-app": patch +--- + +Move field report / emergency title generation logic from client to server diff --git a/app/env.ts b/app/env.ts index e7a9542de..8e2ed9f78 100644 --- a/app/env.ts +++ b/app/env.ts @@ -16,7 +16,7 @@ export default defineConfig({ return value as ('production' | 'staging' | 'testing' | `alpha-${number}` | 'development' | 'APP_ENVIRONMENT_PLACEHOLDER'); }, APP_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }), - APP_ADMIN_URL: Schema.string.optional({ format: 'url', protocol: true }), + APP_ADMIN_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }), APP_MAPBOX_ACCESS_TOKEN: Schema.string(), APP_TINY_API_KEY: Schema.string(), APP_RISK_API_ENDPOINT: Schema.string({ format: 'url', protocol: true }), diff --git a/app/src/views/FieldReportForm/ContextFields/TitlePreview/i18n.json b/app/src/views/FieldReportForm/ContextFields/TitlePreview/i18n.json new file mode 100644 index 000000000..f6a33fc83 --- /dev/null +++ b/app/src/views/FieldReportForm/ContextFields/TitlePreview/i18n.json @@ -0,0 +1,8 @@ +{ + "namespace": "fieldReportForm", + "strings": { + "titlePreview": "Title Preview", + "failedToGenerateTitle": "Failed To Generate the Field Report Title" + } +} + diff --git a/app/src/views/FieldReportForm/ContextFields/TitlePreview/index.tsx b/app/src/views/FieldReportForm/ContextFields/TitlePreview/index.tsx new file mode 100644 index 000000000..261b51edf --- /dev/null +++ b/app/src/views/FieldReportForm/ContextFields/TitlePreview/index.tsx @@ -0,0 +1,70 @@ +import { useMemo } from 'react'; +import { TextOutput } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import useAlert from '#hooks/useAlert'; +import useDebouncedValue from '#hooks/useDebouncedValue'; +import { useRequest } from '#utils/restRequest'; + +import i18n from './i18n.json'; + +interface Props { + country: number; + disasterType: number; + event?: number; + isCovidReport?: boolean; + startDate?: string; + title: string; +} + +function TitlePreview(props: Props) { + const { + country, + disasterType, + event, + isCovidReport, + startDate, + title, + } = props; + + const strings = useTranslation(i18n); + const alert = useAlert(); + + const variables = useMemo(() => ({ + countries: [country], + is_covid_report: isCovidReport, + start_date: startDate, + dtype: disasterType, + event, + title, + }), [country, isCovidReport, startDate, disasterType, event, title]); + + const debouncedVariables = useDebouncedValue(variables); + + const { + response: generateTitleResponse, + } = useRequest({ + url: '/api/v2/field-report/generate-title/', + method: 'POST', + body: debouncedVariables, + preserveResponse: true, + onFailure: () => { + alert.show( + strings.failedToGenerateTitle, + { + variant: 'danger', + }, + ); + }, + }); + + return ( + + ); +} + +export default TitlePreview; diff --git a/app/src/views/FieldReportForm/ContextFields/i18n.json b/app/src/views/FieldReportForm/ContextFields/i18n.json index 82ff32fa8..6f71729d3 100644 --- a/app/src/views/FieldReportForm/ContextFields/i18n.json +++ b/app/src/views/FieldReportForm/ContextFields/i18n.json @@ -33,8 +33,8 @@ "fieldReportFormContextTitle": "Context", "fieldReportFormSearchTitle": "Search for existing emergency", "fieldReportFormSearchDescription": "Type the name of the country you want to report on in the box above to begin the search.", - "fieldPrefix": "Prefix", - "fieldReportFormSuffix": "Suffix" + "originalTitle": "Original Title", + "generatedTitlePreview": "Title Preview" } } diff --git a/app/src/views/FieldReportForm/ContextFields/index.tsx b/app/src/views/FieldReportForm/ContextFields/index.tsx index cc11c4fbf..66c052502 100644 --- a/app/src/views/FieldReportForm/ContextFields/index.tsx +++ b/app/src/views/FieldReportForm/ContextFields/index.tsx @@ -2,6 +2,7 @@ import { useCallback, useMemo, } from 'react'; +import { useParams } from 'react-router-dom'; import { BooleanInput, Container, @@ -9,9 +10,11 @@ import { InputSection, RadioInput, TextInput, + TextOutput, } from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; import { + isDefined, isNotDefined, isTruthyString, } from '@togglecorp/fujs'; @@ -36,6 +39,7 @@ import { } from '#utils/constants'; import { type PartialFormValue } from '../common'; +import TitlePreview from './TitlePreview'; import i18n from './i18n.json'; import styles from './styles.module.css'; @@ -54,10 +58,6 @@ interface Props { setDistrictOptions: React.Dispatch>; setEventOptions: React.Dispatch>; disabled?: boolean; - - fieldReportId: string | undefined; - titlePrefix: string | undefined; - titleSuffix: string | undefined; } function ContextFields(props: Props) { @@ -71,12 +71,10 @@ function ContextFields(props: Props) { setDistrictOptions, setEventOptions, disabled, - titlePrefix, - titleSuffix, - fieldReportId, } = props; const strings = useTranslation(i18n); + const { fieldReportId } = useParams<{ fieldReportId: string }>(); const { api_field_report_status, @@ -171,16 +169,7 @@ function ContextFields(props: Props) { [onValueChange, value.dtype], ); - const prefixVisible = !fieldReportId && isTruthyString(titlePrefix); const summaryVisible = !value.is_covid_report; - const suffixVisible = !fieldReportId && isTruthyString(titleSuffix); - - const preferredColumnNoForSummary = Math.max( - (prefixVisible ? 1 : 0) - + (summaryVisible ? 1 : 0) - + (suffixVisible ? 1 : 0), - 1, - ) as 1 | 2 | 3; return ( - {prefixVisible && ( - { }} - /> - )} {summaryVisible && ( - - )} - {suffixVisible && ( - { }} - // readOnly - /> + <> + + {isDefined(value.country) + && isDefined(value.dtype) + && isDefined(value.title) + && isTruthyString(value.title.trim()) + ? ( + + ) : ( + + )} + )} { - if (isFalsyString(part.value)) { - return undefined; - } - return { - value: part.value, - compose: part.compose ?? ' ', - }; - }) - .filter(isDefined) - .flatMap((validPart, index) => { - if (index === 0) { - return [validPart.value]; - } - return [validPart.compose, validPart.value]; - }); - return validParts.join(''); -} - function getNextStep( current: TabKeys, direction: 1 | -1, @@ -157,8 +127,6 @@ export function Component() { const alert = useAlert(); const strings = useTranslation(i18n); const formContentRef = useRef>(null); - const countries = useCountryRaw(); - const disasterTypeOptions = useDisasterType(); const currentLanguage = useCurrentLanguage(); const { state } = useLocation(); @@ -216,34 +184,6 @@ export function Component() { url: '/api/v2/review-country/', }); - const { - response: eventResponse, - } = useRequest({ - url: '/api/v2/event/{id}/', - skip: isNotDefined(value.event), - pathVariables: isDefined(value.event) ? { - id: value.event, - } : undefined, - }); - - const fieldReportNumber = useMemo( - () => { - if (isNotDefined(value.country)) { - return 1; - } - const reports = eventResponse?.field_reports?.filter( - (fieldReport) => ( - isDefined(fieldReport.countries.find((country) => country.id === value.country)) - ), - ); - if (isNotDefined(reports)) { - return 1; - } - return reports.length + 1; - }, - [eventResponse, value.country], - ); - const { pending: fieldReportPending, error: fieldReportResponseError, @@ -382,118 +322,6 @@ export function Component() { }, }); - const countryIsoOptions = useMemo( - () => ( - countries?.map((country) => { - // FIXME: why are we using this filter for independent and record_type - if ( - !country.independent - // FIXME: Use named constant - || country.record_type !== 1 - || isFalsyString(country.iso3) - ) { - return undefined; - } - return { - ...country, - iso3: country.iso3, - independent: country.independent, - record_type: country.record_type, - }; - }).filter(isDefined) - ), - [countries], - ); - - const getTitle = useCallback( - ( - event: number | null | undefined, - country: number | null | undefined, - is_covid_report: boolean | undefined, - start_date: string | null | undefined, - dtype: number | null | undefined, - // summary: string | null | undefined, - ) => { - const dateLabel = formatDate(new Date(), 'yyyy-MM-dd'); - const iso3Label = isDefined(country) - ? countryIsoOptions?.find(({ id }) => id === country)?.iso3 - : undefined; - - // COVID-19 - if (is_covid_report && isDefined(event)) { - // eslint-disable-next-line max-len - return isTruthyString(iso3Label) && isDefined(fieldReportNumber) && isTruthyString(dateLabel) - ? { - titlePrefix: `${iso3Label}: COVID-19`, - titleSuffix: `#${fieldReportNumber} (${dateLabel})`, - } - : undefined; - } - if (is_covid_report && isNotDefined(event)) { - return isTruthyString(iso3Label) - ? { - titlePrefix: `${iso3Label}: COVID-19`, - titleSuffix: undefined, - } - : undefined; - } - - // NON-COVID-19 - const disasterLabel = isDefined(dtype) - ? disasterTypeOptions?.find(({ id }) => id === dtype)?.name - : undefined; - - const shortDate = formatDate(start_date, 'MM-yyyy'); - - if (isDefined(event)) { - // eslint-disable-next-line max-len - return isTruthyString(iso3Label) && isTruthyString(disasterLabel) && isTruthyString(shortDate) && isDefined(fieldReportNumber) && isTruthyString(dateLabel) - ? { - titlePrefix: `${iso3Label}: ${disasterLabel} - ${shortDate}`, - titleSuffix: `#${fieldReportNumber} (${dateLabel})`, - } - : undefined; - } - // eslint-disable-next-line max-len - return isTruthyString(iso3Label) && isTruthyString(disasterLabel) && isTruthyString(shortDate) - ? { - titlePrefix: `${iso3Label}: ${disasterLabel} - ${shortDate}`, - titleSuffix: undefined, - } - : undefined; - }, - [countryIsoOptions, disasterTypeOptions, fieldReportNumber], - ); - - const generatedTitle = useMemo( - () => { - if (isNotDefined(fieldReportId)) { - return getTitle( - value.event, - value.country, - value.is_covid_report, - value.start_date, - value.dtype, - // value.summary ?? '', - ); - } - return undefined; - }, - [ - getTitle, - fieldReportId, - value.event, - value.country, - value.is_covid_report, - value.start_date, - value.dtype, - // value.summary, - ], - ); - - const titlePrefix = generatedTitle?.titlePrefix; - const titleSuffix = generatedTitle?.titleSuffix; - const isReviewCountry = useMemo(() => { if (isNotDefined(value.country)) { return false; @@ -578,37 +406,12 @@ export function Component() { ...sanitizedValues, } as FieldReportBody); } else { - const title = getTitle( - formValues.event, - formValues.country, - sanitizedValues.is_covid_report, - sanitizedValues.start_date, - sanitizedValues.dtype, - ); - - const generatedTitlePrefix = title?.titlePrefix; - const generatedTitleSuffix = title?.titleSuffix; - - const summary = concat([ - { value: generatedTitlePrefix }, - { - // NOTE: We do not use summary field on covid report - value: sanitizedValues.is_covid_report - ? undefined - : sanitizedValues.summary, - compose: ' - ', - }, - { value: generatedTitleSuffix }, - ]); - createSubmitRequest({ ...sanitizedValues, - summary, } as FieldReportPostBody); } }, [ - getTitle, fieldReportId, editSubmitRequest, createSubmitRequest, @@ -757,9 +560,6 @@ export function Component() { setEventOptions={setEventOptions} eventOptions={eventOptions} disabled={pending} - titlePrefix={titlePrefix} - titleSuffix={titleSuffix} - fieldReportId={fieldReportId} /> diff --git a/package.json b/package.json index dcc67c794..4e94c3894 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,13 @@ }, "engines": { "node": "20", - "pnpm": "8.6.0" + "pnpm": "8.15.9" }, "devDependencies": { "@changesets/cli": "^2.27.10", "knip": "^5.36.3" }, - "packageManager": "pnpm@8.6.0+sha1.71f9126a20cd3d00fa47c188f956918858180e54", + "packageManager": "pnpm@8.15.9", "pnpm": { "patchedDependencies": { "openapi-typescript@6.5.5": "patches/openapi-typescript@6.5.5.patch", diff --git a/translationMigrations/000012-1738141854510.json b/translationMigrations/000012-1738141854510.json new file mode 100644 index 000000000..16dcf73a9 --- /dev/null +++ b/translationMigrations/000012-1738141854510.json @@ -0,0 +1,39 @@ +{ + "parent": "000011-1738138922126.json", + "actions": [ + { + "action": "add", + "key": "failedToGenerateTitle", + "namespace": "fieldReportForm", + "value": "Failed To Generate the Field Report Title" + }, + { + "action": "add", + "key": "generatedTitlePreview", + "namespace": "fieldReportForm", + "value": "Title Preview" + }, + { + "action": "add", + "key": "originalTitle", + "namespace": "fieldReportForm", + "value": "Original Title" + }, + { + "action": "add", + "key": "titlePreview", + "namespace": "fieldReportForm", + "value": "Title Preview" + }, + { + "action": "remove", + "key": "fieldPrefix", + "namespace": "fieldReportForm" + }, + { + "action": "remove", + "key": "fieldReportFormSuffix", + "namespace": "fieldReportForm" + } + ] +} \ No newline at end of file