Skip to content

Commit 989c4ef

Browse files
committed
feat(our-work): add our work page with table, filters and query
1 parent ce7697a commit 989c4ef

8 files changed

Lines changed: 722 additions & 15 deletions

File tree

app/Root/config/routes.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,23 @@ const editUser: RouteConfig = {
6464

6565
const ourWorks: RouteConfig = {
6666
index: true,
67-
path: '/our-works',
68-
load: () => import('#views/OurWorks'),
67+
path: '/OurWorks',
68+
load: () => import('#views/OurWorks/WorksList'),
6969
visibility: 'is-authenticated',
7070
};
71+
72+
const createWorks: RouteConfig = {
73+
path: '/OurWorks/new',
74+
load: () => import('#views/OurWorks/WorksForm'),
75+
visibility: 'is-authenticated',
76+
};
77+
78+
const editWorks: RouteConfig = {
79+
path: '/OurWorks/:id/edit',
80+
load: () => import('#views/OurWorks/WorksForm'),
81+
visibility: 'is-authenticated',
82+
};
83+
7184
const preparedness: RouteConfig = {
7285
index: true,
7386
path: '/preparedness',
@@ -110,6 +123,8 @@ const routes = {
110123
createUser,
111124
editUser,
112125
ourWorks,
126+
createWorks,
127+
editWorks,
113128
preparedness,
114129
dataAndReports,
115130
documents,

app/index.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,21 @@ p {
3636
a {
3737
text-decoration: none;
3838
color: inherit;
39+
}
40+
41+
.status-indicator-active,
42+
.status-indicator-inactive {
43+
display: inline-block;
44+
border-radius: 50%;
45+
width: var(--go-ui-spacing-sm);
46+
height: var(--go-ui-spacing-sm);
47+
}
48+
49+
50+
.status-indicator-active {
51+
background-color: var(--go-ui-color-positive);
52+
}
53+
54+
.status-indicator-inactive {
55+
background-color: var(--go-ui-color-negative);
3956
}
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
import {
2+
useCallback,
3+
useEffect,
4+
useMemo,
5+
} from 'react';
6+
import { useParams } from 'react-router';
7+
import {
8+
BlockLoading,
9+
Button,
10+
Container,
11+
InputSection,
12+
ListView,
13+
NumberInput,
14+
RadioInput,
15+
SelectInput,
16+
TextInput,
17+
} from '@ifrc-go/ui';
18+
import {
19+
isDefined,
20+
isNotDefined,
21+
} from '@togglecorp/fujs';
22+
import {
23+
createSubmitHandler,
24+
getErrorObject,
25+
type ObjectSchema,
26+
type PartialForm,
27+
removeNull,
28+
requiredStringCondition,
29+
useForm,
30+
} from '@togglecorp/toggle-form';
31+
32+
import RegionSelectInput from '#components/RegionSelectInput';
33+
import {
34+
DashboardPage,
35+
type ExternalDashboardCreateInput,
36+
type ExternalDashboardUpdateInput,
37+
useCreateExternalDashboardMutation,
38+
useDashboardEnumsQuery,
39+
useExternalDashboardDetailQuery,
40+
useUpdateExternalDashboardMutation,
41+
} from '#generated/types/graphql';
42+
import useAlert from '#hooks/useAlert';
43+
import useRouting from '#hooks/useRouting';
44+
import {
45+
labelSelector,
46+
statusOptions,
47+
valueSelector,
48+
} from '#utils/common';
49+
50+
type PartialFormType = PartialForm<ExternalDashboardCreateInput>;
51+
type FormSchema = ObjectSchema<PartialFormType>;
52+
type FormSchemaFields = ReturnType<FormSchema['fields']>;
53+
54+
const getWorksSchema = (): FormSchema => ({
55+
fields: (): FormSchemaFields => ({
56+
page: {
57+
required: true,
58+
},
59+
title: {
60+
required: true,
61+
requiredValidation: requiredStringCondition,
62+
},
63+
description: {},
64+
region: {},
65+
url: {
66+
required: true,
67+
requiredValidation: requiredStringCondition,
68+
},
69+
isActive: {},
70+
order: {},
71+
showOnHome: {},
72+
}),
73+
});
74+
75+
const defaultEditFormValue: PartialFormType = {
76+
isActive: false,
77+
showOnHome: false,
78+
};
79+
80+
const pageKeySelector = (item: { key: string }) => item.key as DashboardPage;
81+
82+
function WorksForm() {
83+
const { id } = useParams();
84+
const navigate = useRouting();
85+
const alert = useAlert();
86+
87+
const worksSchema = useMemo(() => getWorksSchema(), []);
88+
89+
const {
90+
setFieldValue,
91+
error: formError,
92+
value,
93+
validate,
94+
setError,
95+
setValue,
96+
} = useForm(worksSchema, { value: defaultEditFormValue });
97+
98+
const [{ data, fetching: worksDetailFetch }] = useExternalDashboardDetailQuery({
99+
variables: { id: isDefined(id) ? id : '' },
100+
pause: isNotDefined(id),
101+
});
102+
103+
const [{ data: enumsData }] = useDashboardEnumsQuery();
104+
105+
const pageOptions = useMemo(
106+
() => enumsData?.enums?.DashboardPage?.filter(
107+
(option) => option.key !== DashboardPage.EmergencyAlerts
108+
&& option.key !== DashboardPage.DisasterResponse,
109+
),
110+
[enumsData],
111+
);
112+
113+
const [
114+
{ fetching: createPending },
115+
createExternalDashboard,
116+
] = useCreateExternalDashboardMutation();
117+
const [
118+
{ fetching: updatePending },
119+
updateExternalDashboard,
120+
] = useUpdateExternalDashboardMutation();
121+
122+
const pending = createPending || updatePending || worksDetailFetch;
123+
124+
const handleMutation = useCallback(async (mutationData: PartialFormType) => {
125+
const redirectPath = 'ourWorks';
126+
const alertMessage = `Initiative ${isDefined(id) ? 'updated' : 'created'} successfully`;
127+
128+
if (isDefined(id)) {
129+
const updatePayload = removeNull(mutationData) as ExternalDashboardUpdateInput;
130+
131+
const res = await updateExternalDashboard({
132+
id,
133+
data: updatePayload,
134+
});
135+
const result = res.data?.updateExternalDashboard;
136+
if (isDefined(result) && result.ok) {
137+
navigate(redirectPath);
138+
alert.show(alertMessage, { variant: 'success' });
139+
} else if (isDefined(result) && isDefined(result.errors)) {
140+
setError(result.errors);
141+
alert.show(result.errors[0]?.messages, { variant: 'danger' });
142+
}
143+
} else {
144+
const createPayload = removeNull(mutationData) as ExternalDashboardCreateInput;
145+
const res = await createExternalDashboard({
146+
data: createPayload,
147+
});
148+
const result = res.data?.createExternalDashboard;
149+
if (isDefined(result) && result.ok) {
150+
navigate(redirectPath);
151+
alert.show(alertMessage, { variant: 'success' });
152+
} else if (isDefined(result) && isDefined(result.errors)) {
153+
setError(result.errors);
154+
alert.show(result.errors[0]?.messages, { variant: 'danger' });
155+
}
156+
}
157+
}, [alert, updateExternalDashboard, id, navigate, setError, createExternalDashboard]);
158+
159+
const handleFormSubmit = useCallback(
160+
() => createSubmitHandler(
161+
validate,
162+
setError,
163+
handleMutation,
164+
)(),
165+
[validate, setError, handleMutation],
166+
);
167+
168+
const handleCancel = useCallback(() => {
169+
navigate('ourWorks');
170+
}, [navigate]);
171+
172+
const error = getErrorObject(formError);
173+
174+
useEffect(() => {
175+
if (isNotDefined(data?.externalDashboard)) {
176+
return;
177+
}
178+
const { regionId, ...otherValues } = removeNull(data.externalDashboard);
179+
setValue({
180+
...otherValues,
181+
region: regionId ?? undefined,
182+
});
183+
}, [data, setValue]);
184+
185+
if (worksDetailFetch || createPending || updatePending) {
186+
return (
187+
<BlockLoading
188+
withoutBorder
189+
compact
190+
message="Loading"
191+
/>
192+
);
193+
}
194+
195+
return (
196+
<Container
197+
heading={isDefined(id) ? 'Edit Initiative' : 'Create New Initiatives'}
198+
headerDescription={isDefined(id)
199+
? 'Manage and update emergency response or project mapping on ongoing and initiative works'
200+
: 'Create a emergency response or project mapping on ongoing and initiative works'}
201+
withPadding
202+
footerActions={(
203+
<ListView>
204+
<Button
205+
name={undefined}
206+
onClick={handleCancel}
207+
>
208+
Cancel
209+
</Button>
210+
<Button
211+
name={undefined}
212+
onClick={handleFormSubmit}
213+
styleVariant="filled"
214+
disabled={pending}
215+
>
216+
Save
217+
</Button>
218+
</ListView>
219+
)}
220+
>
221+
<ListView layout="block">
222+
<InputSection
223+
title="Operations"
224+
description="Select the operation"
225+
withAsteriskOnTitle
226+
>
227+
<SelectInput
228+
name="page"
229+
value={value.page}
230+
onChange={setFieldValue}
231+
options={pageOptions}
232+
keySelector={pageKeySelector}
233+
labelSelector={labelSelector}
234+
error={error?.page}
235+
disabled={pending}
236+
/>
237+
</InputSection>
238+
<InputSection
239+
title="Title"
240+
description="Enter the title name of the dashboard"
241+
withAsteriskOnTitle
242+
>
243+
<TextInput
244+
name="title"
245+
value={value.title}
246+
onChange={setFieldValue}
247+
error={error?.title}
248+
disabled={pending}
249+
/>
250+
</InputSection>
251+
<InputSection
252+
title="Description"
253+
description="Enter the description about the dashboard"
254+
>
255+
<TextInput
256+
name="description"
257+
value={value.description}
258+
onChange={setFieldValue}
259+
error={error?.description}
260+
disabled={pending}
261+
/>
262+
</InputSection>
263+
<InputSection
264+
title="Region"
265+
description="Select the region"
266+
>
267+
<RegionSelectInput
268+
name="region"
269+
value={value.region}
270+
onChange={setFieldValue}
271+
error={error?.region}
272+
disabled={pending}
273+
/>
274+
</InputSection>
275+
<InputSection
276+
title="Embed link"
277+
description="Enter the embed link of the dashboard"
278+
withAsteriskOnTitle
279+
>
280+
<TextInput
281+
name="url"
282+
value={value.url}
283+
onChange={setFieldValue}
284+
error={error?.url}
285+
disabled={pending}
286+
/>
287+
</InputSection>
288+
<InputSection
289+
title="Status"
290+
description="Choose if the dashboard is to be active or inactive "
291+
>
292+
<RadioInput
293+
name="isActive"
294+
value={value.isActive}
295+
onChange={setFieldValue}
296+
options={statusOptions}
297+
keySelector={valueSelector}
298+
labelSelector={labelSelector}
299+
error={error?.isActive}
300+
disabled={pending}
301+
/>
302+
</InputSection>
303+
<InputSection
304+
title="Order"
305+
description="Enter the order in which the dashboard should appear"
306+
>
307+
<NumberInput
308+
name="order"
309+
value={value.order}
310+
onChange={setFieldValue}
311+
error={error?.order}
312+
disabled={pending}
313+
/>
314+
</InputSection>
315+
</ListView>
316+
</Container>
317+
);
318+
}
319+
320+
export default WorksForm;

0 commit comments

Comments
 (0)