From 783166df6d5d885a8e121bf0cb71197432594912 Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Mon, 13 Oct 2025 11:42:11 +0545 Subject: [PATCH 01/19] global(add-global-page): Add a global page --- app/src/App/routes/index.tsx | 30 +++++++++ app/src/components/Navbar/i18n.json | 1 + app/src/views/EapRegistration/i18n.json | 7 ++ app/src/views/EapRegistration/index.tsx | 21 ++++++ app/src/views/EarlyActionProtocols/i18n.json | 17 +++++ app/src/views/EarlyActionProtocols/index.tsx | 66 +++++++++++++++++++ .../EarlyActionProtocols/styles.module.css | 5 ++ go-api | 2 +- 8 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 app/src/views/EapRegistration/i18n.json create mode 100644 app/src/views/EapRegistration/index.tsx create mode 100644 app/src/views/EarlyActionProtocols/i18n.json create mode 100644 app/src/views/EarlyActionProtocols/index.tsx create mode 100644 app/src/views/EarlyActionProtocols/styles.module.css diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index b25ad826a..76a6bb6cc 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -701,6 +701,19 @@ const accountMyFormsDref = customWrapRoute({ }, }); +const earlyActionProtocols = customWrapRoute({ + parent: rootLayout, + path: 'eap', + component: { + render: () => import('#views/EarlyActionProtocols'), + props: {}, + }, + context: { + title: 'Early Action Protocols', + visibility: 'anything', + }, +}); + const accountMyFormsThreeW = customWrapRoute({ parent: accountMyFormsLayout, path: 'three-w', @@ -1146,6 +1159,21 @@ const newPerOverviewForm = customWrapRoute({ }, }); +const eapDevelopmentRegistration = customWrapRoute({ + parent: rootLayout, + path: 'eap-registration/new', + component: { + render: () => import('#views/EapRegistration'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'EAP Development Registration', + visibility: 'is-authenticated', + permissions: ({ isGuestUser }) => !isGuestUser, + }, +}); + const perOverviewForm = customWrapRoute({ parent: perProcessLayout, path: ':perId/overview', @@ -1353,6 +1381,7 @@ const wrappedRoutes = { termsAndConditions, operationalLearning, montandonLandingPage, + eapDevelopmentRegistration, ...regionRoutes, ...countryRoutes, ...surgeRoutes, @@ -1363,6 +1392,7 @@ const wrappedRoutes = { // Redirects preparednessOperationalLearning, obsoleteFieldReportDetails, + earlyActionProtocols, }; export const unwrappedRoutes = unwrapRoute(Object.values(wrappedRoutes)); diff --git a/app/src/components/Navbar/i18n.json b/app/src/components/Navbar/i18n.json index 0132def3c..e0ed399ed 100644 --- a/app/src/components/Navbar/i18n.json +++ b/app/src/components/Navbar/i18n.json @@ -40,6 +40,7 @@ "userMenuDrefProcessDescription":"Disaster Response Emergency Fund (DREF) is the quickest way of getting funding directly to local humanitarian actors. Use one of the links below to submit a DREF Application or an update.", "userMenuCreateDrefApplication":"Create DREF Application", "myDrefApplications": "My DREF Applications", + "earlyActionProtocols": "Early Action Protocols (EAP)", "userMenuSurge":"The section displays the summary of deployments within current and ongoing emergencies. Login to see available details", "userMenuSurgeGlobalOverview":"Surge Global Overview", "userMenuOperationalToolbox":"Operational Toolbox", diff --git a/app/src/views/EapRegistration/i18n.json b/app/src/views/EapRegistration/i18n.json new file mode 100644 index 000000000..9b7a06dd0 --- /dev/null +++ b/app/src/views/EapRegistration/i18n.json @@ -0,0 +1,7 @@ +{ + "namespace": "eapRegistration", + "strings": { + "eapRegistrationHeading": "EAP Development Registration", + "eapRegistrationDescription": "The purpose of this form is for you to notify the IFRC team of the start of your EAP process. If you need assistance with the initiation of the process, send us a message." + } +} diff --git a/app/src/views/EapRegistration/index.tsx b/app/src/views/EapRegistration/index.tsx new file mode 100644 index 000000000..31698b3c5 --- /dev/null +++ b/app/src/views/EapRegistration/index.tsx @@ -0,0 +1,21 @@ +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import Page from '#components/Page'; + +import i18n from './i18n.json'; + +/** @knipignore */ +// eslint-disable-next-line import/prefer-default-export +export function Component() { + const strings = useTranslation(i18n); + + return ( + + {/* TODO: Add the form */} + Application Details + + ); +} diff --git a/app/src/views/EarlyActionProtocols/i18n.json b/app/src/views/EarlyActionProtocols/i18n.json new file mode 100644 index 000000000..93b0362f8 --- /dev/null +++ b/app/src/views/EarlyActionProtocols/i18n.json @@ -0,0 +1,17 @@ +{ + "namespace": "earlyActionProtocols", + "strings": { + "eapHeading": "Disaster Response Emergency Fund (DREF)", + "eapDescription": "The IFRC's Disaster Emergency Fund (DREF): rapid, reliable funding for life-saving action.", + "eapTitle": "Early Action Protocols (EAP)", + "eapRegistrationLink": "EAP in Progress? Let Us Know", + "eapContent": "What is an EAP?", + "eapContentHeading": "Early Action Protocols (EAPs) are a core mechanism of the IFRC's Forecast-based Financing (FbF) approach, designed to ensure that humanitarian action happens before a disaster strikes, rather than only responding afterwards.", + "eapContentSubHeadingOne": "An EAP is a pre-agreed plan developed by a Nation Society together with partners, which outlines:", + "eapDescriptionOne": "The triggers (based on scientific forecasts and risk analysis) that indicate when a hazard is likely to impact communities.", + "eapDescriptionTwo": "The early actions to be implemented once those triggers are met - practical, life-saving measures that reduce the impacts of the forecasted disaster.", + "eapDescriptionThree": "The roles, responsibilities, and budget required to carry out these actions quickly and effectively.", + "eapContentSubHeadingTwo": "Why are the EAPs Important?", + "eapContentSubHeadingThree": "What is the EAP Application Process?" + } +} diff --git a/app/src/views/EarlyActionProtocols/index.tsx b/app/src/views/EarlyActionProtocols/index.tsx new file mode 100644 index 000000000..7688ff66a --- /dev/null +++ b/app/src/views/EarlyActionProtocols/index.tsx @@ -0,0 +1,66 @@ +import { + Container, + ExpandableContainer, +} from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import Link from '#components/Link'; +import Page from '#components/Page'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; + +/** @knipignore */ +// eslint-disable-next-line import/prefer-default-export +export function Component() { + const strings = useTranslation(i18n); + + return ( + + + {strings.eapRegistrationLink} + + )} + childrenContainerClassName={styles.earlyActionProtocols} + > + +

+ {strings.eapContentHeading} +

+

+ {strings.eapContentSubHeadingOne} +

    +
  • + {strings.eapDescriptionOne} +
  • +
  • + {strings.eapDescriptionTwo} +
  • +
  • + {strings.eapDescriptionThree} +
  • +
+

+
+ {/* TODO: Add remaining content */} + + +
+
+ ); +} diff --git a/app/src/views/EarlyActionProtocols/styles.module.css b/app/src/views/EarlyActionProtocols/styles.module.css new file mode 100644 index 000000000..8ec3f610f --- /dev/null +++ b/app/src/views/EarlyActionProtocols/styles.module.css @@ -0,0 +1,5 @@ +.early-action-protocols { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-lg); +} diff --git a/go-api b/go-api index fd94c8fca..ca55a2fee 160000 --- a/go-api +++ b/go-api @@ -1 +1 @@ -Subproject commit fd94c8fca4bd0b1fef3c2ea294ae142a5f650233 +Subproject commit ca55a2fee02fd7271bf9a320f6209ec465618bf9 From 73051d564e2c25007be1a2f51b5a68ef7b29e3ba Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Tue, 14 Oct 2025 13:29:53 +0545 Subject: [PATCH 02/19] eap(add-eap-link): Add EAP Tab in account page --- app/src/App/routes/index.tsx | 47 +++++++++++++++++++ app/src/views/AccountMyFormsLayout/i18n.json | 3 +- app/src/views/AccountMyFormsLayout/index.tsx | 5 ++ app/src/views/EapApplications/i18n.json | 8 ++++ app/src/views/EapApplications/index.tsx | 39 +++++++++++++++ .../views/EapApplications/styles.module.css | 6 +++ app/src/views/EapFullForm/index.tsx | 12 +++++ app/src/views/SimplifiedEapForm/index.tsx | 12 +++++ 8 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 app/src/views/EapApplications/i18n.json create mode 100644 app/src/views/EapApplications/index.tsx create mode 100644 app/src/views/EapApplications/styles.module.css create mode 100644 app/src/views/EapFullForm/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/index.tsx diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index 76a6bb6cc..593597780 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -728,6 +728,50 @@ const accountMyFormsThreeW = customWrapRoute({ }, }); +const accountMyFormsEap = customWrapRoute({ + parent: accountMyFormsLayout, + path: 'eap-applications', + component: { + render: () => import('#views/EapApplications'), + props: {}, + }, + context: { + title: 'Account - EAP Applications', + visibility: 'is-authenticated', + permissions: ({ isGuestUser }) => !isGuestUser, + }, +}); + +const eapFullForm = customWrapRoute({ + parent: rootLayout, + path: 'eap-full-form', + component: { + render: () => import('#views/EapFullForm'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'EAP Full Forms', + visibility: 'is-authenticated', + permissions: ({ isGuestUser }) => !isGuestUser, + }, +}); + +const simplifiedEapForm = customWrapRoute({ + parent: rootLayout, + path: 'simplified-eap-form', + component: { + render: () => import('#views/SimplifiedEapForm'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'Simplified EAP Forms', + visibility: 'is-authenticated', + permissions: ({ isGuestUser }) => !isGuestUser, + }, +}); + const accountNotifications = customWrapRoute({ parent: accountLayout, path: 'notifications', @@ -1345,6 +1389,7 @@ const wrappedRoutes = { accountMyFormsPer, accountMyFormsDref, accountMyFormsThreeW, + accountMyFormsEap, resources, search, allThreeWProject, @@ -1382,6 +1427,8 @@ const wrappedRoutes = { operationalLearning, montandonLandingPage, eapDevelopmentRegistration, + eapFullForm, + simplifiedEapForm, ...regionRoutes, ...countryRoutes, ...surgeRoutes, diff --git a/app/src/views/AccountMyFormsLayout/i18n.json b/app/src/views/AccountMyFormsLayout/i18n.json index 6b70856db..c23cf7ce5 100644 --- a/app/src/views/AccountMyFormsLayout/i18n.json +++ b/app/src/views/AccountMyFormsLayout/i18n.json @@ -4,6 +4,7 @@ "fieldReportTabTitle": "Field Report", "perTabTitle": "PER", "drefTabTitle": "DREF", - "threeWTabTitle": "3W" + "threeWTabTitle": "3W", + "eapApplications": "EAP Applications" } } \ No newline at end of file diff --git a/app/src/views/AccountMyFormsLayout/index.tsx b/app/src/views/AccountMyFormsLayout/index.tsx index 5364a3844..c7eaac05d 100644 --- a/app/src/views/AccountMyFormsLayout/index.tsx +++ b/app/src/views/AccountMyFormsLayout/index.tsx @@ -35,6 +35,11 @@ export function Component() { > {strings.threeWTabTitle} + + {strings.eapApplications} + diff --git a/app/src/views/EapApplications/i18n.json b/app/src/views/EapApplications/i18n.json new file mode 100644 index 000000000..a48b9dee3 --- /dev/null +++ b/app/src/views/EapApplications/i18n.json @@ -0,0 +1,8 @@ +{ + "namespace": "eapApplication", + "strings": { + "eapRegistrationLink": "EAP In Process? Let Us Know", + "eapFormLink": "Start Full EAP", + "simplifiedEapLink": "Start sEAP" + } +} diff --git a/app/src/views/EapApplications/index.tsx b/app/src/views/EapApplications/index.tsx new file mode 100644 index 000000000..dd9f788b5 --- /dev/null +++ b/app/src/views/EapApplications/index.tsx @@ -0,0 +1,39 @@ +import { Container } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import Link from '#components/Link'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; + +/** @knipignore */ +// eslint-disable-next-line import/prefer-default-export +export function Component() { + const strings = useTranslation(i18n); + + return ( + + {/* FIXME: Add eap registration link */} + + {strings.eapRegistrationLink} + + + {strings.eapFormLink} + + + {strings.simplifiedEapLink} + + + ); +} diff --git a/app/src/views/EapApplications/styles.module.css b/app/src/views/EapApplications/styles.module.css new file mode 100644 index 000000000..c111ea1e6 --- /dev/null +++ b/app/src/views/EapApplications/styles.module.css @@ -0,0 +1,6 @@ +.eap-form-links { + display: flex; + align-items: center; + justify-content: center; + gap: var(--go-ui-spacing-sm); +} \ No newline at end of file diff --git a/app/src/views/EapFullForm/index.tsx b/app/src/views/EapFullForm/index.tsx new file mode 100644 index 000000000..e6d0317fe --- /dev/null +++ b/app/src/views/EapFullForm/index.tsx @@ -0,0 +1,12 @@ +import Page from '#components/Page'; + +/** @knipignore */ +// eslint-disable-next-line import/prefer-default-export +export function Component() { + return ( + + {/* TODO: Add EAP form */} + Full EAP Form + + ); +} diff --git a/app/src/views/SimplifiedEapForm/index.tsx b/app/src/views/SimplifiedEapForm/index.tsx new file mode 100644 index 000000000..57bcf2b6c --- /dev/null +++ b/app/src/views/SimplifiedEapForm/index.tsx @@ -0,0 +1,12 @@ +import Page from '#components/Page'; + +/** @knipignore */ +// eslint-disable-next-line import/prefer-default-export +export function Component() { + return ( + + {/* TODO: Add Simplified EAP form */} + Simplified EAP form + + ); +} From c6edf32a67a615b5609906490168f13c91cce307 Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Wed, 5 Nov 2025 11:34:02 +0545 Subject: [PATCH 03/19] dref-process(tabs): Add tabs in dref process link --- app/src/App/routes/index.tsx | 61 ++++++++++--- app/src/components/Navbar/index.tsx | 10 ++- app/src/views/DrefDetail/i18n.json | 15 ++++ app/src/views/DrefDetail/index.tsx | 56 ++++++++++++ app/src/views/DrefDetail/styles.module.css | 11 +++ app/src/views/DrefProcess/i18n.json | 9 ++ app/src/views/DrefProcess/index.tsx | 37 ++++++++ app/src/views/EapApplications/i18n.json | 2 +- app/src/views/EarlyActionProtocols/i18n.json | 5 +- app/src/views/EarlyActionProtocols/index.tsx | 87 +++++++++---------- .../EarlyActionProtocols/styles.module.css | 10 ++- 11 files changed, 236 insertions(+), 67 deletions(-) create mode 100644 app/src/views/DrefDetail/i18n.json create mode 100644 app/src/views/DrefDetail/index.tsx create mode 100644 app/src/views/DrefDetail/styles.module.css create mode 100644 app/src/views/DrefProcess/i18n.json create mode 100644 app/src/views/DrefProcess/index.tsx diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index 593597780..3c4c91523 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -319,6 +319,50 @@ const emergencyAdditionalInfo = customWrapRoute({ }, }); +type DefaultDrefDetailChild = 'dref-detail'; +const drefProcessLayout = customWrapRoute({ + parent: rootLayout, + path: 'dref-process', + forwardPath: 'dref-detail' satisfies DefaultDrefDetailChild, + component: { + render: () => import('#views/DrefProcess'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'DREF Process', + visibility: 'anything', + }, +}); + +const drefDetail = customWrapRoute({ + parent: drefProcessLayout, + path: 'dref-detail' satisfies DefaultDrefDetailChild, + component: { + render: () => import('#views/DrefDetail'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'Response and Imminent DREF', + visibility: 'anything', + }, +}); + +const eapDetail = customWrapRoute({ + parent: drefProcessLayout, + path: 'eap-detail', + component: { + render: () => import('#views/EarlyActionProtocols'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'Early Action Protocols', + visibility: 'anything', + }, +}); + type DefaultPreparednessChild = 'global-summary'; const preparednessLayout = customWrapRoute({ parent: rootLayout, @@ -701,19 +745,6 @@ const accountMyFormsDref = customWrapRoute({ }, }); -const earlyActionProtocols = customWrapRoute({ - parent: rootLayout, - path: 'eap', - component: { - render: () => import('#views/EarlyActionProtocols'), - props: {}, - }, - context: { - title: 'Early Action Protocols', - visibility: 'anything', - }, -}); - const accountMyFormsThreeW = customWrapRoute({ parent: accountMyFormsLayout, path: 'three-w', @@ -1439,7 +1470,9 @@ const wrappedRoutes = { // Redirects preparednessOperationalLearning, obsoleteFieldReportDetails, - earlyActionProtocols, + drefDetail, + eapDetail, + drefProcessLayout, }; export const unwrappedRoutes = unwrapRoute(Object.values(wrappedRoutes)); diff --git a/app/src/components/Navbar/index.tsx b/app/src/components/Navbar/index.tsx index be067b2ef..0786bdea7 100644 --- a/app/src/components/Navbar/index.tsx +++ b/app/src/components/Navbar/index.tsx @@ -370,12 +370,20 @@ function Navbar(props: Props) { {strings.myDrefApplications} + + {strings.earlyActionProtocols} + + +
{strings.drefIntroDetailOne}
+
{strings.drefIntroDetailTwo}
+
+ +
{strings.drefProcessSubHeading}
+
    +
  • + {strings.drefProcessListOne} +
  • +
  • + {strings.drefProcessListTwo} +
  • +
+
+ {strings.drefProcessDetailOne} +
+
+ {strings.drefProcessDetailTwo} +
+
+ + {strings.drefDrefApplication} + + + ); +} + +Component.displayName = 'DrefDetail'; diff --git a/app/src/views/DrefDetail/styles.module.css b/app/src/views/DrefDetail/styles.module.css new file mode 100644 index 000000000..c850c0ee2 --- /dev/null +++ b/app/src/views/DrefDetail/styles.module.css @@ -0,0 +1,11 @@ +.dref-detail { + display: flex; + flex-direction: column; + padding: var(--go-ui-spacing-2xl) 0; + + .content { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-xl); + } +} diff --git a/app/src/views/DrefProcess/i18n.json b/app/src/views/DrefProcess/i18n.json new file mode 100644 index 000000000..01904a6dd --- /dev/null +++ b/app/src/views/DrefProcess/i18n.json @@ -0,0 +1,9 @@ +{ + "namespace": "drefProcess", + "strings": { + "eapHeading": "Disaster Response Emergency Fund (DREF)", + "eapDescription": "The IFRC's Disaster Emergency Fund (DREF): rapid, reliable funding for life-saving action.", + "eapProcessDrefTab": "Response And Imminent Dref", + "eapProcessEapTab": "Early Action Protocols (EAP)" + } +} diff --git a/app/src/views/DrefProcess/index.tsx b/app/src/views/DrefProcess/index.tsx new file mode 100644 index 000000000..2ed605891 --- /dev/null +++ b/app/src/views/DrefProcess/index.tsx @@ -0,0 +1,37 @@ +import { Outlet } from 'react-router-dom'; +import { NavigationTabList } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import NavigationTab from '#components/NavigationTab'; +import Page from '#components/Page'; + +import i18n from './i18n.json'; + +/** @knipignore */ +// eslint-disable-next-line import/prefer-default-export +export function Component() { + const strings = useTranslation(i18n); + + return ( + + + + {strings.eapProcessDrefTab} + + + {strings.eapProcessEapTab} + + + + + ); +} + +Component.displayName = 'DrefProcess'; diff --git a/app/src/views/EapApplications/i18n.json b/app/src/views/EapApplications/i18n.json index a48b9dee3..1d3fbee8c 100644 --- a/app/src/views/EapApplications/i18n.json +++ b/app/src/views/EapApplications/i18n.json @@ -1,7 +1,7 @@ { "namespace": "eapApplication", "strings": { - "eapRegistrationLink": "EAP In Process? Let Us Know", + "eapRegistrationLink": "Register Your EAP", "eapFormLink": "Start Full EAP", "simplifiedEapLink": "Start sEAP" } diff --git a/app/src/views/EarlyActionProtocols/i18n.json b/app/src/views/EarlyActionProtocols/i18n.json index 93b0362f8..9472e2952 100644 --- a/app/src/views/EarlyActionProtocols/i18n.json +++ b/app/src/views/EarlyActionProtocols/i18n.json @@ -1,10 +1,7 @@ { "namespace": "earlyActionProtocols", "strings": { - "eapHeading": "Disaster Response Emergency Fund (DREF)", - "eapDescription": "The IFRC's Disaster Emergency Fund (DREF): rapid, reliable funding for life-saving action.", - "eapTitle": "Early Action Protocols (EAP)", - "eapRegistrationLink": "EAP in Progress? Let Us Know", + "eapRegistrationLink": "Register your EAP", "eapContent": "What is an EAP?", "eapContentHeading": "Early Action Protocols (EAPs) are a core mechanism of the IFRC's Forecast-based Financing (FbF) approach, designed to ensure that humanitarian action happens before a disaster strikes, rather than only responding afterwards.", "eapContentSubHeadingOne": "An EAP is a pre-agreed plan developed by a Nation Society together with partners, which outlines:", diff --git a/app/src/views/EarlyActionProtocols/index.tsx b/app/src/views/EarlyActionProtocols/index.tsx index 7688ff66a..3cd1d79d4 100644 --- a/app/src/views/EarlyActionProtocols/index.tsx +++ b/app/src/views/EarlyActionProtocols/index.tsx @@ -5,7 +5,6 @@ import { import { useTranslation } from '@ifrc-go/ui/hooks'; import Link from '#components/Link'; -import Page from '#components/Page'; import i18n from './i18n.json'; import styles from './styles.module.css'; @@ -16,51 +15,49 @@ export function Component() { const strings = useTranslation(i18n); return ( - + {strings.eapRegistrationLink} + + )} > - - {strings.eapRegistrationLink} - - )} - childrenContainerClassName={styles.earlyActionProtocols} + - -

- {strings.eapContentHeading} -

-

- {strings.eapContentSubHeadingOne} -

    -
  • - {strings.eapDescriptionOne} -
  • -
  • - {strings.eapDescriptionTwo} -
  • -
  • - {strings.eapDescriptionThree} -
  • -
-

-
- {/* TODO: Add remaining content */} - - -
-
+

+ {strings.eapContentHeading} +

+

+ {strings.eapContentSubHeadingOne} +

    +
  • + {strings.eapDescriptionOne} +
  • +
  • + {strings.eapDescriptionTwo} +
  • +
  • + {strings.eapDescriptionThree} +
  • +
+

+ + {/* TODO: Add remaining content */} + + + ); } + +Component.displayName = 'EarlyActionProtocols'; diff --git a/app/src/views/EarlyActionProtocols/styles.module.css b/app/src/views/EarlyActionProtocols/styles.module.css index 8ec3f610f..4df12fe2b 100644 --- a/app/src/views/EarlyActionProtocols/styles.module.css +++ b/app/src/views/EarlyActionProtocols/styles.module.css @@ -1,5 +1,11 @@ -.early-action-protocols { +.eap-detail { display: flex; flex-direction: column; - gap: var(--go-ui-spacing-lg); + padding: var(--go-ui-spacing-2xl) 0; + + .content { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-lg); + } } From f102fc4b0ad0aff072025f9e8ddc166ad65efd44 Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Mon, 10 Nov 2025 16:54:31 +0545 Subject: [PATCH 04/19] eap-table(table): Add eap development table --- .../views/EapApplications/Filters/i18n.json | 6 + .../views/EapApplications/Filters/index.tsx | 47 +++++ app/src/views/EapApplications/i18n.json | 1 + app/src/views/EapApplications/index.tsx | 178 ++++++++++++++++-- .../views/EapApplications/styles.module.css | 12 +- 5 files changed, 221 insertions(+), 23 deletions(-) create mode 100644 app/src/views/EapApplications/Filters/i18n.json create mode 100644 app/src/views/EapApplications/Filters/index.tsx diff --git a/app/src/views/EapApplications/Filters/i18n.json b/app/src/views/EapApplications/Filters/i18n.json new file mode 100644 index 000000000..9aab9233d --- /dev/null +++ b/app/src/views/EapApplications/Filters/i18n.json @@ -0,0 +1,6 @@ +{ + "namespace": "accountMyFormsEap", + "strings": { + "filterStatusPlaceholder": "Select Status" + } +} diff --git a/app/src/views/EapApplications/Filters/index.tsx b/app/src/views/EapApplications/Filters/index.tsx new file mode 100644 index 000000000..8ebee1036 --- /dev/null +++ b/app/src/views/EapApplications/Filters/index.tsx @@ -0,0 +1,47 @@ +import { SelectInput } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; +import { stringValueSelector } from '@ifrc-go/ui/utils'; +import { type EntriesAsList } from '@togglecorp/toggle-form'; + +import { type components } from '#generated/types'; +import useGlobalEnums from '#hooks/domain/useGlobalEnums'; + +import i18n from './i18n.json'; + +type TypeOfEapStatus = components<'read'>['schemas']['EapEapStatusEnumKey']; +function typeOfEapStatusKeySelector({ key } : { key: TypeOfEapStatus }) { + return key; +} + +export interface FilterValue { + status?: TypeOfEapStatus | undefined; +} + +interface Props { + value: FilterValue; + onChange: (...args: EntriesAsList) => void; +} + +function Filters(props: Props) { + const { + value, + onChange, + } = props; + + const strings = useTranslation(i18n); + const { eap_eap_status: eapStatusTypeOptions } = useGlobalEnums(); + + return ( + + ); +} + +export default Filters; diff --git a/app/src/views/EapApplications/i18n.json b/app/src/views/EapApplications/i18n.json index 1d3fbee8c..72be7b0f2 100644 --- a/app/src/views/EapApplications/i18n.json +++ b/app/src/views/EapApplications/i18n.json @@ -2,6 +2,7 @@ "namespace": "eapApplication", "strings": { "eapRegistrationLink": "Register Your EAP", + "eapApplicationsHeading": "EAP Application", "eapFormLink": "Start Full EAP", "simplifiedEapLink": "Start sEAP" } diff --git a/app/src/views/EapApplications/index.tsx b/app/src/views/EapApplications/index.tsx index dd9f788b5..130b9f6f1 100644 --- a/app/src/views/EapApplications/index.tsx +++ b/app/src/views/EapApplications/index.tsx @@ -1,39 +1,179 @@ -import { Container } from '@ifrc-go/ui'; +import { + useCallback, + useMemo, + useState, +} from 'react'; +import { + Container, + Table, +} from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; +import { + createDateColumn, + createExpandColumn, + createExpansionIndicatorColumn, + createStringColumn, + numericIdSelector, +} from '@ifrc-go/ui/utils'; import Link from '#components/Link'; +import useFilterState from '#hooks/useFilterState'; +import { + type GoApiResponse, + useRequest, +} from '#utils/restRequest'; + +import Filters, { type FilterValue } from './Filters'; import i18n from './i18n.json'; import styles from './styles.module.css'; +type EapResponse = GoApiResponse<'/api/v2/eap-registration/'>; +type EapListItem = NonNullable[number]; + +type Key = EapListItem['id']; + /** @knipignore */ // eslint-disable-next-line import/prefer-default-export export function Component() { const strings = useTranslation(i18n); + const { + filter, + offset, + limit, + rawFilter, + filtered, + setFilterField, + } = useFilterState({ + filter: {}, + pageSize: 6, + }); + + const { + response: eapResponse, + pending: eapPending, + } = useRequest({ + url: '/api/v2/eap-registration/', + preserveResponse: true, + query: { + offset, + limit, + status: filter.status, + }, + }); + + const [expandedRow, setExpandedRow] = useState(); + const handleExpandClick = useCallback( + (row: EapListItem) => { + setExpandedRow( + (prevValue) => (prevValue?.id === row.id ? undefined : row), + ); + }, + [], + ); + + const baseColumns = useMemo( + () => ([ + createDateColumn( + 'created_at', + 'Last Updated', + (item) => item.created_at, + { columnClassName: styles.date }, + ), + createStringColumn( + 'name', + 'Name/Phase', + (item) => { + const baseYear = new Date(item.created_at).getFullYear(); + let addedYear = baseYear; + if (item.eap_type === 10) { + addedYear = baseYear + 4; + } else if (item.eap_type === 20) { + addedYear = baseYear + 2; + } + return `${item.country_details?.name}: + ${item.disaster_type_details?.name} + ${baseYear} - ${addedYear}`; + }, + { columnClassName: styles.title }, + ), + createStringColumn( + 'eap_type_display', + 'EAP Type', + (item) => item.eap_type_display, + { columnClassName: styles.type }, + ), + createStringColumn( + 'status_display', + 'Status', + (item) => item.status_display, + { columnClassName: styles.status }, + ), + ]), + [], + ); + + const columns = useMemo( + () => ([ + createExpansionIndicatorColumn(false), + ...baseColumns, + createExpandColumn( + 'expandRow', + '', + (row) => ({ + onClick: handleExpandClick, + expanded: row.id === expandedRow?.id, + }), + ), + ]), + [baseColumns, handleExpandClick, expandedRow], + ); return ( + )} + actions={( + <> + + {strings.eapRegistrationLink} + + {/* TODO: Move this to table action + + {strings.eapFormLink} + + + {strings.simplifiedEapLink} + + */} + + )} > {/* FIXME: Add eap registration link */} - - {strings.eapRegistrationLink} - - - {strings.eapFormLink} - - - {strings.simplifiedEapLink} - + ); } diff --git a/app/src/views/EapApplications/styles.module.css b/app/src/views/EapApplications/styles.module.css index c111ea1e6..2e3cef3c6 100644 --- a/app/src/views/EapApplications/styles.module.css +++ b/app/src/views/EapApplications/styles.module.css @@ -1,6 +1,10 @@ .eap-form-links { - display: flex; - align-items: center; - justify-content: center; - gap: var(--go-ui-spacing-sm); + .table { + .type, + .status, + .date { + width: 0%; + min-width: 7rem; + } + } } \ No newline at end of file From 6a0745a379b6ac66aafff5e5d9102a2fb62a5a46 Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Wed, 15 Oct 2025 15:43:29 +0545 Subject: [PATCH 05/19] eap(eap-registration-form): Add EAP Registration Form --- app/src/views/EapRegistration/i18n.json | 42 +- app/src/views/EapRegistration/index.tsx | 406 +++++++++++++++++- app/src/views/EapRegistration/schema.ts | 56 +++ .../views/EapRegistration/styles.module.css | 22 + 4 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 app/src/views/EapRegistration/schema.ts create mode 100644 app/src/views/EapRegistration/styles.module.css diff --git a/app/src/views/EapRegistration/i18n.json b/app/src/views/EapRegistration/i18n.json index 9b7a06dd0..de60e6530 100644 --- a/app/src/views/EapRegistration/i18n.json +++ b/app/src/views/EapRegistration/i18n.json @@ -2,6 +2,46 @@ "namespace": "eapRegistration", "strings": { "eapRegistrationHeading": "EAP Development Registration", - "eapRegistrationDescription": "The purpose of this form is for you to notify the IFRC team of the start of your EAP process. If you need assistance with the initiation of the process, send us a message." + "eapRegistrationDescription": "Use the following page to submit your National Society's interest in following the Early Action Protocol (EAP) application process. Once you submit this form, the EAP team members will contact you to start the procedure. You can find more information on the process and different ways to apply {link}", + "eapRegistrationLink": "on this page.", + "eapApplicationDetails": "Application Details", + "eapNationalSociety": "National Society (NS)", + "eapNationalSocietyDescription": "Select National Society that is planning to apply for the EAP", + "eapCountry": "Country", + "eapCountryDescription": "The country will be pre-populated based on the NS selection, but can be adapted as needed.", + "eapDisasterType": "Disaster Type", + "eapDisasterTypeDescription": "Select the disaster type for which the EAP is needed.", + "eapType": "EAP Type", + "eapTypeDescription": "Select the EAP type. Find details of both under this link.", + "eapSubmission": "Expected Time of Submission", + "eapSubmissionDescription": "Include the proposed time of submission, accounting for the time it will take to deliver the application.", + "eapPartnersInvolved": "Partners Involved", + "eapPartnersInvolvedDescription": "Select from the list the partners involved in this process. Add as many as needed or select not applicable if no partners involved.", + "eapContacts": "Contacts", + "eapNSContact": "National Society Contact", + "eapNSContactDescription": "National Society contact responsible for the EAP process", + "eapNSName": "Name", + "eapNSTitle": "Title", + "eapNSEmail": "Email", + "eapNSPhoneNumber": "Phone Number", + "eapIFRCContact": "IFRC Contact", + "eapIFRCContactDescription": "The most senior staff in the National Society responsible and knowledgable about the disaster event.", + "eapIFRCName": "Name", + "eapIFRCTitle": "Title", + "eapIFRCEmail": "Email", + "eapIFRCPhoneNumber": "Phone Number", + "eapFocalPoint": "DREF Focal Point", + "eapFocalPointDescription": "The DREF contact person form IFRC", + "eapFocalPointName": "Name", + "eapFocalPointTitle": "Title", + "eapFocalPointEmail": "Email", + "eapFocalPointPhoneNumber": "Phone Number", + "eapSubmitButton": "Submit", + "eapCancelButton": "Cancel", + "eapRegistrationFailure": "Sorry could not register new EAP right now!", + "eapRegistrationSuccess": "Successfully created a new EAP!", + "eapNotSure": "Not Sure", + "eapDevelopmentRegistrationHeading": "EAP Development Registration", + "eapDevelopmentRegistrationDescription": "Thank you for notifying us about the start of your EAP process. We look forward to your completed application." } } diff --git a/app/src/views/EapRegistration/index.tsx b/app/src/views/EapRegistration/index.tsx index 31698b3c5..3e3e08b4f 100644 --- a/app/src/views/EapRegistration/index.tsx +++ b/app/src/views/EapRegistration/index.tsx @@ -1,21 +1,421 @@ +import { + type ElementRef, + useCallback, + useRef, +} from 'react'; +import { + ConfirmButton, + Container, + DateInput, + InputSection, + Radio, + RadioInput, + TextInput, +} from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; +import { + resolveToComponent, + stringValueSelector, +} from '@ifrc-go/ui/utils'; +import { + createSubmitHandler, + getErrorObject, + useForm, +} from '@togglecorp/toggle-form'; +import CountrySelectInput from '#components/domain/CountrySelectInput'; +import DisasterTypeSelectInput from '#components/domain/DisasterTypeSelectInput'; +import NationalSocietyMultiSelectInput from '#components/domain/NationalSocietyMultiSelectInput'; +import NationalSocietySelectInput from '#components/domain/NationalSocietySelectInput'; +import Link from '#components/Link'; import Page from '#components/Page'; +import useGlobalEnums from '#hooks/domain/useGlobalEnums'; +import useAlert from '#hooks/useAlert'; +import useRouting from '#hooks/useRouting'; +import { + type GoApiBody, + type GoApiResponse, + useLazyRequest, +} from '#utils/restRequest'; +import { transformObjectError } from '#utils/restRequest/error'; + +import { + defaultFormValue, + formSchema, +} from './schema'; import i18n from './i18n.json'; +import styles from './styles.module.css'; + +type EapRegisterRequestBody = GoApiBody<'/api/v2/eap-registration/', 'POST'>; +type GlobalEnumsResponse = GoApiResponse<'/api/v2/global-enums/'>; +type EapTypeOption = NonNullable[number]; + +function eapTypeKeySelector(option: EapTypeOption) { + return option.key; +} /** @knipignore */ // eslint-disable-next-line import/prefer-default-export export function Component() { const strings = useTranslation(i18n); + const alert = useAlert(); + const { navigate } = useRouting(); + + const { + value, + setFieldValue, + error: formError, + setError, + validate, + } = useForm(formSchema, { value: defaultFormValue }); + + const { + eap_eap_type: eapFormOptions, + } = useGlobalEnums(); + + const error = getErrorObject(formError); + const formContentRef = useRef>(null); + + const { + pending: eapRegistrationPending, + trigger: eapRegister, + } = useLazyRequest({ + method: 'POST', + url: '/api/v2/eap-registration/', + body: (body: EapRegisterRequestBody) => body, + onSuccess: () => { + const message = strings.eapRegistrationSuccess; + alert.show( + message, + { variant: 'success' }, + ); + navigate('accountMyFormsEap'); + }, + onFailure: (err) => { + const { + value: { + formErrors, + }, + } = err; + + setError(transformObjectError(formErrors, () => undefined)); + + alert.show( + strings.eapRegistrationFailure, + { variant: 'danger' }, + ); + }, + }); + + const handleCountryChange = useCallback( + (val: number | undefined, name: 'country') => { + setFieldValue(val, name); + }, + [setFieldValue], + ); + + const handleEapTypeClick = useCallback(() => { + setFieldValue(undefined, 'eap_type'); + }, [setFieldValue]); + + const handleSubmissionTimeClick = useCallback(() => { + setFieldValue(undefined, 'expected_submission_time'); + }, [setFieldValue]); + + const eapRegistration = useCallback(() => { + const handler = createSubmitHandler( + validate, + setError, + (formValues) => { + eapRegister(formValues as EapRegisterRequestBody); + }, + ); + handler(); + }, [ + setError, + validate, + eapRegister, + ]); + + const handleFormError = useCallback(() => { + setTimeout(() => formContentRef.current?.scrollIntoView(), 200); + }, []); + + const handleEapRegistration = useCallback(() => { + const handler = createSubmitHandler( + validate, + setError, + eapRegistration, + handleFormError, + ); + handler(); + }, [ + handleFormError, + eapRegistration, + validate, + setError, + ]); + + const disabled = eapRegistrationPending; return ( + {strings.eapRegistrationLink} + + ), + }, + )} + actions={( + + {strings.eapCancelButton} + + )} + withBackgroundColorInMainSection > - {/* TODO: Add the form */} - Application Details +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + {strings.eapSubmitButton} + +
); } diff --git a/app/src/views/EapRegistration/schema.ts b/app/src/views/EapRegistration/schema.ts new file mode 100644 index 000000000..640e0b1cb --- /dev/null +++ b/app/src/views/EapRegistration/schema.ts @@ -0,0 +1,56 @@ +import { + emailCondition, + type ObjectSchema, + type PartialForm, +} from '@togglecorp/toggle-form'; + +import { type GoApiBody } from '#utils/restRequest'; + +type EapRegisterRequestBody = GoApiBody<'/api/v2/eap-registration/', 'POST'>; + +export const defaultFormValue: FormFields = { + eap_type: null, + expected_submission_time: null, +}; + +type FormFields = PartialForm; + +type FormSchema = ObjectSchema; +type FormSchemaFields = ReturnType + +export const formSchema: FormSchema = { + fields: (): FormSchemaFields => ({ + national_society: { + required: true, + }, + country: { + required: true, + }, + disaster_type: { + required: true, + }, + eap_type: {}, + expected_submission_time: {}, + partners: { + required: true, + }, + national_society_contact_name: {}, + national_society_contact_title: {}, + national_society_contact_email: { + validations: [emailCondition], + }, + national_society_contact_phone_number: {}, + ifrc_contact_name: {}, + ifrc_contact_title: {}, + ifrc_contact_email: { + validations: [emailCondition], + }, + ifrc_contact_phone_number: {}, + dref_focal_point_name: {}, + dref_focal_point_title: {}, + dref_focal_point_email: { + validations: [emailCondition], + }, + dref_focal_point_phone_number: {}, + }), +}; diff --git a/app/src/views/EapRegistration/styles.module.css b/app/src/views/EapRegistration/styles.module.css new file mode 100644 index 000000000..a6479f54a --- /dev/null +++ b/app/src/views/EapRegistration/styles.module.css @@ -0,0 +1,22 @@ +.eap-registration-form { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-md); + + .content { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-md); + + .radio-content { + display: flex; + gap: var(--go-ui-spacing-md); + } + } + + .footer { + display: flex; + align-items: center; + justify-content: center; + } +} From ac6555ff0263f2c5f6bbbc44c550658485491e92 Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Mon, 10 Nov 2025 16:54:31 +0545 Subject: [PATCH 06/19] eap-table(table): Add eap development table --- app/src/App/routes/index.tsx | 80 +++++++++++++--- .../EapApplications/EapTableActions/i18n.json | 8 ++ .../EapApplications/EapTableActions/index.tsx | 48 ++++++++++ app/src/views/EapApplications/i18n.json | 4 +- app/src/views/EapApplications/index.tsx | 93 ++++++++++++++----- .../views/EapApplications/styles.module.css | 5 + 6 files changed, 195 insertions(+), 43 deletions(-) create mode 100644 app/src/views/EapApplications/EapTableActions/i18n.json create mode 100644 app/src/views/EapApplications/EapTableActions/index.tsx diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index 3c4c91523..00fa1594a 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -1182,6 +1182,68 @@ const fieldReportDetails = customWrapRoute({ }, }); +type DefaultEapRegistrationChild = 'new'; +const eapRegistrationLayout = customWrapRoute({ + parent: rootLayout, + path: 'eap-registration', + forwardPath: 'new' satisfies DefaultEapRegistrationChild, + component: { + render: () => import('#views/EapRegistration'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'EAP Process', + visibility: 'is-authenticated', + }, +}); + +const eapRegistrationFormIndex = customWrapRoute({ + parent: eapRegistrationLayout, + index: true, + component: { + eagerLoad: true, + render: Navigate, + props: { + to: 'new' satisfies DefaultPerProcessChild, + replace: true, + }, + }, + context: { + title: 'EAP Registration', + visibility: 'anything', + }, +}); + +const eapDevelopmentRegistration = customWrapRoute({ + parent: eapRegistrationLayout, + path: 'new' satisfies DefaultEapRegistrationChild, + component: { + render: () => import('#views/EapRegistration'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'New EAP Development Registration', + visibility: 'is-authenticated', + permissions: ({ isGuestUser }) => !isGuestUser, + }, +}); + +const eapDevelopmentRegistrationForm = customWrapRoute({ + parent: eapRegistrationLayout, + path: ':eapId/view', + component: { + render: () => import('#views/EapRegistration'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'View EAP Overview', + visibility: 'is-authenticated', + }, +}); + type DefaultPerProcessChild = 'new'; const perProcessLayout = customWrapRoute({ parent: rootLayout, @@ -1234,21 +1296,6 @@ const newPerOverviewForm = customWrapRoute({ }, }); -const eapDevelopmentRegistration = customWrapRoute({ - parent: rootLayout, - path: 'eap-registration/new', - component: { - render: () => import('#views/EapRegistration'), - props: {}, - }, - wrapperComponent: Auth, - context: { - title: 'EAP Development Registration', - visibility: 'is-authenticated', - permissions: ({ isGuestUser }) => !isGuestUser, - }, -}); - const perOverviewForm = customWrapRoute({ parent: perProcessLayout, path: ':perId/overview', @@ -1473,6 +1520,9 @@ const wrappedRoutes = { drefDetail, eapDetail, drefProcessLayout, + eapRegistrationLayout, + eapDevelopmentRegistrationForm, + eapRegistrationFormIndex, }; export const unwrappedRoutes = unwrapRoute(Object.values(wrappedRoutes)); diff --git a/app/src/views/EapApplications/EapTableActions/i18n.json b/app/src/views/EapApplications/EapTableActions/i18n.json new file mode 100644 index 000000000..dcacb9754 --- /dev/null +++ b/app/src/views/EapApplications/EapTableActions/i18n.json @@ -0,0 +1,8 @@ +{ + "namespace":"accountMyFormsEap", + "strings":{ + "eapViewLabel": "View", + "eapFormLink": "Start Full EAP", + "simplifiedEapLink": "Start sEAP" + } +} diff --git a/app/src/views/EapApplications/EapTableActions/index.tsx b/app/src/views/EapApplications/EapTableActions/index.tsx new file mode 100644 index 000000000..24039a845 --- /dev/null +++ b/app/src/views/EapApplications/EapTableActions/index.tsx @@ -0,0 +1,48 @@ +import { TableActions } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import DropdownMenuItem from '#components/DropdownMenuItem'; +import Link from '#components/Link'; + +import i18n from './i18n.json'; + +export interface Props { + eapId: number; +} + +function EapTableActions(props: Props) { + const { eapId } = props; + + const strings = useTranslation(i18n); + + return ( + + {strings.eapViewLabel} + + )} + > + + {strings.eapFormLink} + + + {strings.simplifiedEapLink} + + + ); +} + +export default EapTableActions; diff --git a/app/src/views/EapApplications/i18n.json b/app/src/views/EapApplications/i18n.json index 72be7b0f2..2c28c7659 100644 --- a/app/src/views/EapApplications/i18n.json +++ b/app/src/views/EapApplications/i18n.json @@ -2,8 +2,6 @@ "namespace": "eapApplication", "strings": { "eapRegistrationLink": "Register Your EAP", - "eapApplicationsHeading": "EAP Application", - "eapFormLink": "Start Full EAP", - "simplifiedEapLink": "Start sEAP" + "eapApplicationsHeading": "EAP Application" } } diff --git a/app/src/views/EapApplications/index.tsx b/app/src/views/EapApplications/index.tsx index 130b9f6f1..c6efb434c 100644 --- a/app/src/views/EapApplications/index.tsx +++ b/app/src/views/EapApplications/index.tsx @@ -5,11 +5,15 @@ import { } from 'react'; import { Container, + type RowOptions, Table, + TableBodyContent, } from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; import { createDateColumn, + createElementColumn, + createEmptyColumn, createExpandColumn, createExpansionIndicatorColumn, createStringColumn, @@ -23,6 +27,7 @@ import { useRequest, } from '#utils/restRequest'; +import EapTableActions, { type Props as EapTableActionProps } from './EapTableActions'; import Filters, { type FilterValue } from './Filters'; import i18n from './i18n.json'; @@ -37,6 +42,7 @@ type Key = EapListItem['id']; // eslint-disable-next-line import/prefer-default-export export function Component() { const strings = useTranslation(i18n); + const { filter, offset, @@ -113,7 +119,7 @@ export function Component() { [], ); - const columns = useMemo( + const aggregatedColumns = useMemo( () => ([ createExpansionIndicatorColumn(false), ...baseColumns, @@ -129,6 +135,59 @@ export function Component() { [baseColumns, handleExpandClick, expandedRow], ); + const detailColumns = useMemo( + () => ([ + createExpansionIndicatorColumn(true), + createStringColumn( + 'title', + '', + () => 'EAP Registration', + { columnClassName: styles.detailTitle }, + ), + createElementColumn( + 'actions', + 'EAP Registration', + EapTableActions, + (eapId) => ({ + eapId, + }), + ), + createEmptyColumn(), + createEmptyColumn(), + createEmptyColumn(), + ]), + [], + ); + + const rowModifier = useCallback( + ({ row, datum }: RowOptions) => { + if (datum.id !== expandedRow?.id) { + return row; + } + + const subRows = eapResponse?.results?.filter( + (subRow) => subRow.id === datum.id, + ); + + return ( + <> + {row} + + + ); + }, + [ + expandedRow, + detailColumns, + eapResponse, + ], + ); + return ( )} actions={( - <> - - {strings.eapRegistrationLink} - - {/* TODO: Move this to table action - - {strings.eapFormLink} - - - {strings.simplifiedEapLink} - - */} - + + {strings.eapRegistrationLink} + )} > - {/* FIXME: Add eap registration link */}
Date: Wed, 12 Nov 2025 16:22:37 +0545 Subject: [PATCH 07/19] eap(lisitng): Add eap listing page --- app/src/App/routes/index.tsx | 26 +-- app/src/utils/constants.ts | 3 + .../EapApplications/EapTableActions/i18n.json | 1 + .../EapApplications/EapTableActions/index.tsx | 25 ++- app/src/views/EapApplications/i18n.json | 7 +- app/src/views/EapApplications/index.tsx | 66 ++++--- .../views/EapApplications/styles.module.css | 7 +- app/src/views/EapRegistration/i18n.json | 6 +- app/src/views/EapRegistration/index.tsx | 164 +++++++++++++++--- app/src/views/EapRegistration/schema.ts | 4 +- app/src/views/EarlyActionProtocols/index.tsx | 2 +- 11 files changed, 223 insertions(+), 88 deletions(-) diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index 00fa1594a..0fb0e0e86 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -1198,24 +1198,7 @@ const eapRegistrationLayout = customWrapRoute({ }, }); -const eapRegistrationFormIndex = customWrapRoute({ - parent: eapRegistrationLayout, - index: true, - component: { - eagerLoad: true, - render: Navigate, - props: { - to: 'new' satisfies DefaultPerProcessChild, - replace: true, - }, - }, - context: { - title: 'EAP Registration', - visibility: 'anything', - }, -}); - -const eapDevelopmentRegistration = customWrapRoute({ +const newEapDevelopmentRegistration = customWrapRoute({ parent: eapRegistrationLayout, path: 'new' satisfies DefaultEapRegistrationChild, component: { @@ -1232,14 +1215,14 @@ const eapDevelopmentRegistration = customWrapRoute({ const eapDevelopmentRegistrationForm = customWrapRoute({ parent: eapRegistrationLayout, - path: ':eapId/view', + path: ':eapId/', component: { render: () => import('#views/EapRegistration'), props: {}, }, wrapperComponent: Auth, context: { - title: 'View EAP Overview', + title: 'View EAP', visibility: 'is-authenticated', }, }); @@ -1504,7 +1487,7 @@ const wrappedRoutes = { termsAndConditions, operationalLearning, montandonLandingPage, - eapDevelopmentRegistration, + newEapDevelopmentRegistration, eapFullForm, simplifiedEapForm, ...regionRoutes, @@ -1522,7 +1505,6 @@ const wrappedRoutes = { drefProcessLayout, eapRegistrationLayout, eapDevelopmentRegistrationForm, - eapRegistrationFormIndex, }; export const unwrappedRoutes = unwrapRoute(Object.values(wrappedRoutes)); diff --git a/app/src/utils/constants.ts b/app/src/utils/constants.ts index 8d3ec82ce..453939d3e 100644 --- a/app/src/utils/constants.ts +++ b/app/src/utils/constants.ts @@ -199,3 +199,6 @@ export const multiMonthSelectDefaultValue = listToMap( export const ERU_READINESS_READY = 1; export const ERU_READINESS_CAN_CONTRIBUTE = 2; export const ERU_READINESS_NO_CAPACITY = 3; + +export const EAP_TYPE_SIMPLIFIED = 20; +export const EAP_TYPE_FULL = 10; diff --git a/app/src/views/EapApplications/EapTableActions/i18n.json b/app/src/views/EapApplications/EapTableActions/i18n.json index dcacb9754..35e2dc216 100644 --- a/app/src/views/EapApplications/EapTableActions/i18n.json +++ b/app/src/views/EapApplications/EapTableActions/i18n.json @@ -2,6 +2,7 @@ "namespace":"accountMyFormsEap", "strings":{ "eapViewLabel": "View", + "eapEditLabel": "Edit", "eapFormLink": "Start Full EAP", "simplifiedEapLink": "Start sEAP" } diff --git a/app/src/views/EapApplications/EapTableActions/index.tsx b/app/src/views/EapApplications/EapTableActions/index.tsx index 24039a845..1294b0098 100644 --- a/app/src/views/EapApplications/EapTableActions/index.tsx +++ b/app/src/views/EapApplications/EapTableActions/index.tsx @@ -18,13 +18,24 @@ function EapTableActions(props: Props) { return ( - {strings.eapViewLabel} - + <> + + {strings.eapViewLabel} + + + {strings.eapEditLabel} + + )} > ; type EapListItem = NonNullable[number]; type Key = EapListItem['id']; +const ITEM_PER_PAGE = 6; /** @knipignore */ // eslint-disable-next-line import/prefer-default-export @@ -50,9 +56,11 @@ export function Component() { rawFilter, filtered, setFilterField, + page, + setPage, } = useFilterState({ filter: {}, - pageSize: 6, + pageSize: ITEM_PER_PAGE, }); const { @@ -82,19 +90,18 @@ export function Component() { () => ([ createDateColumn( 'created_at', - 'Last Updated', + strings.eapLastUpdated, (item) => item.created_at, - { columnClassName: styles.date }, ), createStringColumn( 'name', - 'Name/Phase', + strings.eapName, (item) => { const baseYear = new Date(item.created_at).getFullYear(); let addedYear = baseYear; - if (item.eap_type === 10) { - addedYear = baseYear + 4; - } else if (item.eap_type === 20) { + if (item.eap_type === EAP_TYPE_FULL) { + addedYear = baseYear + 5; + } else if (item.eap_type === EAP_TYPE_SIMPLIFIED) { addedYear = baseYear + 2; } return `${item.country_details?.name}: @@ -105,24 +112,15 @@ export function Component() { ), createStringColumn( 'eap_type_display', - 'EAP Type', + strings.eapType, (item) => item.eap_type_display, - { columnClassName: styles.type }, ), createStringColumn( 'status_display', - 'Status', + strings.eapStatus, (item) => item.status_display, { columnClassName: styles.status }, ), - ]), - [], - ); - - const aggregatedColumns = useMemo( - () => ([ - createExpansionIndicatorColumn(false), - ...baseColumns, createExpandColumn( 'expandRow', '', @@ -132,7 +130,14 @@ export function Component() { }), ), ]), - [baseColumns, handleExpandClick, expandedRow], + [ + strings.eapLastUpdated, + strings.eapName, + strings.eapType, + strings.eapStatus, + expandedRow, + handleExpandClick, + ], ); const detailColumns = useMemo( @@ -141,22 +146,21 @@ export function Component() { createStringColumn( 'title', '', - () => 'EAP Registration', + () => strings.eapRegistration, { columnClassName: styles.detailTitle }, ), + createEmptyColumn(), createElementColumn( 'actions', - 'EAP Registration', + '', EapTableActions, (eapId) => ({ eapId, }), ), createEmptyColumn(), - createEmptyColumn(), - createEmptyColumn(), ]), - [], + [strings.eapRegistration], ); const rowModifier = useCallback( @@ -193,7 +197,7 @@ export function Component() { childrenContainerClassName={styles.eapFormLinks} heading={strings.eapApplicationsHeading} withHeaderBorder - filters={( + filters={(eapResponse?.count ?? 0) > 0 && ( {strings.eapRegistrationLink} )} + footerActions={( + + )} >
(); + + const { state } = useLocation(); + const eapId = eapIdFromParams ?? state?.eapId as string | undefined; + const isReadOnly = state?.mode === 'view'; const { value, setFieldValue, error: formError, setError, + setValue, validate, } = useForm(formSchema, { value: defaultFormValue }); @@ -77,6 +94,23 @@ export function Component() { const error = getErrorObject(formError); const formContentRef = useRef>(null); + const { + pending: fetchingEap, + error: eapError, + } = useRequest({ + skip: isNotDefined(eapId), + url: '/api/v2/eap-registration/{id}/', + pathVariables: isTruthyString(eapId) ? { + id: Number(eapId), + } : undefined, + onSuccess: (response) => { + const { + ...formValues + } = response; + setValue(formValues); + }, + }); + const { pending: eapRegistrationPending, trigger: eapRegister, @@ -108,6 +142,49 @@ export function Component() { }, }); + const { + pending: updateEapRegistrationPending, + trigger: updateEapRegistration, + } = useLazyRequest({ + url: '/api/v2/eap-registration/{id}/', + method: 'PATCH', + pathVariables: { + id: Number(eapId), + }, + body: (formFields: EapRegisterRequestBody) => formFields, + onSuccess: (response) => { + alert.show( + strings.eapRegistrationUpdateMessage, + { variant: 'success' }, + ); + navigate( + 'accountMyFormsEap', + { params: { eapId: response.id } }, + ); + }, + onFailure: (err) => { + const { + value: { + formErrors, + messageForNotification, + }, + } = err; + + setError(transformObjectError( + formErrors, + () => undefined, + )); + + alert.show( + strings.eapRegistrationFailureMessage, + { + variant: 'danger', + description: messageForNotification, + }, + ); + }, + }); + const handleCountryChange = useCallback( (val: number | undefined, name: 'country') => { setFieldValue(val, name); @@ -116,19 +193,32 @@ export function Component() { ); const handleEapTypeClick = useCallback(() => { - setFieldValue(undefined, 'eap_type'); - }, [setFieldValue]); + if (isReadOnly) { + return; + } + setFieldValue(null, 'eap_type'); + }, [isReadOnly, setFieldValue]); const handleSubmissionTimeClick = useCallback(() => { - setFieldValue(undefined, 'expected_submission_time'); - }, [setFieldValue]); + if (isReadOnly) { + return; + } + setFieldValue(null, 'expected_submission_time'); + }, [isReadOnly, setFieldValue]); const eapRegistration = useCallback(() => { const handler = createSubmitHandler( validate, setError, (formValues) => { - eapRegister(formValues as EapRegisterRequestBody); + if (isNotDefined(eapId)) { + eapRegister(formValues as EapRegisterRequestBody); + } else { + updateEapRegistration({ + ...formValues, + id: eapId, + } as EapRegisterRequestBody); + } }, ); handler(); @@ -136,6 +226,8 @@ export function Component() { setError, validate, eapRegister, + updateEapRegistration, + eapId, ]); const handleFormError = useCallback(() => { @@ -157,7 +249,15 @@ export function Component() { setError, ]); - const disabled = eapRegistrationPending; + const disabled = eapRegistrationPending || fetchingEap || updateEapRegistrationPending; + + if (isDefined(eapError)) { + return ( + + ); + } return ( - {strings.eapCancelButton} + {eapId ? strings.eapBackButton : strings.eapCancelButton} )} withBackgroundColorInMainSection @@ -201,6 +301,7 @@ export function Component() { onChange={setFieldValue} value={value?.national_society} disabled={disabled} + readOnly={isReadOnly} /> @@ -301,6 +407,7 @@ export function Component() { onChange={setFieldValue} error={error?.national_society_contact_name} disabled={disabled} + readOnly={isReadOnly} />
- - {strings.eapSubmitButton} - + {!isReadOnly && ( + + {strings.eapSubmitButton} + + )}
); diff --git a/app/src/views/EapRegistration/schema.ts b/app/src/views/EapRegistration/schema.ts index 640e0b1cb..17dca9c0b 100644 --- a/app/src/views/EapRegistration/schema.ts +++ b/app/src/views/EapRegistration/schema.ts @@ -9,8 +9,8 @@ import { type GoApiBody } from '#utils/restRequest'; type EapRegisterRequestBody = GoApiBody<'/api/v2/eap-registration/', 'POST'>; export const defaultFormValue: FormFields = { - eap_type: null, - expected_submission_time: null, + eap_type: undefined, + expected_submission_time: undefined, }; type FormFields = PartialForm; diff --git a/app/src/views/EarlyActionProtocols/index.tsx b/app/src/views/EarlyActionProtocols/index.tsx index 3cd1d79d4..fa7d968d3 100644 --- a/app/src/views/EarlyActionProtocols/index.tsx +++ b/app/src/views/EarlyActionProtocols/index.tsx @@ -21,7 +21,7 @@ export function Component() { childrenContainerClassName={styles.content} actions={( {strings.eapRegistrationLink} From 6767eea23215d1f78a029e317b07f5e3541305e2 Mon Sep 17 00:00:00 2001 From: Shreeyash Shrestha Date: Tue, 18 Nov 2025 11:56:11 +0545 Subject: [PATCH 08/19] fix(eap): update eap section according to new UI --- app/src/App/routes/index.tsx | 2 +- .../EapTableActions/i18n.json | 0 .../EapTableActions/index.tsx | 6 +- .../Filters/i18n.json | 0 .../Filters/index.tsx | 0 .../i18n.json | 0 .../index.tsx | 12 +-- app/src/views/DrefDetail/index.tsx | 76 ++++++++++--------- app/src/views/DrefDetail/styles.module.css | 11 --- .../views/EapApplications/styles.module.css | 12 --- app/src/views/EapRegistration/index.tsx | 42 ++++++---- .../views/EapRegistration/styles.module.css | 23 +----- app/src/views/EarlyActionProtocols/index.tsx | 74 ++++++++++-------- .../EarlyActionProtocols/styles.module.css | 11 --- 14 files changed, 118 insertions(+), 151 deletions(-) rename app/src/views/{EapApplications => AccountMyFormsEap}/EapTableActions/i18n.json (100%) rename app/src/views/{EapApplications => AccountMyFormsEap}/EapTableActions/index.tsx (91%) rename app/src/views/{EapApplications => AccountMyFormsEap}/Filters/i18n.json (100%) rename app/src/views/{EapApplications => AccountMyFormsEap}/Filters/index.tsx (100%) rename app/src/views/{EapApplications => AccountMyFormsEap}/i18n.json (100%) rename app/src/views/{EapApplications => AccountMyFormsEap}/index.tsx (93%) delete mode 100644 app/src/views/DrefDetail/styles.module.css delete mode 100644 app/src/views/EapApplications/styles.module.css delete mode 100644 app/src/views/EarlyActionProtocols/styles.module.css diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index 0fb0e0e86..799bcdf31 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -763,7 +763,7 @@ const accountMyFormsEap = customWrapRoute({ parent: accountMyFormsLayout, path: 'eap-applications', component: { - render: () => import('#views/EapApplications'), + render: () => import('#views/AccountMyFormsEap'), props: {}, }, context: { diff --git a/app/src/views/EapApplications/EapTableActions/i18n.json b/app/src/views/AccountMyFormsEap/EapTableActions/i18n.json similarity index 100% rename from app/src/views/EapApplications/EapTableActions/i18n.json rename to app/src/views/AccountMyFormsEap/EapTableActions/i18n.json diff --git a/app/src/views/EapApplications/EapTableActions/index.tsx b/app/src/views/AccountMyFormsEap/EapTableActions/index.tsx similarity index 91% rename from app/src/views/EapApplications/EapTableActions/index.tsx rename to app/src/views/AccountMyFormsEap/EapTableActions/index.tsx index 1294b0098..bf783af4c 100644 --- a/app/src/views/EapApplications/EapTableActions/index.tsx +++ b/app/src/views/AccountMyFormsEap/EapTableActions/index.tsx @@ -41,14 +41,16 @@ function EapTableActions(props: Props) { {strings.eapFormLink} {strings.simplifiedEapLink} diff --git a/app/src/views/EapApplications/Filters/i18n.json b/app/src/views/AccountMyFormsEap/Filters/i18n.json similarity index 100% rename from app/src/views/EapApplications/Filters/i18n.json rename to app/src/views/AccountMyFormsEap/Filters/i18n.json diff --git a/app/src/views/EapApplications/Filters/index.tsx b/app/src/views/AccountMyFormsEap/Filters/index.tsx similarity index 100% rename from app/src/views/EapApplications/Filters/index.tsx rename to app/src/views/AccountMyFormsEap/Filters/index.tsx diff --git a/app/src/views/EapApplications/i18n.json b/app/src/views/AccountMyFormsEap/i18n.json similarity index 100% rename from app/src/views/EapApplications/i18n.json rename to app/src/views/AccountMyFormsEap/i18n.json diff --git a/app/src/views/EapApplications/index.tsx b/app/src/views/AccountMyFormsEap/index.tsx similarity index 93% rename from app/src/views/EapApplications/index.tsx rename to app/src/views/AccountMyFormsEap/index.tsx index 7e92dea62..35a643200 100644 --- a/app/src/views/EapApplications/index.tsx +++ b/app/src/views/AccountMyFormsEap/index.tsx @@ -36,7 +36,6 @@ import EapTableActions, { type Props as EapTableActionProps } from './EapTableAc import Filters, { type FilterValue } from './Filters'; import i18n from './i18n.json'; -import styles from './styles.module.css'; type EapResponse = GoApiResponse<'/api/v2/eap-registration/'>; type EapListItem = NonNullable[number]; @@ -108,7 +107,6 @@ export function Component() { ${item.disaster_type_details?.name} ${baseYear} - ${addedYear}`; }, - { columnClassName: styles.title }, ), createStringColumn( 'eap_type_display', @@ -119,7 +117,6 @@ export function Component() { 'status_display', strings.eapStatus, (item) => item.status_display, - { columnClassName: styles.status }, ), createExpandColumn( 'expandRow', @@ -147,7 +144,6 @@ export function Component() { 'title', '', () => strings.eapRegistration, - { columnClassName: styles.detailTitle }, ), createEmptyColumn(), createElementColumn( @@ -180,7 +176,6 @@ export function Component() { keySelector={numericIdSelector} data={subRows} columns={detailColumns} - cellClassName={styles.subCell} /> ); @@ -194,7 +189,6 @@ export function Component() { return ( 0 && ( @@ -203,10 +197,11 @@ export function Component() { onChange={setFilterField} /> )} - actions={( + headerActions={( {strings.eapRegistrationLink} @@ -221,7 +216,6 @@ export function Component() { )} >
- + -
{strings.drefIntroDetailOne}
-
{strings.drefIntroDetailTwo}
-
- -
{strings.drefProcessSubHeading}
-
    -
  • - {strings.drefProcessListOne} -
  • -
  • - {strings.drefProcessListTwo} -
  • -
-
- {strings.drefProcessDetailOne} -
-
- {strings.drefProcessDetailTwo} -
-
- - {strings.drefDrefApplication} - + +
{strings.drefIntroDetailOne}
+
{strings.drefIntroDetailTwo}
+
+ +
{strings.drefProcessSubHeading}
+
    +
  • + {strings.drefProcessListOne} +
  • +
  • + {strings.drefProcessListTwo} +
  • +
+
+ {strings.drefProcessDetailOne} +
+
+ {strings.drefProcessDetailTwo} +
+
+ + {strings.drefDrefApplication} + + ); } diff --git a/app/src/views/DrefDetail/styles.module.css b/app/src/views/DrefDetail/styles.module.css deleted file mode 100644 index c850c0ee2..000000000 --- a/app/src/views/DrefDetail/styles.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.dref-detail { - display: flex; - flex-direction: column; - padding: var(--go-ui-spacing-2xl) 0; - - .content { - display: flex; - flex-direction: column; - gap: var(--go-ui-spacing-xl); - } -} diff --git a/app/src/views/EapApplications/styles.module.css b/app/src/views/EapApplications/styles.module.css deleted file mode 100644 index f78eeb90a..000000000 --- a/app/src/views/EapApplications/styles.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.eap-form-links { - .table { - .status { - width: 0%; - min-width: 19rem; - } - .sub-cell { - border-bottom: unset; - background-color: var(--go-ui-color-gray-20); - } - } -} \ No newline at end of file diff --git a/app/src/views/EapRegistration/index.tsx b/app/src/views/EapRegistration/index.tsx index c776ad9da..16d65dc60 100644 --- a/app/src/views/EapRegistration/index.tsx +++ b/app/src/views/EapRegistration/index.tsx @@ -12,6 +12,7 @@ import { Container, DateInput, InputSection, + ListView, Radio, RadioInput, TextInput, @@ -261,7 +262,6 @@ export function Component() { return ( {eapId ? strings.eapBackButton : strings.eapCancelButton} )} + elementRef={formContentRef} withBackgroundColorInMainSection > -
- + + {/* FIXME: label is not showing */} + {/* FIXME: label is not showing */} - - + + + - -
+ +
{!isReadOnly && ( {strings.eapRegistrationLink} )} > - -

- {strings.eapContentHeading} -

-

- {strings.eapContentSubHeadingOne} -

    -
  • - {strings.eapDescriptionOne} -
  • -
  • - {strings.eapDescriptionTwo} -
  • -
  • - {strings.eapDescriptionThree} -
  • -
-

-
- {/* TODO: Add remaining content */} - - + +

+ {strings.eapContentHeading} +

+

+ {strings.eapContentSubHeadingOne} +

    +
  • + {strings.eapDescriptionOne} +
  • +
  • + {strings.eapDescriptionTwo} +
  • +
  • + {strings.eapDescriptionThree} +
  • +
+

+
+ {/* TODO: Add remaining content */} + + {/* TODO: Add real content and replace with strings */} + EAP content sub heading description two + + + {/* TODO: Add real content and replace with strings */} + EAP content sub heading description three + + ); } diff --git a/app/src/views/EarlyActionProtocols/styles.module.css b/app/src/views/EarlyActionProtocols/styles.module.css deleted file mode 100644 index 4df12fe2b..000000000 --- a/app/src/views/EarlyActionProtocols/styles.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.eap-detail { - display: flex; - flex-direction: column; - padding: var(--go-ui-spacing-2xl) 0; - - .content { - display: flex; - flex-direction: column; - gap: var(--go-ui-spacing-lg); - } -} From 4395c69c11ed29aa0b761d5e32f2298ad34faf6e Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Tue, 28 Oct 2025 16:00:13 +0545 Subject: [PATCH 09/19] feat(eap): add simplified EAP form - Update DREF table actions --- app/src/App/routes/index.tsx | 11 +- .../domain/GoMultiFileInput/index.tsx | 2 +- .../domain/GoSingleFileInput/index.tsx | 2 +- .../domain/ImageWithCaptionInput/index.tsx | 2 +- .../MultiImageWithCaptionInput/index.tsx | 2 +- app/src/utils/constants.ts | 14 +- .../EapTableActions/i18n.json | 6 +- .../EapTableActions/index.tsx | 69 +- app/src/views/AccountMyFormsEap/index.tsx | 19 +- app/src/views/DrefApplicationForm/index.tsx | 1 + app/src/views/EapRegistration/index.tsx | 49 +- .../DeliveryAndBudget/i18n.json | 21 + .../DeliveryAndBudget/index.tsx | 152 ++++ .../DistrictMapModal/DistrictItem/i18n.json | 7 + .../DistrictMapModal/DistrictItem/index.tsx | 106 +++ .../DistrictItem/styles.module.css | 56 ++ .../DistrictMap/DistrictMapModal/i18n.json | 11 + .../DistrictMap/DistrictMapModal/index.tsx | 688 ++++++++++++++++++ .../DistrictMapModal/styles.module.css | 36 + .../EarlyAction/DistrictMap/i18n.json | 6 + .../EarlyAction/DistrictMap/index.tsx | 127 ++++ .../EarlyAction/DistrictMap/styles.module.css | 14 + .../SimplifiedEapForm/EarlyAction/i18n.json | 28 + .../SimplifiedEapForm/EarlyAction/index.tsx | 277 +++++++ .../ApproachesInput/ActivityInput/i18n.json | 8 + .../ApproachesInput/ActivityInput/index.tsx | 166 +++++ .../ApproachesInput/i18n.json | 14 + .../ApproachesInput/index.tsx | 315 ++++++++ .../EnablingApproaches/i18n.json | 7 + .../EnablingApproaches/index.tsx | 169 +++++ .../SimplifiedEapForm/Overview/i18n.json | 69 ++ .../SimplifiedEapForm/Overview/index.tsx | 525 +++++++++++++ .../OperationsInput/ActivityInput/i18n.json | 8 + .../OperationsInput/ActivityInput/index.tsx | 165 +++++ .../OperationsInput/i18n.json | 14 + .../OperationsInput/index.tsx | 317 ++++++++ .../PlannedOperations/i18n.json | 7 + .../PlannedOperations/index.tsx | 167 +++++ .../SimplifiedEapForm/RiskAnalysis/i18n.json | 15 + .../SimplifiedEapForm/RiskAnalysis/index.tsx | 142 ++++ app/src/views/SimplifiedEapForm/common.tsx | 122 ++++ app/src/views/SimplifiedEapForm/i18n.json | 20 + app/src/views/SimplifiedEapForm/index.tsx | 418 ++++++++++- app/src/views/SimplifiedEapForm/schema.ts | 339 +++++++++ .../views/SimplifiedEapForm/styles.module.css | 18 + 45 files changed, 4662 insertions(+), 69 deletions(-) create mode 100644 app/src/views/SimplifiedEapForm/DeliveryAndBudget/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/DeliveryAndBudget/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/DistrictMapModal/DistrictItem/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/DistrictMapModal/DistrictItem/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/DistrictMapModal/DistrictItem/styles.module.css create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/DistrictMapModal/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/DistrictMapModal/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/DistrictMapModal/styles.module.css create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/DistrictMap/styles.module.css create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EarlyAction/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EnablingApproaches/ApproachesInput/ActivityInput/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EnablingApproaches/ApproachesInput/ActivityInput/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EnablingApproaches/ApproachesInput/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EnablingApproaches/ApproachesInput/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/EnablingApproaches/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/EnablingApproaches/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/Overview/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/Overview/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/PlannedOperations/OperationsInput/ActivityInput/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/PlannedOperations/OperationsInput/ActivityInput/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/PlannedOperations/OperationsInput/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/PlannedOperations/OperationsInput/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/PlannedOperations/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/PlannedOperations/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/RiskAnalysis/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/RiskAnalysis/index.tsx create mode 100644 app/src/views/SimplifiedEapForm/common.tsx create mode 100644 app/src/views/SimplifiedEapForm/i18n.json create mode 100644 app/src/views/SimplifiedEapForm/schema.ts create mode 100644 app/src/views/SimplifiedEapForm/styles.module.css diff --git a/app/src/App/routes/index.tsx b/app/src/App/routes/index.tsx index 799bcdf31..75e1e370e 100644 --- a/app/src/App/routes/index.tsx +++ b/app/src/App/routes/index.tsx @@ -773,9 +773,9 @@ const accountMyFormsEap = customWrapRoute({ }, }); -const eapFullForm = customWrapRoute({ +const fullEapForm = customWrapRoute({ parent: rootLayout, - path: 'eap-full-form', + path: 'eap/:eapId/full', component: { render: () => import('#views/EapFullForm'), props: {}, @@ -790,16 +790,15 @@ const eapFullForm = customWrapRoute({ const simplifiedEapForm = customWrapRoute({ parent: rootLayout, - path: 'simplified-eap-form', + path: 'eap/:eapId/simplified', component: { render: () => import('#views/SimplifiedEapForm'), props: {}, }, wrapperComponent: Auth, context: { - title: 'Simplified EAP Forms', + title: 'Simplified EAP Form', visibility: 'is-authenticated', - permissions: ({ isGuestUser }) => !isGuestUser, }, }); @@ -1488,7 +1487,7 @@ const wrappedRoutes = { operationalLearning, montandonLandingPage, newEapDevelopmentRegistration, - eapFullForm, + fullEapForm, simplifiedEapForm, ...regionRoutes, ...countryRoutes, diff --git a/app/src/components/domain/GoMultiFileInput/index.tsx b/app/src/components/domain/GoMultiFileInput/index.tsx index d4f8f7491..c15ffb360 100644 --- a/app/src/components/domain/GoMultiFileInput/index.tsx +++ b/app/src/components/domain/GoMultiFileInput/index.tsx @@ -31,7 +31,7 @@ import { transformObjectError } from '#utils/restRequest/error'; import i18n from './i18n.json'; -export type SupportedPaths = '/api/v2/per-file/multiple/' | '/api/v2/dref-files/multiple/' | '/api/v2/flash-update-file/multiple/'; +export type SupportedPaths = '/api/v2/per-file/multiple/' | '/api/v2/dref-files/multiple/' | '/api/v2/flash-update-file/multiple/' | '/api/v2/eap-file/multiple/'; interface FileUploadResult { id: number; diff --git a/app/src/components/domain/GoSingleFileInput/index.tsx b/app/src/components/domain/GoSingleFileInput/index.tsx index dc885dfe7..af10bd788 100644 --- a/app/src/components/domain/GoSingleFileInput/index.tsx +++ b/app/src/components/domain/GoSingleFileInput/index.tsx @@ -23,7 +23,7 @@ import { transformObjectError } from '#utils/restRequest/error'; import i18n from './i18n.json'; -export type SupportedPaths = '/api/v2/per-file/' | '/api/v2/dref-files/' | '/api/v2/flash-update-file/' | '/api/v2/per-document-upload/'; +export type SupportedPaths = '/api/v2/per-file/' | '/api/v2/dref-files/' | '/api/v2/flash-update-file/' | '/api/v2/per-document-upload/' | '/api/v2/eap-file/'; type Props = Omit, 'value'> & { name: NAME; diff --git a/app/src/components/domain/ImageWithCaptionInput/index.tsx b/app/src/components/domain/ImageWithCaptionInput/index.tsx index cf74833d9..5a3b877fb 100644 --- a/app/src/components/domain/ImageWithCaptionInput/index.tsx +++ b/app/src/components/domain/ImageWithCaptionInput/index.tsx @@ -24,7 +24,7 @@ import i18n from './i18n.json'; type Value = { id?: number | undefined; client_id: string; - caption?: string | undefined; + caption?: string | undefined | null; }; interface Props { diff --git a/app/src/components/domain/MultiImageWithCaptionInput/index.tsx b/app/src/components/domain/MultiImageWithCaptionInput/index.tsx index c7e2be846..5fe4a5c01 100644 --- a/app/src/components/domain/MultiImageWithCaptionInput/index.tsx +++ b/app/src/components/domain/MultiImageWithCaptionInput/index.tsx @@ -32,7 +32,7 @@ import styles from './styles.module.css'; type Value = { client_id: string; id?: number; - caption?: string; + caption?: string | null; }; interface Props { diff --git a/app/src/utils/constants.ts b/app/src/utils/constants.ts index 453939d3e..855310536 100644 --- a/app/src/utils/constants.ts +++ b/app/src/utils/constants.ts @@ -56,8 +56,8 @@ export const CATEGORY_RISK_VERY_HIGH = 5; // Colors export const COLOR_WHITE = '#ffffff'; -// export const COLOR_TEXT = '#313131'; -// export const COLOR_TEXT_ON_DARK = '#ffffff'; +export const COLOR_TEXT = '#313131'; +export const COLOR_TEXT_ON_DARK = '#ffffff'; export const COLOR_LIGHT_GREY = '#e0e0e0'; export const COLOR_DARK_GREY = '#a5a5a5'; export const COLOR_BLACK = '#000000'; @@ -196,9 +196,19 @@ export const multiMonthSelectDefaultValue = listToMap( () => false, ); +// FIXME these need to satisfy some enum export const ERU_READINESS_READY = 1; export const ERU_READINESS_CAN_CONTRIBUTE = 2; export const ERU_READINESS_NO_CAPACITY = 3; +// FIXME these need to satisfy some enum export const EAP_TYPE_SIMPLIFIED = 20; export const EAP_TYPE_FULL = 10; + +// Timeframe + +// FIXME these need to satisfy some enum +export const TIMEFRAME_YEAR = 10; +export const TIMEFRAME_MONTHS = 20; +// export const TIMEFRAME_DAYS = 30; +// export const TIMEFRAME_HOURS = 40; diff --git a/app/src/views/AccountMyFormsEap/EapTableActions/i18n.json b/app/src/views/AccountMyFormsEap/EapTableActions/i18n.json index 35e2dc216..e4d9e1211 100644 --- a/app/src/views/AccountMyFormsEap/EapTableActions/i18n.json +++ b/app/src/views/AccountMyFormsEap/EapTableActions/i18n.json @@ -3,7 +3,9 @@ "strings":{ "eapViewLabel": "View", "eapEditLabel": "Edit", - "eapFormLink": "Start Full EAP", - "simplifiedEapLink": "Start sEAP" + "eapStartFullLink": "Start Full EAP", + "eapStartSimplifiedLink": "Start sEAP", + "eapEditFullLink": "Edit Full EAP", + "eapEditSimplifiedLink": "Edit sEAP" } } diff --git a/app/src/views/AccountMyFormsEap/EapTableActions/index.tsx b/app/src/views/AccountMyFormsEap/EapTableActions/index.tsx index bf783af4c..b355a6fda 100644 --- a/app/src/views/AccountMyFormsEap/EapTableActions/index.tsx +++ b/app/src/views/AccountMyFormsEap/EapTableActions/index.tsx @@ -3,15 +3,24 @@ import { useTranslation } from '@ifrc-go/ui/hooks'; import DropdownMenuItem from '#components/DropdownMenuItem'; import Link from '#components/Link'; +import { type GoApiResponse } from '#utils/restRequest'; import i18n from './i18n.json'; +import { isNotDefined } from '@togglecorp/fujs'; +import { EAP_TYPE_FULL, EAP_TYPE_SIMPLIFIED } from '#utils/constants'; + +type EapResponse = GoApiResponse<'/api/v2/eap-registration/{id}/'>; export interface Props { eapId: number; + eapType: EapResponse['eap_type']; } function EapTableActions(props: Props) { - const { eapId } = props; + const { + eapId, + eapType, + } = props; const strings = useTranslation(i18n); @@ -23,6 +32,7 @@ function EapTableActions(props: Props) { type="link" to="eapDevelopmentRegistrationForm" urlParams={{ eapId }} + // FIXME: we should use the route for read-only view state={{ mode: 'view' }} > {strings.eapViewLabel} @@ -31,6 +41,7 @@ function EapTableActions(props: Props) { type="link" to="eapDevelopmentRegistrationForm" urlParams={{ eapId }} + // FIXME: we should use separate route for edit view state={{ mode: 'edit' }} > {strings.eapEditLabel} @@ -38,22 +49,46 @@ function EapTableActions(props: Props) { )} > - - {strings.eapFormLink} - - - {strings.simplifiedEapLink} - + {isNotDefined(eapType) && ( + + {strings.eapStartFullLink} + + )} + {isNotDefined(eapType) && ( + + {strings.eapStartSimplifiedLink} + + )} + {eapType === EAP_TYPE_FULL && ( + + {strings.eapEditFullLink} + + )} + {eapType === EAP_TYPE_SIMPLIFIED && ( + + {strings.eapEditSimplifiedLink} + + )} ); } diff --git a/app/src/views/AccountMyFormsEap/index.tsx b/app/src/views/AccountMyFormsEap/index.tsx index 35a643200..ced69d136 100644 --- a/app/src/views/AccountMyFormsEap/index.tsx +++ b/app/src/views/AccountMyFormsEap/index.tsx @@ -63,8 +63,8 @@ export function Component() { }); const { - response: eapResponse, - pending: eapPending, + response: eapListResponse, + pending: eapListPending, } = useRequest({ url: '/api/v2/eap-registration/', preserveResponse: true, @@ -150,8 +150,9 @@ export function Component() { 'actions', '', EapTableActions, - (eapId) => ({ + (eapId, eap) => ({ eapId, + eapType: eap.eap_type, }), ), createEmptyColumn(), @@ -165,7 +166,7 @@ export function Component() { return row; } - const subRows = eapResponse?.results?.filter( + const subRows = eapListResponse?.results?.filter( (subRow) => subRow.id === datum.id, ); @@ -183,7 +184,7 @@ export function Component() { [ expandedRow, detailColumns, - eapResponse, + eapListResponse, ], ); @@ -191,7 +192,7 @@ export function Component() { 0 && ( + filters={(eapListResponse?.count ?? 0) > 0 && ( )} >
diff --git a/app/src/views/DrefApplicationForm/index.tsx b/app/src/views/DrefApplicationForm/index.tsx index 226d5a1c4..58b95680c 100644 --- a/app/src/views/DrefApplicationForm/index.tsx +++ b/app/src/views/DrefApplicationForm/index.tsx @@ -308,6 +308,7 @@ export function Component() { const loadResponseToFormValue = useCallback((response: GetDrefResponse) => { handleDrefLoad(response); + const { planned_interventions, proposed_action, diff --git a/app/src/views/EapRegistration/index.tsx b/app/src/views/EapRegistration/index.tsx index 16d65dc60..82c44ec6a 100644 --- a/app/src/views/EapRegistration/index.tsx +++ b/app/src/views/EapRegistration/index.tsx @@ -341,31 +341,32 @@ export function Component() { description={strings.eapTypeDescription} withAsteriskOnTitle > - - {/* FIXME: label is not showing */} - + + + + {strings.eapNotSure} + + - {/* FIXME: label is not showing */} + > + {strings.eapNotSure} + ) => void; + error: Error | undefined; + disabled?: boolean; + fileIdToUrlMap: Record; + setFileIdToUrlMap?: React.Dispatch>>; +} + +function DeliveryAndBudget(props: Props) { + const { + value, + setFieldValue, + error: formError, + disabled, + fileIdToUrlMap, + setFileIdToUrlMap, + } = props; + + const strings = useTranslation(i18n); + const error = getErrorObject(formError); + + return ( + + + +