diff --git a/.changeset/angry-rings-shave.md b/.changeset/angry-rings-shave.md new file mode 100644 index 0000000000..0c665345c3 --- /dev/null +++ b/.changeset/angry-rings-shave.md @@ -0,0 +1,13 @@ +--- +"go-web-app": patch +--- + +Enable editing of DREF forms in all languages + +- Added new FINALIZING DREF status which indicates form is being finalized and the original language content is being translated into english. +- Added new FINALIZED DREF status which indicates for hase been finalized and the original language has been switched to english. +- Added new FAILED DREF status which indicates form finalization process has failed. +- Added final report and operational update confirmation modal. +- Added view mode for all DREF forms. +- Integrated finalize API in DREF table. +- Updated DREF status and publish APIs to support approval workflow. diff --git a/app/src/components/Navbar/LanguageDropdown/index.tsx b/app/src/components/Navbar/LanguageDropdown/index.tsx index b6d3040d38..210fb243f8 100644 --- a/app/src/components/Navbar/LanguageDropdown/index.tsx +++ b/app/src/components/Navbar/LanguageDropdown/index.tsx @@ -16,17 +16,10 @@ import { } from '@togglecorp/fujs'; import DropdownMenuItem from '#components/DropdownMenuItem'; +import { languageNameMap } from '#utils/common'; import styles from './styles.module.css'; -// NOTE: these doesn't need to be translated -const languageNameMap: Record = { - en: 'English', - fr: 'Français', - es: 'Español', - ar: 'عربي', -}; - const languageList = mapToList( languageNameMap, (value, key) => ({ key: key as Language, value }), diff --git a/app/src/components/domain/GoMultiFileInput/index.tsx b/app/src/components/domain/GoMultiFileInput/index.tsx index 78eb052d30..d39ed19438 100644 --- a/app/src/components/domain/GoMultiFileInput/index.tsx +++ b/app/src/components/domain/GoMultiFileInput/index.tsx @@ -62,6 +62,7 @@ type Props = Omit, 'multiple' | 'value' withoutPreview?: boolean; error?: React.ReactNode; description?: React.ReactNode; + useCurrentLanguageForMutation?: boolean; } function GoMultiFileInput(props: Props) { @@ -85,6 +86,7 @@ function GoMultiFileInput(props: Props) { withoutPreview, error, description, + useCurrentLanguageForMutation = false, } = props; const strings = useTranslation(i18n); @@ -97,6 +99,7 @@ function GoMultiFileInput(props: Props) { formData: true, url, method: 'POST', + useCurrentLanguageForMutation, body: (body: { files: File[] }) => { const formData = new FormData(); diff --git a/app/src/components/domain/GoSingleFileInput/index.tsx b/app/src/components/domain/GoSingleFileInput/index.tsx index 7458b29b7f..f4201b08f3 100644 --- a/app/src/components/domain/GoSingleFileInput/index.tsx +++ b/app/src/components/domain/GoSingleFileInput/index.tsx @@ -45,6 +45,7 @@ type Props = Omit, 'multiple' | 'value' description?: React.ReactNode; onSuccess?: () => void; withoutStatus?: boolean; + useCurrentLanguageForMutation?: boolean; } function GoSingleFileInput(props: Props) { @@ -72,6 +73,7 @@ function GoSingleFileInput(props: Props) { requestBody, onSuccess, withoutStatus, + useCurrentLanguageForMutation = false, } = props; const strings = useTranslation(i18n); @@ -88,6 +90,7 @@ function GoSingleFileInput(props: Props) { // FIXME: fix typing in server (low priority) // the server generated type for response and body is the same body: (body) => body as never, + useCurrentLanguageForMutation, onSuccess: (response) => { const { id, file } = response; onChange(id, name); diff --git a/app/src/components/domain/ImageWithCaptionInput/index.tsx b/app/src/components/domain/ImageWithCaptionInput/index.tsx index 406a54690a..4ab0ec9c82 100644 --- a/app/src/components/domain/ImageWithCaptionInput/index.tsx +++ b/app/src/components/domain/ImageWithCaptionInput/index.tsx @@ -30,6 +30,7 @@ interface Props { name: N; url: SupportedPaths; value: Value | null | undefined; + readOnly: boolean; onChange: (value: SetValueArg | undefined, name: N) => void; error: ObjectError | undefined; fileIdToUrlMap: Record; @@ -38,12 +39,14 @@ interface Props { icons?: React.ReactNode; actions?: React.ReactNode; disabled?: boolean; + useCurrentLanguageForMutation?: boolean; } // FIXME: Move this to components function ImageWithCaptionInput(props: Props) { const { className, + readOnly, name, value, url, @@ -55,6 +58,7 @@ function ImageWithCaptionInput(props: Props) icons, actions, disabled, + useCurrentLanguageForMutation, } = props; const strings = useTranslation(i18n); @@ -92,6 +96,7 @@ function ImageWithCaptionInput(props: Props) name="id" accept="image/*" value={value?.id} + readOnly={readOnly} onChange={handleFileInputChange} url={url} fileIdToUrlMap={fileIdToUrlMap} @@ -108,6 +113,7 @@ function ImageWithCaptionInput(props: Props) /> ) : undefined} clearable + useCurrentLanguageForMutation={useCurrentLanguageForMutation} > {label} @@ -116,6 +122,7 @@ function ImageWithCaptionInput(props: Props) className={styles.captionInput} name="caption" value={value?.caption} + readOnly={readOnly} onChange={setFieldValue} error={error?.caption} placeholder={strings.imageWithCaptionEnterCaption} diff --git a/app/src/components/domain/LanguageMismatchMessage/i18n.json b/app/src/components/domain/LanguageMismatchMessage/i18n.json index cbf3857ced..fd8ba0f291 100644 --- a/app/src/components/domain/LanguageMismatchMessage/i18n.json +++ b/app/src/components/domain/LanguageMismatchMessage/i18n.json @@ -1,8 +1,8 @@ { "namespace": "languageMismatchMessage", "strings": { - "languageMismatchErrorTitle": "Edit not available in selected language!", - "languageMismatchErrorMessage": "This form was originally created in {originalLanguage} and cannot be edited in a different language!", - "languageMismatchHelpMessage": "Please change the language to {originalLanguage} to edit it!" + "languageMismatchErrorTitle": "Edit not available in {selectedLanguage} language!", + "languageMismatchErrorMessage": "This is because form was originally created in {originalLanguage} language and edits cannot be edited in the {selectedLanguage} language.", + "languageMismatchHelpMessage": "To make changes, please switch to the original language of the form." } } diff --git a/app/src/components/domain/LanguageMismatchMessage/index.tsx b/app/src/components/domain/LanguageMismatchMessage/index.tsx index 104a6a567b..6e37324f62 100644 --- a/app/src/components/domain/LanguageMismatchMessage/index.tsx +++ b/app/src/components/domain/LanguageMismatchMessage/index.tsx @@ -14,6 +14,7 @@ interface Props { // FIXME: typings should be fixed in the server // this should be of type Language originalLanguage: string | undefined; + selectedLanguage: Language; } function LanguageMismatchMessage(props: Props) { @@ -21,7 +22,8 @@ function LanguageMismatchMessage(props: Props) { const { title = strings.languageMismatchErrorTitle, - originalLanguage = 'en', + originalLanguage, + selectedLanguage, } = props; return ( @@ -32,16 +34,13 @@ function LanguageMismatchMessage(props: Props) { resolveToString( strings.languageMismatchErrorMessage, // FIXME: this should not require cast - { originalLanguage: languageNameMapEn[originalLanguage as Language] ?? '--' }, - ) - } - actions={ - resolveToString( - strings.languageMismatchHelpMessage, - // FIXME: this should not require cast - { originalLanguage: languageNameMapEn[originalLanguage as Language] ?? '--' }, + { + originalLanguage: languageNameMapEn[originalLanguage as Language] ?? '--', + selectedLanguage: languageNameMapEn[selectedLanguage] ?? '--', + }, ) } + actions={strings.languageMismatchHelpMessage} /> ); } diff --git a/app/src/components/domain/MultiImageWithCaptionInput/index.tsx b/app/src/components/domain/MultiImageWithCaptionInput/index.tsx index 9e7ba273a1..220b04118b 100644 --- a/app/src/components/domain/MultiImageWithCaptionInput/index.tsx +++ b/app/src/components/domain/MultiImageWithCaptionInput/index.tsx @@ -45,7 +45,9 @@ interface Props { label: React.ReactNode; icons?: React.ReactNode; actions?: React.ReactNode; + readOnly?: boolean; disabled?: boolean; + useCurrentLanguageForMutation?: boolean; } // FIXME: Move this to components @@ -62,7 +64,9 @@ function MultiImageWithCaptionInput(props: Prop label, icons, actions, + readOnly, disabled, + useCurrentLanguageForMutation = false, } = props; const strings = useTranslation(i18n); @@ -139,7 +143,9 @@ function MultiImageWithCaptionInput(props: Prop icons={icons} actions={actions} withoutPreview + readOnly={readOnly} disabled={disabled} + useCurrentLanguageForMutation={useCurrentLanguageForMutation} > {label} @@ -168,7 +174,7 @@ function MultiImageWithCaptionInput(props: Prop ariaLabel={strings.removeImagesButtonTitle} variant="secondary" spacing="none" - disabled={disabled} + disabled={disabled || readOnly} > @@ -187,6 +193,7 @@ function MultiImageWithCaptionInput(props: Prop onChange={handleCaptionChange} error={imageError?.caption} placeholder={strings.enterCaptionPlaceholder} + readOnly={readOnly} disabled={disabled} /> diff --git a/app/src/components/domain/SourceInformationInput/index.tsx b/app/src/components/domain/SourceInformationInput/index.tsx index 8ee3a02713..33dc8dc73c 100644 --- a/app/src/components/domain/SourceInformationInput/index.tsx +++ b/app/src/components/domain/SourceInformationInput/index.tsx @@ -31,6 +31,7 @@ interface Props { onRemove: (index: number) => void; index: number; disabled?: boolean; + readOnly: boolean; } function SourceInformationInput(props: Props) { @@ -41,6 +42,7 @@ function SourceInformationInput(props: Props) { index, onRemove, disabled, + readOnly, } = props; const strings = useTranslation(i18n); @@ -92,6 +94,7 @@ function SourceInformationInput(props: Props) { value={value.source_name} error={error?.source_name} onChange={onFieldChange} + readOnly={readOnly} disabled={disabled} /> + )} + > +

{strings.dropdownActionNewOpsUpdateConfirmationMessage}

+ {shouldConfirmImminentAddOpsUpdate + && strings.dropdownActionImminentNewOpsUpdateConfirmationMessage} + {startingLanguage !== 'en' && ( + <> +

+ {strings + .dropdownActionNewOpsUpdateLanguageSelectLanguageMessage} +

+ + + )} + + ) + } + { + showFinalReportConfirmModal && ( + + {strings.dropdownActionAddFinalReportLabel} + + )} + > +

{strings.dropdownActionNewFinalReportConfirmationMessage}

+ {startingLanguage !== 'en' && ( + <> +

+ {strings + .dropdownActionNewFinalReportLanguageSelectLanguageMessage} +

+ + + )} +
+ ) + } + { + drefApprovalPending && ( + + + + ) + } ); } diff --git a/app/src/views/AccountMyFormsDref/DrefTableActions/styles.module.css b/app/src/views/AccountMyFormsDref/DrefTableActions/styles.module.css index 5e3fdb7f3b..8513915b69 100644 --- a/app/src/views/AccountMyFormsDref/DrefTableActions/styles.module.css +++ b/app/src/views/AccountMyFormsDref/DrefTableActions/styles.module.css @@ -1,3 +1,9 @@ .icon { font-size: var(--go-ui-height-icon-multiplier); } + +.add-ops-update-modal { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-lg); +} diff --git a/app/src/views/DrefApplicationForm/Actions/index.tsx b/app/src/views/DrefApplicationForm/Actions/index.tsx index d70672049a..b318c19bec 100644 --- a/app/src/views/DrefApplicationForm/Actions/index.tsx +++ b/app/src/views/DrefApplicationForm/Actions/index.tsx @@ -59,6 +59,7 @@ function needOptionKeySelector(option: NeedOption) { interface Props { value: Value; + readOnly: boolean; setFieldValue: (...entries: EntriesAsList) => void; error: Error | undefined; fileIdToUrlMap: Record; @@ -69,6 +70,7 @@ interface Props { function Actions(props: Props) { const { value, + readOnly, setFieldValue, error: formError, fileIdToUrlMap, @@ -202,6 +204,7 @@ function Actions(props: Props) { > {strings.drefFormAddButton} @@ -258,7 +263,7 @@ function Actions(props: Props) { onRemove={onNsActionRemove} error={getErrorObject(error?.national_society_actions)} titleDisplayMap={nsActionTitleDisplayMap} - disabled={disabled} + disabled={readOnly || disabled} /> ))} @@ -272,6 +277,7 @@ function Actions(props: Props) {