1
|
|
|
/* eslint-disable jsx-a11y/label-has-associated-control, @typescript-eslint/no-non-null-assertion, react/no-array-index-key, camelcase, jsx-a11y/no-noninteractive-tabindex */ |
2
|
|
|
import React, { useState, useRef } from "react"; |
3
|
|
|
import { FormattedMessage, defineMessages, useIntl } from "react-intl"; |
4
|
|
|
import { |
5
|
|
|
Form, |
6
|
|
|
Formik, |
7
|
|
|
FieldArray, |
8
|
|
|
FormikErrors, |
9
|
|
|
FormikValues, |
10
|
|
|
FastField, |
11
|
|
|
} from "formik"; |
12
|
|
|
import { array, object, string } from "yup"; |
13
|
|
|
import nprogress from "nprogress"; |
14
|
|
|
import { v4 as uuidv4 } from "uuid"; |
15
|
|
|
import Modal from "../../Modal"; |
16
|
|
|
import { validationMessages } from "../../Form/Messages"; |
17
|
|
|
import TextAreaInput from "../../Form/TextAreaInput"; |
18
|
|
|
import { JobPosterKeyTask } from "../../../models/types"; |
19
|
|
|
import { find } from "../../../helpers/queries"; |
20
|
|
|
import { emptyTasks } from "../../../models/jobUtil"; |
21
|
|
|
import { localizeFieldNonNull, getLocale } from "../../../helpers/localize"; |
22
|
|
|
|
23
|
|
|
interface JobTasksProps { |
24
|
|
|
/** Job ID to pass to tasks. */ |
25
|
|
|
jobId: number | null; |
26
|
|
|
/** Key Tasks collection to populate the form */ |
27
|
|
|
keyTasks: JobPosterKeyTask[] | null; |
28
|
|
|
/** Amount of tasks on the page considered 'valid'. Adding |
29
|
|
|
* additional tasks will insert error markup and add invalid |
30
|
|
|
* prop to textareas, as well as prevent form submission. |
31
|
|
|
*/ |
32
|
|
|
validCount: number; |
33
|
|
|
/** Function to run after successful form validation. |
34
|
|
|
* It must return true if the submission was succesful, false otherwise. |
35
|
|
|
*/ |
36
|
|
|
handleSubmit: (values: JobPosterKeyTask[]) => Promise<JobPosterKeyTask[]>; |
37
|
|
|
// The function to run when user clicks Prev Page |
38
|
|
|
handleReturn: () => void; |
39
|
|
|
/** Function to run when modal cancel is clicked. */ |
40
|
|
|
handleModalCancel: () => void; |
41
|
|
|
/** Function to run when modal confirm is clicked. */ |
42
|
|
|
handleModalConfirm: () => void; |
43
|
|
|
/** Whether the entire job is complete and valid for submission. */ |
44
|
|
|
jobIsComplete: boolean; |
45
|
|
|
/** Function that skips to final review. */ |
46
|
|
|
handleSkipToReview: () => Promise<void>; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
interface TaskFormValues { |
50
|
|
|
id: string | number; |
51
|
|
|
jobPosterId: number; |
52
|
|
|
description: string; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
const formMessages = defineMessages({ |
56
|
|
|
taskPlaceholder: { |
57
|
|
|
id: "jobBuilder.tasks.taskPlaceholder", |
58
|
|
|
defaultMessage: "Try for a casual, frank, friendly tone...", |
59
|
|
|
description: "Placeholder shown inside a Task text area.", |
60
|
|
|
}, |
61
|
|
|
taskLabel: { |
62
|
|
|
id: "jobBuilder.tasks.taskLabel", |
63
|
|
|
defaultMessage: "Task", |
64
|
|
|
description: "Label shown above a Task text area.", |
65
|
|
|
}, |
66
|
|
|
tasksRequired: { |
67
|
|
|
id: "jobBuilder.tasks.tasksRequired", |
68
|
|
|
defaultMessage: "At least one task is required.", |
69
|
|
|
description: |
70
|
|
|
"Validation message shown when a user tries to submit no tasks.", |
71
|
|
|
}, |
72
|
|
|
tasksMaximum: { |
73
|
|
|
id: "jobBuilder.tasks.tasksMaximum", |
74
|
|
|
defaultMessage: "Please remove any additional tasks before continuing.", |
75
|
|
|
description: |
76
|
|
|
"Validation message shown when a user tries to submit more than the allowed number of tasks.", |
77
|
|
|
}, |
78
|
|
|
}); |
79
|
|
|
|
80
|
|
|
export const JobTasks: React.FunctionComponent<JobTasksProps> = ({ |
81
|
|
|
jobId, |
82
|
|
|
keyTasks, |
83
|
|
|
validCount, |
84
|
|
|
handleSubmit, |
85
|
|
|
handleReturn, |
86
|
|
|
handleModalCancel, |
87
|
|
|
handleModalConfirm, |
88
|
|
|
jobIsComplete, |
89
|
|
|
handleSkipToReview, |
90
|
|
|
}): React.ReactElement => { |
91
|
|
|
const intl = useIntl(); |
92
|
|
|
const locale = getLocale(intl.locale); |
93
|
|
|
const modalId = "tasks-modal"; |
94
|
|
|
const [isModalVisible, setIsModalVisible] = useState(false); |
95
|
|
|
const modalParentRef = useRef<HTMLDivElement>(null); |
96
|
|
|
|
97
|
|
|
const tasksToValues = ( |
98
|
|
|
tasks: JobPosterKeyTask[], |
99
|
|
|
): { tasks: TaskFormValues[] } => ({ |
100
|
|
|
tasks: tasks.map( |
101
|
|
|
(task: JobPosterKeyTask): TaskFormValues => ({ |
102
|
|
|
id: task.id, |
103
|
|
|
jobPosterId: task.job_poster_id, |
104
|
|
|
description: localizeFieldNonNull(locale, task, "description"), |
105
|
|
|
}), |
106
|
|
|
), |
107
|
|
|
}); |
108
|
|
|
|
109
|
|
|
const updateTasksWithValues = ( |
110
|
|
|
formTasks: TaskFormValues[], |
111
|
|
|
canonicalTasks: JobPosterKeyTask[], |
112
|
|
|
): JobPosterKeyTask[] => { |
113
|
|
|
return formTasks |
114
|
|
|
.map( |
115
|
|
|
(task: TaskFormValues): JobPosterKeyTask => { |
116
|
|
|
const keyTask = |
117
|
|
|
task.id && typeof task.id === "number" |
118
|
|
|
? find(canonicalTasks, task.id) |
119
|
|
|
: null; |
120
|
|
|
if (keyTask) { |
121
|
|
|
return { |
122
|
|
|
...keyTask, |
123
|
|
|
description: { |
124
|
|
|
en: locale === "en" ? task.description : keyTask.description.en, |
125
|
|
|
fr: locale === "fr" ? task.description : keyTask.description.fr, |
126
|
|
|
}, |
127
|
|
|
}; |
128
|
|
|
} |
129
|
|
|
return { |
130
|
|
|
id: 0, |
131
|
|
|
job_poster_id: task.jobPosterId, |
132
|
|
|
description: { |
133
|
|
|
en: locale === "en" ? task.description : "", |
134
|
|
|
fr: locale === "fr" ? task.description : "", |
135
|
|
|
}, |
136
|
|
|
}; |
137
|
|
|
}, |
138
|
|
|
) |
139
|
|
|
.filter((task: JobPosterKeyTask) => { |
140
|
|
|
const { description } = task; |
141
|
|
|
return ( |
142
|
|
|
description !== undefined && |
143
|
|
|
description !== null && |
144
|
|
|
description[locale] !== "" |
145
|
|
|
); |
146
|
|
|
}); |
147
|
|
|
}; |
148
|
|
|
|
149
|
|
|
const taskSchema = object().shape({ |
150
|
|
|
tasks: array() |
151
|
|
|
.of( |
152
|
|
|
object().shape({ |
153
|
|
|
description: string().required( |
154
|
|
|
intl.formatMessage(validationMessages.required), |
155
|
|
|
), |
156
|
|
|
}), |
157
|
|
|
) |
158
|
|
|
.required(intl.formatMessage(formMessages.tasksRequired)) |
159
|
|
|
.max(validCount, intl.formatMessage(formMessages.tasksMaximum)), |
160
|
|
|
}); |
161
|
|
|
|
162
|
|
|
const initialValues = keyTasks ? tasksToValues(keyTasks) : { tasks: [] }; |
163
|
|
|
|
164
|
|
|
const updateValuesAndReturn = (values: { tasks: TaskFormValues[] }): void => { |
165
|
|
|
// The following only triggers after validations pass |
166
|
|
|
nprogress.start(); |
167
|
|
|
handleSubmit(updateTasksWithValues(values.tasks, keyTasks || emptyTasks())) |
168
|
|
|
.then((): void => { |
169
|
|
|
nprogress.done(); |
170
|
|
|
handleReturn(); |
171
|
|
|
}) |
172
|
|
|
.catch((): void => { |
173
|
|
|
nprogress.done(); |
174
|
|
|
}); |
175
|
|
|
}; |
176
|
|
|
|
177
|
|
|
return ( |
178
|
|
|
<div |
179
|
|
|
data-c-container="form" |
180
|
|
|
data-c-padding="top(triple) bottom(triple)" |
181
|
|
|
ref={modalParentRef} |
182
|
|
|
> |
183
|
|
|
<h3 |
184
|
|
|
data-c-font-size="h3" |
185
|
|
|
data-c-font-weight="bold" |
186
|
|
|
data-c-margin="bottom(double)" |
187
|
|
|
> |
188
|
|
|
<FormattedMessage |
189
|
|
|
id="jobBuilder.tasks.heading" |
190
|
|
|
defaultMessage="Add Key Tasks" |
191
|
|
|
description="Job Tasks page heading" |
192
|
|
|
/> |
193
|
|
|
</h3> |
194
|
|
|
<ul data-c-margin="bottom(double)"> |
195
|
|
|
<li> |
196
|
|
|
<FormattedMessage |
197
|
|
|
id="jobBuilder.tasks.intro.first" |
198
|
|
|
defaultMessage="What will your new team member spend their time on? What will they deliver?" |
199
|
|
|
description="Job Tasks page first intro section" |
200
|
|
|
/> |
201
|
|
|
</li> |
202
|
|
|
<li> |
203
|
|
|
<FormattedMessage |
204
|
|
|
id="jobBuilder.tasks.intro.second" |
205
|
|
|
defaultMessage="Focus on the key tasks. You don’t need to list every detail of the job, but applicants want to know how they will be spending most of their time." |
206
|
|
|
description="Job Tasks page second intro section" |
207
|
|
|
/> |
208
|
|
|
</li> |
209
|
|
|
<li> |
210
|
|
|
<FormattedMessage |
211
|
|
|
id="jobBuilder.tasks.intro.third" |
212
|
|
|
defaultMessage="Aim to provide between four and six key tasks. (You can add as many key tasks as you want as you brainstorm here, but you can include no more than six in the final job poster.)" |
213
|
|
|
description="Job Tasks page third intro section" |
214
|
|
|
/> |
215
|
|
|
</li> |
216
|
|
|
<li> |
217
|
|
|
<FormattedMessage |
218
|
|
|
id="jobBuilder.tasks.intro.fourth" |
219
|
|
|
defaultMessage="Once you have finished entering key tasks, you will move on to identify the individual skills needed to accomplish them." |
220
|
|
|
description="Job Tasks page fourth intro section" |
221
|
|
|
/> |
222
|
|
|
</li> |
223
|
|
|
</ul> |
224
|
|
|
<Formik |
225
|
|
|
enableReinitialize |
226
|
|
|
initialValues={initialValues} |
227
|
|
|
validationSchema={taskSchema} |
228
|
|
|
onSubmit={(values, actions): void => { |
229
|
|
|
nprogress.start(); |
230
|
|
|
// The following only triggers after validations pass |
231
|
|
|
nprogress.start(); |
232
|
|
|
handleSubmit( |
233
|
|
|
updateTasksWithValues(values.tasks, keyTasks || emptyTasks()), |
234
|
|
|
) |
235
|
|
|
.then((updatedTasks): void => { |
236
|
|
|
/** Reseting form with new values adds the new, true ids from the server. |
237
|
|
|
* This stops tasks from being recreated (instead of updated) if you save the form again. |
238
|
|
|
* FIXME: However, this resets the ordering as well, to whatever order the server returns them in. |
239
|
|
|
*/ |
240
|
|
|
actions.resetForm({ values: tasksToValues(updatedTasks) }); |
241
|
|
|
nprogress.done(); |
242
|
|
|
setIsModalVisible(true); |
243
|
|
|
}) |
244
|
|
|
.catch((): void => { |
245
|
|
|
nprogress.done(); |
246
|
|
|
}) |
247
|
|
|
.finally((): void => { |
248
|
|
|
actions.setSubmitting(false); // Required by Formik to finish the submission cycle |
249
|
|
|
}); |
250
|
|
|
}} |
251
|
|
|
> |
252
|
|
|
{({ |
253
|
|
|
isSubmitting, |
254
|
|
|
values, |
255
|
|
|
errors, |
256
|
|
|
setFieldValue, |
257
|
|
|
}): React.ReactElement => ( |
258
|
|
|
<> |
259
|
|
|
{values.tasks.length > 0 && ( |
260
|
|
|
<p data-c-alignment="tl(right)" data-c-margin="bottom(double)"> |
261
|
|
|
<FormattedMessage |
262
|
|
|
id="jobBuilder.tasks.taskCount.some" |
263
|
|
|
defaultMessage="You have {taskCount, plural, one {# task} other {# tasks}} added." |
264
|
|
|
description="Indicates how many tasks are present on the page." |
265
|
|
|
values={{ |
266
|
|
|
taskCount: values.tasks.length, |
267
|
|
|
}} |
268
|
|
|
/> |
269
|
|
|
</p> |
270
|
|
|
)} |
271
|
|
|
{values.tasks.length === 0 && ( |
272
|
|
|
<div |
273
|
|
|
data-c-margin="top(normal) bottom(double)" |
274
|
|
|
data-c-background="grey(20)" |
275
|
|
|
data-c-padding="normal" |
276
|
|
|
data-c-radius="rounded" |
277
|
|
|
data-c-border="all(thin, solid, grey)" |
278
|
|
|
data-c-alignment="centre" |
279
|
|
|
> |
280
|
|
|
<p> |
281
|
|
|
<FormattedMessage |
282
|
|
|
id="jobBuilder.tasks.taskCount.none" |
283
|
|
|
defaultMessage="You don't have any tasks added yet!" |
284
|
|
|
description="Message displayed when there are no tasks present on the page." |
285
|
|
|
/> |
286
|
|
|
</p> |
287
|
|
|
</div> |
288
|
|
|
)} |
289
|
|
|
<Form id="job-tasks"> |
290
|
|
|
<FieldArray |
291
|
|
|
name="tasks" |
292
|
|
|
render={({ push }): React.ReactElement => { |
293
|
|
|
/* The next two methods are workaround replacements |
294
|
|
|
* for Formik's built-in array helpers. Due to the |
295
|
|
|
* way they're called, they end up crashing the page |
296
|
|
|
* when a Yup validation on the array is thrown, |
297
|
|
|
* see https://github.com/jaredpalmer/formik/issues/1158#issuecomment-510868126 |
298
|
|
|
*/ |
299
|
|
|
const move = (from: number, to: number): void => { |
300
|
|
|
const copy = [...(values.tasks || [])]; |
301
|
|
|
const value = copy[from]; |
302
|
|
|
copy.splice(from, 1); |
303
|
|
|
copy.splice(to, 0, value); |
304
|
|
|
setFieldValue("tasks", copy); |
305
|
|
|
}; |
306
|
|
|
|
307
|
|
|
const remove = (position: number): void => { |
308
|
|
|
const copy = values.tasks ? [...values.tasks] : []; |
309
|
|
|
copy.splice(position, 1); |
310
|
|
|
setFieldValue("tasks", copy); |
311
|
|
|
}; |
312
|
|
|
|
313
|
|
|
const taskArrayErrors = ( |
314
|
|
|
arrayErrors: FormikErrors<FormikValues>, |
315
|
|
|
): React.ReactElement | null => |
316
|
|
|
typeof arrayErrors.tasks === "string" ? ( |
317
|
|
|
<div |
318
|
|
|
data-c-alert="error" |
319
|
|
|
data-c-radius="rounded" |
320
|
|
|
role="alert" |
321
|
|
|
data-c-margin="top(normal)" |
322
|
|
|
> |
323
|
|
|
<div data-c-padding="half"> |
324
|
|
|
<p>{arrayErrors.tasks}</p> |
325
|
|
|
</div> |
326
|
|
|
</div> |
327
|
|
|
) : null; |
328
|
|
|
|
329
|
|
|
const tempId = uuidv4(); |
330
|
|
|
|
331
|
|
|
return ( |
332
|
|
|
<> |
333
|
|
|
<div data-c-grid="gutter"> |
334
|
|
|
{values.tasks && |
335
|
|
|
values.tasks.length > 0 && |
336
|
|
|
values.tasks.map( |
337
|
|
|
(task, index): React.ReactElement => ( |
338
|
|
|
<React.Fragment key={task.id}> |
339
|
|
|
{validCount === index && ( |
340
|
|
|
<div |
341
|
|
|
key="taskError" |
342
|
|
|
className="job-builder-task-warning" |
343
|
|
|
data-c-grid-item="base(1of1)" |
344
|
|
|
> |
345
|
|
|
<div |
346
|
|
|
data-c-alert="error" |
347
|
|
|
data-c-radius="rounded" |
348
|
|
|
role="alert" |
349
|
|
|
data-c-margin="bottom(normal)" |
350
|
|
|
> |
351
|
|
|
<div data-c-padding="half"> |
352
|
|
|
<span |
353
|
|
|
data-c-margin="bottom(quarter)" |
354
|
|
|
data-c-font-weight="bold" |
355
|
|
|
> |
356
|
|
|
<i |
357
|
|
|
aria-hidden="true" |
358
|
|
|
className="fas fa-exclamation-circle" |
359
|
|
|
/> |
360
|
|
|
<FormattedMessage |
361
|
|
|
id="jobBuilder.tasks.taskCount.error.title" |
362
|
|
|
description="Error message displayed when too many tasks are on screen." |
363
|
|
|
defaultMessage="Just a heads up!" |
364
|
|
|
/> |
365
|
|
|
</span> |
366
|
|
|
<p> |
367
|
|
|
<FormattedMessage |
368
|
|
|
id="jobBuilder.tasks.taskCount.error.body" |
369
|
|
|
description="Error message displayed when too many tasks are on screen." |
370
|
|
|
defaultMessage="You have exceeded the maximum number of key tasks allowed, but that’s okay. You can continue to add key tasks as you brainstorm here, but you will be asked to trim your list to 6 key tasks or fewer to proceed." |
371
|
|
|
/> |
372
|
|
|
</p> |
373
|
|
|
</div> |
374
|
|
|
</div> |
375
|
|
|
</div> |
376
|
|
|
)} |
377
|
|
|
<div |
378
|
|
|
key={task.id} |
379
|
|
|
className={`job-builder-task${ |
380
|
|
|
index + 1 > validCount ? " invalid" : "" |
381
|
|
|
}`} |
382
|
|
|
data-c-grid-item="base(1of1)" |
383
|
|
|
data-tc-up-down-item |
384
|
|
|
> |
385
|
|
|
<div data-c-grid="gutter middle"> |
386
|
|
|
<div |
387
|
|
|
data-c-grid-item="base(1of7) tl(1of10)" |
388
|
|
|
data-c-align="base(centre)" |
389
|
|
|
> |
390
|
|
|
<button |
391
|
|
|
type="button" |
392
|
|
|
data-tc-move-up-trigger |
393
|
|
|
onClick={(): void => |
394
|
|
|
move(index, index - 1) |
395
|
|
|
} |
396
|
|
|
> |
397
|
|
|
<i className="fas fa-angle-up" /> |
398
|
|
|
</button> |
399
|
|
|
<button |
400
|
|
|
type="button" |
401
|
|
|
data-tc-move-down-trigger |
402
|
|
|
onClick={(): void => |
403
|
|
|
move(index, index + 1) |
404
|
|
|
} |
405
|
|
|
> |
406
|
|
|
<i className="fas fa-angle-down" /> |
407
|
|
|
</button> |
408
|
|
|
</div> |
409
|
|
|
<FastField |
410
|
|
|
id={`task-${task.id}`} |
411
|
|
|
name={`tasks.${index}.description`} |
412
|
|
|
grid="base(5of7) tl(8of10)" |
413
|
|
|
label={`${intl.formatMessage( |
414
|
|
|
formMessages.taskLabel, |
415
|
|
|
)} ${index + 1}`} |
416
|
|
|
component={TextAreaInput} |
417
|
|
|
placeholder={intl.formatMessage( |
418
|
|
|
formMessages.taskPlaceholder, |
419
|
|
|
)} |
420
|
|
|
required |
421
|
|
|
/> |
422
|
|
|
<div |
423
|
|
|
data-c-grid-item="base(1of7) tl(1of10)" |
424
|
|
|
data-c-align="base(centre)" |
425
|
|
|
> |
426
|
|
|
<button |
427
|
|
|
type="button" |
428
|
|
|
data-tc-builder-task-delete-trigger |
429
|
|
|
onClick={(): void => { |
430
|
|
|
remove(index); |
431
|
|
|
}} |
432
|
|
|
> |
433
|
|
|
<i |
434
|
|
|
className="fas fa-trash" |
435
|
|
|
data-c-colour="stop" |
436
|
|
|
/> |
437
|
|
|
</button> |
438
|
|
|
</div> |
439
|
|
|
</div> |
440
|
|
|
</div> |
441
|
|
|
</React.Fragment> |
442
|
|
|
), |
443
|
|
|
)} |
444
|
|
|
</div> |
445
|
|
|
<div data-c-grid="gutter"> |
446
|
|
|
<div |
447
|
|
|
data-c-grid-item="base(1of1)" |
448
|
|
|
data-c-alignment="base(centre)" |
449
|
|
|
> |
450
|
|
|
<button |
451
|
|
|
data-c-button="solid(c2)" |
452
|
|
|
data-c-radius="rounded" |
453
|
|
|
type="button" |
454
|
|
|
disabled={isSubmitting} |
455
|
|
|
onClick={(): void => |
456
|
|
|
push({ |
457
|
|
|
id: tempId, |
458
|
|
|
job_poster_id: jobId, |
459
|
|
|
en: { description: "" }, |
460
|
|
|
fr: { description: "" }, |
461
|
|
|
}) |
462
|
|
|
} |
463
|
|
|
> |
464
|
|
|
<FormattedMessage |
465
|
|
|
id="jobBuilder.tasks.addJob" |
466
|
|
|
description="Text on the Add Task button." |
467
|
|
|
defaultMessage="Add a Task" |
468
|
|
|
/> |
469
|
|
|
</button> |
470
|
|
|
</div> |
471
|
|
|
<div data-c-grid-item="base(1of1)"> |
472
|
|
|
<hr data-c-margin="top(normal) bottom(normal)" /> |
473
|
|
|
</div> |
474
|
|
|
<div |
475
|
|
|
data-c-alignment="base(centre) tp(left)" |
476
|
|
|
data-c-grid-item="tp(1of2)" |
477
|
|
|
> |
478
|
|
|
{/* TODO: Navigate to previous page */} |
479
|
|
|
<button |
480
|
|
|
data-c-button="outline(c2)" |
481
|
|
|
data-c-radius="rounded" |
482
|
|
|
type="button" |
483
|
|
|
disabled={isSubmitting} |
484
|
|
|
onClick={(): void => { |
485
|
|
|
updateValuesAndReturn(values); |
486
|
|
|
}} |
487
|
|
|
> |
488
|
|
|
<FormattedMessage |
489
|
|
|
id="jobBuilder.tasks.previous" |
490
|
|
|
description="Text on the Previous Step button." |
491
|
|
|
defaultMessage="Save & Return to Impact" |
492
|
|
|
/> |
493
|
|
|
</button> |
494
|
|
|
</div> |
495
|
|
|
<div |
496
|
|
|
data-c-alignment="base(centre) tp(right)" |
497
|
|
|
data-c-grid-item="tp(1of2)" |
498
|
|
|
> |
499
|
|
|
<button |
500
|
|
|
data-c-button="solid(c2)" |
501
|
|
|
data-c-radius="rounded" |
502
|
|
|
type="submit" |
503
|
|
|
disabled={isSubmitting} |
504
|
|
|
> |
505
|
|
|
<FormattedMessage |
506
|
|
|
id="jobBuilder.tasks.preview" |
507
|
|
|
description="Text on the Preview Tasks button." |
508
|
|
|
defaultMessage="Save & Preview Tasks" |
509
|
|
|
/> |
510
|
|
|
</button> |
511
|
|
|
{/* TODO: Figure out how to display FieldArray validation errors. */} |
512
|
|
|
{taskArrayErrors(errors)} |
513
|
|
|
</div> |
514
|
|
|
</div> |
515
|
|
|
</> |
516
|
|
|
); |
517
|
|
|
}} |
518
|
|
|
/> |
519
|
|
|
</Form> |
520
|
|
|
<Modal |
521
|
|
|
id={modalId} |
522
|
|
|
parentElement={modalParentRef.current} |
523
|
|
|
visible={isModalVisible} |
524
|
|
|
onModalCancel={(): void => { |
525
|
|
|
handleModalCancel(); |
526
|
|
|
setIsModalVisible(false); |
527
|
|
|
}} |
528
|
|
|
onModalConfirm={(): void => { |
529
|
|
|
handleModalConfirm(); |
530
|
|
|
}} |
531
|
|
|
onModalMiddle={(): void => { |
532
|
|
|
handleSkipToReview(); |
533
|
|
|
}} |
534
|
|
|
> |
535
|
|
|
<Modal.Header> |
536
|
|
|
<div |
537
|
|
|
data-c-background="c1(100)" |
538
|
|
|
data-c-border="bottom(thin, solid, black)" |
539
|
|
|
data-c-padding="normal" |
540
|
|
|
> |
541
|
|
|
<h5 |
542
|
|
|
data-c-colour="white" |
543
|
|
|
data-c-font-size="h4" |
544
|
|
|
id={`${modalId}-title`} |
545
|
|
|
> |
546
|
|
|
<FormattedMessage |
547
|
|
|
id="jobBuilder.tasks.modal.title" |
548
|
|
|
defaultMessage="Keep it up!" |
549
|
|
|
description="Text displayed on the title of the Job Task page Modal." |
550
|
|
|
/> |
551
|
|
|
</h5> |
552
|
|
|
</div> |
553
|
|
|
</Modal.Header> |
554
|
|
|
<Modal.Body> |
555
|
|
|
<div data-c-border="bottom(thin, solid, black)"> |
556
|
|
|
<div |
557
|
|
|
data-c-border="bottom(thin, solid, black)" |
558
|
|
|
data-c-padding="normal" |
559
|
|
|
id={`${modalId}-description`} |
560
|
|
|
> |
561
|
|
|
<p> |
562
|
|
|
<FormattedMessage |
563
|
|
|
id="jobBuilder.tasks.modal.body" |
564
|
|
|
description="Text displayed above the body of the Job Task page Modal." |
565
|
|
|
defaultMessage="Here's a preview of the Tasks you just entered. Feel free to go back and edit things or move to the next step if you're happy with it." |
566
|
|
|
/> |
567
|
|
|
</p> |
568
|
|
|
</div> |
569
|
|
|
<div data-c-background="grey(20)" data-c-padding="normal"> |
570
|
|
|
<div |
571
|
|
|
className="manager-job-card" |
572
|
|
|
data-c-background="white(100)" |
573
|
|
|
data-c-padding="normal" |
574
|
|
|
data-c-radius="rounded" |
575
|
|
|
> |
576
|
|
|
<h4 |
577
|
|
|
data-c-border="bottom(thin, solid, black)" |
578
|
|
|
data-c-font-size="h4" |
579
|
|
|
data-c-font-weight="600" |
580
|
|
|
data-c-margin="bottom(normal)" |
581
|
|
|
data-c-padding="bottom(normal)" |
582
|
|
|
> |
583
|
|
|
<FormattedMessage |
584
|
|
|
id="jobBuilder.tasks.modal.body.heading" |
585
|
|
|
description="Text displayed above the lists of Tasks inside the Modal body." |
586
|
|
|
defaultMessage="Tasks" |
587
|
|
|
/> |
588
|
|
|
</h4> |
589
|
|
|
<ul> |
590
|
|
|
{values.tasks && |
591
|
|
|
values.tasks.map( |
592
|
|
|
(task: TaskFormValues): React.ReactElement => ( |
593
|
|
|
<li key={task.id}>{task.description}</li> |
594
|
|
|
), |
595
|
|
|
)} |
596
|
|
|
</ul> |
597
|
|
|
</div> |
598
|
|
|
</div> |
599
|
|
|
</div> |
600
|
|
|
</Modal.Body> |
601
|
|
|
<Modal.Footer> |
602
|
|
|
<Modal.FooterCancelBtn> |
603
|
|
|
<FormattedMessage |
604
|
|
|
id="jobBuilder.tasks.modal.cancelButtonLabel" |
605
|
|
|
description="The text displayed on the cancel button of the Job Tasks modal." |
606
|
|
|
defaultMessage="Go Back" |
607
|
|
|
/> |
608
|
|
|
</Modal.FooterCancelBtn> |
609
|
|
|
{jobIsComplete && ( |
610
|
|
|
<Modal.FooterMiddleBtn> |
611
|
|
|
<FormattedMessage |
612
|
|
|
id="jobBuilder.tasks.modal.middleButtonLabel" |
613
|
|
|
description="The text displayed on the Skip to Review button of the Job Tasks modal." |
614
|
|
|
defaultMessage="Skip to Review" |
615
|
|
|
/> |
616
|
|
|
</Modal.FooterMiddleBtn> |
617
|
|
|
)} |
618
|
|
|
<Modal.FooterConfirmBtn> |
619
|
|
|
<FormattedMessage |
620
|
|
|
id="jobBuilder.tasks.modal.confirmButtonLabel" |
621
|
|
|
description="The text displayed on the confirm button of the Job Tasks modal." |
622
|
|
|
defaultMessage="Next Step" |
623
|
|
|
/> |
624
|
|
|
</Modal.FooterConfirmBtn> |
625
|
|
|
</Modal.Footer> |
626
|
|
|
</Modal> |
627
|
|
|
<div data-c-dialog-overlay={isModalVisible ? "active" : ""} /> |
628
|
|
|
</> |
629
|
|
|
)} |
630
|
|
|
</Formik> |
631
|
|
|
</div> |
632
|
|
|
); |
633
|
|
|
}; |
634
|
|
|
|
635
|
|
|
export default JobTasks; |
636
|
|
|
|