diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index b35e83bd2f..3453968436 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -1,5 +1,38 @@ # go-web-app +## 7.6.6 + +### Patch Changes + +- 8cdc946: Hide Local unit contact details on the list view for logged in users in [#1485](https://github.com/ifRCGo/go-web-app/issues/1485) + Update `tinymce-react` plugin to the latest version and enabled additional plugins, including support for lists in [#1481](https://github.com/ifRCGo/go-web-app/issues/1481) +- ecca810: Replace the from-communication-copied text of CoS Health header +- 7cf2514: Prioritize GDACS as the Primary Source for Imminent Risk Watch in [#1547](https://github.com/IFRCGo/go-web-app/issues/1547) +- 8485076: Add Organization type and Learning type filter in Operational learning in [#1469](https://github.com/IFRCGo/go-web-app/issues/1469) +- 766d98d: Auto append https:// for incomplete URLs in [#1505](https://github.com/IFRCGo/go-web-app/issues/1505) + +## 7.6.5 + +### Patch Changes + +- 478e73b: Update labels for severity control in Imminent Risk Map + Update navigation for the events in Imminent Risk Map + Fix issue displayed when opening a DREF import template + Fix submission issue when importing a DREF import file +- f82f846: Update Health Section in Catalogue of Surge Services +- ade84aa: Display ICRC Presence + - Display ICRC presence across partner countries + - Highlight key operational countries + +## 7.6.4 + +### Patch Changes + +- d85f64d: Update Imminent Events + + - Hide WFP ADAM temporarily from list sources + - Show exposure control for cyclones from GDACS only + ## 7.6.3 ### Patch Changes diff --git a/app/package.json b/app/package.json index 819902edd4..1728394c57 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "go-web-app", - "version": "7.6.3", + "version": "7.6.6", "type": "module", "private": true, "license": "MIT", @@ -45,7 +45,7 @@ "@ifrc-go/ui": "^1.2.1", "@mapbox/mapbox-gl-draw": "^1.2.0", "@sentry/react": "^7.81.1", - "@tinymce/tinymce-react": "^4.3.0", + "@tinymce/tinymce-react": "^5.1.1", "@togglecorp/fujs": "^2.1.1", "@togglecorp/re-map": "^0.2.0-beta-6", "@togglecorp/toggle-form": "^2.0.4", diff --git a/app/scripts/translatte/main.ts b/app/scripts/translatte/main.ts index cdedb2c371..fd657ec75c 100644 --- a/app/scripts/translatte/main.ts +++ b/app/scripts/translatte/main.ts @@ -112,7 +112,6 @@ yargs(hideBin(process.argv)) }); }, async (argv) => { - console.warn(argv); await applyMigrations( currentDir, argv.SOURCE_FILE as string, diff --git a/app/src/App/routes/SurgeRoutes.tsx b/app/src/App/routes/SurgeRoutes.tsx index 44952dd320..eef68fb222 100644 --- a/app/src/App/routes/SurgeRoutes.tsx +++ b/app/src/App/routes/SurgeRoutes.tsx @@ -416,30 +416,30 @@ const surgeCatalogueHealthEmergencyClinic = customWrapRoute({ }, }); -const surgeCatalogueHealthEruChloreaTreatment = customWrapRoute({ +const surgeCatalogueHealthEruCholeraTreatment = customWrapRoute({ parent: surgeCatalogueLayout, - path: 'health/eru-chlorea-treatment', + path: 'health/eru-cholera-treatment', component: { - render: () => import('#views/SurgeCatalogueHealthEruChloreaTreatment'), + render: () => import('#views/SurgeCatalogueHealthEruCholeraTreatment'), props: {}, }, wrapperComponent: Auth, context: { - title: 'Emergency Response Unit Chlorea Treatment Center', + title: 'Emergency Response Unit Cholera Treatment Center', visibility: 'anything', }, }); -const surgeCatalogueHealthCommunityCaseManagementChlorea = customWrapRoute({ +const surgeCatalogueHealthCommunityCaseManagementCholera = customWrapRoute({ parent: surgeCatalogueLayout, - path: 'health/community-case-management-chlorea', + path: 'health/community-case-management-cholera', component: { - render: () => import('#views/SurgeCatalogueHealthCommunityCaseManagementChlorea'), + render: () => import('#views/SurgeCatalogueHealthCommunityCaseManagementCholera'), props: {}, }, wrapperComponent: Auth, context: { - title: 'Community Case Management of Chlorea', + title: 'Community Case Management of Cholera', visibility: 'anything', }, }); @@ -472,6 +472,20 @@ const surgeCatalogueHealthSafeDignifiedBurials = customWrapRoute({ }, }); +const surgeCatalogueHealthInfectionPreventionAndControl = customWrapRoute({ + parent: surgeCatalogueLayout, + path: 'health/infection-prevention-and-control', + component: { + render: () => import('#views/SurgeCatalogueHealthInfectionPreventionAndControl'), + props: {}, + }, + wrapperComponent: Auth, + context: { + title: 'Infection Prevention and Control', + visibility: 'anything', + }, +}); + const surgeCatalogueHealthCommunityManagementMalnutrition = customWrapRoute({ parent: surgeCatalogueLayout, path: 'health/community-management-malnutrition', @@ -1301,9 +1315,10 @@ function DeploymentCatalogueNavigate() { 'eru-pss-module': surgeCatalogueHealthEruPsychosocialSupport, 'community-case-management-of-malnutrition-ccmm': surgeCatalogueHealthCommunityManagementMalnutrition, 'safe-and-dignified-burials': surgeCatalogueHealthSafeDignifiedBurials, + 'infection-prevention-and-control': surgeCatalogueHealthInfectionPreventionAndControl, 'community-based-surveillance-cbs': surgeCatalogueHealthCommunityBasedSurveillance, - 'community-case-management-of-cholera-ccmc': surgeCatalogueHealthCommunityCaseManagementChlorea, - 'eru-cholera-treatment-center': surgeCatalogueHealthEruChloreaTreatment, + 'community-case-management-of-cholera-ccmc': surgeCatalogueHealthCommunityCaseManagementCholera, + 'eru-cholera-treatment-center': surgeCatalogueHealthEruCholeraTreatment, 'emergency-mobile-clinic': surgeCatalogueHealthEmergencyClinic, 'maternal-newborn-health-clinic': surgeCatalogueHealthMaternalNewbornClinic, 'surgical-surge': surgeCatalogueHealthEruSurgical, @@ -1457,10 +1472,11 @@ export default { surgeCatalogueHealthEruSurgical, surgeCatalogueHealthMaternalNewbornClinic, surgeCatalogueHealthEmergencyClinic, - surgeCatalogueHealthEruChloreaTreatment, - surgeCatalogueHealthCommunityCaseManagementChlorea, + surgeCatalogueHealthEruCholeraTreatment, + surgeCatalogueHealthCommunityCaseManagementCholera, surgeCatalogueHealthCommunityBasedSurveillance, surgeCatalogueHealthSafeDignifiedBurials, + surgeCatalogueHealthInfectionPreventionAndControl, surgeCatalogueHealthCommunityManagementMalnutrition, surgeCatalogueHealthEruPsychosocialSupport, surgeCatalogueInformationManagement, diff --git a/app/src/components/RichTextArea/index.tsx b/app/src/components/RichTextArea/index.tsx index 28aa7cd5c1..65c38752ef 100644 --- a/app/src/components/RichTextArea/index.tsx +++ b/app/src/components/RichTextArea/index.tsx @@ -19,11 +19,11 @@ const editorOptions: Omit = { menubar: false, // https://www.tiny.cloud/docs/advanced/available-toolbar-buttons statusbar: false, paste_data_images: false, - plugins: ['advlist autolink code help link lists preview'], - toolbar: 'formatselect | bold italic superscript link | ' + plugins: 'advlist autolink code help link lists preview', + toolbar: 'bold italic subscript superscript link | ' + 'alignleft aligncenter alignright alignjustify | ' - + 'bullist numlist outdent indent | code removeformat preview | help', - contextmenu: 'formats link', + + 'bullist numlist outdent indent | code removeformat preview fullscreen | help', + contextmenu: 'link', // https://www.tiny.cloud/docs/configure/content-filtering/#invalid_styles invalid_styles: { '*': 'opacity' }, }; diff --git a/app/src/components/domain/ExportButton/i18n.json b/app/src/components/domain/ExportButton/i18n.json index 59505eeee2..10f101de88 100644 --- a/app/src/components/domain/ExportButton/i18n.json +++ b/app/src/components/domain/ExportButton/i18n.json @@ -1,8 +1,8 @@ { "namespace": "common", "strings": { - "exportTableButtonLabel": "Export", - "exportTableDownloadingButtonLabel": "Downloading... ({progress}%)", + "exportButtonLabel": "Export", + "exportDownloadingButtonLabel": "Downloading... ({progress}%)", "pendingExportLabel": "Downloading..." } } diff --git a/app/src/components/domain/ExportButton/index.tsx b/app/src/components/domain/ExportButton/index.tsx index 5d21be37f7..8f845f4ce5 100644 --- a/app/src/components/domain/ExportButton/index.tsx +++ b/app/src/components/domain/ExportButton/index.tsx @@ -29,11 +29,11 @@ function ExportButton(props: Props) { const exportButtonLabel = useMemo(() => { if (!pendingExport) { - return strings.exportTableButtonLabel; + return strings.exportButtonLabel; } if (progress) { return resolveToComponent( - strings.exportTableDownloadingButtonLabel, + strings.exportDownloadingButtonLabel, { progress: ( >; + + exposureAreaControlHidden?: boolean; } function LayerOptions(props: Props) { const { + exposureAreaControlHidden, value, onChange, } = props; @@ -54,20 +57,22 @@ function LayerOptions(props: Props) { const setFieldValue = useSetFieldValue(onChange); // FIXME: use strings + // FIXME: These are hard-coded for Gdacs source. + // Currently we are only showing severity control for Gdacs const severityLegendItems = useMemo(() => ([ { severity: 'green', - label: 'Green', + label: '60 km/h', color: COLOR_GREEN, }, { severity: 'orange', - label: 'Orange', + label: '90 km/h', color: COLOR_ORANGE, }, { severity: 'red', - label: 'Red', + label: '120 km/h', color: COLOR_RED, }, { @@ -92,28 +97,30 @@ function LayerOptions(props: Props) { withBackground withInvertedView /> -
- - {value.showExposedArea && ( - + - )} -
+ {value.showExposedArea && ( + + )} + + )} { data: EVENT; + expanded: boolean; onExpandClick: (eventId: number | string) => void; className?: string; + children?: React.ReactNode; } export interface RiskEventDetailProps { @@ -99,7 +99,11 @@ export interface RiskEventDetailProps { type Footprint = GeoJSON.FeatureCollection | undefined; +// FIXME: read this from common type +export type ImminentEventSource = 'pdc' | 'wfpAdam' | 'gdacs' | 'meteoSwiss'; + interface Props { + source: ImminentEventSource; events: EVENT[] | undefined; keySelector: (event: EVENT) => KEY; hazardTypeSelector: (event: EVENT) => HazardType | '' | undefined; @@ -134,6 +138,7 @@ function RiskImminentEventMap< bbox, onActiveEventChange, activeEventExposurePending, + source, } = props; const strings = useTranslation(i18n); @@ -238,10 +243,15 @@ function RiskImminentEventMap< (eventId: string | number | undefined) => { const eventIdSafe = eventId as KEY | undefined; - setActiveEventId(eventIdSafe); - onActiveEventChange(eventIdSafe); + if (activeEventId === eventIdSafe) { + setActiveEventId(undefined); + onActiveEventChange(undefined); + } else { + setActiveEventId(eventIdSafe); + onActiveEventChange(eventIdSafe); + } }, - [onActiveEventChange], + [onActiveEventChange, activeEventId], ); const handlePointClick = useCallback( @@ -253,17 +263,44 @@ function RiskImminentEventMap< [setActiveEventIdSafe], ); + const DetailComponent = detailRenderer; + const eventListRendererParams = useCallback( (_: string | number, event: EVENT): RiskEventListItemProps => ({ data: event, onExpandClick: setActiveEventIdSafe, + expanded: activeEventId === keySelector(event), className: styles.riskEventListItem, + children: activeEventId === keySelector(event) && ( + + {hazardTypeSelector(event) === 'TC' && ( + + )} + + ), }), - [setActiveEventIdSafe], + [ + setActiveEventIdSafe, + activeEventExposure, + activeEventExposurePending, + layerOptions, + hazardTypeSelector, + DetailComponent, + activeEventId, + keySelector, + source, + ], ); - const DetailComponent = detailRenderer; - const [loadedIcons, setLoadedIcons] = useState>({}); const handleIconLoad = useCallback( @@ -438,46 +475,18 @@ function RiskImminentEventMap< withInternalPadding childrenContainerClassName={styles.content} spacing="cozy" - actions={isDefined(activeEventId) && ( - - )} > - {isNotDefined(activeEventId) && ( - - )} - {isDefined(activeEvent) && ( - - {hazardTypeSelector(activeEvent) === 'TC' && ( - - )} - - )} + ); diff --git a/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/i18n.json b/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/i18n.json index ddcd9c1f75..418693af3f 100644 --- a/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/i18n.json +++ b/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/i18n.json @@ -1,7 +1,6 @@ { "namespace": "common", "strings": { - "eventStartOnLabel": "Started on", "eventMoreDetailsLink": "More Details", "eventSourceLabel": "Forecast provider", "eventDeathLabel": "Estimated deaths", diff --git a/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/index.tsx b/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/index.tsx index e4b3906b1d..c8d6a488d2 100644 --- a/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/Gdacs/EventDetails/index.tsx @@ -70,8 +70,6 @@ type Props = RiskEventDetailProps; function EventDetails(props: Props) { const { data: { - hazard_name, - start_date, event_details, }, exposure, @@ -87,16 +85,7 @@ function EventDetails(props: Props) { return ( - )} withBorderAndHeaderBackground pending={pending} > diff --git a/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/i18n.json b/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/i18n.json index 05c98dbe01..a834b17335 100644 --- a/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/i18n.json +++ b/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/i18n.json @@ -1,7 +1,7 @@ { "namespace": "common", "strings": { - "gdacsEventViewDetails": "View Details", + "gdacsEventViewDetails": "View / Hide Details", "gdacsEventStartedOn": "Started On" } } diff --git a/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/index.tsx b/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/index.tsx index c20a9d799b..c7e4798e1f 100644 --- a/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/Gdacs/EventListItem/index.tsx @@ -1,4 +1,11 @@ -import { ChevronRightLineIcon } from '@ifrc-go/icons'; +import { + useEffect, + useRef, +} from 'react'; +import { + ChevronDownLineIcon, + ChevronUpLineIcon, +} from '@ifrc-go/icons'; import { Button, Header, @@ -25,35 +32,62 @@ function EventListItem(props: Props) { hazard_name, start_date, }, + expanded, onExpandClick, className, + children, } = props; const strings = useTranslation(i18n); + const elementRef = useRef(null); + + useEffect( + () => { + if (expanded && elementRef.current) { + const y = window.scrollY; + const x = window.scrollX; + elementRef.current.scrollIntoView({ + behavior: 'instant', + block: 'start', + }); + // NOTE: We need to scroll back because scrollIntoView also + // scrolls the parent container + window.scroll(x, y); + } + }, + [expanded], + ); + return ( -
- - - )} - spacing="cozy" - > - -
+ <> +
+ {expanded + ? + : } + + )} + spacing="cozy" + > + +
+ {children} + ); } diff --git a/app/src/components/domain/RiskImminentEvents/Gdacs/index.tsx b/app/src/components/domain/RiskImminentEvents/Gdacs/index.tsx index 49f5bd3c13..60caa76c10 100644 --- a/app/src/components/domain/RiskImminentEvents/Gdacs/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/Gdacs/index.tsx @@ -265,6 +265,7 @@ function Gdacs(props: Props) { return ( { if (windspeed < 33) { return strings.tropicalStormDescription; @@ -179,25 +169,6 @@ function EventDetails(props: Props) { - - - - )} contentViewType="vertical" > {pending && } diff --git a/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/i18n.json b/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/i18n.json index 8afd1399ef..33064d1f8d 100644 --- a/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/i18n.json +++ b/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/i18n.json @@ -1,7 +1,7 @@ { "namespace": "common", "strings": { - "meteoSwissEventListViewDetails": "View Details", + "meteoSwissEventListViewDetails": "View / Hide Details", "meteoSwissEventListStartedOn": "Started On" } } diff --git a/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/index.tsx b/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/index.tsx index 51b5f05ca3..7a87b8d436 100644 --- a/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/MeteoSwiss/EventListItem/index.tsx @@ -1,4 +1,11 @@ -import { ChevronRightLineIcon } from '@ifrc-go/icons'; +import { + useEffect, + useRef, +} from 'react'; +import { + ChevronDownLineIcon, + ChevronUpLineIcon, +} from '@ifrc-go/icons'; import { Button, Header, @@ -27,37 +34,64 @@ function EventListItem(props: Props) { start_date, hazard_name, }, + expanded, onExpandClick, className, + children, } = props; const strings = useTranslation(i18n); const hazardName = `${hazard_type_display} - ${country_details?.name ?? hazard_name}`; + const elementRef = useRef(null); + + useEffect( + () => { + if (expanded && elementRef.current) { + const y = window.scrollY; + const x = window.scrollX; + elementRef.current.scrollIntoView({ + behavior: 'instant', + block: 'start', + }); + // NOTE: We need to scroll back because scrollIntoView also + // scrolls the parent container + window.scroll(x, y); + } + }, + [expanded], + ); + return ( -
- - - )} - spacing="condensed" - > - -
+ <> +
+ {expanded + ? + : } + + )} + spacing="condensed" + > + +
+ {children} + ); } diff --git a/app/src/components/domain/RiskImminentEvents/MeteoSwiss/index.tsx b/app/src/components/domain/RiskImminentEvents/MeteoSwiss/index.tsx index f93c7641ff..30ffccdd57 100644 --- a/app/src/components/domain/RiskImminentEvents/MeteoSwiss/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/MeteoSwiss/index.tsx @@ -209,6 +209,7 @@ function MeteoSwiss(props: Props) { return ( ; function EventDetails(props: Props) { const { data: { - hazard_name, - start_date, pdc_created_at, pdc_updated_at, description, @@ -53,16 +51,7 @@ function EventDetails(props: Props) { return ( - )} withBorderAndHeaderBackground pending={pending} > diff --git a/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/i18n.json b/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/i18n.json index 407f9de522..b7dc5a96b9 100644 --- a/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/i18n.json +++ b/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/i18n.json @@ -1,7 +1,7 @@ { "namespace": "common", "strings": { - "eventListViewDetails": "View Details", + "eventListViewDetails": "View / Hide Details", "eventListStartedOn": "Started on" } } diff --git a/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/index.tsx b/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/index.tsx index e797243af2..1cfc7eecc5 100644 --- a/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/Pdc/EventListItem/index.tsx @@ -1,4 +1,11 @@ -import { ChevronRightLineIcon } from '@ifrc-go/icons'; +import { + useEffect, + useRef, +} from 'react'; +import { + ChevronDownLineIcon, + ChevronUpLineIcon, +} from '@ifrc-go/icons'; import { Button, Header, @@ -25,35 +32,62 @@ function EventListItem(props: Props) { hazard_name, start_date, }, + expanded, onExpandClick, className, + children, } = props; const strings = useTranslation(i18n); + const elementRef = useRef(null); + + useEffect( + () => { + if (expanded && elementRef.current) { + const y = window.scrollY; + const x = window.scrollX; + elementRef.current.scrollIntoView({ + behavior: 'instant', + block: 'start', + }); + // NOTE: We need to scroll back because scrollIntoView also + // scrolls the parent container + window.scroll(x, y); + } + }, + [expanded], + ); + return ( -
- - - )} - spacing="cozy" - > - -
+ <> +
+ {expanded + ? + : } + + )} + spacing="cozy" + > + +
+ {children} + ); } diff --git a/app/src/components/domain/RiskImminentEvents/Pdc/index.tsx b/app/src/components/domain/RiskImminentEvents/Pdc/index.tsx index 2938074ff2..3a0e784736 100644 --- a/app/src/components/domain/RiskImminentEvents/Pdc/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/Pdc/index.tsx @@ -246,6 +246,7 @@ function Pdc(props: Props) { return ( ; function EventDetails(props: Props) { const { data: { - title, - publish_date, event_details, }, exposure, @@ -162,17 +160,7 @@ function EventDetails(props: Props) { className={styles.eventDetails} contentViewType="vertical" childrenContainerClassName={styles.content} - heading={title} - headingLevel={5} spacing="cozy" - headerDescription={( - - )} pending={pending} > {stormPoints && stormPoints.length > 0 && isDefined(maxWindSpeed) && ( diff --git a/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/i18n.json b/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/i18n.json index 7e62fc4b38..0830c5d6b9 100644 --- a/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/i18n.json +++ b/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/i18n.json @@ -1,7 +1,7 @@ { "namespace": "common", "strings": { - "wfpEventListViewDetails": "View Details", + "wfpEventListViewDetails": "View / Hide Details", "wfpEventListPublishedOn": "Published on" } } diff --git a/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/index.tsx b/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/index.tsx index 1becd6c66c..701068a292 100644 --- a/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/WfpAdam/EventListItem/index.tsx @@ -1,4 +1,11 @@ -import { ChevronRightLineIcon } from '@ifrc-go/icons'; +import { + useEffect, + useRef, +} from 'react'; +import { + ChevronDownLineIcon, + ChevronUpLineIcon, +} from '@ifrc-go/icons'; import { Button, Header, @@ -25,35 +32,62 @@ function EventListItem(props: Props) { publish_date, title, }, + expanded, onExpandClick, className, + children, } = props; const strings = useTranslation(i18n); + const elementRef = useRef(null); + + useEffect( + () => { + if (expanded && elementRef.current) { + const y = window.scrollY; + const x = window.scrollX; + elementRef.current.scrollIntoView({ + behavior: 'instant', + block: 'start', + }); + // NOTE: We need to scroll back because scrollIntoView also + // scrolls the parent container + window.scroll(x, y); + } + }, + [expanded], + ); + return ( -
- - - )} - spacing="cozy" - > - -
+ <> +
+ {expanded + ? + : } + + )} + spacing="cozy" + > + +
+ {children} + ); } diff --git a/app/src/components/domain/RiskImminentEvents/WfpAdam/index.tsx b/app/src/components/domain/RiskImminentEvents/WfpAdam/index.tsx index a3b7682b17..f6cb955671 100644 --- a/app/src/components/domain/RiskImminentEvents/WfpAdam/index.tsx +++ b/app/src/components/domain/RiskImminentEvents/WfpAdam/index.tsx @@ -216,6 +216,7 @@ function WfpAdam(props: Props) { return ( (defaultSource); @@ -169,48 +169,21 @@ function RiskImminentEvents(props: Props) { footerActions={( <> - {strings.here} - - ), - }, - )} - /> - )} - /> - @@ -223,21 +196,21 @@ function RiskImminentEvents(props: Props) { )} /> @@ -249,6 +222,35 @@ function RiskImminentEvents(props: Props) { /> )} /> + {environment !== 'production' && ( + + {strings.here} + + ), + }, + )} + /> + )} + /> + )} {environment !== 'production' && ( , fieldName: string | undefined = undefined, - transformListObject: (item: object) => object = addClientId, + transformListObject: ((item: object) => object) = addClientId, ): unknown { const optionsReverseMap = mapToMap( optionsMap, @@ -346,6 +346,18 @@ export function getValueFromImportTemplate< if (schema.type === 'input') { const value = formValues[fieldName]; + if ( + (fieldName === 'source_information__source__0__source_link' + || fieldName === 'source_information__source__1__source_link' + || fieldName === 'source_information__source__2__source_link' + || fieldName === 'source_information__source__3__source_link' + || fieldName === 'source_information__source__4__source_link' + ) + && isDefined(value) && (typeof value === 'string') && !value.includes('://') + ) { + // TODO: we need to find a better solution to this + return `https://${value}`; + } // TODO: add validation from schema.validation return value; } @@ -374,7 +386,7 @@ export function getValueFromImportTemplate< if (schema.keyFieldName) { return { [schema.keyFieldName]: option.key, - ...value, + ...transformListObject(value), }; } return transformListObject(value); diff --git a/app/src/utils/richText.ts b/app/src/utils/richText.ts index 8fd042da39..ef5eccb2d4 100644 --- a/app/src/utils/richText.ts +++ b/app/src/utils/richText.ts @@ -102,9 +102,11 @@ export function parsePseudoHtml( (acc, plugin) => plugin.transformer(token, acc), { text: token }, ); + if (richTextItem.text === '') { + return undefined; + } return richTextItem; }).filter(isDefined); - // TODO: Check correctness to check that stack is empty return { richText }; } diff --git a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts index 7959482757..ef7a3dac26 100644 --- a/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts +++ b/app/src/views/AccountMyFormsDref/DownloadImportTemplateButton/DownloadImportTemplateModal/useImportTemplateSchema.ts @@ -234,7 +234,7 @@ function useImportTemplateSchema() { optionsKey: '__boolean', validation: 'boolean', description: ( - 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + 'Indicate only if it affected the same population groups\n' + 'Otherwise, leave the box empty.' ), }, @@ -245,7 +245,7 @@ function useImportTemplateSchema() { optionsKey: '__boolean', validation: 'boolean', description: ( - 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + 'Indicate only if the national society responded\n' + 'Otherwise, leave the box empty.' ), }, @@ -255,7 +255,7 @@ function useImportTemplateSchema() { label: 'If yes, please specify which operations', validation: 'string', description: ( - 'Indicate only if there was a similar event affecting the same area in the last 3 years.\n' + 'Indicate only if the national society requested funding from DREF for that event(s).\n' + 'Otherwise, leave the box empty.' ), }, @@ -465,6 +465,17 @@ function useImportTemplateSchema() { ), }, + major_coordination_mechanism: { + type: 'input', + validation: 'textArea', + label: 'Major coordination mechanism', + description: ( + 'List coordination mechanisms/platform in place at local/district and national level. Indicate the lead authorities/agencies. How the National Society is involved/positioned in this coordination. Does the NS in any lead/co-lead role? Any identified gap/overlap in the coordination (e.g., sector missing…)?\n' + + 'Indicate only if there are major coordination mechanism in place\n' + + 'Otherwise, leave the box empty.' + ), + }, + needs_identified: { type: 'list', label: 'Needs (Gaps) Identified', diff --git a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsFormModal/LocalUnitsForm/index.tsx b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsFormModal/LocalUnitsForm/index.tsx index 70403102bd..27a55387a1 100644 --- a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsFormModal/LocalUnitsForm/index.tsx +++ b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsFormModal/LocalUnitsForm/index.tsx @@ -42,6 +42,7 @@ import CountrySelectInput from '#components/domain/CountrySelectInput'; import NonFieldError from '#components/NonFieldError'; import { environment } from '#config'; import useGlobalEnums from '#hooks/domain/useGlobalEnums'; +import usePermissions from '#hooks/domain/usePermissions'; import useAlert from '#hooks/useAlert'; import { getFirstTruthyString } from '#utils/common'; import { VISIBILITY_PUBLIC } from '#utils/constants'; @@ -129,10 +130,13 @@ function LocalUnitsForm(props: Props) { const alert = useAlert(); const strings = useTranslation(i18n); + const { isSuperUser, isCountryAdmin } = usePermissions(); const formFieldsContainerRef = useRef(null); const { api_visibility_choices: visibilityOptions } = useGlobalEnums(); const { countryId } = useOutletContext(); + const hasAddEditLocalUnitPermission = isCountryAdmin(Number(countryId)) || isSuperUser; + const { value, error: formError, @@ -317,7 +321,7 @@ function LocalUnitsForm(props: Props) {
{readOnly && isDefined(actionsContainerRef.current) && ( - {(environment !== 'production') && ( + {hasAddEditLocalUnitPermission && environment !== 'production' && ( @@ -470,7 +470,7 @@ function LocalUnitsMap(props: Props) { value={localUnitAddress} /> diff --git a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/LocalUnitTableActions/index.tsx b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/LocalUnitTableActions/index.tsx index 2b19f7d223..fc1f84b520 100644 --- a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/LocalUnitTableActions/index.tsx +++ b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/LocalUnitTableActions/index.tsx @@ -28,6 +28,7 @@ export interface Props { localUnitId: number; isValidated: boolean; onActionSuccess: () => void; + hasAddEditLocalUnitPermission: boolean; } export type LocalUnitValidateResponsePostBody = GoApiResponse<'/api/v2/local-units/{id}/'>; @@ -39,6 +40,7 @@ function LocalUnitsTableActions(props: Props) { localUnitId, isValidated, onActionSuccess, + hasAddEditLocalUnitPermission, } = props; const { isCountryAdmin, isSuperUser } = usePermissions(); @@ -89,7 +91,6 @@ function LocalUnitsTableActions(props: Props) { type="button" name={localUnitId} onClick={handleViewLocalUnitClick} - disabled={!hasValidatePermission} > {strings.localUnitsView} @@ -97,7 +98,7 @@ function LocalUnitsTableActions(props: Props) { type="button" name={localUnitId} onClick={handleEditLocalUnitClick} - disabled={!hasValidatePermission} + disabled={!hasAddEditLocalUnitPermission} > {strings.localUnitsEdit} diff --git a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/index.tsx b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/index.tsx index 9886f24e6b..dfa8a08a10 100644 --- a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/index.tsx +++ b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsTable/index.tsx @@ -19,6 +19,7 @@ import { isNotDefined, } from '@togglecorp/fujs'; +import usePermissions from '#hooks/domain/usePermissions'; import useFilterState from '#hooks/useFilterState'; import { getFirstTruthyString } from '#utils/common'; import { type CountryOutletContext } from '#utils/outletContext'; @@ -52,6 +53,8 @@ function LocalUnitsTable(props: Props) { const strings = useTranslation(i18n); const { countryResponse } = useOutletContext(); + const { isSuperUser, isCountryAdmin } = usePermissions(); + const hasAddEditLocalUnitPermission = isCountryAdmin(countryResponse?.id) || isSuperUser; const { limit, @@ -89,57 +92,111 @@ function LocalUnitsTable(props: Props) { }); const columns = useMemo( - () => ([ - createStringColumn( - 'branch_name', - strings.localUnitsTableName, - (item) => getFirstTruthyString(item.local_branch_name, item.english_branch_name), - ), - createStringColumn( - 'address', - strings.localUnitsTableAddress, - (item) => getFirstTruthyString(item.address_loc, item.address_en), - ), - createStringColumn( - 'type', - strings.localUnitsTableType, - (item) => item.type_details.name, - { columnClassName: styles.type }, - ), - createStringColumn( - 'focal', - strings.localUnitsTableFocal, - (item) => getFirstTruthyString(item.focal_person_loc, item.focal_person_en), - ), - createStringColumn( - 'phone', - strings.localUnitsTablePhoneNumber, - (item) => item.phone, - ), - createStringColumn( - 'email', - strings.localUnitsTableEmail, - (item) => item.email, - ), - createElementColumn( - 'actions', - '', - LocalUnitsTableActions, - // FIXME: this should be added to a callback - (_, item) => ({ - countryId: item.country, - localUnitId: item.id, - isValidated: item.validated, - localUnitName: getFirstTruthyString( + () => { + if (hasAddEditLocalUnitPermission) { + return [ + createStringColumn( + 'branch_name', + strings.localUnitsTableName, + (item) => getFirstTruthyString( + item.local_branch_name, + item.english_branch_name, + ), + ), + createStringColumn( + 'address', + strings.localUnitsTableAddress, + (item) => getFirstTruthyString(item.address_loc, item.address_en), + ), + createStringColumn( + 'type', + strings.localUnitsTableType, + (item) => item.type_details.name, + { columnClassName: styles.type }, + ), + createStringColumn( + 'focal', + strings.localUnitsTableFocal, + (item) => getFirstTruthyString( + item.focal_person_loc, + item.focal_person_en, + ), + ), + createStringColumn( + 'phone', + strings.localUnitsTablePhoneNumber, + (item) => item.phone, + ), + createStringColumn( + 'email', + strings.localUnitsTableEmail, + (item) => item.email, + ), + createElementColumn< + LocalUnitsTableListItem, + number, + LocalUnitsTableActionsProps + >( + 'actions', + '', + LocalUnitsTableActions, + // FIXME: this should be added to a callback + (_, item) => ({ + countryId: item.country, + localUnitId: item.id, + isValidated: item.validated, + localUnitName: getFirstTruthyString( + item.local_branch_name, + item.english_branch_name, + ), + onActionSuccess: refetchLocalUnits, + }), + { columnClassName: styles.actions }, + ), + ]; + } + return [ + createStringColumn( + 'branch_name', + strings.localUnitsTableName, + (item) => getFirstTruthyString( item.local_branch_name, item.english_branch_name, ), - onActionSuccess: refetchLocalUnits, - }), - { columnClassName: styles.actions }, - ), - ]), + ), + createStringColumn( + 'address', + strings.localUnitsTableAddress, + (item) => getFirstTruthyString(item.address_loc, item.address_en), + ), + createStringColumn( + 'type', + strings.localUnitsTableType, + (item) => item.type_details.name, + { columnClassName: styles.type }, + ), + createElementColumn( + 'actions', + '', + LocalUnitsTableActions, + // FIXME: this should be added to a callback + (_, item) => ({ + countryId: item.country, + localUnitId: item.id, + isValidated: item.validated, + localUnitName: getFirstTruthyString( + item.local_branch_name, + item.english_branch_name, + ), + onActionSuccess: refetchLocalUnits, + hasAddEditLocalUnitPermission, + }), + { columnClassName: styles.actions }, + ), + ]; + }, [ + hasAddEditLocalUnitPermission, strings.localUnitsTableAddress, strings.localUnitsTableName, strings.localUnitsTableType, diff --git a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/index.tsx b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/index.tsx index bdcc77c460..d43a51477f 100644 --- a/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/index.tsx +++ b/app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/index.tsx @@ -111,7 +111,7 @@ function NationalSocietyLocalUnits(props: Props) { const strings = useTranslation(i18n); - const hasAddLocalUnitPermission = isCountryAdmin(countryResponse?.id) || isSuperUser; + const hasAddEditLocalUnitPermission = isCountryAdmin(countryResponse?.id) || isSuperUser; useEffect(() => { document.addEventListener('fullscreenchange', handleFullScreenChange); @@ -148,7 +148,7 @@ function NationalSocietyLocalUnits(props: Props) { /> )} // NOTE: disable local units add/edit for now - actions={hasAddLocalUnitPermission && (environment !== 'production') && ( + actions={hasAddEditLocalUnitPermission && (environment !== 'production') && (