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