Skip to content

Commit 8a9e9da

Browse files
committed
feat(eap): add eap export modal
1 parent a6f45ab commit 8a9e9da

File tree

5 files changed

+249
-7
lines changed

5 files changed

+249
-7
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"namespace": "eapExportModal",
3+
"strings": {
4+
"exportTitle": "Export EAP",
5+
"preparingExport": "Preparing for export...",
6+
"waitingExport": "Waiting for the export to complete...",
7+
"exportFailed": "Export failed",
8+
"exportSuccessfully": "Export completed successfully!",
9+
"downloadLinkDescription": "Click on the download link below!",
10+
"downloadLinkLabel": "Download PDF",
11+
"failureToExportMessage":"Failed to export PDF."
12+
}
13+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import {
2+
useEffect,
3+
useMemo,
4+
useState,
5+
} from 'react';
6+
import { DownloadLineIcon } from '@ifrc-go/icons';
7+
import {
8+
Message,
9+
Modal,
10+
} from '@ifrc-go/ui';
11+
import { useTranslation } from '@ifrc-go/ui/hooks';
12+
import {
13+
isDefined,
14+
isNotDefined,
15+
} from '@togglecorp/fujs';
16+
17+
import Link from '#components/Link';
18+
import { type components } from '#generated/types';
19+
import useAlert from '#hooks/useAlert';
20+
import { EAP_TYPE_SIMPLIFIED } from '#utils/constants';
21+
import {
22+
type GoApiBody,
23+
useLazyRequest,
24+
useRequest,
25+
} from '#utils/restRequest';
26+
27+
import i18n from './i18n.json';
28+
import styles from './styles.module.css';
29+
30+
type EapType = components['schemas']['EapEapTypeEnumKey'];
31+
type ExportStatusEnum = components<'read'>['schemas']['ExportStatusEnum'];
32+
33+
type ExportBody = GoApiBody<'/api/v2/pdf-export/', 'POST'>;
34+
35+
const EXPORT_STATUS_PENDING = 0 satisfies ExportStatusEnum;
36+
const EXPORT_STATUS_COMPLETED = 1 satisfies ExportStatusEnum;
37+
const EXPORT_STATUS_ERRORED = 2 satisfies ExportStatusEnum;
38+
39+
interface Props {
40+
eapId: number;
41+
eapType: EapType;
42+
onClose: () => void;
43+
}
44+
45+
function EapExportModal(props: Props) {
46+
const {
47+
eapId,
48+
eapType,
49+
onClose,
50+
} = props;
51+
52+
const strings = useTranslation(i18n);
53+
const alert = useAlert();
54+
55+
const [exportId, setExportId] = useState<number | undefined>();
56+
57+
const exportTriggerBody = useMemo<ExportBody>(
58+
() => ({
59+
export_id: eapId,
60+
export_type: eapType === EAP_TYPE_SIMPLIFIED ? 'simplified-eap' : 'full-eap',
61+
selector: '#pdf-preview-ready',
62+
is_pga: false,
63+
per_country: undefined,
64+
}),
65+
[eapId, eapType],
66+
);
67+
68+
const {
69+
pending: exportPending,
70+
error: exportError,
71+
trigger: triggerExport,
72+
} = useLazyRequest({
73+
method: 'POST',
74+
useCurrentLanguageForMutation: true,
75+
url: '/api/v2/pdf-export/',
76+
body: exportTriggerBody,
77+
onSuccess: (response) => {
78+
if (isDefined(response.id)) {
79+
setExportId(response.id);
80+
}
81+
},
82+
onFailure: () => {
83+
alert.show(
84+
strings.failureToExportMessage,
85+
{ variant: 'danger' },
86+
);
87+
},
88+
});
89+
90+
useEffect(() => {
91+
triggerExport(null);
92+
}, [triggerExport]);
93+
94+
const {
95+
pending: exportStatusPending,
96+
response: exportStatusResponse,
97+
error: exportStatusError,
98+
} = useRequest({
99+
skip: isNotDefined(exportId),
100+
url: '/api/v2/pdf-export/{id}/',
101+
// FIXME: typings should be fixed in the server
102+
pathVariables: isDefined(exportId) ? ({ id: String(exportId) }) : undefined,
103+
shouldPoll: (poll) => {
104+
if (poll?.errored || poll?.value?.status !== EXPORT_STATUS_PENDING) {
105+
return -1;
106+
}
107+
108+
return 5000;
109+
},
110+
});
111+
112+
const exportStatus = useMemo(() => {
113+
if (exportPending) {
114+
return 'PREPARE';
115+
}
116+
117+
if (exportStatusPending || exportStatusResponse?.status === EXPORT_STATUS_PENDING) {
118+
return 'WAITING';
119+
}
120+
121+
if (isDefined(exportStatusError)
122+
|| isDefined(exportError)
123+
|| (isDefined(exportStatusResponse)
124+
&& exportStatusResponse.status === EXPORT_STATUS_ERRORED)
125+
) {
126+
return 'FAILED';
127+
}
128+
129+
if (isDefined(exportStatusResponse)
130+
&& isDefined(exportStatusResponse.status === EXPORT_STATUS_COMPLETED)
131+
&& isDefined(exportStatusResponse.pdf_file)
132+
) {
133+
return 'SUCCESS';
134+
}
135+
136+
return 'NOT_STARTED';
137+
}, [
138+
exportPending,
139+
exportStatusError,
140+
exportError,
141+
exportStatusPending,
142+
exportStatusResponse,
143+
]);
144+
145+
return (
146+
<Modal
147+
heading={strings.exportTitle}
148+
onClose={onClose}
149+
className={styles.drefExportModal}
150+
>
151+
{exportStatus === 'PREPARE' && (
152+
<Message
153+
pending
154+
title={strings.preparingExport}
155+
/>
156+
)}
157+
{exportStatus === 'WAITING' && (
158+
<Message
159+
pending
160+
title={strings.waitingExport}
161+
/>
162+
)}
163+
{exportStatus === 'FAILED' && (
164+
<Message
165+
title={strings.exportFailed}
166+
description={exportError?.value.messageForNotification
167+
?? exportStatusError?.value.messageForNotification}
168+
/>
169+
)}
170+
{exportStatus === 'SUCCESS' && (
171+
<Message
172+
title={strings.exportSuccessfully}
173+
description={strings.downloadLinkDescription}
174+
actions={(
175+
<Link
176+
colorVariant="primary"
177+
styleVariant="outline"
178+
href={exportStatusResponse?.pdf_file}
179+
before={<DownloadLineIcon className={styles.icon} />}
180+
external
181+
>
182+
{strings.downloadLinkLabel}
183+
</Link>
184+
)}
185+
/>
186+
)}
187+
</Modal>
188+
);
189+
}
190+
191+
export default EapExportModal;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.dref-export-modal {
2+
.icon {
3+
font-size: var(--go-ui-height-icon-multiplier);
4+
}
5+
}

app/src/views/AccountMyFormsEap/EapStatus/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function EapStatus(props: Props) {
9292
const requestBody = useMemo<EapStatusBody>(() => ({
9393
status: newStatus,
9494
review_checklist_file: undefined,
95-
}), [newStatus]);
95+
} as EapStatusBody), [newStatus]);
9696

9797
const handleStatusUpdateCancel = useCallback(() => {
9898
setNewStatus(undefined);

app/src/views/AccountMyFormsEap/EapTableActions/index.tsx

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1-
import { TableActions } from '@ifrc-go/ui';
2-
import { useTranslation } from '@ifrc-go/ui/hooks';
3-
import { isNotDefined } from '@togglecorp/fujs';
1+
import {
2+
Button,
3+
TableActions,
4+
} from '@ifrc-go/ui';
5+
import {
6+
useBooleanState,
7+
useTranslation,
8+
} from '@ifrc-go/ui/hooks';
9+
import {
10+
isDefined,
11+
isNotDefined,
12+
} from '@togglecorp/fujs';
413

14+
import EapExportModal from '#components/domain/EapExportModal';
515
import DropdownMenuItem from '#components/DropdownMenuItem';
616
import Link from '#components/Link';
17+
import { type components } from '#generated/types';
718
import {
819
EAP_TYPE_FULL,
920
EAP_TYPE_SIMPLIFIED,
1021
} from '#utils/constants';
11-
import { type GoApiResponse } from '#utils/restRequest';
1222

1323
import i18n from './i18n.json';
1424

15-
type EapResponse = GoApiResponse<'/api/v2/eap-registration/{id}/'>;
25+
type EapType = components['schemas']['EapEapTypeEnumKey'];
1626

1727
export interface Props {
1828
eapId: number;
19-
eapType: EapResponse['eap_type'];
29+
eapType: EapType | null | undefined;
2030
}
2131

2232
function EapTableActions(props: Props) {
@@ -26,6 +36,13 @@ function EapTableActions(props: Props) {
2636
} = props;
2737

2838
const strings = useTranslation(i18n);
39+
const [
40+
showExportModal,
41+
{
42+
setTrue: setShowExportModalTrue,
43+
setFalse: setShowExportModalFalse,
44+
},
45+
] = useBooleanState(false);
2946

3047
return (
3148
<TableActions
@@ -92,6 +109,22 @@ function EapTableActions(props: Props) {
92109
{strings.eapEditSimplifiedLink}
93110
</Link>
94111
)}
112+
{eapType === EAP_TYPE_SIMPLIFIED && (
113+
<Button
114+
name={undefined}
115+
onClick={setShowExportModalTrue}
116+
// FIXME: use strings
117+
>
118+
Export
119+
</Button>
120+
)}
121+
{showExportModal && isDefined(eapType) && (
122+
<EapExportModal
123+
eapId={eapId}
124+
eapType={eapType}
125+
onClose={setShowExportModalFalse}
126+
/>
127+
)}
95128
</TableActions>
96129
);
97130
}

0 commit comments

Comments
 (0)