Passed
Push — dev ( ac9030...cd4801 )
by Tristan
05:24 queued 10s
created

resources/assets/js/components/FindSkillsModal.tsx   A

Complexity

Total Complexity 11
Complexity/F 0

Size

Lines of Code 503
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 11
eloc 403
mnd 11
bc 11
fnc 0
dl 0
loc 503
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import React, { useState } from "react";
2
import { defineMessages, useIntl } from "react-intl";
3
import { getLocale, localizeFieldNonNull } from "../helpers/localize";
4
import { createOrRemove } from "../helpers/queries";
5
import { Skill, SkillCategory } from "../models/types";
6
import Accordion from "./H2Components/Accordion";
7
import Dialog from "./H2Components/Dialog";
8
9
const messages = defineMessages({
10
  modalButtonLabel: {
11
    id: "findSkillsModal.modalButtonLabel",
12
    defaultMessage: "Add Skills",
13
  },
14
  modalHeading: {
15
    id: "findSkillsModal.modalHeading",
16
    defaultMessage: "Find and add skills",
17
  },
18
  accordianButtonLabel: {
19
    id: "findSkillsModal.accordianButtonLabel",
20
    defaultMessage: "Click to view...",
21
  },
22
  skillsResultsHeading: {
23
    id: "findSkillsModal.skillsResultsHeading",
24
    defaultMessage: "Explore Categories",
25
  },
26
  skillResultsSubHeading: {
27
    id: "findSkillsModal.skillResultsSubHeading",
28
    defaultMessage:
29
      "Click on the categories on the left to explore skills. Only select the skills that you have experience with.",
30
  },
31
  skills: {
32
    id: "findSkillsModal.skills",
33
    defaultMessage: "Skills",
34
  },
35
  noSkills: {
36
    id: "findSkillsModal.noSkills",
37
    defaultMessage: "No skills available.",
38
  },
39
  backButton: {
40
    id: "findSkillsModal.backButton",
41
    defaultMessage: "Back",
42
  },
43
  disabledSkillButton: {
44
    id: "findSkillsModal.disabledSkillButton",
45
    defaultMessage: "Already Added",
46
  },
47
  selectSkillButton: {
48
    id: "findSkillsModal.selectSkillButton",
49
    defaultMessage: "Select",
50
  },
51
  removeSkillButton: {
52
    id: "findSkillsModal.removeSkillButton",
53
    defaultMessage: "Remove",
54
  },
55
  cancelButton: {
56
    id: "findSkillsModal.noSkills",
57
    defaultMessage: "Cancel",
58
  },
59
  saveButton: {
60
    id: "findSkillsModal.noSkills",
61
    defaultMessage: "Save Skills",
62
  },
63
  searchResultsTitle: {
64
    id: "findSkillsModal.searchResultsTitle",
65
    defaultMessage: `There are {numOfSkills} results for skills related to "{searchQuery}".`,
66
  },
67
});
68
69
interface FindSkillsModalProps {
70
  oldSkills: Skill[];
71
  portal: "applicant" | "manager";
72
  skillCategories: SkillCategory[];
73
  handleSubmit: (values: Skill[]) => Promise<void>;
74
}
75
76
const FindSkillsModal: React.FunctionComponent<FindSkillsModalProps> = ({
77
  oldSkills,
78
  portal,
79
  skillCategories,
80
  handleSubmit,
81
}) => {
82
  const intl = useIntl();
83
  const locale = getLocale(intl.locale);
84
  const parentSkillCategories: SkillCategory[] = skillCategories.filter(
85
    (skillCategory) =>
86
      skillCategory.depth === 1 && skillCategory.parent_id === 0,
87
  );
88
89
  // List of new Skills that will be saved to the user on submit.
90
  const [newSkills, setNewSkills] = useState<Skill[]>([]);
91
  // List of skills that displayed in the results section of the modal.
92
  const [skillsResults, setSkillsResults] = useState<Skill[]>([]);
93
  // Stores the skill category's name and description for the results section.
94
  const [resultsSectionText, setResultsSectionText] = useState<{
95
    title: string;
96
    description: string;
97
  }>({ title: "", description: "" });
98
  const [firstVisit, setFirstVisit] = useState(true);
99
  // Stores a list of skills category keys of which accordions are expanded, for styling purposes.
100
  const [expandedAccordions, setExpandedAccordions] = useState<string[]>([]);
101
  // Used to set the button color of an active skill category.
102
  const [buttonClicked, setButtonClicked] = useState("");
103
104
  return (
105
    <section>
106
      <Dialog.Trigger
107
        id="findSkills"
108
        data-h2-button="white, round, solid"
109
        data-h2-card="white, round"
110
        data-h2-padding="b(tb, .5)"
111
      >
112
        <div data-h2-grid="b(top, expanded, flush, 0)">
113
          <div data-h2-grid-item="b(1of1)">
114
            <img alt="" src="https://via.placeholder.com/75" />
115
          </div>
116
          <p data-h2-grid-item="b(1of1)">
117
            {intl.formatMessage(messages.modalButtonLabel)}
118
          </p>
119
        </div>
120
      </Dialog.Trigger>
121
      <Dialog id="findSkills" data-h2-radius="b(round)">
122
        <Dialog.Header className="gradient-left-right">
123
          <Dialog.Title
124
            data-h2-padding="b(all, 1)"
125
            data-h2-font-color="b(white)"
126
            data-h2-font-size="b(h4)"
127
          >
128
            {intl.formatMessage(messages.modalHeading)}
129
          </Dialog.Title>
130
        </Dialog.Header>
131
        <Dialog.Content
132
          data-h2-grid="b(top, expanded, flush, 0)"
133
          style={{ height: "35rem", overflow: "auto", alignItems: "stretch" }}
134
        >
135
          {/* Parent Skill Category Accordions Section */}
136
          <div data-h2-grid-item="s(2of5) b(1of1)">
137
            <ul data-h2-padding="b(left, 0)" className="no-list-style-type">
138
              {parentSkillCategories.map((parentSkillCategory, index) => {
139
                const { id, key } = parentSkillCategory;
140
                // Get children skill categories of parent skill category.
141
                const childrenSkillCategories = skillCategories.filter(
142
                  (childSkillCategory) =>
143
                    childSkillCategory.depth === 2 &&
144
                    childSkillCategory.parent_id === id,
145
                );
146
                return (
147
                  <li
148
                    key={id}
149
                    data-h2-bg-color={`b(gray-1, ${
150
                      expandedAccordions.includes(key) ? ".5" : "0"
151
                    })`}
152
                    data-h2-padding="b(tb, 1)"
153
                    data-h2-border={`${
154
                      index + 1 !== parentSkillCategories.length
155
                        ? "b(gray-2, bottom, solid, thin)"
156
                        : ""
157
                    }`}
158
                    data-h2-margin="b(tb, 0)"
159
                  >
160
                    <Accordion triggerPos="left">
161
                      <Accordion.Btn
162
                        type="button"
163
                        addIcon={
164
                          <i
165
                            data-h2-font-weight="b(700)"
166
                            className="fas fa-plus"
167
                          />
168
                        }
169
                        removeIcon={
170
                          <i
171
                            data-h2-font-weight="b(700)"
172
                            className="fas fa-minus"
173
                          />
174
                        }
175
                        onClick={() =>
176
                          setExpandedAccordions(
177
                            createOrRemove(key, expandedAccordions),
178
                          )
179
                        }
180
                      >
181
                        <p data-h2-font-weight="b(700)">
182
                          {localizeFieldNonNull(
183
                            locale,
184
                            parentSkillCategory,
185
                            "name",
186
                          )}
187
                        </p>
188
                      </Accordion.Btn>
189
                      <p
190
                        data-h2-padding="b(top, .25) b(bottom, 1) b(right, .5)"
191
                        data-h2-font-color="b(black)"
192
                        data-h2-font-size="b(small)"
193
                        style={{ paddingLeft: "5rem" }}
194
                      >
195
                        {localizeFieldNonNull(
196
                          locale,
197
                          parentSkillCategory,
198
                          "description",
199
                        )}
200
                      </p>
201
                      <Accordion.Content>
202
                        <ul
203
                          data-h2-padding="b(all, 0)"
204
                          className="no-list-style-type"
205
                        >
206
                          {childrenSkillCategories.map(
207
                            (childSkillCatergory) => {
208
                              return (
209
                                <li key={childSkillCatergory.key}>
210
                                  <div
211
                                    data-h2-grid="b(middle, expanded, flush, 0)"
212
                                    data-h2-margin="b(right, .5)"
213
                                  >
214
                                    <div
215
                                      data-h2-align="b(left)"
216
                                      data-h2-grid-item="b(5of6)"
217
                                    >
218
                                      <button
219
                                        data-h2-button=""
220
                                        type="button"
221
                                        onClick={() => {
222
                                          setFirstVisit(false);
223
                                          setButtonClicked(
224
                                            childSkillCatergory.key,
225
                                          );
226
                                          setResultsSectionText({
227
                                            title: `${localizeFieldNonNull(
228
                                              locale,
229
                                              childSkillCatergory,
230
                                              "name",
231
                                            )} ${intl.formatMessage(
232
                                              messages.skills,
233
                                            )}`,
234
                                            description: localizeFieldNonNull(
235
                                              locale,
236
                                              childSkillCatergory,
237
                                              "description",
238
                                            ),
239
                                          });
240
                                          setSkillsResults(
241
                                            childSkillCatergory.skills,
242
                                          );
243
                                        }}
244
                                      >
245
                                        <p
246
                                          data-h2-button-label
247
                                          data-h2-font-weight="b(700)"
248
                                          data-h2-display="b(block)"
249
                                          data-h2-font-style={`${
250
                                            buttonClicked ===
251
                                            childSkillCatergory.key
252
                                              ? "b(none)"
253
                                              : "b(underline)"
254
                                          }`}
255
                                          data-h2-font-color={`${
256
                                            buttonClicked ===
257
                                            childSkillCatergory.key
258
                                              ? "b(theme-1)"
259
                                              : "b(black)"
260
                                          }`}
261
                                          data-h2-align="b(left)"
262
                                        >
263
                                          {localizeFieldNonNull(
264
                                            locale,
265
                                            childSkillCatergory,
266
                                            "name",
267
                                          )}
268
                                        </p>
269
                                      </button>
270
                                    </div>
271
                                    <div
272
                                      data-h2-grid-item="b(1of6)"
273
                                      data-h2-align="b(center)"
274
                                      data-h2-radius="b(round)"
275
                                      data-h2-bg-color={`${
276
                                        buttonClicked ===
277
                                        childSkillCatergory.key
278
                                          ? "b(theme-1, 1)"
279
                                          : "b(white, 1)"
280
                                      }`}
281
                                      data-h2-font-color={`${
282
                                        buttonClicked ===
283
                                        childSkillCatergory.key
284
                                          ? "b(white)"
285
                                          : "b(black)"
286
                                      }`}
287
                                    >
288
                                      <p>{childSkillCatergory.skills.length}</p>
289
                                    </div>
290
                                  </div>
291
                                </li>
292
                              );
293
                            },
294
                          )}
295
                        </ul>
296
                      </Accordion.Content>
297
                    </Accordion>
298
                  </li>
299
                );
300
              })}
301
            </ul>
302
          </div>
303
          {/* Skill Results Section */}
304
          <div
305
            data-h2-grid-item="s(3of5) b(1of1)"
306
            data-h2-border="s(gray-2, left, solid, thin) b(gray-2, top, solid, thin)"
307
          >
308
            {firstVisit ? (
309
              <div
310
                data-h2-padding="b(tb, 5) b(right, 3) b(left, 4)"
311
                data-h2-container="b(center, large)"
312
              >
313
                <p
314
                  data-h2-font-size="b(h4)"
315
                  data-h2-font-weight="b(700)"
316
                  data-h2-padding="b(top, 3) b(bottom, 1)"
317
                >
318
                  <i
319
                    data-h2-padding="b(right, .5)"
320
                    className="fas fa-arrow-left"
321
                  />
322
                  {intl.formatMessage(messages.skillsResultsHeading)}
323
                </p>
324
                <p>{intl.formatMessage(messages.skillResultsSubHeading)}</p>
325
              </div>
326
            ) : (
327
              <div>
328
                {/* Back Button */}
329
                <button
330
                  data-h2-button
331
                  type="button"
332
                  data-h2-padding="b(all, 1)"
333
                  onClick={() => {
334
                    setFirstVisit(true);
335
                    setButtonClicked("");
336
                    setSkillsResults([]);
337
                  }}
338
                >
339
                  <p
340
                    data-h2-button-label
341
                    data-h2-font-weight="b(700)"
342
                    data-h2-font-style="b(underline)"
343
                  >
344
                    <i
345
                      data-h2-padding="b(right, .25)"
346
                      className="fas fa-caret-left"
347
                    />
348
                    {intl.formatMessage(messages.backButton)}
349
                  </p>
350
                </button>
351
352
                <p
353
                  data-h2-font-size="b(h4)"
354
                  data-h2-font-weight="b(700)"
355
                  data-h2-padding="b(rl, 1) b(bottom, .5)"
356
                >
357
                  {resultsSectionText.title}
358
                </p>
359
                <p
360
                  data-h2-font-size="b(small)"
361
                  data-h2-padding="b(rl, 1) b(bottom, 2)"
362
                >
363
                  {resultsSectionText.description}
364
                </p>
365
                {!firstVisit && skillsResults.length > 0 ? (
366
                  <ul
367
                    data-h2-padding="b(left, 0)"
368
                    className="no-list-style-type"
369
                  >
370
                    {skillsResults.map((skill) => {
371
                      const { id } = skill;
372
                      const isAdded = newSkills.find(
373
                        (newSkill) => newSkill.id === skill.id,
374
                      );
375
                      const isOldSkill =
376
                        portal === "applicant" &&
377
                        oldSkills.find(
378
                          (oldSkill) => oldSkill.id === skill.id,
379
                        ) !== undefined;
380
                      return (
381
                        <li
382
                          key={id}
383
                          data-h2-grid="b(middle, contained, padded, 0)"
384
                        >
385
                          <Accordion data-h2-grid-item="b(3of4)">
386
                            <Accordion.Btn>
387
                              <p
388
                                data-h2-font-weight="b(700)"
389
                                data-h2-font-style="b(underline)"
390
                              >
391
                                {localizeFieldNonNull(locale, skill, "name")}
392
                                {isAdded && (
393
                                  <i
394
                                    data-h2-padding="b(left, .5)"
395
                                    data-h2-font-color="b(theme-1)"
396
                                    aria-hidden="true"
397
                                    className="fas fa-check"
398
                                  />
399
                                )}
400
                              </p>
401
                            </Accordion.Btn>
402
                            <Accordion.Content>
403
                              <p data-h2-focus>
404
                                {localizeFieldNonNull(
405
                                  locale,
406
                                  skill,
407
                                  "description",
408
                                )}
409
                              </p>
410
                            </Accordion.Content>
411
                          </Accordion>
412
                          {isOldSkill ? (
413
                            <button
414
                              data-h2-button=""
415
                              data-h2-grid-item="b(1of4)"
416
                              disabled
417
                              type="button"
418
                            >
419
                              <span data-h2-button-label>
420
                                {intl.formatMessage(
421
                                  messages.disabledSkillButton,
422
                                )}
423
                              </span>
424
                            </button>
425
                          ) : (
426
                            <button
427
                              data-h2-button=""
428
                              data-h2-grid-item="b(1of4)"
429
                              type="button"
430
                              disabled={isOldSkill}
431
                              onClick={() => {
432
                                // If the skill has been selected then remove it.
433
                                // Else, if the has not been selected then add it to addedSkills list.
434
                                setNewSkills(createOrRemove(skill, newSkills));
435
                              }}
436
                            >
437
                              <p
438
                                data-h2-button-label
439
                                data-h2-font-weight="b(700)"
440
                                data-h2-font-style="b(underline)"
441
                                data-h2-font-color={`${
442
                                  isAdded ? "b(theme-1)" : "b(black)"
443
                                }`}
444
                              >
445
                                {isAdded
446
                                  ? intl.formatMessage(
447
                                      messages.removeSkillButton,
448
                                    )
449
                                  : intl.formatMessage(
450
                                      messages.selectSkillButton,
451
                                    )}
452
                              </p>
453
                            </button>
454
                          )}
455
                        </li>
456
                      );
457
                    })}
458
                  </ul>
459
                ) : (
460
                  <p data-h2-padding="b(rl, 1) b(bottom, .5)">
461
                    {intl.formatMessage(messages.noSkills)}
462
                  </p>
463
                )}
464
              </div>
465
            )}
466
          </div>
467
        </Dialog.Content>
468
        <Dialog.Actions
469
          data-h2-grid="b(middle, expanded, padded, .5)"
470
          data-h2-margin="b(all, 0)"
471
          data-h2-bg-color="b(gray-1, 1)"
472
        >
473
          <div data-h2-align="b(left)" data-h2-grid-item="b(1of2)">
474
            <Dialog.ActionBtn
475
              buttonStyling="stop, round, solid"
476
              data-h2-padding="b(rl, 2) b(tb, .5)"
477
              data-h2-bg-color="b(white, 1)"
478
              onClick={() => {
479
                setSkillsResults([]);
480
              }}
481
            >
482
              <p>{intl.formatMessage(messages.cancelButton)}</p>
483
            </Dialog.ActionBtn>
484
          </div>
485
          <div data-h2-align="b(right)" data-h2-grid-item="b(1of2)">
486
            <Dialog.ActionBtn
487
              buttonStyling="theme-1, round, solid"
488
              data-h2-padding="b(rl, 2) b(tb, .5)"
489
              onClick={() => handleSubmit(newSkills)}
490
              disabled={newSkills.length === 0}
491
            >
492
              <p>{intl.formatMessage(messages.saveButton)}</p>
493
            </Dialog.ActionBtn>
494
          </div>
495
        </Dialog.Actions>
496
      </Dialog>
497
      <Dialog.Overlay />
498
    </section>
499
  );
500
};
501
502
export default FindSkillsModal;
503