Skip to content

Commit

Permalink
Add export and view all activities in country threeW activities tab
Browse files Browse the repository at this point in the history
  • Loading branch information
puranban committed Dec 1, 2023
1 parent 9816132 commit 6089504
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ function ResponseActivitiesMap(props: Props) {
} = props;

const strings = useTranslation(i18n);
const {
// countryId,
countryResponse,
} = useOutletContext<CountryOutletContext>();
const { countryResponse } = useOutletContext<CountryOutletContext>();

const bounds = useMemo(
() => (countryResponse ? getBbox(countryResponse?.bbox) : undefined),
Expand Down Expand Up @@ -153,23 +150,23 @@ function ResponseActivitiesMap(props: Props) {
footer={(
<div className={styles.legend}>
<div className={styles.label}>
{strings.numberOfProjects}
{strings.countryResponseActivitiesNumberOfProjects}
</div>
<LegendItem
color={COLOR_SEVERITY_LOW}
label={strings.severityLow}
label={strings.countryResponseActivitiesSeverityLow}
/>
<LegendItem
color={COLOR_SEVERITY_MEDIUM}
label={strings.severityMedium}
label={strings.countryResponseActivitiesSeverityMedium}
/>
<LegendItem
color={COLOR_SEVERITY_HIGH}
label={strings.severityHigh}
label={strings.countryResponseActivitiesSeverityHigh}
/>
<LegendItem
color={COLOR_SEVERITY_SEVERE}
label={strings.severitySevere}
label={strings.countryResponseActivitiesSeveritySevere}
/>
</div>
)}
Expand Down
3 changes: 2 additions & 1 deletion src/views/CountryOngoingActivitiesThreeWActivities/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
201 changes: 192 additions & 9 deletions src/views/CountryOngoingActivitiesThreeWActivities/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useMemo } from 'react';
import { useMemo, useCallback } from 'react';
import { useOutletContext } from 'react-router-dom';
import {
compareNumber,
isDefined,
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';
Expand All @@ -14,28 +17,47 @@ 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';
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';
import styles from './styles.module.css';

type EmergencyProjectResponse = GoApiResponse<'/api/v2/emergency-project/'>;
type EmergencyProject = NonNullable<EmergencyProjectResponse['results']>[number];
type DistrictDetails = EmergencyProject['districts_details'][number];

type ProjectKey = 'reporting_ns' | 'deployed_eru' | 'status' | 'country' | 'districts';
type FilterKey = ProjectKey | 'sector';
Expand Down Expand Up @@ -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));

Expand All @@ -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<CountryOutletContext>();
const { countryId, countryResponse } = useOutletContext<CountryOutletContext>();
const strings = useTranslation(i18n);

const {
Expand Down Expand Up @@ -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 ?? [],
Expand All @@ -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]);

Expand All @@ -182,13 +247,104 @@ export function Component() {
)
), [sectorGroupedEmergencyProjects]);

const columns = useMemo(
() => ([
createStringColumn<EmergencyProject, number>(
'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<EmergencyProject, number>(
'title',
strings.emergencyProjectTitle,
(item) => item.title,
),
createDateColumn<EmergencyProject, number>(
'start_date',
strings.emergencyProjectStartDate,
(item) => item.start_date,
),
createStringColumn<EmergencyProject, number>(
'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<EmergencyProject, number>(
'status',
strings.emergencyProjectStatus,
(item) => item.status_display,
),
createNumberColumn<EmergencyProject, number>(
'people_reached',
strings.emergencyProjectPeopleReached,
(item) => getPeopleReached(item),
),
createElementColumn<EmergencyProject, number, ActivityActionsProps>(
'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 (
<div className={styles.threewActivities}>
<Container>
<Container
withHeaderBorder
footerContent={(
<div className={styles.chartDescription}>
<InformationLineIcon className={styles.icon} />
{strings.chartDescription}
</div>
)}
actions={(
<Link
variant="secondary"
title={strings.addThreeWActivity}
to="newThreeWActivity"
>
{strings.addThreeWActivity}
</Link>
)}
>
{emergencyProjectListResponsePending && <BlockLoading />}
{!emergencyProjectListResponsePending && (
<div className={styles.keyFigureCardList}>
Expand Down Expand Up @@ -218,7 +374,7 @@ export function Component() {
/>
<PieChart
className={styles.pieChart}
data={aggreatedProjectCountListBySector}
data={aggregatedProjectCountListBySector}
valueSelector={numericCountSelector}
labelSelector={stringTitleSelector}
keySelector={stringTitleSelector}
Expand All @@ -235,7 +391,7 @@ export function Component() {
/>
<PieChart
className={styles.pieChart}
data={aggreatedProjectCountListByStatus}
data={aggregatedProjectCountListByStatus}
valueSelector={numericCountSelector}
labelSelector={stringTitleSelector}
keySelector={stringTitleSelector}
Expand All @@ -258,6 +414,16 @@ export function Component() {
onChange={setFilters}
/>
)}
actions={(
<Link
to="allThreeWActivity"
withLinkIcon
withUnderline
urlSearch={`country=${countryId}`}
>
{strings.threeWViewAllActivityLabel}
</Link>
)}
footerActions={(
<Pager
activePage={activePage}
Expand All @@ -282,7 +448,6 @@ export function Component() {
compact
/>
)}
{/* FIXME: use List, add pending, filtered state */}
{sectorGroupedEmergencyProjectList.map((sectorGroupedProject) => (
<ActivityDetail
key={sectorGroupedProject.sector}
Expand All @@ -293,6 +458,24 @@ export function Component() {
</Container>
)}
/>
<Container
actions={(
<ExportButton
onClick={handleExportClick}
progress={progress}
pendingExport={pendingExport}
totalCount={emergencyProjectListResponse?.count}
/>
)}
>
<Table
filtered={isFiltered}
pending={emergencyProjectListResponsePending}
data={paginatedEmergencyProjectList}
columns={columns}
keySelector={numericIdSelector}
/>
</Container>
</Container>
</div>
);
Expand Down
Loading

0 comments on commit 6089504

Please sign in to comment.