1
|
|
|
import React, { useState, useRef, useCallback } from "react"; |
2
|
|
|
import { |
3
|
|
|
injectIntl, |
4
|
|
|
WrappedComponentProps, |
5
|
|
|
FormattedMessage, |
6
|
|
|
defineMessages, |
7
|
|
|
useIntl, |
8
|
|
|
} from "react-intl"; |
9
|
|
|
import { |
10
|
|
|
JobPosterKeyTask, |
11
|
|
|
Criteria, |
12
|
|
|
Job, |
13
|
|
|
Skill, |
14
|
|
|
Department, |
15
|
|
|
Manager, |
16
|
|
|
User, |
17
|
|
|
Comment, |
18
|
|
|
} from "../../../models/types"; |
19
|
|
|
import { |
20
|
|
|
jobBuilderDetails, |
21
|
|
|
jobBuilderTasks, |
22
|
|
|
jobBuilderImpact, |
23
|
|
|
jobBuilderSkills, |
24
|
|
|
managerEditProfile, |
25
|
|
|
jobBuilderEnv, |
26
|
|
|
imageUrl, |
27
|
|
|
managerJobSummary, |
28
|
|
|
} from "../../../helpers/routes"; |
29
|
|
|
import { |
30
|
|
|
find, |
31
|
|
|
mapToObject, |
32
|
|
|
hasKey, |
33
|
|
|
getId, |
34
|
|
|
notEmpty, |
35
|
|
|
} from "../../../helpers/queries"; |
36
|
|
|
import { |
37
|
|
|
provinceName, |
38
|
|
|
securityClearance, |
39
|
|
|
languageRequirement, |
40
|
|
|
languageRequirementDescription, |
41
|
|
|
languageRequirementContext, |
42
|
|
|
jobReviewLocations, |
43
|
|
|
} from "../../../models/localizedConstants"; |
44
|
|
|
import { |
45
|
|
|
CriteriaTypeId, |
46
|
|
|
LanguageRequirementId, |
47
|
|
|
LocationId, |
48
|
|
|
} from "../../../models/lookupConstants"; |
49
|
|
|
import Criterion from "../Criterion"; |
50
|
|
|
import JobWorkEnv from "../WorkEnv/WorkEnvFeatures"; |
51
|
|
|
import JobWorkCulture from "../JobWorkCulture"; |
52
|
|
|
import Modal from "../../Modal"; |
53
|
|
|
import { textToParagraphs } from "../../../helpers/textToParagraphs"; |
54
|
|
|
import { useUrlHash, Link } from "../../../helpers/router"; |
55
|
|
|
import { classificationString } from "../../../models/jobUtil"; |
56
|
|
|
import DemoSubmitJobModal from "./DemoSubmitJobModal"; |
57
|
|
|
import ManagerSurveyModal from "./ManagerSurveyModal"; |
58
|
|
|
import ActivityFeed from "../../ActivityFeed"; |
59
|
|
|
import { localizeFieldNonNull, localizeField } from "../../../helpers/localize"; |
60
|
|
|
|
61
|
|
|
interface JobReviewSectionProps { |
62
|
|
|
title: string; |
63
|
|
|
isSubsection?: boolean; |
64
|
|
|
link: string; |
65
|
|
|
linkLabel: string; |
66
|
|
|
description?: string; |
67
|
|
|
hideLink: boolean; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
const messages = defineMessages({ |
71
|
|
|
titleHeading: { |
72
|
|
|
id: "jobBuilder.review.jobPageHeading", |
73
|
|
|
defaultMessage: "Job Page Heading", |
74
|
|
|
description: "Section title.", |
75
|
|
|
}, |
76
|
|
|
infoEditLink: { |
77
|
|
|
id: "jobBuilder.review.infoEditLink", |
78
|
|
|
defaultMessage: "Edit This in Step 01: Job Info", |
79
|
|
|
description: "Link to edit job details.", |
80
|
|
|
}, |
81
|
|
|
impactEditLink: { |
82
|
|
|
id: "jobBuilder.review.impactEditLink", |
83
|
|
|
defaultMessage: "Edit This in Step 03: Impact", |
84
|
|
|
description: "Link to edit impact statements.", |
85
|
|
|
}, |
86
|
|
|
tasksEditLink: { |
87
|
|
|
id: "jobBuilder.review.tasksEditLink", |
88
|
|
|
defaultMessage: "Edit This in Step 04: Tasks", |
89
|
|
|
description: "Link to edit tasks.", |
90
|
|
|
}, |
91
|
|
|
skillsEditLink: { |
92
|
|
|
id: "jobBuilder.review.skillsEditLink", |
93
|
|
|
defaultMessage: "Edit This in Step 05: Skills", |
94
|
|
|
description: "Link to edit skills.", |
95
|
|
|
}, |
96
|
|
|
workEnvEditLink: { |
97
|
|
|
id: "jobBuilder.review.workEnvEditLink", |
98
|
|
|
defaultMessage: "Edit This in Step 02: Work Environment", |
99
|
|
|
description: "Link to edit work environment.", |
100
|
|
|
}, |
101
|
|
|
managerProfileLink: { |
102
|
|
|
id: "jobBuilder.review.managerProfileLink", |
103
|
|
|
defaultMessage: "Edit This in Your Profile", |
104
|
|
|
description: "Link to edit a manager's profile.", |
105
|
|
|
}, |
106
|
|
|
nullProvince: { |
107
|
|
|
id: "jobBuilder.review.nullProvince", |
108
|
|
|
defaultMessage: "MISSING PROVINCE", |
109
|
|
|
description: "Error text for missing province information.", |
110
|
|
|
}, |
111
|
|
|
basicHeading: { |
112
|
|
|
id: "jobBuilder.review.basicInformationHeading", |
113
|
|
|
defaultMessage: "Basic Information", |
114
|
|
|
description: "Heading for Basic Information section", |
115
|
|
|
}, |
116
|
|
|
impactHeading: { |
117
|
|
|
id: "jobBuilder.review.impactHeading", |
118
|
|
|
defaultMessage: "Impact", |
119
|
|
|
description: "Heading for Impact section", |
120
|
|
|
}, |
121
|
|
|
tasksHeading: { |
122
|
|
|
id: "jobBuilder.review.tasksHeading", |
123
|
|
|
defaultMessage: "Tasks", |
124
|
|
|
description: "Heading for Tasks section", |
125
|
|
|
}, |
126
|
|
|
criteriaSection: { |
127
|
|
|
id: "jobBuilder.review.criteriaSection", |
128
|
|
|
defaultMessage: "Criteria", |
129
|
|
|
description: "Title for criteria section", |
130
|
|
|
}, |
131
|
|
|
educationalHeading: { |
132
|
|
|
id: "jobBuilder.review.educationalHeading", |
133
|
|
|
defaultMessage: "Education Requirements", |
134
|
|
|
description: "Heading for Educational section", |
135
|
|
|
}, |
136
|
|
|
skillsHeading: { |
137
|
|
|
id: "jobBuilder.review.skillsHeading", |
138
|
|
|
defaultMessage: "Skills You Need to Have", |
139
|
|
|
description: "Heading for Skills section", |
140
|
|
|
}, |
141
|
|
|
assetHeading: { |
142
|
|
|
id: "jobBuilder.review.assetHeading", |
143
|
|
|
defaultMessage: "Skills That Are Nice to Have", |
144
|
|
|
description: "Heading for Asset Skills section", |
145
|
|
|
}, |
146
|
|
|
languageHeading: { |
147
|
|
|
id: "jobBuilder.review.languageHeading", |
148
|
|
|
defaultMessage: "Language Requirements", |
149
|
|
|
description: "Heading for Language section", |
150
|
|
|
}, |
151
|
|
|
cultureSection: { |
152
|
|
|
id: "jobBuilder.review.cultureSection", |
153
|
|
|
defaultMessage: "Environment & Culture", |
154
|
|
|
description: "Title for culture section", |
155
|
|
|
}, |
156
|
|
|
managerHeading: { |
157
|
|
|
id: "jobBuilder.review.managerHeading", |
158
|
|
|
defaultMessage: "Manager Information", |
159
|
|
|
description: "Heading for Manager section", |
160
|
|
|
}, |
161
|
|
|
workCultureHeading: { |
162
|
|
|
id: "jobBuilder.review.workCultureHeading", |
163
|
|
|
defaultMessage: "Work Culture", |
164
|
|
|
description: "Heading for Work Culture section", |
165
|
|
|
}, |
166
|
|
|
workEnvHeading: { |
167
|
|
|
id: "jobBuilder.review.workEnvHeading", |
168
|
|
|
defaultMessage: "Work Environment", |
169
|
|
|
description: "Heading for Work Environment section", |
170
|
|
|
}, |
171
|
|
|
workEnvDescription: { |
172
|
|
|
id: "jobBuilder.review.workDescription", |
173
|
|
|
defaultMessage: |
174
|
|
|
"Please note that some Work Environment information is only presented to the applicant after they've clicked the \"View the team's work environment and culture\" button that appears on the job poster.", |
175
|
|
|
description: "A note about the information in the work description section", |
176
|
|
|
}, |
177
|
|
|
otherInfoHeading: { |
178
|
|
|
id: "jobBuilder.review.otherInfoHeading", |
179
|
|
|
defaultMessage: "Other Team Information", |
180
|
|
|
description: "Heading for other info section", |
181
|
|
|
}, |
182
|
|
|
}); |
183
|
|
|
|
184
|
|
|
const JobReviewSection: React.FunctionComponent<JobReviewSectionProps> = ({ |
185
|
|
|
title, |
186
|
|
|
isSubsection, |
187
|
|
|
link, |
188
|
|
|
linkLabel, |
189
|
|
|
description, |
190
|
|
|
children, |
191
|
|
|
hideLink, |
192
|
|
|
}): React.ReactElement => { |
193
|
|
|
return ( |
194
|
|
|
<> |
195
|
|
|
<div |
196
|
|
|
data-c-margin={ |
197
|
|
|
isSubsection ? "tb(normal)" : "top(triple) bottom(normal)" |
198
|
|
|
} |
199
|
|
|
> |
200
|
|
|
<div data-c-grid="gutter middle"> |
201
|
|
|
<div |
202
|
|
|
data-c-grid-item="tp(1of2)" |
203
|
|
|
data-c-alignment="base(centre) tp(left)" |
204
|
|
|
> |
205
|
|
|
{isSubsection ? ( |
206
|
|
|
<h5 data-c-font-weight="bold" data-c-font-size="h5"> |
207
|
|
|
{title} |
208
|
|
|
</h5> |
209
|
|
|
) : ( |
210
|
|
|
<h4 data-c-colour="c2" data-c-font-size="h4"> |
211
|
|
|
{title} |
212
|
|
|
</h4> |
213
|
|
|
)} |
214
|
|
|
</div> |
215
|
|
|
{!hideLink && ( |
216
|
|
|
<div |
217
|
|
|
data-c-grid-item="tp(1of2)" |
218
|
|
|
data-c-alignment="base(centre) tp(right)" |
219
|
|
|
> |
220
|
|
|
<Link href={link} title={linkLabel}> |
221
|
|
|
<i data-c-colour="c2" className="fas fa-edit" /> |
222
|
|
|
{linkLabel} |
223
|
|
|
</Link> |
224
|
|
|
</div> |
225
|
|
|
)} |
226
|
|
|
</div> |
227
|
|
|
</div> |
228
|
|
|
{description && <p data-c-margin="bottom(normal)">{description}</p>} |
229
|
|
|
<div |
230
|
|
|
data-c-border="all(thin, solid, grey)" |
231
|
|
|
data-c-padding="normal" |
232
|
|
|
data-c-radius="rounded" |
233
|
|
|
> |
234
|
|
|
{children} |
235
|
|
|
</div> |
236
|
|
|
</> |
237
|
|
|
); |
238
|
|
|
}; |
239
|
|
|
|
240
|
|
|
const sectionTitle = (title: string): React.ReactElement => { |
241
|
|
|
return ( |
242
|
|
|
<div data-c-margin="top(triple) bottom(normal)"> |
243
|
|
|
<div data-c-grid="gutter middle"> |
244
|
|
|
<div |
245
|
|
|
data-c-grid-item="base(1of1)" |
246
|
|
|
data-c-alignment="base(centre) tp(left)" |
247
|
|
|
> |
248
|
|
|
<h4 data-c-colour="c2" data-c-font-size="h4"> |
249
|
|
|
{title} |
250
|
|
|
</h4> |
251
|
|
|
</div> |
252
|
|
|
</div> |
253
|
|
|
</div> |
254
|
|
|
); |
255
|
|
|
}; |
256
|
|
|
|
257
|
|
|
const languageRequirementIcons = ( |
258
|
|
|
languageRequirementId: number, |
259
|
|
|
): React.ReactElement => { |
260
|
|
|
const enIcon = <img src={imageUrl("icon_english_requirement.svg")} alt="" />; |
261
|
|
|
const frIcon = <img src={imageUrl("icon_french_requirement.svg")} alt="" />; |
262
|
|
|
switch (languageRequirementId) { |
263
|
|
|
case LanguageRequirementId.bilingualIntermediate: |
264
|
|
|
case LanguageRequirementId.bilingualAdvanced: |
265
|
|
|
return ( |
266
|
|
|
<> |
267
|
|
|
{enIcon} |
268
|
|
|
& |
269
|
|
|
{frIcon} |
270
|
|
|
</> |
271
|
|
|
); |
272
|
|
|
case LanguageRequirementId.englishOrFrench: |
273
|
|
|
return ( |
274
|
|
|
<> |
275
|
|
|
{enIcon} |
276
|
|
|
<FormattedMessage |
277
|
|
|
id="jobBuilder.review.or" |
278
|
|
|
defaultMessage="or" |
279
|
|
|
description="Displayed between language icons for the English Or French option." |
280
|
|
|
/> |
281
|
|
|
|
282
|
|
|
{frIcon} |
283
|
|
|
</> |
284
|
|
|
); |
285
|
|
|
case LanguageRequirementId.english: |
286
|
|
|
return enIcon; |
287
|
|
|
case LanguageRequirementId.french: |
288
|
|
|
return frIcon; |
289
|
|
|
default: |
290
|
|
|
return enIcon; |
291
|
|
|
} |
292
|
|
|
}; |
293
|
|
|
|
294
|
|
|
const renderManagerSection = ( |
295
|
|
|
manager: Manager | null, |
296
|
|
|
managerDeptName: string, |
297
|
|
|
locale: "en" | "fr", |
298
|
|
|
): React.ReactElement => { |
299
|
|
|
if (manager === null) { |
300
|
|
|
return ( |
301
|
|
|
<p> |
302
|
|
|
<FormattedMessage |
303
|
|
|
id="jobBuilder.review.managerDataLoading" |
304
|
|
|
defaultMessage="Manager data is loading..." |
305
|
|
|
description="Placeholder text as Manager data loads." |
306
|
|
|
/> |
307
|
|
|
</p> |
308
|
|
|
); |
309
|
|
|
} |
310
|
|
|
const leadershipStyle = localizeField(locale, manager, "leadership_style"); |
311
|
|
|
const position = localizeField(locale, manager, "position"); |
312
|
|
|
|
313
|
|
|
if (leadershipStyle !== null && position !== null) { |
314
|
|
|
return ( |
315
|
|
|
<> |
316
|
|
|
<p data-c-margin="bottom(normal)">{manager.full_name}</p> |
317
|
|
|
<p data-c-margin={`${leadershipStyle && "{bottom(normal)"}`}> |
318
|
|
|
<FormattedMessage |
319
|
|
|
id="jobBuilder.review.managerPosition" |
320
|
|
|
defaultMessage="{position} at {department}" |
321
|
|
|
description="Description of the Manager's position & department." |
322
|
|
|
values={{ |
323
|
|
|
position, |
324
|
|
|
department: managerDeptName, |
325
|
|
|
}} |
326
|
|
|
/> |
327
|
|
|
</p> |
328
|
|
|
{leadershipStyle && <p>{leadershipStyle}</p>} |
329
|
|
|
</> |
330
|
|
|
); |
331
|
|
|
} |
332
|
|
|
return ( |
333
|
|
|
<p> |
334
|
|
|
<FormattedMessage |
335
|
|
|
id="jobBuilder.review.managerIncomplete" |
336
|
|
|
defaultMessage="Please complete your manager profile." |
337
|
|
|
description="Note that the Manager's profile is incomplete and should be edited before continuing." |
338
|
|
|
/> |
339
|
|
|
</p> |
340
|
|
|
); |
341
|
|
|
}; |
342
|
|
|
|
343
|
|
|
interface JobReviewDisplayProps { |
344
|
|
|
job: Job; |
345
|
|
|
classificationKey: string; |
346
|
|
|
manager: Manager | null; |
347
|
|
|
tasks: JobPosterKeyTask[]; |
348
|
|
|
criteria: Criteria[]; |
349
|
|
|
// List of all possible skills. |
350
|
|
|
skills: Skill[]; |
351
|
|
|
// List of all possible departments. |
352
|
|
|
departments: Department[]; |
353
|
|
|
user: User | null; |
354
|
|
|
hideBuilderLinks: boolean; |
355
|
|
|
} |
356
|
|
|
export const JobReviewDisplay: React.FC<JobReviewDisplayProps> = ({ |
357
|
|
|
job, |
358
|
|
|
classificationKey, |
359
|
|
|
manager, |
360
|
|
|
tasks, |
361
|
|
|
criteria, |
362
|
|
|
skills, |
363
|
|
|
departments, |
364
|
|
|
user, |
365
|
|
|
hideBuilderLinks = false, |
366
|
|
|
}): React.ReactElement => { |
367
|
|
|
// Scroll to element specified in the url hash, if possible |
368
|
|
|
useUrlHash(); |
369
|
|
|
const intl = useIntl(); |
370
|
|
|
const { locale } = intl; |
371
|
|
|
if (locale !== "en" && locale !== "fr") { |
372
|
|
|
throw new Error("Unknown intl.locale"); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
const getDeptName = (departmentId: number | null): string => { |
376
|
|
|
const department = |
377
|
|
|
departmentId !== null ? find(departments, departmentId) : null; |
378
|
|
|
return department !== null |
379
|
|
|
? localizeFieldNonNull(locale, department, "name") |
380
|
|
|
: "MISSING DEPARTMENT"; |
381
|
|
|
}; |
382
|
|
|
const departmentName = getDeptName(job.department_id); |
383
|
|
|
const userDeptName = user ? getDeptName(user.department_id) : ""; |
384
|
|
|
|
385
|
|
|
// Map the skills into a dictionary for quicker access |
386
|
|
|
const skillsById = mapToObject(skills, getId); |
387
|
|
|
const getSkillOfCriteria = (criterion: Criteria): Skill | null => { |
388
|
|
|
return hasKey(skillsById, criterion.skill_id) |
389
|
|
|
? skillsById[criterion.skill_id] |
390
|
|
|
: null; |
391
|
|
|
}; |
392
|
|
|
const essentialCriteria = criteria.filter( |
393
|
|
|
(criterion): boolean => |
394
|
|
|
criterion.criteria_type_id === CriteriaTypeId.Essential, |
395
|
|
|
); |
396
|
|
|
const assetCriteria = criteria.filter( |
397
|
|
|
(criterion): boolean => criterion.criteria_type_id === CriteriaTypeId.Asset, |
398
|
|
|
); |
399
|
|
|
|
400
|
|
|
const selectedEnvOptions: string[] = job.work_env_features |
401
|
|
|
? Object.entries(job.work_env_features) |
402
|
|
|
.map(([feature, selected]): string | null => |
403
|
|
|
selected ? feature : null, |
404
|
|
|
) |
405
|
|
|
.filter(notEmpty) |
406
|
|
|
: []; |
407
|
|
|
|
408
|
|
|
return ( |
409
|
|
|
<> |
410
|
|
|
<JobReviewSection |
411
|
|
|
title={intl.formatMessage(messages.titleHeading)} |
412
|
|
|
linkLabel={intl.formatMessage(messages.infoEditLink)} |
413
|
|
|
link={jobBuilderDetails(locale, job.id)} |
414
|
|
|
hideLink={hideBuilderLinks} |
415
|
|
|
> |
416
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(half)"> |
417
|
|
|
{localizeField(locale, job, "title")} |
418
|
|
|
</p> |
419
|
|
|
<p data-c-margin="bottom(normal)">{departmentName}</p> |
420
|
|
|
<p data-c-margin="bottom(half)"> |
421
|
|
|
<i |
422
|
|
|
data-c-colour="c2" |
423
|
|
|
className="fas fa-map-marker-alt" |
424
|
|
|
title="Location Icon." |
425
|
|
|
> |
426
|
|
|
|
427
|
|
|
</i> |
428
|
|
|
{localizeField(locale, job, "city")},{" "} |
429
|
|
|
{job.province_id !== null |
430
|
|
|
? intl.formatMessage(provinceName(job.province_id)) |
431
|
|
|
: intl.formatMessage(messages.nullProvince)} |
432
|
|
|
</p> |
433
|
|
|
<p> |
434
|
|
|
<i |
435
|
|
|
data-c-colour="c2" |
436
|
|
|
className="fas fa-home" |
437
|
|
|
title="Remote Work Icon." |
438
|
|
|
> |
439
|
|
|
|
440
|
|
|
</i> |
441
|
|
|
{job.remote_work_allowed ? ( |
442
|
|
|
<FormattedMessage |
443
|
|
|
id="jobBuilder.review.remoteAllowed" |
444
|
|
|
defaultMessage="Remote Work Allowed" |
445
|
|
|
description="Text displayed when remote work is allowed." |
446
|
|
|
/> |
447
|
|
|
) : ( |
448
|
|
|
<FormattedMessage |
449
|
|
|
id="jobBuilder.review.remoteNotAllowed" |
450
|
|
|
defaultMessage="Remote Work Not Allowed" |
451
|
|
|
description="Text displayed when remote work is not allowed." |
452
|
|
|
/> |
453
|
|
|
)} |
454
|
|
|
</p> |
455
|
|
|
</JobReviewSection> |
456
|
|
|
<JobReviewSection |
457
|
|
|
title={intl.formatMessage(messages.basicHeading)} |
458
|
|
|
linkLabel={intl.formatMessage(messages.infoEditLink)} |
459
|
|
|
link={jobBuilderDetails(locale, job.id)} |
460
|
|
|
hideLink={hideBuilderLinks} |
461
|
|
|
> |
462
|
|
|
<div data-c-grid="gutter"> |
463
|
|
|
<div data-c-grid-item="tp(1of2)"> |
464
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(quarter)"> |
465
|
|
|
<FormattedMessage |
466
|
|
|
id="jobBuilder.review.averageAnnualSalary" |
467
|
|
|
defaultMessage="Annual Salary Range" |
468
|
|
|
description="Label for salary information." |
469
|
|
|
/> |
470
|
|
|
</p> |
471
|
|
|
<p> |
472
|
|
|
<FormattedMessage |
473
|
|
|
id="jobBuilder.review.tCAdds" |
474
|
|
|
defaultMessage="Talent Cloud will add this." |
475
|
|
|
description="Information will be added placeholder." |
476
|
|
|
/> |
477
|
|
|
</p> |
478
|
|
|
</div> |
479
|
|
|
<div data-c-grid-item="tp(1of2)"> |
480
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(quarter)"> |
481
|
|
|
<FormattedMessage |
482
|
|
|
id="jobBuilder.review.languageProfile" |
483
|
|
|
defaultMessage="Language Profile" |
484
|
|
|
description="Information will be added placeholder." |
485
|
|
|
/> |
486
|
|
|
</p> |
487
|
|
|
<p> |
488
|
|
|
{job.language_requirement_id |
489
|
|
|
? intl.formatMessage( |
490
|
|
|
languageRequirement(job.language_requirement_id), |
491
|
|
|
) |
492
|
|
|
: ""} |
493
|
|
|
</p> |
494
|
|
|
</div> |
495
|
|
|
<div data-c-grid-item="tp(1of2)"> |
496
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(quarter)"> |
497
|
|
|
<FormattedMessage |
498
|
|
|
id="jobBuilder.review.duration" |
499
|
|
|
defaultMessage="Duration" |
500
|
|
|
description="Label for duration of Term" |
501
|
|
|
/> |
502
|
|
|
</p> |
503
|
|
|
<p> |
504
|
|
|
<FormattedMessage |
505
|
|
|
id="jobBuilder.review.months" |
506
|
|
|
defaultMessage="{termMonths, plural, =0 {No Months} one {# Month} other {# Months}}" |
507
|
|
|
description="Length of term in months" |
508
|
|
|
values={{ termMonths: job.term_qty }} |
509
|
|
|
/> |
510
|
|
|
</p> |
511
|
|
|
</div> |
512
|
|
|
<div data-c-grid-item="tp(1of2)"> |
513
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(quarter)"> |
514
|
|
|
<FormattedMessage |
515
|
|
|
id="jobBuilder.review.securityClearance" |
516
|
|
|
defaultMessage="Security Clearance" |
517
|
|
|
description="Label for Security Clearance info" |
518
|
|
|
/> |
519
|
|
|
</p> |
520
|
|
|
<p> |
521
|
|
|
{job.security_clearance_id |
522
|
|
|
? intl.formatMessage( |
523
|
|
|
securityClearance(job.security_clearance_id), |
524
|
|
|
) |
525
|
|
|
: ""} |
526
|
|
|
</p> |
527
|
|
|
</div> |
528
|
|
|
<div data-c-grid-item="tp(1of2)"> |
529
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(quarter)"> |
530
|
|
|
<FormattedMessage |
531
|
|
|
id="jobBuilder.review.targetStartDate" |
532
|
|
|
defaultMessage="Target Start Date" |
533
|
|
|
description="Label for start date info" |
534
|
|
|
/> |
535
|
|
|
</p> |
536
|
|
|
<p> |
537
|
|
|
<FormattedMessage |
538
|
|
|
id="jobBuilder.review.comesLater" |
539
|
|
|
defaultMessage="This comes later." |
540
|
|
|
description="Placeholder for information that comes later" |
541
|
|
|
/> |
542
|
|
|
</p> |
543
|
|
|
</div> |
544
|
|
|
<div data-c-grid-item="tp(1of2)"> |
545
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(quarter)"> |
546
|
|
|
<FormattedMessage |
547
|
|
|
id="jobBuilder.review.GovernmentClass" |
548
|
|
|
defaultMessage="Government Classification" |
549
|
|
|
description="Placeholder for information that comes later" |
550
|
|
|
/> |
551
|
|
|
</p> |
552
|
|
|
<p>{classificationString(classificationKey, job)}</p> |
553
|
|
|
</div> |
554
|
|
|
</div> |
555
|
|
|
</JobReviewSection> |
556
|
|
|
<JobReviewSection |
557
|
|
|
title={intl.formatMessage(messages.impactHeading)} |
558
|
|
|
linkLabel={intl.formatMessage(messages.impactEditLink)} |
559
|
|
|
link={jobBuilderImpact(locale, job.id)} |
560
|
|
|
hideLink={hideBuilderLinks} |
561
|
|
|
> |
562
|
|
|
<p data-c-margin="bottom(normal)"> |
563
|
|
|
{localizeField(locale, job, "dept_impact")} |
564
|
|
|
</p> |
565
|
|
|
<p data-c-margin="bottom(normal)"> |
566
|
|
|
{localizeField(locale, job, "team_impact")} |
567
|
|
|
</p> |
568
|
|
|
<p>{localizeField(locale, job, "hire_impact")}</p> |
569
|
|
|
</JobReviewSection> |
570
|
|
|
<JobReviewSection |
571
|
|
|
title={intl.formatMessage(messages.tasksHeading)} |
572
|
|
|
linkLabel={intl.formatMessage(messages.tasksEditLink)} |
573
|
|
|
link={jobBuilderTasks(locale, job.id)} |
574
|
|
|
hideLink={hideBuilderLinks} |
575
|
|
|
> |
576
|
|
|
<ul> |
577
|
|
|
{tasks.map( |
578
|
|
|
(task: JobPosterKeyTask): React.ReactElement => ( |
579
|
|
|
<li key={task.id}> |
580
|
|
|
{localizeField(locale, task, "description")} |
581
|
|
|
</li> |
582
|
|
|
), |
583
|
|
|
)} |
584
|
|
|
</ul> |
585
|
|
|
</JobReviewSection> |
586
|
|
|
{sectionTitle(intl.formatMessage(messages.criteriaSection))} |
587
|
|
|
<JobReviewSection |
588
|
|
|
title={intl.formatMessage(messages.educationalHeading)} |
589
|
|
|
isSubsection |
590
|
|
|
linkLabel={intl.formatMessage(messages.infoEditLink)} |
591
|
|
|
link={jobBuilderDetails(locale, job.id)} |
592
|
|
|
hideLink={hideBuilderLinks} |
593
|
|
|
> |
594
|
|
|
{textToParagraphs(localizeField(locale, job, "education") || "")} |
595
|
|
|
</JobReviewSection> |
596
|
|
|
<JobReviewSection |
597
|
|
|
title={intl.formatMessage(messages.skillsHeading)} |
598
|
|
|
isSubsection |
599
|
|
|
linkLabel={intl.formatMessage(messages.skillsEditLink)} |
600
|
|
|
link={jobBuilderSkills(locale, job.id)} |
601
|
|
|
hideLink={hideBuilderLinks} |
602
|
|
|
> |
603
|
|
|
{essentialCriteria.length === 0 ? ( |
604
|
|
|
<p> |
605
|
|
|
<FormattedMessage |
606
|
|
|
id="jobBuilder.review.skills.nullState" |
607
|
|
|
defaultMessage="You haven't added any Nice to Have skills to this poster." |
608
|
|
|
description="The text displayed for skills when you haven't added any skills." |
609
|
|
|
/> |
610
|
|
|
</p> |
611
|
|
|
) : ( |
612
|
|
|
essentialCriteria.map((criterion): React.ReactElement | null => { |
613
|
|
|
const skill = getSkillOfCriteria(criterion); |
614
|
|
|
if (skill === null) { |
615
|
|
|
return null; |
616
|
|
|
} |
617
|
|
|
return ( |
618
|
|
|
<Criterion |
619
|
|
|
criterion={criterion} |
620
|
|
|
skill={skill} |
621
|
|
|
key={criterion.id} |
622
|
|
|
/> |
623
|
|
|
); |
624
|
|
|
}) |
625
|
|
|
)} |
626
|
|
|
</JobReviewSection> |
627
|
|
|
<JobReviewSection |
628
|
|
|
title={intl.formatMessage(messages.assetHeading)} |
629
|
|
|
isSubsection |
630
|
|
|
linkLabel={intl.formatMessage(messages.skillsEditLink)} |
631
|
|
|
link={jobBuilderSkills(locale, job.id)} |
632
|
|
|
hideLink={hideBuilderLinks} |
633
|
|
|
> |
634
|
|
|
{assetCriteria.length === 0 ? ( |
635
|
|
|
<p> |
636
|
|
|
<FormattedMessage |
637
|
|
|
id="jobBuilder.review.skills.nullState" |
638
|
|
|
defaultMessage="You haven't added any Nice to Have skills to this poster." |
639
|
|
|
description="The text displayed for skills when you haven't added any skills." |
640
|
|
|
/> |
641
|
|
|
</p> |
642
|
|
|
) : ( |
643
|
|
|
assetCriteria.map((criterion): React.ReactElement | null => { |
644
|
|
|
const skill = getSkillOfCriteria(criterion); |
645
|
|
|
if (skill === null) { |
646
|
|
|
return null; |
647
|
|
|
} |
648
|
|
|
return ( |
649
|
|
|
<Criterion |
650
|
|
|
criterion={criterion} |
651
|
|
|
skill={skill} |
652
|
|
|
key={criterion.id} |
653
|
|
|
/> |
654
|
|
|
); |
655
|
|
|
}) |
656
|
|
|
)} |
657
|
|
|
</JobReviewSection> |
658
|
|
|
<JobReviewSection |
659
|
|
|
title={intl.formatMessage(messages.languageHeading)} |
660
|
|
|
linkLabel={intl.formatMessage(messages.infoEditLink)} |
661
|
|
|
link={jobBuilderDetails(locale, job.id)} |
662
|
|
|
hideLink={hideBuilderLinks} |
663
|
|
|
> |
664
|
|
|
{/** TODO: get lang data from job */} |
665
|
|
|
{job.language_requirement_id && ( |
666
|
|
|
<> |
667
|
|
|
<p |
668
|
|
|
className="job-builder-review-language" |
669
|
|
|
data-c-margin="bottom(normal)" |
670
|
|
|
> |
671
|
|
|
{languageRequirementIcons(job.language_requirement_id)} |
672
|
|
|
{intl.formatMessage( |
673
|
|
|
languageRequirement(job.language_requirement_id), |
674
|
|
|
)} |
675
|
|
|
</p> |
676
|
|
|
<p data-c-margin="bottom(normal)"> |
677
|
|
|
{intl.formatMessage( |
678
|
|
|
languageRequirementDescription(job.language_requirement_id), |
679
|
|
|
)} |
680
|
|
|
</p> |
681
|
|
|
<p data-c-margin="top(normal)"> |
682
|
|
|
{intl.formatMessage( |
683
|
|
|
languageRequirementContext(job.language_requirement_id), |
684
|
|
|
)} |
685
|
|
|
</p> |
686
|
|
|
</> |
687
|
|
|
)} |
688
|
|
|
</JobReviewSection> |
689
|
|
|
{sectionTitle(intl.formatMessage(messages.cultureSection))} |
690
|
|
|
<JobReviewSection |
691
|
|
|
title={intl.formatMessage(messages.managerHeading)} |
692
|
|
|
isSubsection |
693
|
|
|
linkLabel={intl.formatMessage(messages.managerProfileLink)} |
694
|
|
|
link={managerEditProfile(locale)} |
695
|
|
|
hideLink={hideBuilderLinks} |
696
|
|
|
> |
697
|
|
|
{renderManagerSection(manager, userDeptName, locale)} |
698
|
|
|
</JobReviewSection> |
699
|
|
|
<JobReviewSection |
700
|
|
|
title={intl.formatMessage(messages.workCultureHeading)} |
701
|
|
|
isSubsection |
702
|
|
|
linkLabel={intl.formatMessage(messages.workEnvEditLink)} |
703
|
|
|
link={jobBuilderEnv(locale, job.id)} |
704
|
|
|
hideLink={hideBuilderLinks} |
705
|
|
|
> |
706
|
|
|
{localizeField(locale, job, "culture_summary") && ( |
707
|
|
|
<p>{localizeField(locale, job, "culture_summary")}</p> |
708
|
|
|
)} |
709
|
|
|
{localizeField(locale, job, "culture_special") && ( |
710
|
|
|
<p>{localizeField(locale, job, "culture_special")}</p> |
711
|
|
|
)} |
712
|
|
|
</JobReviewSection> |
713
|
|
|
<JobReviewSection |
714
|
|
|
title={intl.formatMessage(messages.workEnvHeading)} |
715
|
|
|
isSubsection |
716
|
|
|
linkLabel={intl.formatMessage(messages.workEnvEditLink)} |
717
|
|
|
link={jobBuilderEnv(locale, job.id)} |
718
|
|
|
description={intl.formatMessage(messages.workEnvDescription)} |
719
|
|
|
hideLink={hideBuilderLinks} |
720
|
|
|
> |
721
|
|
|
<JobWorkEnv |
722
|
|
|
teamSize={job.team_size || 0} |
723
|
|
|
selectedEnvOptions={selectedEnvOptions} |
724
|
|
|
envDescription={ |
725
|
|
|
localizeField(locale, job, "work_env_description") || "" |
726
|
|
|
} |
727
|
|
|
/> |
728
|
|
|
</JobReviewSection> |
729
|
|
|
<JobReviewSection |
730
|
|
|
title={intl.formatMessage(messages.otherInfoHeading)} |
731
|
|
|
isSubsection |
732
|
|
|
linkLabel={intl.formatMessage(messages.infoEditLink)} |
733
|
|
|
link={jobBuilderDetails(locale, job.id)} |
734
|
|
|
hideLink={hideBuilderLinks} |
735
|
|
|
> |
736
|
|
|
<JobWorkCulture job={job} /> |
737
|
|
|
</JobReviewSection> |
738
|
|
|
</> |
739
|
|
|
); |
740
|
|
|
}; |
741
|
|
|
|
742
|
|
|
interface JobReviewProps { |
743
|
|
|
job: Job; |
744
|
|
|
classificationKey: string; |
745
|
|
|
manager: Manager | null; |
746
|
|
|
tasks: JobPosterKeyTask[]; |
747
|
|
|
criteria: Criteria[]; |
748
|
|
|
// List of all possible skills. |
749
|
|
|
skills: Skill[]; |
750
|
|
|
// List of all possible departments. |
751
|
|
|
departments: Department[]; |
752
|
|
|
user: User | null; |
753
|
|
|
validForSubmission?: boolean; |
754
|
|
|
handleSubmit: (job: Job) => Promise<void>; |
755
|
|
|
handleContinue: () => void; |
756
|
|
|
handleReturn: () => void; |
757
|
|
|
} |
758
|
|
|
|
759
|
|
|
export const JobReview: React.FunctionComponent< |
760
|
|
|
JobReviewProps & WrappedComponentProps |
761
|
|
|
> = ({ |
762
|
|
|
job, |
763
|
|
|
classificationKey, |
764
|
|
|
manager, |
765
|
|
|
tasks, |
766
|
|
|
criteria, |
767
|
|
|
skills, |
768
|
|
|
departments, |
769
|
|
|
user, |
770
|
|
|
validForSubmission = false, |
771
|
|
|
handleSubmit, |
772
|
|
|
handleReturn, |
773
|
|
|
intl, |
774
|
|
|
}): React.ReactElement => { |
775
|
|
|
const { locale } = intl; |
776
|
|
|
if (locale !== "en" && locale !== "fr") { |
777
|
|
|
throw new Error("Unknown intl.locale"); |
778
|
|
|
} |
779
|
|
|
const [isModalVisible, setIsModalVisible] = useState(false); |
780
|
|
|
const [isSurveyModalVisible, setIsSurveyModalVisible] = useState(false); |
781
|
|
|
const modalId = "job-review-modal"; |
782
|
|
|
const modalParentRef = useRef<HTMLDivElement>(null); |
783
|
|
|
|
784
|
|
|
const filterComments = useCallback( |
785
|
|
|
(comment: Comment): boolean => hasKey(jobReviewLocations, comment.location), |
786
|
|
|
[], |
787
|
|
|
); |
788
|
|
|
|
789
|
|
|
return ( |
790
|
|
|
<> |
791
|
|
|
<div |
792
|
|
|
data-c-container="form" |
793
|
|
|
data-c-padding="top(triple) bottom(triple)" |
794
|
|
|
ref={modalParentRef} |
795
|
|
|
> |
796
|
|
|
<h3 |
797
|
|
|
data-c-font-size="h3" |
798
|
|
|
data-c-font-weight="bold" |
799
|
|
|
data-c-margin="bottom(normal)" |
800
|
|
|
> |
801
|
|
|
<FormattedMessage |
802
|
|
|
id="jobBuilder.review.reviewYourPoster" |
803
|
|
|
defaultMessage="Review Your Job Poster for:" |
804
|
|
|
description="Title for Review Job Poster section." |
805
|
|
|
/>{" "} |
806
|
|
|
<span data-c-colour="c2">{localizeField(locale, job, "title")}</span> |
807
|
|
|
</h3> |
808
|
|
|
<p data-c-margin="bottom(double)"> |
809
|
|
|
<FormattedMessage |
810
|
|
|
id="jobBuilder.review.headsUp" |
811
|
|
|
defaultMessage="Just a heads up! We've rearranged some of your information to help you |
812
|
|
|
understand how an applicant will see it once published." |
813
|
|
|
description="Description under primary title of review section" |
814
|
|
|
/> |
815
|
|
|
</p> |
816
|
|
|
<ActivityFeed |
817
|
|
|
jobId={job.id} |
818
|
|
|
isHrAdvisor={false} |
819
|
|
|
generalLocation={LocationId.jobGeneric} |
820
|
|
|
locationMessages={jobReviewLocations} |
821
|
|
|
filterComments={filterComments} |
822
|
|
|
/> |
823
|
|
|
<JobReviewDisplay |
824
|
|
|
job={job} |
825
|
|
|
classificationKey={classificationKey} |
826
|
|
|
manager={manager} |
827
|
|
|
tasks={tasks} |
828
|
|
|
criteria={criteria} |
829
|
|
|
skills={skills} |
830
|
|
|
departments={departments} |
831
|
|
|
user={user} |
832
|
|
|
hideBuilderLinks={false} |
833
|
|
|
/> |
834
|
|
|
<div data-c-grid="gutter"> |
835
|
|
|
<div data-c-grid-item="base(1of1)"> |
836
|
|
|
<hr data-c-margin="top(normal) bottom(normal)" /> |
837
|
|
|
</div> |
838
|
|
|
<div |
839
|
|
|
data-c-alignment="base(centre) tp(left)" |
840
|
|
|
data-c-grid-item="tp(1of2)" |
841
|
|
|
> |
842
|
|
|
<button |
843
|
|
|
data-c-button="outline(c2)" |
844
|
|
|
data-c-radius="rounded" |
845
|
|
|
type="button" |
846
|
|
|
onClick={(): void => handleReturn()} |
847
|
|
|
> |
848
|
|
|
<FormattedMessage |
849
|
|
|
id="jobBuilder.review.button.return" |
850
|
|
|
defaultMessage="Save & Return to Skills" |
851
|
|
|
description="Label of 'Previous Step' button." |
852
|
|
|
/> |
853
|
|
|
</button> |
854
|
|
|
</div> |
855
|
|
|
<div |
856
|
|
|
data-c-alignment="base(centre) tp(right)" |
857
|
|
|
data-c-grid-item="tp(1of2)" |
858
|
|
|
> |
859
|
|
|
<a |
860
|
|
|
href={managerJobSummary(locale, job.id)} |
861
|
|
|
title="" |
862
|
|
|
data-c-button="solid(c1)" |
863
|
|
|
data-c-dialog-action="close" |
864
|
|
|
data-c-radius="rounded" |
865
|
|
|
style={{ textDecoration: "none" }} |
866
|
|
|
> |
867
|
|
|
<FormattedMessage |
868
|
|
|
id="jobBuilder.review.button.submit" |
869
|
|
|
defaultMessage="Save and Return to Summary" |
870
|
|
|
description="Label of Job Review Submission Button" |
871
|
|
|
/> |
872
|
|
|
</a> |
873
|
|
|
</div> |
874
|
|
|
</div> |
875
|
|
|
</div> |
876
|
|
|
{/* Congrats modal below is temporarily being removed from the review page, until it can be repurposed on the summary page. */} |
877
|
|
|
<div |
878
|
|
|
data-c-dialog-overlay={ |
879
|
|
|
isModalVisible || isSurveyModalVisible ? "active" : "" |
880
|
|
|
} |
881
|
|
|
/> |
882
|
|
|
{manager === null || manager.is_demo_manager ? ( |
883
|
|
|
<DemoSubmitJobModal |
884
|
|
|
isVisible={isModalVisible} |
885
|
|
|
handleCancel={(): void => setIsModalVisible(false)} |
886
|
|
|
parentElement={modalParentRef.current} |
887
|
|
|
/> |
888
|
|
|
) : ( |
889
|
|
|
<Modal |
890
|
|
|
id={modalId} |
891
|
|
|
visible={isModalVisible} |
892
|
|
|
onModalCancel={(): void => setIsModalVisible(false)} |
893
|
|
|
onModalConfirm={async (): Promise<void> => { |
894
|
|
|
try { |
895
|
|
|
await handleSubmit(job); |
896
|
|
|
setIsModalVisible(false); |
897
|
|
|
setIsSurveyModalVisible(true); |
898
|
|
|
} catch { |
899
|
|
|
setIsModalVisible(false); |
900
|
|
|
} |
901
|
|
|
}} |
902
|
|
|
parentElement={modalParentRef.current} |
903
|
|
|
> |
904
|
|
|
<Modal.Header> |
905
|
|
|
<div |
906
|
|
|
data-c-background="c1(100)" |
907
|
|
|
data-c-border="bottom(thin, solid, black)" |
908
|
|
|
data-c-padding="normal" |
909
|
|
|
> |
910
|
|
|
<h5 |
911
|
|
|
data-c-colour="white" |
912
|
|
|
data-c-font-size="h4" |
913
|
|
|
id={`${modalId}-title`} |
914
|
|
|
> |
915
|
|
|
<FormattedMessage |
916
|
|
|
id="jobBuilder.review.confirm.title" |
917
|
|
|
defaultMessage="Congrats! Are You Ready to Submit?" |
918
|
|
|
description="Title of Submit Confirmation modal." |
919
|
|
|
/> |
920
|
|
|
</h5> |
921
|
|
|
</div> |
922
|
|
|
</Modal.Header> |
923
|
|
|
<Modal.Body> |
924
|
|
|
<div data-c-padding="normal"> |
925
|
|
|
<p data-c-margin="bottom(normal)"> |
926
|
|
|
<FormattedMessage |
927
|
|
|
id="jobBuilder.review.readyToSubmit" |
928
|
|
|
defaultMessage="If you're ready to submit your poster, click the Submit button below." |
929
|
|
|
description="Instructions on how to submit" |
930
|
|
|
/> |
931
|
|
|
</p> |
932
|
|
|
<p data-c-font-weight="bold" data-c-margin="bottom(normal)"> |
933
|
|
|
<FormattedMessage |
934
|
|
|
id="jobBuilder.review.whatHappens" |
935
|
|
|
defaultMessage="What happens next?" |
936
|
|
|
description="Rhetorical question" |
937
|
|
|
/> |
938
|
|
|
</p> |
939
|
|
|
<p data-c-margin="bottom(normal)"> |
940
|
|
|
<FormattedMessage |
941
|
|
|
id="jobBuilder.review.sendYourDraft" |
942
|
|
|
defaultMessage="Talent Cloud will send your draft to your department's HR advisor who will notify you with comments." |
943
|
|
|
description="Information about next steps" |
944
|
|
|
/> |
945
|
|
|
</p> |
946
|
|
|
<p> |
947
|
|
|
<FormattedMessage |
948
|
|
|
id="jobBuilder.review.meantime" |
949
|
|
|
defaultMessage="In the meantime, feel free to go ahead and create a screening plan for your selection process. Alternatively, you can wait for comments to come back from HR before you take the next step." |
950
|
|
|
description="Suggestion on what to do while you wait." |
951
|
|
|
/> |
952
|
|
|
</p> |
953
|
|
|
</div> |
954
|
|
|
</Modal.Body> |
955
|
|
|
<Modal.Footer> |
956
|
|
|
<Modal.FooterCancelBtn> |
957
|
|
|
<FormattedMessage |
958
|
|
|
id="jobBuilder.review.confirm.cancel" |
959
|
|
|
defaultMessage="Cancel" |
960
|
|
|
description="Cancel button of Job Review confirmation modal." |
961
|
|
|
/> |
962
|
|
|
</Modal.FooterCancelBtn> |
963
|
|
|
<Modal.FooterConfirmBtn> |
964
|
|
|
<FormattedMessage |
965
|
|
|
id="jobBuilder.review.confirm.submit" |
966
|
|
|
defaultMessage="Yes, Submit" |
967
|
|
|
description="Submit button of Job Review confirmation modal." |
968
|
|
|
/> |
969
|
|
|
</Modal.FooterConfirmBtn> |
970
|
|
|
</Modal.Footer> |
971
|
|
|
</Modal> |
972
|
|
|
)} |
973
|
|
|
<ManagerSurveyModal |
974
|
|
|
isVisible={isSurveyModalVisible} |
975
|
|
|
parentElement={modalParentRef.current} |
976
|
|
|
/> |
977
|
|
|
</> |
978
|
|
|
); |
979
|
|
|
}; |
980
|
|
|
|
981
|
|
|
export default injectIntl(JobReview); |
982
|
|
|
|