Passed
Push — task/routes-in-lang-files ( ae4724...fe6ccb )
by Tristan
03:53
created

resources/assets/js/components/JobBuilder/Impact/JobImpact.tsx   A

Complexity

Total Complexity 10
Complexity/F 0

Size

Lines of Code 492
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 10
eloc 394
mnd 10
bc 10
fnc 0
dl 0
loc 492
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
/* eslint-disable @typescript-eslint/camelcase */
2
import React, { useState, useRef } from "react";
3
import { FormattedMessage, defineMessages, useIntl } from "react-intl";
4
import * as Yup from "yup";
5
import { Formik, Form, FastField } from "formik";
6
import nprogress from "nprogress";
7
import { Job, Department } from "../../../models/types";
8
import { emptyJob } from "../../../models/jobUtil";
9
import Modal from "../../Modal";
10
import { validationMessages } from "../../Form/Messages";
11
import TextAreaInput from "../../Form/TextAreaInput";
12
import { find } from "../../../helpers/queries";
13
import { getLocale } from "../../../helpers/localize";
14
15
interface JobImpactProps {
16
  /** Optional Job to prepopulate form values from. */
17
  job: Job | null;
18
  /** The list of known departments. Used to determine the Department statement. */
19
  departments: Department[];
20
  /** Function to run after successful form validation.
21
   *  It must return true if the submission was successful, false otherwise.
22
   */
23
  handleSubmit: (values: Job) => Promise<boolean>;
24
  // The function to run when user clicks Prev Page
25
  handleReturn: () => void;
26
  /** Function to run when modal cancel is clicked. */
27
  handleModalCancel: () => void;
28
  /** Function to run when modal confirm is clicked. */
29
  handleModalConfirm: () => void;
30
31
  jobIsComplete: boolean;
32
  handleSkipToReview: () => Promise<void>;
33
}
34
35
interface ImpactFormValues {
36
  teamImpact: string;
37
  hireImpact: string;
38
}
39
40
const messages = defineMessages({
41
  hireLabel: {
42
    id: "jobBuilder.impact.hireLabel",
43
    defaultMessage: "Hire Impact Statement",
44
    description: "Label for hire impact statement text area",
45
  },
46
  teamLabel: {
47
    id: "jobBuilder.impact.teamLabel",
48
    defaultMessage: "Team Impact Statement",
49
    description: "Label for team impact statement text area",
50
  },
51
  hirePlaceholder: {
52
    id: "jobBuilder.impact.hirePlaceholder",
53
    defaultMessage: "Remember, don't use Government speak...",
54
    description: "",
55
  },
56
  teamPlaceholder: {
57
    id: "jobBuilder.impact.teamPlaceholder",
58
    defaultMessage: "Try for a casual, frank, friendly tone...",
59
    description: "",
60
  },
61
});
62
63
const updateJobWithValues = (
64
  initialJob: Job,
65
  locale: "en" | "fr",
66
  { teamImpact, hireImpact }: ImpactFormValues,
67
  deptImpacts: { en: string; fr: string },
68
): Job => ({
69
  ...initialJob,
70
  // Adding this until the impact statement for the office privacy canada gets added
71
  dept_impact:
72
    deptImpacts.en && deptImpacts.fr ? deptImpacts : { en: "N/A", fr: "S/O" },
73
  team_impact: {
74
    ...initialJob.team_impact,
75
    [locale]: teamImpact,
76
  },
77
  hire_impact: {
78
    ...initialJob.hire_impact,
79
    [locale]: hireImpact,
80
  },
81
});
82
83
const determineDeptImpact = (
84
  departments: Department[],
85
  job: Job | null,
86
): { en: string; fr: string } => {
87
  if (job === null || job.department_id === null) {
88
    return { en: "", fr: "" };
89
  }
90
  const dept = find(departments, job.department_id);
91
  if (dept === null) {
92
    return { en: "", fr: "" };
93
  }
94
  return {
95
    en: dept.impact.en,
96
    fr: dept.impact.fr,
97
  };
98
};
99
100
const deptImpactStatement = (
101
  departments: Department[],
102
  job: Job | null,
103
  deptImpacts: { en: string; fr: string },
104
  locale: "en" | "fr",
105
): React.ReactElement => {
106
  if (job === null || job.department_id === null) {
107
    return (
108
      <p data-c-margin="bottom(double)">
109
        <FormattedMessage
110
          id="jobBuilder.impact.selectDepartment"
111
          defaultMessage="You must select a Department for this Job."
112
          description="Message warning user that they must have a department selected to complete impact statements."
113
        />
114
      </p>
115
    );
116
  }
117
  if (departments.length === 0) {
118
    return (
119
      <p data-c-margin="bottom(double)">
120
        <i
121
          aria-hidden="true"
122
          className="fa fa-spinner fa-spin"
123
          data-c-margin="right"
124
        />
125
        <FormattedMessage
126
          id="jobBuilder.impact.departmentsLoading"
127
          defaultMessage="Loading department data..."
128
          description="Placeholder message while department data is being retrieved from the server."
129
        />
130
      </p>
131
    );
132
  }
133
134
  return (
135
    <p id="deptImpactStatement" data-c-margin="bottom(double)">
136
      {deptImpacts[locale] || (
137
        <FormattedMessage
138
          id="jobBuilder.impact.unknownDepartment"
139
          defaultMessage="Error: Unknown Department selected."
140
          description="Error message shown when the job has a department selected for which data has not been passed to this component."
141
        />
142
      )}
143
    </p>
144
  );
145
};
146
147
const JobImpact: React.FunctionComponent<JobImpactProps> = ({
148
  departments,
149
  job,
150
  handleSubmit,
151
  handleReturn,
152
  handleModalCancel,
153
  handleModalConfirm,
154
  jobIsComplete,
155
  handleSkipToReview,
156
}): React.ReactElement => {
157
  const intl = useIntl();
158
  const locale = getLocale(intl.locale);
159
  const modalId = "impact-dialog";
160
  const [isModalVisible, setIsModalVisible] = useState(false);
161
  const modalParentRef = useRef<HTMLDivElement>(null);
162
163
  const initialTeamImpact = job ? job.team_impact[locale] : null;
164
  const initialHireImpact = job ? job.hire_impact[locale] : null;
165
  const initialValues: ImpactFormValues = {
166
    teamImpact: initialTeamImpact || "",
167
    hireImpact: initialHireImpact || "",
168
  };
169
170
  const validationSchema = Yup.object().shape({
171
    teamImpact: Yup.string().required(
172
      intl.formatMessage(validationMessages.required),
173
    ),
174
    hireImpact: Yup.string().required(
175
      intl.formatMessage(validationMessages.required),
176
    ),
177
  });
178
179
  const deptImpacts: { en: string; fr: string } = determineDeptImpact(
180
    departments,
181
    job,
182
  );
183
184
  const updateValuesAndReturn = (values: ImpactFormValues): void => {
185
    // The following only triggers after validations pass
186
    nprogress.start();
187
    handleSubmit(
188
      updateJobWithValues(job || emptyJob(), locale, values, deptImpacts),
189
    ).then((isSuccessful: boolean): void => {
190
      if (isSuccessful) {
191
        nprogress.done();
192
        handleReturn();
193
      }
194
    });
195
  };
196
197
  return (
198
    <section ref={modalParentRef}>
199
      <div data-c-container="form" data-c-padding="top(triple) bottom(triple)">
200
        <h3
201
          data-c-font-size="h3"
202
          data-c-font-weight="bold"
203
          data-c-margin="bottom(double)"
204
        >
205
          <FormattedMessage
206
            id="jobBuilder.impact.title"
207
            defaultMessage="Create an Impact Statement"
208
            description="Header of Job Poster Builder Impact Step"
209
          />
210
        </h3>
211
        <ul data-c-margin="bottom(double)">
212
          <li>
213
            <FormattedMessage
214
              id="jobBuilder.impact.points.opportunity"
215
              defaultMessage="Working in the federal government offers an important opportunity to have a broad impact for Canadians."
216
              description="Bullet Point on Job Poster Builder Impact Step"
217
            />
218
          </li>
219
          <li>
220
            <FormattedMessage
221
              id="jobBuilder.impact.points.highlight"
222
              defaultMessage="This is your chance to highlight what makes your work valuable and interesting."
223
              description="Bullet Point on Job Poster Builder Impact Step"
224
            />
225
          </li>
226
          <li>
227
            <FormattedMessage
228
              id="jobBuilder.impact.points.counts"
229
              defaultMessage="Your impact statement is the first thing that applicants will see when they click your job poster so make sure it counts!"
230
              description="Bullet Point on Job Poster Builder Impact Step"
231
            />
232
          </li>
233
        </ul>
234
        <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
235
          <FormattedMessage
236
            id="jobBuilder.impact.header.department"
237
            defaultMessage="How our department makes an impact:"
238
            description="Header of Department Impact Section on Job Poster Builder Impact Step"
239
          />
240
        </p>
241
        {deptImpactStatement &&
242
          deptImpactStatement(departments, job, deptImpacts, locale)}
243
        <Formik
244
          enableReinitialize
245
          initialValues={initialValues}
246
          validationSchema={validationSchema}
247
          onSubmit={(values, actions): void => {
248
            nprogress.start();
249
            // The following only triggers after validations pass
250
            handleSubmit(
251
              updateJobWithValues(
252
                job || emptyJob(),
253
                locale,
254
                values,
255
                deptImpacts,
256
              ),
257
            )
258
              .then((isSuccessful: boolean): void => {
259
                if (isSuccessful) {
260
                  nprogress.done();
261
                  setIsModalVisible(true);
262
                }
263
              })
264
              .finally((): void => {
265
                actions.setSubmitting(false); // Required by Formik to finish the submission cycle
266
              });
267
          }}
268
        >
269
          {({ values, isSubmitting }): React.ReactElement => (
270
            <>
271
              <Form id="form" data-c-grid="gutter">
272
                <div data-c-grid-item="base(1of1)" data-c-input="textarea">
273
                  <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
274
                    <FormattedMessage
275
                      id="jobBuilder.impact.teamHeader"
276
                      defaultMessage="How our team makes an impact:"
277
                      description="Header of Job Poster Builder Team Impact Section"
278
                    />
279
                  </p>
280
                  <p data-c-margin="bottom(normal)">
281
                    <FormattedMessage
282
                      id="jobBuilder.impact.teamBody"
283
                      defaultMessage="Describe the value your team/service/initiative brings to Canadians. It doesn’t matter if your work is direct to citizens or back office, innovative or maintenance, top priority or ongoing. Describe how it contributes to making Canada better the way you would to someone who knows nothing about your work."
284
                      description="Body of Job Poster Builder Team Impact Section"
285
                    />
286
                  </p>
287
                  <div>
288
                    <FastField
289
                      name="teamImpact"
290
                      id="TeamImpact"
291
                      placeholder={intl.formatMessage(messages.teamPlaceholder)}
292
                      label={intl.formatMessage(messages.teamLabel)}
293
                      required
294
                      component={TextAreaInput}
295
                    />
296
                  </div>
297
                </div>
298
                <div data-c-grid-item="base(1of1)" data-c-input="textarea">
299
                  <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
300
                    <FormattedMessage
301
                      id="jobBuilder.impact.hireHeader"
302
                      defaultMessage="How the new hire makes an impact:"
303
                      description="Header of Job Poster Builder Hire Impact Section"
304
                    />
305
                  </p>
306
                  <p data-c-margin="bottom(normal)">
307
                    <FormattedMessage
308
                      id="jobBuilder.impact.hireBody"
309
                      defaultMessage="Describe how the new hire will contribute in this role. Focus on the value they’ll bring, not on specific tasks (you’ll provide these later on). For example “In this role, you’ll contribute to…” or, “As a member of this team, you’ll be responsible for helping us…”"
310
                      description="Body of Job Poster Builder Hire Impact Section"
311
                    />
312
                  </p>
313
                  <div>
314
                    <FastField
315
                      id="HireImpact"
316
                      name="hireImpact"
317
                      label={intl.formatMessage(messages.hireLabel)}
318
                      placeholder={intl.formatMessage(messages.hirePlaceholder)}
319
                      required
320
                      component={TextAreaInput}
321
                    />
322
                  </div>
323
                </div>
324
                <div data-c-grid="gutter" data-c-grid-item="base(1of1)">
325
                  <div data-c-grid-item="base(1of1)">
326
                    <hr data-c-margin="top(normal) bottom(normal)" />
327
                  </div>
328
                  <div
329
                    data-c-alignment="base(centre) tp(left)"
330
                    data-c-grid-item="tp(1of2)"
331
                  >
332
                    <button
333
                      data-c-button="outline(c2)"
334
                      data-c-radius="rounded"
335
                      type="button"
336
                      disabled={isSubmitting}
337
                      onClick={(): void => {
338
                        updateValuesAndReturn(values);
339
                      }}
340
                    >
341
                      <FormattedMessage
342
                        id="jobBuilder.impact.button.return"
343
                        defaultMessage="Save & Return to Work Environment"
344
                        description="Label for Save & Return button on Impact form."
345
                      />
346
                    </button>
347
                  </div>
348
                  <div
349
                    data-c-alignment="base(centre) tp(right)"
350
                    data-c-grid-item="tp(1of2)"
351
                  >
352
                    <button
353
                      data-c-button="solid(c1)"
354
                      data-c-radius="rounded"
355
                      type="submit"
356
                      disabled={isSubmitting}
357
                    >
358
                      <FormattedMessage
359
                        id="jobBuilder.impact.button.next"
360
                        defaultMessage="Save & Preview"
361
                        description="Label for Save & Preview button on Impact form."
362
                      />
363
                    </button>
364
                  </div>
365
                </div>
366
              </Form>
367
              <Modal
368
                id={modalId}
369
                parentElement={modalParentRef.current}
370
                visible={isModalVisible}
371
                onModalConfirm={(): void => {
372
                  handleModalConfirm();
373
                  setIsModalVisible(false);
374
                }}
375
                onModalCancel={(): void => {
376
                  handleModalCancel();
377
                  setIsModalVisible(false);
378
                }}
379
                onModalMiddle={(): void => {
380
                  handleSkipToReview().finally((): void =>
381
                    setIsModalVisible(false),
382
                  );
383
                }}
384
              >
385
                <Modal.Header>
386
                  <div
387
                    data-c-background="c1(100)"
388
                    data-c-border="bottom(thin, solid, black)"
389
                    data-c-padding="normal"
390
                  >
391
                    <h5
392
                      data-c-colour="white"
393
                      data-c-font-size="h4"
394
                      id={`${modalId}-title`}
395
                    >
396
                      <FormattedMessage
397
                        id="jobBuilder.impact.modalTitle"
398
                        defaultMessage="Awesome work!"
399
                        description="Title of modal dialog for Impact review."
400
                      />
401
                    </h5>
402
                  </div>
403
                </Modal.Header>
404
                <Modal.Body>
405
                  <div
406
                    data-c-border="bottom(thin, solid, black)"
407
                    data-c-padding="normal"
408
                    id={`${modalId}-description`}
409
                  >
410
                    <p>
411
                      <FormattedMessage
412
                        id="jobBuilder.impact.modalDescription"
413
                        defaultMessage="Here's a preview of the Impact Statement you just entered. Feel free to go back and edit things or move to the next step if you're happy with it."
414
                        description="Description of modal dialog for Impact review."
415
                      />
416
                    </p>
417
                  </div>
418
                  <div
419
                    data-c-background="grey(20)"
420
                    data-c-border="bottom(thin, solid, black)"
421
                    data-c-padding="normal"
422
                  >
423
                    <div
424
                      className="manager-job-card"
425
                      data-c-background="white(100)"
426
                      data-c-padding="normal"
427
                      data-c-radius="rounded"
428
                    >
429
                      <h4
430
                        data-c-border="bottom(thin, solid, black)"
431
                        data-c-font-size="h4"
432
                        data-c-font-weight="600"
433
                        data-c-margin="bottom(normal)"
434
                        data-c-padding="bottom(normal)"
435
                      >
436
                        <FormattedMessage
437
                          id="jobBuilder.impactPreview.title"
438
                          defaultMessage="Impact"
439
                          description="Heading for Impact preview on modal dialog."
440
                        />
441
                      </h4>
442
                      <p id="deptImpactPreview" data-c-margin="bottom(normal)">
443
                        {deptImpacts[locale] || (
444
                          <FormattedMessage
445
                            id="jobBuilder.impact.unknownDepartment"
446
                            defaultMessage="Error: Unknown Department selected."
447
                            description="Error message shown when the job has a department selected for which data has not been passed to this component."
448
                          />
449
                        )}
450
                      </p>
451
                      <p data-c-margin="bottom(normal)">{values.teamImpact}</p>
452
                      <p>{values.hireImpact}</p>
453
                    </div>
454
                  </div>
455
                </Modal.Body>
456
                <Modal.Footer>
457
                  <Modal.FooterCancelBtn>
458
                    <FormattedMessage
459
                      id="jobBuilder.impact.button.goBack"
460
                      defaultMessage="Go Back"
461
                      description="Label for Go Back button on Impact review modal."
462
                    />
463
                  </Modal.FooterCancelBtn>
464
                  {jobIsComplete && (
465
                    <Modal.FooterMiddleBtn>
466
                      <FormattedMessage
467
                        id="jobBuilder.impact.button.skipToReview"
468
                        defaultMessage="Skip to Review"
469
                        description="Label for Skip to Review button on Impact review modal."
470
                      />
471
                    </Modal.FooterMiddleBtn>
472
                  )}
473
                  <Modal.FooterConfirmBtn>
474
                    <FormattedMessage
475
                      id="jobBuilder.impact.button.nextStep"
476
                      defaultMessage="Next Step"
477
                      description="Label for Next Step button on Impact review modal."
478
                    />
479
                  </Modal.FooterConfirmBtn>
480
                </Modal.Footer>
481
              </Modal>
482
            </>
483
          )}
484
        </Formik>
485
      </div>
486
      <div data-c-dialog-overlay={isModalVisible ? "active" : ""} />
487
    </section>
488
  );
489
};
490
491
export default JobImpact;
492