Passed
Push — dev ( 0b1244...c6594a )
by
unknown
06:00
created

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

Complexity

Total Complexity 10
Complexity/F 0

Size

Lines of Code 491
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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