1
|
|
|
/* eslint-disable jsx-a11y/label-has-associated-control, camelcase */ |
2
|
|
|
import React, { useState, useRef } from "react"; |
3
|
|
|
import { |
4
|
|
|
FormattedMessage, |
5
|
|
|
MessageDescriptor, |
6
|
|
|
IntlShape, |
7
|
|
|
useIntl, |
8
|
|
|
} from "react-intl"; |
9
|
|
|
import { Formik, Form, Field, FastField } from "formik"; |
10
|
|
|
import nprogress from "nprogress"; |
11
|
|
|
import * as Yup from "yup"; |
12
|
|
|
import { connect } from "react-redux"; |
13
|
|
|
import RadioGroup from "../../Form/RadioGroup"; |
14
|
|
|
import TextInput from "../../Form/TextInput"; |
15
|
|
|
import NumberInput from "../../Form/NumberInput"; |
16
|
|
|
import SelectInput from "../../Form/SelectInput"; |
17
|
|
|
import JobPreview from "../../JobPreview"; |
18
|
|
|
import Modal from "../../Modal"; |
19
|
|
|
import { RootState } from "../../../store/store"; |
20
|
|
|
import { getJob as selectJob } from "../../../store/Job/jobSelector"; |
21
|
|
|
import { Classification, Job } from "../../../models/types"; |
22
|
|
|
import { DispatchType } from "../../../configureStore"; |
23
|
|
|
import { updateJob, createJob } from "../../../store/Job/jobActions"; |
24
|
|
|
import { validationMessages } from "../../Form/Messages"; |
25
|
|
|
import RadioInput from "../../Form/RadioInput"; |
26
|
|
|
import { |
27
|
|
|
LanguageRequirementId, |
28
|
|
|
SecurityClearanceId, |
29
|
|
|
ProvinceId, |
30
|
|
|
FrequencyId, |
31
|
|
|
TravelRequirementId, |
32
|
|
|
OvertimeRequirementId, |
33
|
|
|
getKeyByValue, |
34
|
|
|
} from "../../../models/lookupConstants"; |
35
|
|
|
import { emptyJob } from "../../../models/jobUtil"; |
36
|
|
|
import { |
37
|
|
|
securityClearance, |
38
|
|
|
languageRequirement, |
39
|
|
|
provinceName, |
40
|
|
|
frequencyName, |
41
|
|
|
travelRequirementDescription, |
42
|
|
|
overtimeRequirementDescription, |
43
|
|
|
} from "../../../models/localizedConstants"; |
44
|
|
|
import ContextBlockItem from "../../ContextBlock/ContextBlockItem"; |
45
|
|
|
import CopyToClipboardButton from "../../CopyToClipboardButton"; |
46
|
|
|
import TextAreaInput from "../../Form/TextAreaInput"; |
47
|
|
|
import { |
48
|
|
|
formMessages, |
49
|
|
|
educationMessages, |
50
|
|
|
buttonMessages, |
51
|
|
|
} from "./JobDetailsMessages"; |
52
|
|
|
import { localizeField, getLocale } from "../../../helpers/localize"; |
53
|
|
|
import textToParagraphs from "../../../helpers/textToParagraphs"; |
54
|
|
|
import { |
55
|
|
|
classificationsExtractKeyValueJsonArray, |
56
|
|
|
classificationsExtractKeyValueJson, |
57
|
|
|
} from "../../../store/Classification/classificationSelector"; |
58
|
|
|
|
59
|
|
|
interface JobDetailsProps { |
60
|
|
|
// Optional Job to prepopulate form values from. |
61
|
|
|
job: Job | null; |
62
|
|
|
classifications: Classification[]; |
63
|
|
|
// Function to run after successful form validation. |
64
|
|
|
// It must return true if the submission was successful, false otherwise. |
65
|
|
|
handleSubmit: (values: Job) => Promise<boolean>; |
66
|
|
|
// The function to run when user clicks Prev Page |
67
|
|
|
handleReturn: () => void; |
68
|
|
|
// Function to run when modal cancel is clicked. |
69
|
|
|
handleModalCancel: () => void; |
70
|
|
|
// Function to run when modal confirm is clicked. |
71
|
|
|
handleModalConfirm: () => void; |
72
|
|
|
jobIsComplete: boolean; |
73
|
|
|
handleSkipToReview: () => Promise<void>; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
type RemoteWorkType = "remoteWorkNone" | "remoteWorkCanada" | "remoteWorkWorld"; |
77
|
|
|
|
78
|
|
|
const remoteWorkMessages = { |
79
|
|
|
remoteWorkWorld: formMessages.remoteWorkWorldLabel, |
80
|
|
|
remoteWorkCanada: formMessages.remoteWorkCanadaLabel, |
81
|
|
|
remoteWorkNone: formMessages.remoteWorkNoneLabel, |
82
|
|
|
}; |
83
|
|
|
|
84
|
|
|
type TeleworkOptionType = |
85
|
|
|
| "teleworkNever" |
86
|
|
|
| "teleworkRarely" |
87
|
|
|
| "teleworkOccasionally" |
88
|
|
|
| "teleworkFrequently" |
89
|
|
|
| "teleworkAlways"; |
90
|
|
|
|
91
|
|
|
const teleworkMessages: { |
92
|
|
|
[key in TeleworkOptionType]: MessageDescriptor; |
93
|
|
|
} = { |
94
|
|
|
teleworkNever: frequencyName(FrequencyId.never), |
95
|
|
|
teleworkRarely: frequencyName(FrequencyId.rarely), |
96
|
|
|
teleworkOccasionally: frequencyName(FrequencyId.occasionally), |
97
|
|
|
teleworkFrequently: frequencyName(FrequencyId.frequently), |
98
|
|
|
teleworkAlways: frequencyName(FrequencyId.always), |
99
|
|
|
}; |
100
|
|
|
|
101
|
|
|
const teleworkFrequencies: TeleworkOptionType[] = Object.keys( |
102
|
|
|
teleworkMessages, |
103
|
|
|
) as TeleworkOptionType[]; |
104
|
|
|
|
105
|
|
|
type FlexHourOptionType = |
106
|
|
|
| "flexHoursNever" |
107
|
|
|
| "flexHoursRarely" |
108
|
|
|
| "flexHoursOccasionally" |
109
|
|
|
| "flexHoursFrequently" |
110
|
|
|
| "flexHoursAlways"; |
111
|
|
|
|
112
|
|
|
const flexHourMessages: { |
113
|
|
|
[key in FlexHourOptionType]: MessageDescriptor; |
114
|
|
|
} = { |
115
|
|
|
flexHoursNever: frequencyName(FrequencyId.never), |
116
|
|
|
flexHoursRarely: frequencyName(FrequencyId.rarely), |
117
|
|
|
flexHoursOccasionally: frequencyName(FrequencyId.occasionally), |
118
|
|
|
flexHoursFrequently: frequencyName(FrequencyId.frequently), |
119
|
|
|
flexHoursAlways: frequencyName(FrequencyId.always), |
120
|
|
|
}; |
121
|
|
|
const flexHourFrequencies: FlexHourOptionType[] = Object.keys( |
122
|
|
|
flexHourMessages, |
123
|
|
|
) as FlexHourOptionType[]; |
124
|
|
|
|
125
|
|
|
type TravelOptionType = |
126
|
|
|
| "travelFrequently" |
127
|
|
|
| "travelOpportunitiesAvailable" |
128
|
|
|
| "travelNoneRequired"; |
129
|
|
|
|
130
|
|
|
const travelMessages: { |
131
|
|
|
[key in TravelOptionType]: MessageDescriptor; |
132
|
|
|
} = { |
133
|
|
|
travelFrequently: travelRequirementDescription( |
134
|
|
|
TravelRequirementId.frequently, |
135
|
|
|
), |
136
|
|
|
travelOpportunitiesAvailable: travelRequirementDescription( |
137
|
|
|
TravelRequirementId.available, |
138
|
|
|
), |
139
|
|
|
travelNoneRequired: travelRequirementDescription(TravelRequirementId.none), |
140
|
|
|
}; |
141
|
|
|
const travelRequirements: TravelOptionType[] = Object.keys( |
142
|
|
|
travelMessages, |
143
|
|
|
) as TravelOptionType[]; |
144
|
|
|
|
145
|
|
|
type OvertimeOptionType = |
146
|
|
|
| "overtimeFrequently" |
147
|
|
|
| "overtimeOpportunitiesAvailable" |
148
|
|
|
| "overtimeNoneRequired"; |
149
|
|
|
|
150
|
|
|
const overtimeMessages: { |
151
|
|
|
[key in OvertimeOptionType]: MessageDescriptor; |
152
|
|
|
} = { |
153
|
|
|
overtimeFrequently: overtimeRequirementDescription( |
154
|
|
|
OvertimeRequirementId.frequently, |
155
|
|
|
), |
156
|
|
|
overtimeOpportunitiesAvailable: overtimeRequirementDescription( |
157
|
|
|
OvertimeRequirementId.available, |
158
|
|
|
), |
159
|
|
|
overtimeNoneRequired: overtimeRequirementDescription( |
160
|
|
|
OvertimeRequirementId.none, |
161
|
|
|
), |
162
|
|
|
}; |
163
|
|
|
const overtimeRequirements: OvertimeOptionType[] = Object.keys( |
164
|
|
|
overtimeMessages, |
165
|
|
|
) as OvertimeOptionType[]; |
166
|
|
|
|
167
|
|
|
interface DetailsFormValues { |
168
|
|
|
title: string; |
169
|
|
|
termLength: number | ""; |
170
|
|
|
classification: number | ""; |
171
|
|
|
level: number | ""; |
172
|
|
|
educationRequirements: string; |
173
|
|
|
securityLevel: number | ""; |
174
|
|
|
language: number | ""; |
175
|
|
|
city: string; |
176
|
|
|
province: number | ""; |
177
|
|
|
remoteWork: RemoteWorkType; |
178
|
|
|
telework: TeleworkOptionType; |
179
|
|
|
flexHours: FlexHourOptionType; |
180
|
|
|
travel: TravelOptionType; |
181
|
|
|
overtime: OvertimeOptionType; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
const isClassificationSet = (values: DetailsFormValues): boolean => { |
185
|
|
|
return values.classification !== "" && values.level !== ""; |
186
|
|
|
}; |
187
|
|
|
|
188
|
|
|
const getEducationMsgForClassification = ( |
189
|
|
|
classifications: Classification[], |
190
|
|
|
classification: number | string, |
191
|
|
|
intl: IntlShape, |
192
|
|
|
locale: string, |
193
|
|
|
): string => { |
194
|
|
|
const classificationObj: Classification | null = |
195
|
|
|
classifications.find((item) => item.id === Number(classification)) || null; |
196
|
|
|
return classificationObj !== null |
197
|
|
|
? classificationObj.education_requirements[locale] |
198
|
|
|
: intl.formatMessage(educationMessages.classificationNotFound); |
199
|
|
|
}; |
200
|
|
|
|
201
|
|
|
const jobToValues = ( |
202
|
|
|
job: Job | null, |
203
|
|
|
classifications: Classification[], |
204
|
|
|
locale: "en" | "fr", |
205
|
|
|
intl: IntlShape, |
206
|
|
|
): DetailsFormValues => { |
207
|
|
|
const values: DetailsFormValues = job |
208
|
|
|
? { |
209
|
|
|
title: localizeField(locale, job, "title") || "", // TODO: use utility method |
210
|
|
|
termLength: job.term_qty || "", |
211
|
|
|
classification: job.classification_id || "", |
212
|
|
|
level: job.classification_level || "", |
213
|
|
|
educationRequirements: localizeField(locale, job, "education") || "", |
214
|
|
|
securityLevel: job.security_clearance_id || "", |
215
|
|
|
language: job.language_requirement_id || "", |
216
|
|
|
city: localizeField(locale, job, "city") || "", |
217
|
|
|
province: job.province_id || "", |
218
|
|
|
remoteWork: job.remote_work_allowed |
219
|
|
|
? "remoteWorkCanada" |
220
|
|
|
: "remoteWorkNone", |
221
|
|
|
// frequency ids range from 1-5 |
222
|
|
|
telework: job.telework_allowed_frequency_id |
223
|
|
|
? teleworkFrequencies[job.telework_allowed_frequency_id - 1] |
224
|
|
|
: "teleworkFrequently", |
225
|
|
|
flexHours: job.flexible_hours_frequency_id |
226
|
|
|
? flexHourFrequencies[job.flexible_hours_frequency_id - 1] |
227
|
|
|
: "flexHoursFrequently", |
228
|
|
|
travel: job.travel_requirement_id |
229
|
|
|
? travelRequirements[job.travel_requirement_id - 1] |
230
|
|
|
: "travelFrequently", |
231
|
|
|
overtime: job.overtime_requirement_id |
232
|
|
|
? overtimeRequirements[job.overtime_requirement_id - 1] |
233
|
|
|
: "overtimeFrequently", |
234
|
|
|
} |
235
|
|
|
: { |
236
|
|
|
title: "", |
237
|
|
|
termLength: "", |
238
|
|
|
classification: "", |
239
|
|
|
level: "", |
240
|
|
|
educationRequirements: "", |
241
|
|
|
securityLevel: "", |
242
|
|
|
language: "", |
243
|
|
|
city: "", |
244
|
|
|
province: "", |
245
|
|
|
remoteWork: "remoteWorkCanada", |
246
|
|
|
telework: "teleworkFrequently", |
247
|
|
|
flexHours: "flexHoursFrequently", |
248
|
|
|
travel: "travelFrequently", |
249
|
|
|
overtime: "overtimeFrequently", |
250
|
|
|
}; |
251
|
|
|
// If the job has the standard education requirements saved, no need to fill the custom textbox |
252
|
|
|
if ( |
253
|
|
|
values.classification && |
254
|
|
|
values.educationRequirements === |
255
|
|
|
getEducationMsgForClassification( |
256
|
|
|
classifications, |
257
|
|
|
values.classification, |
258
|
|
|
intl, |
259
|
|
|
locale, |
260
|
|
|
) |
261
|
|
|
) { |
262
|
|
|
return { |
263
|
|
|
...values, |
264
|
|
|
educationRequirements: "", |
265
|
|
|
}; |
266
|
|
|
} |
267
|
|
|
return values; |
268
|
|
|
}; |
269
|
|
|
|
270
|
|
|
const updateJobWithValues = ( |
271
|
|
|
initialJob: Job, |
272
|
|
|
locale: "en" | "fr", |
273
|
|
|
{ |
274
|
|
|
title, |
275
|
|
|
termLength, |
276
|
|
|
classification, |
277
|
|
|
level, |
278
|
|
|
educationRequirements, |
279
|
|
|
securityLevel, |
280
|
|
|
language, |
281
|
|
|
city, |
282
|
|
|
province, |
283
|
|
|
remoteWork, |
284
|
|
|
telework, |
285
|
|
|
flexHours, |
286
|
|
|
travel, |
287
|
|
|
overtime, |
288
|
|
|
}: DetailsFormValues, |
289
|
|
|
): Job => ({ |
290
|
|
|
...initialJob, |
291
|
|
|
term_qty: termLength || null, |
292
|
|
|
classification_id: classification || null, |
293
|
|
|
classification_level: level || null, |
294
|
|
|
security_clearance_id: securityLevel || null, |
295
|
|
|
language_requirement_id: language || null, |
296
|
|
|
province_id: province || null, |
297
|
|
|
remote_work_allowed: remoteWork !== "remoteWorkNone", |
298
|
|
|
telework_allowed_frequency_id: teleworkFrequencies.indexOf(telework) + 1, |
299
|
|
|
flexible_hours_frequency_id: flexHourFrequencies.indexOf(flexHours) + 1, |
300
|
|
|
travel_requirement_id: travelRequirements.indexOf(travel) + 1, |
301
|
|
|
overtime_requirement_id: overtimeRequirements.indexOf(overtime) + 1, |
302
|
|
|
title: { |
303
|
|
|
...initialJob.title, |
304
|
|
|
[locale]: title, |
305
|
|
|
}, |
306
|
|
|
city: { |
307
|
|
|
...initialJob.city, |
308
|
|
|
[locale]: city, |
309
|
|
|
}, |
310
|
|
|
education: { |
311
|
|
|
...initialJob.education, |
312
|
|
|
[locale]: educationRequirements, |
313
|
|
|
}, |
314
|
|
|
}); |
315
|
|
|
|
316
|
|
|
export const JobDetails: React.FunctionComponent<JobDetailsProps> = ({ |
317
|
|
|
job, |
318
|
|
|
classifications, |
319
|
|
|
handleSubmit, |
320
|
|
|
handleReturn, |
321
|
|
|
handleModalCancel, |
322
|
|
|
handleModalConfirm, |
323
|
|
|
jobIsComplete, |
324
|
|
|
handleSkipToReview, |
325
|
|
|
}: JobDetailsProps): React.ReactElement => { |
326
|
|
|
const intl = useIntl(); |
327
|
|
|
const locale = getLocale(intl.locale); |
328
|
|
|
const [isModalVisible, setIsModalVisible] = useState(false); |
329
|
|
|
const modalParentRef = useRef<HTMLDivElement>(null); |
330
|
|
|
if (locale !== "en" && locale !== "fr") { |
331
|
|
|
throw Error("Unexpected intl.locale"); // TODO: Deal with this more elegantly. |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
const initialValues: DetailsFormValues = jobToValues( |
335
|
|
|
job || null, |
336
|
|
|
classifications, |
337
|
|
|
locale, |
338
|
|
|
intl, |
339
|
|
|
); |
340
|
|
|
|
341
|
|
|
const remoteWorkPossibleValues: RemoteWorkType[] = [ |
342
|
|
|
"remoteWorkNone", |
343
|
|
|
"remoteWorkCanada", |
344
|
|
|
"remoteWorkWorld", |
345
|
|
|
]; |
346
|
|
|
|
347
|
|
|
const jobSchema = Yup.object().shape({ |
348
|
|
|
title: Yup.string() |
349
|
|
|
.min(2, intl.formatMessage(validationMessages.tooShort)) |
350
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
351
|
|
|
termLength: Yup.number() |
352
|
|
|
.min(1, intl.formatMessage(validationMessages.tooShort)) |
353
|
|
|
.max(36, intl.formatMessage(validationMessages.tooLong)) |
354
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
355
|
|
|
classification: Yup.number() |
356
|
|
|
.oneOf( |
357
|
|
|
Object.values(classificationsExtractKeyValueJson(classifications)), |
358
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
359
|
|
|
) |
360
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
361
|
|
|
level: Yup.number() |
362
|
|
|
.min(1, intl.formatMessage(validationMessages.invalidSelection)) |
363
|
|
|
.max(9, intl.formatMessage(validationMessages.invalidSelection)) |
364
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
365
|
|
|
educationRequirements: Yup.string(), |
366
|
|
|
securityLevel: Yup.number() |
367
|
|
|
.oneOf( |
368
|
|
|
Object.values(SecurityClearanceId), |
369
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
370
|
|
|
) |
371
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
372
|
|
|
language: Yup.number() |
373
|
|
|
.oneOf( |
374
|
|
|
Object.values(LanguageRequirementId), |
375
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
376
|
|
|
) |
377
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
378
|
|
|
city: Yup.string() |
379
|
|
|
.min(3, intl.formatMessage(validationMessages.tooShort)) |
380
|
|
|
.max(50, intl.formatMessage(validationMessages.tooLong)) |
381
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
382
|
|
|
province: Yup.number() |
383
|
|
|
.oneOf( |
384
|
|
|
Object.values(ProvinceId), |
385
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
386
|
|
|
) |
387
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
388
|
|
|
remoteWork: Yup.mixed() |
389
|
|
|
.oneOf( |
390
|
|
|
remoteWorkPossibleValues, |
391
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
392
|
|
|
) |
393
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
394
|
|
|
telework: Yup.mixed() |
395
|
|
|
.oneOf( |
396
|
|
|
teleworkFrequencies, |
397
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
398
|
|
|
) |
399
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
400
|
|
|
flexHours: Yup.mixed() |
401
|
|
|
.oneOf( |
402
|
|
|
flexHourFrequencies, |
403
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
404
|
|
|
) |
405
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
406
|
|
|
travel: Yup.mixed() |
407
|
|
|
.oneOf( |
408
|
|
|
travelRequirements, |
409
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
410
|
|
|
) |
411
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
412
|
|
|
overtime: Yup.mixed() |
413
|
|
|
.oneOf( |
414
|
|
|
overtimeRequirements, |
415
|
|
|
intl.formatMessage(validationMessages.invalidSelection), |
416
|
|
|
) |
417
|
|
|
.required(intl.formatMessage(validationMessages.required)), |
418
|
|
|
}); |
419
|
|
|
|
420
|
|
|
const handleEducationRequirements = (values: DetailsFormValues): string => { |
421
|
|
|
return values.educationRequirements.length > 0 |
422
|
|
|
? values.educationRequirements |
423
|
|
|
: getEducationMsgForClassification( |
424
|
|
|
classifications, |
425
|
|
|
values.classification, |
426
|
|
|
intl, |
427
|
|
|
locale, |
428
|
|
|
); |
429
|
|
|
}; |
430
|
|
|
|
431
|
|
|
const updateValuesAndReturn = (values: DetailsFormValues): void => { |
432
|
|
|
nprogress.start(); |
433
|
|
|
// The following only triggers after validations pass |
434
|
|
|
const educationRequirements = handleEducationRequirements(values); |
435
|
|
|
const modifiedValues: DetailsFormValues = { |
436
|
|
|
...values, |
437
|
|
|
educationRequirements, |
438
|
|
|
}; |
439
|
|
|
handleSubmit( |
440
|
|
|
updateJobWithValues(job || emptyJob(), locale, modifiedValues), |
441
|
|
|
).then((isSuccessful: boolean): void => { |
442
|
|
|
if (isSuccessful) { |
443
|
|
|
nprogress.done(); |
444
|
|
|
handleReturn(); |
445
|
|
|
} |
446
|
|
|
}); |
447
|
|
|
}; |
448
|
|
|
|
449
|
|
|
return ( |
450
|
|
|
<section> |
451
|
|
|
<div |
452
|
|
|
data-c-container="form" |
453
|
|
|
data-c-padding="top(triple) bottom(triple)" |
454
|
|
|
ref={modalParentRef} |
455
|
|
|
> |
456
|
|
|
<h3 |
457
|
|
|
data-c-font-size="h3" |
458
|
|
|
data-c-font-weight="bold" |
459
|
|
|
data-c-margin="bottom(double)" |
460
|
|
|
> |
461
|
|
|
<FormattedMessage |
462
|
|
|
id="jobBuilder.details.heading" |
463
|
|
|
defaultMessage="Job Details" |
464
|
|
|
description="Job Details page heading" |
465
|
|
|
/> |
466
|
|
|
</h3> |
467
|
|
|
<Formik |
468
|
|
|
enableReinitialize |
469
|
|
|
initialValues={initialValues} |
470
|
|
|
validationSchema={jobSchema} |
471
|
|
|
onSubmit={(values, actions): void => { |
472
|
|
|
// The following only triggers after validations pass |
473
|
|
|
const educationRequirements: string = handleEducationRequirements( |
474
|
|
|
values, |
475
|
|
|
); |
476
|
|
|
const detailsFormValues: DetailsFormValues = { |
477
|
|
|
...values, |
478
|
|
|
educationRequirements, |
479
|
|
|
}; |
480
|
|
|
|
481
|
|
|
nprogress.start(); |
482
|
|
|
handleSubmit( |
483
|
|
|
updateJobWithValues(job || emptyJob(), locale, detailsFormValues), |
484
|
|
|
) |
485
|
|
|
.then((isSuccessful: boolean): void => { |
486
|
|
|
if (isSuccessful) { |
487
|
|
|
nprogress.done(); |
488
|
|
|
setIsModalVisible(true); |
489
|
|
|
} |
490
|
|
|
}) |
491
|
|
|
.finally((): void => { |
492
|
|
|
actions.setSubmitting(false); // Required by Formik to finish the submission cycle |
493
|
|
|
}); |
494
|
|
|
}} |
495
|
|
|
> |
496
|
|
|
{({ errors, touched, isSubmitting, values }): React.ReactElement => ( |
497
|
|
|
<section> |
498
|
|
|
<Form id="job-information" data-c-grid="gutter"> |
499
|
|
|
<FastField |
500
|
|
|
id="builder02JobTitle" |
501
|
|
|
type="text" |
502
|
|
|
name="title" |
503
|
|
|
component={TextInput} |
504
|
|
|
required |
505
|
|
|
grid="tl(1of2)" |
506
|
|
|
label={intl.formatMessage(formMessages.titleLabel)} |
507
|
|
|
placeholder={intl.formatMessage( |
508
|
|
|
formMessages.titlePlaceholder, |
509
|
|
|
)} |
510
|
|
|
/> |
511
|
|
|
<FastField |
512
|
|
|
id="builder02TermLength" |
513
|
|
|
type="number" |
514
|
|
|
name="termLength" |
515
|
|
|
component={NumberInput} |
516
|
|
|
min={1} |
517
|
|
|
max={36} |
518
|
|
|
required |
519
|
|
|
grid="tl(1of2)" |
520
|
|
|
label={intl.formatMessage(formMessages.termLengthLabel)} |
521
|
|
|
placeholder={intl.formatMessage( |
522
|
|
|
formMessages.termLengthPlaceholder, |
523
|
|
|
)} |
524
|
|
|
/> |
525
|
|
|
<FastField |
526
|
|
|
id="builder02Classification" |
527
|
|
|
name="classification" |
528
|
|
|
label={intl.formatMessage(formMessages.classificationLabel)} |
529
|
|
|
grid="tl(1of2)" |
530
|
|
|
component={SelectInput} |
531
|
|
|
required |
532
|
|
|
nullSelection={intl.formatMessage( |
533
|
|
|
formMessages.classificationNullSelection, |
534
|
|
|
)} |
535
|
|
|
options={classificationsExtractKeyValueJsonArray( |
536
|
|
|
classifications, |
537
|
|
|
)} |
538
|
|
|
/> |
539
|
|
|
<FastField |
540
|
|
|
name="level" |
541
|
|
|
id="builder02Level" |
542
|
|
|
component={SelectInput} |
543
|
|
|
required |
544
|
|
|
label={intl.formatMessage(formMessages.levelLabel)} |
545
|
|
|
grid="tl(1of2)" |
546
|
|
|
nullSelection={intl.formatMessage( |
547
|
|
|
formMessages.levelNullSelection, |
548
|
|
|
)} |
549
|
|
|
options={[ |
550
|
|
|
{ value: 1, label: "1" }, |
551
|
|
|
{ value: 2, label: "2" }, |
552
|
|
|
{ value: 3, label: "3" }, |
553
|
|
|
{ value: 4, label: "4" }, |
554
|
|
|
{ value: 5, label: "5" }, |
555
|
|
|
{ value: 6, label: "6" }, |
556
|
|
|
{ value: 7, label: "7" }, |
557
|
|
|
{ value: 8, label: "8" }, |
558
|
|
|
{ value: 9, label: "9" }, |
559
|
|
|
]} |
560
|
|
|
/> |
561
|
|
|
<div data-c-grid-item="base(1of1)"> |
562
|
|
|
{!isClassificationSet(values) ? ( |
563
|
|
|
<p |
564
|
|
|
data-c-font-weight="bold" |
565
|
|
|
data-c-margin="bottom(normal)" |
566
|
|
|
data-c-colour="grey" |
567
|
|
|
data-c-border="all(thin, solid, grey)" |
568
|
|
|
data-c-background="white(100)" |
569
|
|
|
data-c-padding="all(normal)" |
570
|
|
|
data-c-alignment="base(center)" |
571
|
|
|
> |
572
|
|
|
<FormattedMessage |
573
|
|
|
id="jobBuilder.details.SelectClassAndLvlMessage" |
574
|
|
|
defaultMessage="Please select a classification and level before preparing the education requirements." |
575
|
|
|
description="Message displayed after classification and level select boxes." |
576
|
|
|
/> |
577
|
|
|
</p> |
578
|
|
|
) : ( |
579
|
|
|
<> |
580
|
|
|
<p |
581
|
|
|
data-c-font-weight="bold" |
582
|
|
|
data-c-margin="bottom(normal)" |
583
|
|
|
> |
584
|
|
|
<FormattedMessage |
585
|
|
|
id="jobBuilder.details.educationRequirementHeader" |
586
|
|
|
defaultMessage="Based on the classification level you selected, this standard paragraph will appear on the job poster." |
587
|
|
|
description="Header message displayed for the Education requirement section." |
588
|
|
|
/> |
589
|
|
|
</p> |
590
|
|
|
<div> |
591
|
|
|
<ContextBlockItem |
592
|
|
|
wrapperMargin="bottom(normal)" |
593
|
|
|
bodyText={textToParagraphs( |
594
|
|
|
getEducationMsgForClassification( |
595
|
|
|
classifications, |
596
|
|
|
values.classification, |
597
|
|
|
intl, |
598
|
|
|
locale, |
599
|
|
|
), |
600
|
|
|
{}, |
601
|
|
|
{ |
602
|
|
|
0: { "data-c-font-weight": "bold" }, |
603
|
|
|
5: { "data-c-font-weight": "bold" }, |
604
|
|
|
}, |
605
|
|
|
)} |
606
|
|
|
/> |
607
|
|
|
</div> |
608
|
|
|
|
609
|
|
|
<div className="job-builder-education-customization active"> |
610
|
|
|
<p data-c-margin="bottom(normal)"> |
611
|
|
|
<FormattedMessage |
612
|
|
|
id="jobBuilder.details.educationRequirementCopyAndPaste" |
613
|
|
|
defaultMessage="If you want to customize this paragraph, copy and paste it into the textbox below." |
614
|
|
|
description="Footer message displayed for the Education requirement section." |
615
|
|
|
/> |
616
|
|
|
</p> |
617
|
|
|
<p |
618
|
|
|
data-c-font-weight="bold" |
619
|
|
|
data-c-margin="bottom(normal)" |
620
|
|
|
> |
621
|
|
|
<FormattedMessage |
622
|
|
|
id="jobBuilder.details.educationRequirementReviewChanges" |
623
|
|
|
defaultMessage="Your HR advisor will review your changes." |
624
|
|
|
description="Footer message displayed for the Education requirement section." |
625
|
|
|
/> |
626
|
|
|
</p> |
627
|
|
|
<div |
628
|
|
|
data-c-alignment="base(centre)" |
629
|
|
|
data-c-margin="top(normal) bottom(half)" |
630
|
|
|
> |
631
|
|
|
<CopyToClipboardButton |
632
|
|
|
actionText={intl.formatMessage( |
633
|
|
|
buttonMessages.buttonCopyToClipboard, |
634
|
|
|
)} |
635
|
|
|
postActionText={intl.formatMessage( |
636
|
|
|
buttonMessages.buttonCopied, |
637
|
|
|
)} |
638
|
|
|
textToCopy={getEducationMsgForClassification( |
639
|
|
|
classifications, |
640
|
|
|
values.classification, |
641
|
|
|
intl, |
642
|
|
|
locale, |
643
|
|
|
)} |
644
|
|
|
/> |
645
|
|
|
</div> |
646
|
|
|
<Field |
647
|
|
|
type="textarea" |
648
|
|
|
id="education_requirements" |
649
|
|
|
name="educationRequirements" |
650
|
|
|
label={intl.formatMessage( |
651
|
|
|
formMessages.educationRequirementsLabel, |
652
|
|
|
)} |
653
|
|
|
placeholder={intl.formatMessage( |
654
|
|
|
formMessages.educationRequirementPlaceholder, |
655
|
|
|
)} |
656
|
|
|
component={TextAreaInput} |
657
|
|
|
grid="base(1of1)" |
658
|
|
|
/> |
659
|
|
|
</div> |
660
|
|
|
</> |
661
|
|
|
)} |
662
|
|
|
</div> |
663
|
|
|
<FastField |
664
|
|
|
name="securityLevel" |
665
|
|
|
id="builder02SecurityLevel" |
666
|
|
|
component={SelectInput} |
667
|
|
|
required |
668
|
|
|
grid="tl(1of2)" |
669
|
|
|
label={intl.formatMessage(formMessages.securityLevelLabel)} |
670
|
|
|
nullSelection={intl.formatMessage( |
671
|
|
|
formMessages.securityLevelNullSelection, |
672
|
|
|
)} |
673
|
|
|
options={Object.values(SecurityClearanceId).map( |
674
|
|
|
(id: number): { value: number; label: string } => ({ |
675
|
|
|
value: id, |
676
|
|
|
label: intl.formatMessage(securityClearance(id)), |
677
|
|
|
}), |
678
|
|
|
)} |
679
|
|
|
/> |
680
|
|
|
<FastField |
681
|
|
|
name="language" |
682
|
|
|
id="builder02Language" |
683
|
|
|
component={SelectInput} |
684
|
|
|
required |
685
|
|
|
grid="tl(1of2)" |
686
|
|
|
label={intl.formatMessage(formMessages.languageLabel)} |
687
|
|
|
nullSelection={intl.formatMessage( |
688
|
|
|
formMessages.languageNullSelection, |
689
|
|
|
)} |
690
|
|
|
options={Object.values(LanguageRequirementId).map( |
691
|
|
|
(id: number): { value: number; label: string } => ({ |
692
|
|
|
value: id, |
693
|
|
|
label: intl.formatMessage(languageRequirement(id)), |
694
|
|
|
}), |
695
|
|
|
)} |
696
|
|
|
/> |
697
|
|
|
<FastField |
698
|
|
|
name="city" |
699
|
|
|
type="text" |
700
|
|
|
component={TextInput} |
701
|
|
|
required |
702
|
|
|
grid="tl(1of2)" |
703
|
|
|
id="builder02City" |
704
|
|
|
label={intl.formatMessage(formMessages.cityLabel)} |
705
|
|
|
placeholder={intl.formatMessage(formMessages.cityPlaceholder)} |
706
|
|
|
/> |
707
|
|
|
<FastField |
708
|
|
|
name="province" |
709
|
|
|
id="builder02Province" |
710
|
|
|
component={SelectInput} |
711
|
|
|
required |
712
|
|
|
grid="tl(1of2)" |
713
|
|
|
label={intl.formatMessage(formMessages.provinceLabel)} |
714
|
|
|
nullSelection={intl.formatMessage( |
715
|
|
|
formMessages.provinceNullSelection, |
716
|
|
|
)} |
717
|
|
|
options={Object.values(ProvinceId).map((id: number): { |
718
|
|
|
value: number; |
719
|
|
|
label: string; |
720
|
|
|
} => ({ |
721
|
|
|
value: id, |
722
|
|
|
label: intl.formatMessage(provinceName(id)), |
723
|
|
|
}))} |
724
|
|
|
/> |
725
|
|
|
<p data-c-margin="bottom(normal)" data-c-font-weight="bold"> |
726
|
|
|
<FormattedMessage |
727
|
|
|
id="jobBuilder.details.remoteWorkGroupHeader" |
728
|
|
|
defaultMessage="Is remote work allowed?" |
729
|
|
|
description="Header message displayed on the remote work group input." |
730
|
|
|
/> |
731
|
|
|
</p> |
732
|
|
|
<p data-c-margin="bottom(normal)"> |
733
|
|
|
<FormattedMessage |
734
|
|
|
id="jobBuilder.details.remoteWorkGroupBody" |
735
|
|
|
defaultMessage="Want the best talent in Canada? You increase your chances when you allow those in other parts of Canada to apply. Regional diversity also adds perspective to your team culture. Make sure to discuss this in advance with your HR Advisor." |
736
|
|
|
description="Body message displayed on the remote work group input." |
737
|
|
|
/> |
738
|
|
|
</p> |
739
|
|
|
<RadioGroup |
740
|
|
|
id="remoteWork" |
741
|
|
|
label={intl.formatMessage(formMessages.remoteWorkGroupLabel)} |
742
|
|
|
required |
743
|
|
|
grid="base(1of1)" |
744
|
|
|
error={errors.remoteWork} |
745
|
|
|
touched={touched.remoteWork} |
746
|
|
|
value={values.remoteWork} |
747
|
|
|
> |
748
|
|
|
{Object.keys(remoteWorkMessages).map( |
749
|
|
|
(key): React.ReactElement => { |
750
|
|
|
return ( |
751
|
|
|
<FastField |
752
|
|
|
key={key} |
753
|
|
|
name="remoteWork" |
754
|
|
|
component={RadioInput} |
755
|
|
|
id={key} |
756
|
|
|
label={intl.formatMessage(remoteWorkMessages[key])} |
757
|
|
|
/> |
758
|
|
|
); |
759
|
|
|
}, |
760
|
|
|
)} |
761
|
|
|
</RadioGroup> |
762
|
|
|
<p data-c-margin="bottom(normal)" data-c-font-weight="bold"> |
763
|
|
|
<FormattedMessage |
764
|
|
|
id="jobBuilder.details.teleworkGroupHeader" |
765
|
|
|
defaultMessage="How often is telework allowed?" |
766
|
|
|
description="Header message displayed on the telework group input." |
767
|
|
|
/> |
768
|
|
|
</p> |
769
|
|
|
<p data-c-margin="bottom(normal)"> |
770
|
|
|
<FormattedMessage |
771
|
|
|
id="jobBuilder.details.teleworkGroupBody" |
772
|
|
|
defaultMessage="Demonstrate that you trust your employees and you have a positive workplace culture. Allow telework as an option." |
773
|
|
|
description="Body message displayed on the telework group input." |
774
|
|
|
/> |
775
|
|
|
</p> |
776
|
|
|
<RadioGroup |
777
|
|
|
id="telework" |
778
|
|
|
label={intl.formatMessage(formMessages.teleworkGroupLabel)} |
779
|
|
|
required |
780
|
|
|
grid="base(1of1)" |
781
|
|
|
error={errors.telework} |
782
|
|
|
touched={touched.telework} |
783
|
|
|
value={values.telework} |
784
|
|
|
> |
785
|
|
|
{Object.keys(teleworkMessages).map( |
786
|
|
|
(key): React.ReactElement => { |
787
|
|
|
return ( |
788
|
|
|
<FastField |
789
|
|
|
key={key} |
790
|
|
|
name="telework" |
791
|
|
|
component={RadioInput} |
792
|
|
|
id={key} |
793
|
|
|
label={intl.formatMessage(teleworkMessages[key])} |
794
|
|
|
/> |
795
|
|
|
); |
796
|
|
|
}, |
797
|
|
|
)} |
798
|
|
|
</RadioGroup> |
799
|
|
|
<p data-c-margin="bottom(normal)" data-c-font-weight="bold"> |
800
|
|
|
<FormattedMessage |
801
|
|
|
id="jobBuilder.details.flexHoursGroupHeader" |
802
|
|
|
defaultMessage="How often are flexible hours allowed?" |
803
|
|
|
description="Header message displayed on the flex hours group input." |
804
|
|
|
/> |
805
|
|
|
</p> |
806
|
|
|
<p data-c-margin="bottom(normal)"> |
807
|
|
|
<FormattedMessage |
808
|
|
|
id="jobBuilder.details.flexHoursGroupBody" |
809
|
|
|
defaultMessage={`Want to support a more gender inclusive workplace? |
810
|
|
|
Studies show allowing flex hours is a great way to improve opportunities for women and parents.`} |
811
|
|
|
description="Body message displayed on the flex hours group input." |
812
|
|
|
/> |
813
|
|
|
</p> |
814
|
|
|
<RadioGroup |
815
|
|
|
id="flexHours" |
816
|
|
|
required |
817
|
|
|
grid="base(1of1)" |
818
|
|
|
label={intl.formatMessage(formMessages.flexHoursGroupLabel)} |
819
|
|
|
error={errors.flexHours} |
820
|
|
|
touched={touched.flexHours} |
821
|
|
|
value={values.flexHours} |
822
|
|
|
> |
823
|
|
|
{Object.keys(flexHourMessages).map( |
824
|
|
|
(key): React.ReactElement => { |
825
|
|
|
return ( |
826
|
|
|
<FastField |
827
|
|
|
key={key} |
828
|
|
|
name="flexHours" |
829
|
|
|
component={RadioInput} |
830
|
|
|
id={key} |
831
|
|
|
label={intl.formatMessage(flexHourMessages[key])} |
832
|
|
|
/> |
833
|
|
|
); |
834
|
|
|
}, |
835
|
|
|
)} |
836
|
|
|
</RadioGroup> |
837
|
|
|
<p data-c-margin="bottom(normal)" data-c-font-weight="bold"> |
838
|
|
|
<FormattedMessage |
839
|
|
|
id="jobBuilder.details.travelGroupHeader" |
840
|
|
|
defaultMessage="Is travel required?" |
841
|
|
|
description="Header message displayed on the travel group input." |
842
|
|
|
/> |
843
|
|
|
</p> |
844
|
|
|
<RadioGroup |
845
|
|
|
id="travel" |
846
|
|
|
required |
847
|
|
|
grid="base(1of1)" |
848
|
|
|
label={intl.formatMessage(formMessages.travelGroupLabel)} |
849
|
|
|
error={errors.travel} |
850
|
|
|
touched={touched.travel} |
851
|
|
|
value={values.travel} |
852
|
|
|
> |
853
|
|
|
{Object.keys(travelMessages).map( |
854
|
|
|
(key): React.ReactElement => { |
855
|
|
|
return ( |
856
|
|
|
<FastField |
857
|
|
|
key={key} |
858
|
|
|
name="travel" |
859
|
|
|
component={RadioInput} |
860
|
|
|
id={key} |
861
|
|
|
label={intl.formatMessage(travelMessages[key])} |
862
|
|
|
/> |
863
|
|
|
); |
864
|
|
|
}, |
865
|
|
|
)} |
866
|
|
|
</RadioGroup> |
867
|
|
|
<p data-c-margin="bottom(normal)" data-c-font-weight="bold"> |
868
|
|
|
<FormattedMessage |
869
|
|
|
id="jobBuilder.details.overtimeGroupHeader" |
870
|
|
|
defaultMessage="Is overtime required?" |
871
|
|
|
description="Header message displayed on the overtime group input." |
872
|
|
|
/> |
873
|
|
|
</p> |
874
|
|
|
<RadioGroup |
875
|
|
|
id="overtime" |
876
|
|
|
required |
877
|
|
|
grid="base(1of1)" |
878
|
|
|
label={intl.formatMessage(formMessages.overtimeGroupLabel)} |
879
|
|
|
error={errors.overtime} |
880
|
|
|
touched={touched.overtime} |
881
|
|
|
value={values.overtime} |
882
|
|
|
> |
883
|
|
|
{Object.keys(overtimeMessages).map( |
884
|
|
|
(key): React.ReactElement => { |
885
|
|
|
return ( |
886
|
|
|
<FastField |
887
|
|
|
key={key} |
888
|
|
|
name="overtime" |
889
|
|
|
component={RadioInput} |
890
|
|
|
id={key} |
891
|
|
|
label={intl.formatMessage(overtimeMessages[key])} |
892
|
|
|
/> |
893
|
|
|
); |
894
|
|
|
}, |
895
|
|
|
)} |
896
|
|
|
</RadioGroup> |
897
|
|
|
<div data-c-grid="gutter" data-c-grid-item="base(1of1)"> |
898
|
|
|
<div data-c-grid-item="base(1of1)"> |
899
|
|
|
<hr data-c-margin="top(normal) bottom(normal)" /> |
900
|
|
|
</div> |
901
|
|
|
<div |
902
|
|
|
data-c-alignment="base(centre) tp(left)" |
903
|
|
|
data-c-grid-item="tp(1of2)" |
904
|
|
|
> |
905
|
|
|
<button |
906
|
|
|
data-c-button="outline(c2)" |
907
|
|
|
data-c-radius="rounded" |
908
|
|
|
type="button" |
909
|
|
|
disabled={isSubmitting} |
910
|
|
|
onClick={(): void => { |
911
|
|
|
updateValuesAndReturn(values); |
912
|
|
|
}} |
913
|
|
|
> |
914
|
|
|
<FormattedMessage |
915
|
|
|
id="jobBuilder.details.returnButtonLabel" |
916
|
|
|
defaultMessage="Save & Return to Intro" |
917
|
|
|
description="The text displayed on the Save & Return button of the Job Details form." |
918
|
|
|
/> |
919
|
|
|
</button> |
920
|
|
|
</div> |
921
|
|
|
<div |
922
|
|
|
data-c-alignment="base(centre) tp(right)" |
923
|
|
|
data-c-grid-item="tp(1of2)" |
924
|
|
|
> |
925
|
|
|
<button |
926
|
|
|
data-c-button="solid(c1)" |
927
|
|
|
data-c-radius="rounded" |
928
|
|
|
type="submit" |
929
|
|
|
disabled={isSubmitting} |
930
|
|
|
> |
931
|
|
|
<FormattedMessage |
932
|
|
|
id="jobBuilder.details.submitButtonLabel" |
933
|
|
|
defaultMessage="Save & Preview" |
934
|
|
|
description="The text displayed on the submit button for the Job Details form." |
935
|
|
|
/> |
936
|
|
|
</button> |
937
|
|
|
</div> |
938
|
|
|
</div> |
939
|
|
|
</Form> |
940
|
|
|
<Modal |
941
|
|
|
id="job-details-preview" |
942
|
|
|
parentElement={modalParentRef.current} |
943
|
|
|
visible={isModalVisible} |
944
|
|
|
onModalConfirm={(): void => { |
945
|
|
|
handleModalConfirm(); |
946
|
|
|
setIsModalVisible(false); |
947
|
|
|
}} |
948
|
|
|
onModalCancel={(): void => { |
949
|
|
|
handleModalCancel(); |
950
|
|
|
setIsModalVisible(false); |
951
|
|
|
}} |
952
|
|
|
onModalMiddle={(): void => { |
953
|
|
|
handleSkipToReview().finally((): void => { |
954
|
|
|
setIsModalVisible(false); |
955
|
|
|
}); |
956
|
|
|
}} |
957
|
|
|
> |
958
|
|
|
<Modal.Header> |
959
|
|
|
<div |
960
|
|
|
data-c-background="c1(100)" |
961
|
|
|
data-c-border="bottom(thin, solid, black)" |
962
|
|
|
data-c-padding="normal" |
963
|
|
|
> |
964
|
|
|
<h5 |
965
|
|
|
data-c-colour="white" |
966
|
|
|
data-c-font-size="h4" |
967
|
|
|
id="job-details-preview-title" |
968
|
|
|
> |
969
|
|
|
<FormattedMessage |
970
|
|
|
id="jobBuilder.details.modalHeader" |
971
|
|
|
defaultMessage="You're off to a great start!" |
972
|
|
|
description="The text displayed in the header of the Job Details modal." |
973
|
|
|
/> |
974
|
|
|
</h5> |
975
|
|
|
</div> |
976
|
|
|
</Modal.Header> |
977
|
|
|
<Modal.Body> |
978
|
|
|
<div |
979
|
|
|
data-c-border="bottom(thin, solid, black)" |
980
|
|
|
data-c-padding="normal" |
981
|
|
|
id="job-details-preview-description" |
982
|
|
|
> |
983
|
|
|
<p> |
984
|
|
|
<FormattedMessage |
985
|
|
|
id="jobBuilder.details.modalBody" |
986
|
|
|
defaultMessage="Here's a preview of the Job Information you just entered. Feel free to go back and edit things or move to the next step if you're happy with it." |
987
|
|
|
description="The text displayed in the body of the Job Details modal." |
988
|
|
|
/> |
989
|
|
|
</p> |
990
|
|
|
</div> |
991
|
|
|
<div |
992
|
|
|
data-c-background="grey(20)" |
993
|
|
|
data-c-border="bottom(thin, solid, black)" |
994
|
|
|
data-c-padding="normal" |
995
|
|
|
> |
996
|
|
|
{/* TODO: Pull in the signed-in Manager's department */} |
997
|
|
|
<JobPreview |
998
|
|
|
title={values.title} |
999
|
|
|
department="Department" |
1000
|
|
|
remoteWork={intl.formatMessage( |
1001
|
|
|
remoteWorkMessages[values.remoteWork], |
1002
|
|
|
)} |
1003
|
|
|
language={ |
1004
|
|
|
typeof values.language === "string" |
1005
|
|
|
? "" |
1006
|
|
|
: intl.formatMessage( |
1007
|
|
|
languageRequirement(Number(values.language)), |
1008
|
|
|
) |
1009
|
|
|
} |
1010
|
|
|
city={values.city} |
1011
|
|
|
province={ |
1012
|
|
|
typeof values.province === "string" |
1013
|
|
|
? "" |
1014
|
|
|
: intl.formatMessage( |
1015
|
|
|
provinceName(Number(values.province)), |
1016
|
|
|
) |
1017
|
|
|
} |
1018
|
|
|
education={ |
1019
|
|
|
values.educationRequirements.length > 0 |
1020
|
|
|
? values.educationRequirements |
1021
|
|
|
: getEducationMsgForClassification( |
1022
|
|
|
classifications, |
1023
|
|
|
values.classification, |
1024
|
|
|
intl, |
1025
|
|
|
locale, |
1026
|
|
|
) |
1027
|
|
|
} |
1028
|
|
|
termLength={ |
1029
|
|
|
typeof values.termLength === "string" |
1030
|
|
|
? null |
1031
|
|
|
: Number(values.termLength) |
1032
|
|
|
} |
1033
|
|
|
telework={intl.formatMessage( |
1034
|
|
|
teleworkMessages[values.telework], |
1035
|
|
|
)} |
1036
|
|
|
flexHours={intl.formatMessage( |
1037
|
|
|
flexHourMessages[values.flexHours], |
1038
|
|
|
)} |
1039
|
|
|
securityLevel={ |
1040
|
|
|
typeof values.securityLevel === "string" |
1041
|
|
|
? "" |
1042
|
|
|
: intl.formatMessage( |
1043
|
|
|
securityClearance(Number(values.securityLevel)), |
1044
|
|
|
) |
1045
|
|
|
} |
1046
|
|
|
classification={getKeyByValue( |
1047
|
|
|
classificationsExtractKeyValueJson(classifications), |
1048
|
|
|
values.classification, |
1049
|
|
|
)} |
1050
|
|
|
level={String(values.level)} |
1051
|
|
|
travel={intl.formatMessage(travelMessages[values.travel])} |
1052
|
|
|
overtime={intl.formatMessage( |
1053
|
|
|
overtimeMessages[values.overtime], |
1054
|
|
|
)} |
1055
|
|
|
/> |
1056
|
|
|
</div> |
1057
|
|
|
</Modal.Body> |
1058
|
|
|
<Modal.Footer> |
1059
|
|
|
<Modal.FooterCancelBtn> |
1060
|
|
|
<FormattedMessage |
1061
|
|
|
id="jobBuilder.details.modalCancelLabel" |
1062
|
|
|
defaultMessage="Go Back" |
1063
|
|
|
description="The text displayed on the cancel button of the Job Details modal." |
1064
|
|
|
/> |
1065
|
|
|
</Modal.FooterCancelBtn> |
1066
|
|
|
{jobIsComplete && ( |
1067
|
|
|
<Modal.FooterMiddleBtn> |
1068
|
|
|
<FormattedMessage |
1069
|
|
|
id="jobBuilder.details.modalMiddleLabel" |
1070
|
|
|
defaultMessage="Skip to Review" |
1071
|
|
|
description="The text displayed on the 'Skip to Review' button of the Job Details modal." |
1072
|
|
|
/> |
1073
|
|
|
</Modal.FooterMiddleBtn> |
1074
|
|
|
)} |
1075
|
|
|
<Modal.FooterConfirmBtn> |
1076
|
|
|
<FormattedMessage |
1077
|
|
|
id="jobBuilder.details.modalConfirmLabel" |
1078
|
|
|
defaultMessage="Next Step" |
1079
|
|
|
description="The text displayed on the confirm button of the Job Details modal." |
1080
|
|
|
/> |
1081
|
|
|
</Modal.FooterConfirmBtn> |
1082
|
|
|
</Modal.Footer> |
1083
|
|
|
</Modal> |
1084
|
|
|
</section> |
1085
|
|
|
)} |
1086
|
|
|
</Formik> |
1087
|
|
|
</div> |
1088
|
|
|
<div data-c-dialog-overlay={isModalVisible ? "active" : ""} /> |
1089
|
|
|
</section> |
1090
|
|
|
); |
1091
|
|
|
}; |
1092
|
|
|
|
1093
|
|
|
interface JobDetailsContainerProps { |
1094
|
|
|
jobId: number | null; |
1095
|
|
|
handleModalCancel: () => void; |
1096
|
|
|
handleModalConfirm: () => void; |
1097
|
|
|
} |
1098
|
|
|
|
1099
|
|
|
const mapStateToProps = ( |
1100
|
|
|
state: RootState, |
1101
|
|
|
ownProps: JobDetailsContainerProps, |
1102
|
|
|
): { |
1103
|
|
|
job: Job | null; |
1104
|
|
|
} => ({ |
1105
|
|
|
job: ownProps.jobId ? selectJob(state, ownProps as { jobId: number }) : null, |
1106
|
|
|
}); |
1107
|
|
|
|
1108
|
|
|
const mapDispatchToProps = ( |
1109
|
|
|
dispatch: DispatchType, |
1110
|
|
|
ownProps: JobDetailsContainerProps, |
1111
|
|
|
): { |
1112
|
|
|
handleSubmit: (newJob: Job) => Promise<boolean>; |
1113
|
|
|
} => ({ |
1114
|
|
|
handleSubmit: ownProps.jobId |
1115
|
|
|
? async (newJob: Job): Promise<boolean> => { |
1116
|
|
|
const result = await dispatch(updateJob(newJob)); |
1117
|
|
|
return !result.error; |
1118
|
|
|
} |
1119
|
|
|
: async (newJob: Job): Promise<boolean> => { |
1120
|
|
|
const result = await dispatch(createJob(newJob)); |
1121
|
|
|
return !result.error; |
1122
|
|
|
}, |
1123
|
|
|
}); |
1124
|
|
|
|
1125
|
|
|
export const JobDetailsContainer = connect( |
1126
|
|
|
mapStateToProps, |
1127
|
|
|
mapDispatchToProps, |
1128
|
|
|
)(JobDetails); |
1129
|
|
|
|
1130
|
|
|
export default JobDetailsContainer; |
1131
|
|
|
|