diff --git a/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/i18n.json b/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/i18n.json index d357be7ffd..67ab4339b9 100644 --- a/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/i18n.json +++ b/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/i18n.json @@ -1,10 +1,10 @@ { - "namespace": "emergencyActivities", + "namespace": "countryResponseActivities", "strings": { - "severityLow": "Less than 2", - "severityMedium": "2 to 4", - "severityHigh": "5 to 9", - "severitySevere": "10 or more", - "numberOfProjects": "Number of Projects" + "countryResponseActivitiesSeverityLow": "Less than 2", + "countryResponseActivitiesSeverityMedium": "2 to 4", + "countryResponseActivitiesSeverityHigh": "5 to 9", + "countryResponseActivitiesSeveritySevere": "10 or more", + "countryResponseActivitiesNumberOfProjects": "Number of Projects" } } diff --git a/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/index.tsx b/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/index.tsx index 06b66b5bcb..549a1b4a18 100644 --- a/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/index.tsx +++ b/src/views/CountryOngoingActivitiesThreeWActivities/ResponseActivitiesMap/index.tsx @@ -52,10 +52,7 @@ function ResponseActivitiesMap(props: Props) { } = props; const strings = useTranslation(i18n); - const { - // countryId, - countryResponse, - } = useOutletContext(); + const { countryResponse } = useOutletContext(); const bounds = useMemo( () => (countryResponse ? getBbox(countryResponse?.bbox) : undefined), @@ -153,23 +150,23 @@ function ResponseActivitiesMap(props: Props) { footer={(
- {strings.numberOfProjects} + {strings.countryResponseActivitiesNumberOfProjects}
)} diff --git a/src/views/CountryOngoingActivitiesThreeWActivities/i18n.json b/src/views/CountryOngoingActivitiesThreeWActivities/i18n.json index de69740195..f341742766 100644 --- a/src/views/CountryOngoingActivitiesThreeWActivities/i18n.json +++ b/src/views/CountryOngoingActivitiesThreeWActivities/i18n.json @@ -22,6 +22,7 @@ "emergencyProjectPeopleReached": "Services Provided to People in Need", "responseActivities": "Response Activities", "activitiesBySector": "Activities by Sector", - "dataNotAvailable": "No Activities" + "dataNotAvailable": "No Activities", + "threeWViewAllActivityLabel": "View all Activities" } } diff --git a/src/views/CountryOngoingActivitiesThreeWActivities/index.tsx b/src/views/CountryOngoingActivitiesThreeWActivities/index.tsx index 21d70b2edb..1ac4856338 100644 --- a/src/views/CountryOngoingActivitiesThreeWActivities/index.tsx +++ b/src/views/CountryOngoingActivitiesThreeWActivities/index.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useMemo, useCallback } from 'react'; import { useOutletContext } from 'react-router-dom'; import { compareNumber, @@ -6,6 +6,9 @@ import { isNotDefined, mapToList, } from '@togglecorp/fujs'; +import Papa from 'papaparse'; +import { saveAs } from 'file-saver'; +import { InformationLineIcon } from '@ifrc-go/icons'; import PieChart from '#components/PieChart'; import BlockLoading from '#components/BlockLoading'; @@ -14,6 +17,16 @@ import KeyFigure from '#components/KeyFigure'; import InfoPopup from '#components/InfoPopup'; import Pager from '#components/Pager'; import Message from '#components/Message'; +import ExportButton from '#components/domain/ExportButton'; +import Table from '#components/Table'; +import Link from '#components/Link'; +import { + createDateColumn, + createElementColumn, + createListDisplayColumn, + createNumberColumn, + createStringColumn, +} from '#components/Table/ColumnShortcuts'; import useTranslation from '#hooks/useTranslation'; import useFilterState from '#hooks/useFilterState'; @@ -21,14 +34,22 @@ import { useRequest } from '#utils/restRequest'; import { type GoApiResponse } from '#utils/restRequest'; import { CountryOutletContext } from '#utils/outletContext'; import { sumSafe } from '#utils/common'; +import useAlert from '#hooks/useAlert'; +import useRecursiveCsvExport from '#hooks/useRecursiveCsvRequest'; import { numericCountSelector, + numericIdSelector, stringTitleSelector, } from '#utils/selectors'; import Filters, { type FilterValue } from '#views/EmergencyActivities/Filters'; -import useEmergencyProjectStats from '#views/EmergencyActivities/useEmergencyProjectStats'; +import useEmergencyProjectStats, { + getPeopleReached, +} from '#views/EmergencyActivities/useEmergencyProjectStats'; import ActivityDetail from '#views/EmergencyActivities/ActivityDetail'; +import ActivityActions, { + type Props as ActivityActionsProps, +} from '#views/EmergencyActivities/ActivityActions'; import ResponseActivitiesMap from './ResponseActivitiesMap'; import i18n from './i18n.json'; @@ -36,6 +57,7 @@ import styles from './styles.module.css'; type EmergencyProjectResponse = GoApiResponse<'/api/v2/emergency-project/'>; type EmergencyProject = NonNullable[number]; +type DistrictDetails = EmergencyProject['districts_details'][number]; type ProjectKey = 'reporting_ns' | 'deployed_eru' | 'status' | 'country' | 'districts'; type FilterKey = ProjectKey | 'sector'; @@ -79,6 +101,10 @@ function filterEmergencyProjects( )); } +function DistrictNameOutput({ districtName }: { districtName: string }) { + return districtName; +} + function getAggregatedValues(values: { title: string, count: number }[]) { const sortedValues = [...values].sort((a, b) => compareNumber(b.count, a.count)); @@ -103,7 +129,7 @@ function getAggregatedValues(values: { title: string, count: number }[]) { // eslint-disable-next-line import/prefer-default-export export function Component() { - const { countryId } = useOutletContext(); + const { countryId, countryResponse } = useOutletContext(); const strings = useTranslation(i18n); const { @@ -139,6 +165,45 @@ export function Component() { limit: 9999, } : undefined, }); + const alert = useAlert(); + + const [ + pendingExport, + progress, + triggerExportStart, + ] = useRecursiveCsvExport({ + onFailure: () => { + alert.show( + strings.failedToCreateExport, + { variant: 'danger' }, + ); + }, + onSuccess: (data) => { + const unparseData = Papa.unparse(data); + const blob = new Blob( + [unparseData], + { type: 'text/csv' }, + ); + saveAs(blob, `${countryResponse?.name}-emergency-activities.csv`); + }, + }); + + const handleExportClick = useCallback(() => { + if (!emergencyProjectListResponse?.count || !countryId) { + return; + } + triggerExportStart( + '/api/v2/emergency-project/', + emergencyProjectListResponse.count, + { + country: [Number(countryId)], + }, + ); + }, [ + triggerExportStart, + countryId, + emergencyProjectListResponse?.count, + ]); const filteredProjectList = filterEmergencyProjects( emergencyProjectListResponse?.results ?? [], @@ -159,11 +224,11 @@ export function Component() { filteredProjectList, ); - const aggreatedProjectCountListBySector = useMemo(() => ( + const aggregatedProjectCountListBySector = useMemo(() => ( getAggregatedValues(emergencyProjectCountListBySector) ), [emergencyProjectCountListBySector]); - const aggreatedProjectCountListByStatus = useMemo(() => ( + const aggregatedProjectCountListByStatus = useMemo(() => ( getAggregatedValues(emergencyProjectCountListByStatus) ), [emergencyProjectCountListByStatus]); @@ -182,13 +247,104 @@ export function Component() { ) ), [sectorGroupedEmergencyProjects]); + const columns = useMemo( + () => ([ + createStringColumn( + 'national_society_eru', + strings.emergencyProjectNationalSociety, + (item) => ( + item.activity_lead === 'deployed_eru' + ? item.deployed_eru_details + ?.eru_owner_details + ?.national_society_country_details + ?.society_name + : item.reporting_ns_details?.society_name + ), + ), + createStringColumn( + 'title', + strings.emergencyProjectTitle, + (item) => item.title, + ), + createDateColumn( + 'start_date', + strings.emergencyProjectStartDate, + (item) => item.start_date, + ), + createStringColumn( + 'country', + strings.emergencyProjectCountry, + (item) => item.country_details?.name, + ), + createListDisplayColumn< + EmergencyProject, + number, + DistrictDetails, + { districtName: string } + >( + 'districts', + strings.emergencyProjectDistrict, + (activity) => ({ + list: activity.districts_details, + renderer: DistrictNameOutput, + rendererParams: (districtDetail) => ({ districtName: districtDetail.name }), + keySelector: (districtDetail) => districtDetail.id, + }), + ), + createStringColumn( + 'status', + strings.emergencyProjectStatus, + (item) => item.status_display, + ), + createNumberColumn( + 'people_reached', + strings.emergencyProjectPeopleReached, + (item) => getPeopleReached(item), + ), + createElementColumn( + 'actions', + '', + ActivityActions, + (_, item) => ({ + activityId: item.id, + className: styles.activityActions, + }), + ), + ]), + [ + strings.emergencyProjectNationalSociety, + strings.emergencyProjectTitle, + strings.emergencyProjectStartDate, + strings.emergencyProjectCountry, + strings.emergencyProjectDistrict, + strings.emergencyProjectStatus, + strings.emergencyProjectPeopleReached, + ], + ); const noActivitiesBySector = (isNotDefined(sectorGroupedEmergencyProjectList) || (isDefined(sectorGroupedEmergencyProjectList) && (sectorGroupedEmergencyProjectList.length < 1))); return (
- + + + {strings.chartDescription} +
+ )} + actions={( + + {strings.addThreeWActivity} + + )} + > {emergencyProjectListResponsePending && } {!emergencyProjectListResponsePending && (
@@ -218,7 +374,7 @@ export function Component() { /> )} + actions={( + + {strings.threeWViewAllActivityLabel} + + )} footerActions={( )} - {/* FIXME: use List, add pending, filtered state */} {sectorGroupedEmergencyProjectList.map((sectorGroupedProject) => ( )} /> + + )} + > + + ); diff --git a/src/views/CountryOngoingActivitiesThreeWActivities/styles.module.css b/src/views/CountryOngoingActivitiesThreeWActivities/styles.module.css index a308b275a2..1062dc353f 100644 --- a/src/views/CountryOngoingActivitiesThreeWActivities/styles.module.css +++ b/src/views/CountryOngoingActivitiesThreeWActivities/styles.module.css @@ -31,4 +31,48 @@ } } } + + .chart-description { + display: flex; + align-items: center; + gap: var(--go-ui-spacing-xs); + + .icon { + flex-shrink: 0; + font-size: var(--go-ui-height-icon-multiplier); + } + } + + .response-activities { + overflow: auto; + + .content { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-xl); + } + + .sidebar { + height: 100%; + overflow: auto; + + .sidebar-content { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-sm); + overflow: auto; + } + } + } + + .activity-actions { + font-size: var(--go-ui-height-icon-multiplier); + } +} + +.tooltip { + display: flex; + align-items: center; + flex-direction: column; + padding: var(--go-ui-spacing-sm); }