Skip to content

Commit 37acde2

Browse files
committed
feat(vacancy): Added vacancy listing and edit add form
1 parent 9287534 commit 37acde2

File tree

4 files changed

+520
-0
lines changed

4 files changed

+520
-0
lines changed

app/root/config/routes.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,27 @@ const addRadioProgram: RouteConfig = {
181181
visibility: 'is-authenticated',
182182
};
183183

184+
const vacancy: RouteConfig = {
185+
index: true,
186+
path: 'vacancy',
187+
load: () => import('#views/Vacancy/VacancyList'),
188+
visibility: 'is-authenticated',
189+
};
190+
191+
const editVacancy: RouteConfig = {
192+
index: true,
193+
path: 'vacancy/:id/edit',
194+
load: () => import('#views/Vacancy/VacancyForm'),
195+
visibility: 'is-authenticated',
196+
};
197+
198+
const addVacancy: RouteConfig = {
199+
index: true,
200+
path: 'vacancy/add',
201+
load: () => import('#views/Vacancy/VacancyForm'),
202+
visibility: 'is-authenticated',
203+
};
204+
184205
const routes = {
185206
login,
186207
home,
@@ -208,6 +229,9 @@ const routes = {
208229
radioProgram,
209230
editRadioProgram,
210231
addRadioProgram,
232+
vacancy,
233+
addVacancy,
234+
editVacancy,
211235
};
212236

213237
export type RouteKeys = keyof typeof routes;
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import {
2+
useCallback,
3+
useEffect,
4+
} from 'react';
5+
import {
6+
useNavigate,
7+
useParams,
8+
} from 'react-router';
9+
import {
10+
Button,
11+
Checkbox,
12+
DateInput,
13+
Heading,
14+
NumberInput,
15+
SelectInput,
16+
TextArea,
17+
TextInput,
18+
} from '@ifrc-go/ui';
19+
import {
20+
createSubmitHandler,
21+
getErrorObject,
22+
integerCondition,
23+
ObjectSchema,
24+
PartialForm,
25+
requiredStringCondition,
26+
useForm,
27+
} from '@togglecorp/toggle-form';
28+
29+
import ContainerWrapper from '#components/ContainerWrapper';
30+
import FileUpload from '#components/FileUpload';
31+
import FormSection from '#components/FormSection';
32+
import Page from '#components/Page';
33+
import {
34+
JobVacancyCreateInput,
35+
useCreateVacancyMutation,
36+
useDepartmentsQuery,
37+
useUpdateVacancyMutation,
38+
useVacancyDetailQuery,
39+
} from '#generated/types/graphql';
40+
import urlToFile from '#utils/urlToFile';
41+
42+
type PartialFormType = PartialForm<JobVacancyCreateInput> &
43+
{ createdBy: string, modifiedBy: string }
44+
45+
type FormSchema = ObjectSchema<PartialFormType>;
46+
type FormSchemaFields = ReturnType<FormSchema['fields']>;
47+
48+
const EditBlogSchema: FormSchema = {
49+
fields: (): FormSchemaFields => ({
50+
title: {
51+
required: true,
52+
requiredValidation: requiredStringCondition,
53+
},
54+
description: {
55+
required: true,
56+
requiredValidation: requiredStringCondition,
57+
},
58+
position: {
59+
required: true,
60+
requiredValidation: requiredStringCondition,
61+
},
62+
publishedAt: {
63+
required: true,
64+
requiredValidation: requiredStringCondition,
65+
},
66+
expiryDate: {
67+
required: true,
68+
requiredValidation: requiredStringCondition,
69+
},
70+
department: {
71+
required: true,
72+
},
73+
file: {
74+
required: true,
75+
},
76+
numberOfVacancies: {
77+
required: true,
78+
requiredValidation: integerCondition,
79+
80+
},
81+
isArchived: {},
82+
createdBy: {},
83+
modifiedBy: {},
84+
85+
}),
86+
};
87+
88+
const defaultEditFormValue: PartialFormType = {
89+
createdBy: '',
90+
modifiedBy: '',
91+
};
92+
function VacancyForm() {
93+
const { id } = useParams();
94+
const navigate = useNavigate();
95+
const [{ data }] = useVacancyDetailQuery({
96+
variables: { id: id || '' }, pause: !id,
97+
});
98+
const [{ data: departments }] = useDepartmentsQuery();
99+
100+
const [{ fetching: createPending }, createVacancyMutate] = useCreateVacancyMutation();
101+
const [{ fetching: updatePending }, updateVacancyMutate] = useUpdateVacancyMutation();
102+
const {
103+
setFieldValue,
104+
error: formError,
105+
value,
106+
validate,
107+
setError,
108+
} = useForm(EditBlogSchema, { value: defaultEditFormValue });
109+
110+
const error = getErrorObject(formError);
111+
112+
const handleFormSubmit = useCallback(() => {
113+
const handler = createSubmitHandler(
114+
validate,
115+
setError,
116+
async (val) => {
117+
const mutateData = {
118+
file: val.file ?? null,
119+
title: val.title ?? '',
120+
publishedAt: val.publishedAt,
121+
department: val.department ?? null,
122+
description: val.description ?? '',
123+
expiryDate: val.expiryDate,
124+
numberOfVacancies: val.numberOfVacancies ?? 0,
125+
position: val.position ?? '',
126+
isArchived: val.isArchived,
127+
128+
};
129+
if (id) {
130+
const res = await updateVacancyMutate({
131+
pk: id,
132+
data: mutateData,
133+
});
134+
if (res.data?.updateJobVacancy?.ok) {
135+
navigate('/vacancy');
136+
} else if (res.data?.updateJobVacancy.errors) {
137+
setError(res.data.updateJobVacancy.errors);
138+
}
139+
} else {
140+
const res = await createVacancyMutate({
141+
data: mutateData,
142+
});
143+
144+
if (res.data?.createJobVacancy.ok) {
145+
navigate('/vacancy');
146+
} else if (res.data?.createJobVacancy?.errors) {
147+
setError(res.data.createJobVacancy.errors);
148+
}
149+
}
150+
},
151+
);
152+
handler();
153+
}, [setError, validate, id, createVacancyMutate, updateVacancyMutate, navigate]);
154+
155+
useEffect(() => {
156+
if (data?.jobVacancy) {
157+
const { jobVacancy } = data;
158+
if (jobVacancy.file) {
159+
urlToFile(jobVacancy?.file?.url, jobVacancy?.file?.name)
160+
.then((file) => {
161+
setFieldValue(file, 'file');
162+
});
163+
}
164+
setFieldValue(jobVacancy?.title, 'title');
165+
setFieldValue(jobVacancy?.description, 'description');
166+
setFieldValue(jobVacancy?.publishedAt, 'publishedAt');
167+
setFieldValue(jobVacancy?.isArchived, 'isArchived');
168+
setFieldValue(jobVacancy?.expiryDate, 'expiryDate');
169+
setFieldValue(jobVacancy?.departmentId, 'department');
170+
setFieldValue(jobVacancy?.numberOfVacancies, 'numberOfVacancies');
171+
setFieldValue(jobVacancy?.position, 'position');
172+
setFieldValue(`${jobVacancy.modifiedBy?.firstName} ${jobVacancy.modifiedBy?.lastName}`, 'modifiedBy');
173+
setFieldValue(`${jobVacancy.createdBy?.firstName} ${jobVacancy.createdBy?.lastName}`, 'createdBy');
174+
}
175+
}, [data, setFieldValue]);
176+
177+
const departmentOptions = departments?.departments.results.map(
178+
(dept) => ({
179+
id: dept.id,
180+
name: dept.title,
181+
}),
182+
) ?? [];
183+
184+
return (
185+
<Page>
186+
<ContainerWrapper>
187+
<FormSection headingLevel={3} label="VACANCY DETAILS" />
188+
{(value.createdBy && value.modifiedBy) && (
189+
<FormSection>
190+
<Heading level={6}>
191+
Created by:
192+
{' '}
193+
{value.createdBy}
194+
</Heading>
195+
<Heading level={6}>
196+
Modified by:
197+
{' '}
198+
{value.createdBy}
199+
</Heading>
200+
</FormSection>
201+
)}
202+
<FormSection label="Title" description="Enter the Title" withAsteriskOnTitle>
203+
<TextInput
204+
name="title"
205+
value={value.title}
206+
error={error?.title as string}
207+
onChange={setFieldValue}
208+
/>
209+
</FormSection>
210+
<FormSection label="File" description="Add a File, which will be attached and shown on Radio Page" withAsteriskOnTitle>
211+
<FileUpload
212+
name="file"
213+
onChange={(files) => setFieldValue(files, 'file')}
214+
value={value.file}
215+
/>
216+
</FormSection>
217+
<FormSection label="Vacancy Position" description="Enter the Vacancy Position" withAsteriskOnTitle>
218+
<TextInput
219+
name="position"
220+
value={value.position}
221+
error={error?.position as string}
222+
onChange={setFieldValue}
223+
/>
224+
</FormSection>
225+
<FormSection label="Description" description="Enter the Description" withAsteriskOnTitle>
226+
<TextArea
227+
name="description"
228+
value={value.description}
229+
error={error?.description as string}
230+
onChange={setFieldValue}
231+
/>
232+
</FormSection>
233+
<FormSection label="Number of Vacancies" description="Enter the Number of Vacancies" withAsteriskOnTitle>
234+
<NumberInput
235+
name="numberOfVacancies"
236+
value={value.numberOfVacancies}
237+
error={error?.numberOfVacancies as string}
238+
onChange={setFieldValue}
239+
/>
240+
</FormSection>
241+
<FormSection label="Published Date" description="This date should be the Published Date of the Vacancy" withAsteriskOnTitle>
242+
<DateInput
243+
name="publishedAt"
244+
value={value.publishedAt}
245+
onChange={setFieldValue}
246+
placeholder="Select Date"
247+
error={error?.publishedAt as string}
248+
/>
249+
</FormSection>
250+
<FormSection label="Expire Date" description="This date should be the Expire Date of the Vacancy" withAsteriskOnTitle>
251+
<DateInput
252+
name="expiryDate"
253+
value={value.expiryDate}
254+
onChange={setFieldValue}
255+
placeholder="Select Date"
256+
error={error?.expiryDate as string}
257+
/>
258+
</FormSection>
259+
<FormSection label="Type" description="Add type to either Tuesday Program or Radio Red Cross" withAsteriskOnTitle>
260+
<SelectInput
261+
name="department"
262+
options={departmentOptions}
263+
value={value.department}
264+
keySelector={(o) => o.id}
265+
labelSelector={(o) => o.name}
266+
onChange={setFieldValue}
267+
placeholder="Select Status"
268+
error={error?.department}
269+
/>
270+
</FormSection>
271+
<FormSection label="Archive" description="Click on the checkbox if the Vacancy is to be archived?" withAsteriskOnTitle>
272+
<Checkbox
273+
name="isArchived"
274+
value={value.isArchived}
275+
onChange={setFieldValue}
276+
error={error?.isArchived as string}
277+
/>
278+
</FormSection>
279+
<FormSection>
280+
<Button name="save" onClick={handleFormSubmit} variant="primary">
281+
{createPending || updatePending ? 'Saving' : 'Save'}
282+
</Button>
283+
</FormSection>
284+
285+
</ContainerWrapper>
286+
</Page>
287+
);
288+
}
289+
290+
export default VacancyForm;

0 commit comments

Comments
 (0)