Passed
Push — task/update-profile-experience ( 9cba2e...f7db59 )
by Tristan
07:12
created

resources/assets/js/components/ApplicantProfile/Experience/ProfileExperience.tsx   A

Complexity

Total Complexity 42
Complexity/F 0

Size

Lines of Code 402
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 42
eloc 336
mnd 42
bc 42
fnc 0
dl 0
loc 402
rs 9.0399
bpm 0
cpm 0
noi 0
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like resources/assets/js/components/ApplicantProfile/Experience/ProfileExperience.tsx often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/* eslint-disable camelcase */
2
import React, { useEffect } from "react";
3
import { FormattedMessage, useIntl } from "react-intl";
4
import {
5
  Experience,
6
  Skill,
7
  ExperienceSkill,
8
  ExperienceEducation,
9
  ExperienceWork,
10
  ExperienceCommunity,
11
  ExperiencePersonal,
12
  ExperienceAward,
13
} from "../../../models/types";
14
import {
15
  modalButtonProps,
16
  ModalButton,
17
} from "../../Application/Experience/Experience";
18
import { mapToObject, getId } from "../../../helpers/queries";
19
import { experienceMessages } from "../../Application/applicationMessages";
20
import { toggleAccordion } from "../../../helpers/forms";
21
import { useUrlHash } from "../../../helpers/router";
22
23
import { getExperienceSkillsOfExperience } from "../../Application/helpers";
24
import { ProfileEducationAccordion } from "../../Application/ExperienceAccordions/ExperienceEducationAccordion";
25
import { ProfileWorkAccordion } from "../../Application/ExperienceAccordions/ExperienceWorkAccordion";
26
import { ProfileCommunityAccordion } from "../../Application/ExperienceAccordions/ExperienceCommunityAccordion";
27
import { ProfilePersonalAccordion } from "../../Application/ExperienceAccordions/ExperiencePersonalAccordion";
28
import { ProfileAwardAccordion } from "../../Application/ExperienceAccordions/ExperienceAwardAccordion";
29
import ProfileEducationModal from "./EducationExperienceProfileModal";
30
import ProfileWorkModal from "./WorkExperienceProfileModal";
31
import ProfileCommunityModal from "./CommunityExperienceProfileModal";
32
import ProfilePersonalModal from "./PersonalExperienceProfileModal";
33
import ProfileAwardModal from "./AwardExperienceProfileModal";
34
import {
35
  FormEducationStatus,
36
  FormEducationType,
37
} from "../../Application/ExperienceModals/EducationExperienceModal";
38
import {
39
  FormAwardRecipientType,
40
  FormAwardRecognitionType,
41
} from "../../Application/ExperienceModals/AwardExperienceModal";
42
43
const profileExperienceAccordion = (
44
  experience: Experience,
45
  relevantSkills: ExperienceSkill[],
46
  skillsById: { [id: number]: Skill },
47
  handleEdit: () => void,
48
  handleDelete: () => Promise<void>,
49
): React.ReactElement | null => {
50
  switch (experience.type) {
51
    case "experience_education":
52
      return (
53
        <ProfileEducationAccordion
54
          key={`${experience.id}-${experience.type}`}
55
          experience={experience}
56
          handleDelete={handleDelete}
57
          handleEdit={handleEdit}
58
          relevantSkills={relevantSkills}
59
          skillsById={skillsById}
60
        />
61
      );
62
    case "experience_work":
63
      return (
64
        <ProfileWorkAccordion
65
          key={`${experience.id}-${experience.type}`}
66
          experience={experience}
67
          handleDelete={handleDelete}
68
          handleEdit={handleEdit}
69
          relevantSkills={relevantSkills}
70
          skillsById={skillsById}
71
        />
72
      );
73
    case "experience_community":
74
      return (
75
        <ProfileCommunityAccordion
76
          key={`${experience.id}-${experience.type}`}
77
          experience={experience}
78
          handleDelete={handleDelete}
79
          handleEdit={handleEdit}
80
          relevantSkills={relevantSkills}
81
          skillsById={skillsById}
82
        />
83
      );
84
    case "experience_personal":
85
      return (
86
        <ProfilePersonalAccordion
87
          key={`${experience.id}-${experience.type}`}
88
          experience={experience}
89
          handleDelete={handleDelete}
90
          handleEdit={handleEdit}
91
          relevantSkills={relevantSkills}
92
          skillsById={skillsById}
93
        />
94
      );
95
    case "experience_award":
96
      return (
97
        <ProfileAwardAccordion
98
          key={`${experience.id}-${experience.type}`}
99
          experience={experience}
100
          handleDelete={handleDelete}
101
          handleEdit={handleEdit}
102
          relevantSkills={relevantSkills}
103
          skillsById={skillsById}
104
        />
105
      );
106
    default:
107
      return null;
108
  }
109
};
110
export interface ProfileExperienceProps {
111
  experiences: Experience[];
112
  educationStatuses: FormEducationStatus[];
113
  educationTypes: FormEducationType[];
114
  experienceSkills: ExperienceSkill[];
115
  skills: Skill[];
116
  recipientTypes: FormAwardRecipientType[];
117
  recognitionTypes: FormAwardRecognitionType[];
118
  handleCreateExperience: (data: Experience) => Promise<void>;
119
  handleUpdateExperience: (data: Experience) => Promise<void>;
120
  handleDeleteExperience: (
121
    id: number,
122
    type: Experience["type"],
123
  ) => Promise<void>;
124
  handleUpdateExperienceSkill: (expSkill: ExperienceSkill) => Promise<void>;
125
  handleDeleteExperienceSkill: (expSkill: ExperienceSkill) => Promise<void>;
126
}
127
128
export const ProfileExperience: React.FC<ProfileExperienceProps> = ({
129
  experiences,
130
  educationStatuses,
131
  educationTypes,
132
  experienceSkills,
133
  skills,
134
  handleCreateExperience,
135
  handleUpdateExperience,
136
  handleDeleteExperience,
137
  handleUpdateExperienceSkill,
138
  handleDeleteExperienceSkill,
139
  recipientTypes,
140
  recognitionTypes,
141
}) => {
142
  const intl = useIntl();
143
144
  const [isModalVisible, setIsModalVisible] = React.useState<{
145
    id: Experience["type"] | "";
146
    visible: boolean;
147
  }>({
148
    id: "",
149
    visible: false,
150
  });
151
152
  const [experienceData, setExperienceData] = React.useState<Experience | null>(
153
    null,
154
  );
155
156
  const modalButtons = modalButtonProps(intl);
157
158
  const openModal = (id: Experience["type"]): void => {
159
    setIsModalVisible({ id, visible: true });
160
  };
161
162
  const closeModal = (): void => {
163
    setExperienceData(null);
164
    setIsModalVisible({ id: "", visible: false });
165
  };
166
167
  const updateExperience = (data) =>
168
    handleUpdateExperience(data).then(closeModal);
169
  const createExperience = (data) =>
170
    handleCreateExperience(data).then(closeModal);
171
172
  const editExperience = (experience: Experience): void => {
173
    setExperienceData(experience);
174
    setIsModalVisible({ id: experience.type, visible: true });
175
  };
176
177
  const deleteExperience = (experience: Experience): Promise<void> =>
178
    handleDeleteExperience(experience.id, experience.type).then(closeModal);
179
180
  const skillsById = mapToObject(skills, getId);
181
182
  const modalRoot = document.getElementById("modal-root");
183
184
  // Open modal for editing if URI has a hash.
185
  const uriHash = useUrlHash();
186
  let experienceType: string;
187
  let experienceId: number;
188
189
  if (uriHash) {
190
    const uriHashFragments = uriHash.substring(1).split("_");
191
    // Get experience type from first two fragments of URI hash.
192
    experienceType = `${uriHashFragments[0]}_${uriHashFragments[1]}`;
193
    // Get experience id from third fragment of URI hash.
194
    experienceId = Number(uriHashFragments[2]);
195
  }
196
197
  const experienceCurrent: Experience | undefined = experiences.find(
198
    (experience) =>
199
      experience.type === experienceType && experience.id === experienceId,
200
  );
201
202
  useEffect(() => {
203
    if (uriHash && experienceCurrent) {
204
      toggleAccordion(uriHash.substring(1));
205
      // Open edit experience modal.
206
      editExperience(experienceCurrent);
207
    }
208
    // eslint-disable-next-line react-hooks/exhaustive-deps
209
  }, []); // useEffect should only run on mount and unmount.
210
211
  return (
212
    <>
213
      <div>
214
        <h2 data-c-heading="h2" data-c-margin="bottom(1)">
215
          {intl.formatMessage(experienceMessages.heading)}
216
        </h2>
217
        <p data-c-margin="bottom(1)">
218
          <FormattedMessage
219
            id="profile.experience.preamble"
220
            defaultMessage="Use the buttons below to add experiences you want to share with the manager. Experiences you have added in the past also appear below, and you can edit them to link them to skills required for this job when necessary."
221
            description="First section of text on the experience step of the Application Timeline."
222
          />
223
        </p>
224
        {/* Experience Modal Buttons */}
225
        <div data-c-grid="gutter(all, 1)">
226
          {Object.values(modalButtons).map((buttonProps) => {
227
            const { id, title, icon } = buttonProps;
228
            return (
229
              <ModalButton
230
                key={id}
231
                id={id}
232
                title={title}
233
                icon={icon}
234
                openModal={openModal}
235
              />
236
            );
237
          })}
238
        </div>
239
        {/* Experience Accordion List */}
240
        {experiences && experiences.length > 0 ? (
241
          <div className="experience-list" data-c-margin="top(2)">
242
            <div data-c-accordion-group>
243
              {experiences.map((experience) => {
244
                const relevantSkills: ExperienceSkill[] = getExperienceSkillsOfExperience(
245
                  experienceSkills,
246
                  experience,
247
                );
248
                const handleEdit = () => editExperience(experience);
249
                const handleDelete = () => deleteExperience(experience);
250
                const errorAccordion = () => (
251
                  <div
252
                    data-c-background="gray(10)"
253
                    data-c-radius="rounded"
254
                    data-c-border="all(thin, solid, gray)"
255
                    data-c-margin="top(1)"
256
                    data-c-padding="all(1)"
257
                  >
258
                    <div data-c-align="base(center)">
259
                      <p data-c-color="stop">
260
                        {intl.formatMessage(
261
                          experienceMessages.errorRenderingExperience,
262
                        )}
263
                      </p>
264
                    </div>
265
                  </div>
266
                );
267
                return (
268
                  profileExperienceAccordion(
269
                    experience,
270
                    relevantSkills,
271
                    skillsById,
272
                    handleEdit,
273
                    handleDelete,
274
                  ) ?? errorAccordion()
275
                );
276
              })}
277
            </div>
278
          </div>
279
        ) : (
280
          <div
281
            data-c-background="gray(10)"
282
            data-c-radius="rounded"
283
            data-c-border="all(thin, solid, gray)"
284
            data-c-margin="top(2)"
285
            data-c-padding="all(1)"
286
          >
287
            <div data-c-align="base(center)">
288
              <p data-c-color="gray">
289
                <FormattedMessage
290
                  id="profile.experience.noExperiences"
291
                  defaultMessage="Looks like you don't have any experience added yet. Use the buttons above to add experience. Don't forget that experience will always be saved to your profile so that you can use it on future applications!"
292
                  description="Message displayed when application has no experiences."
293
                />
294
              </p>
295
            </div>
296
          </div>
297
        )}
298
      </div>
299
      <div data-c-dialog-overlay={isModalVisible.visible ? "active" : ""} />
300
      <ProfileEducationModal
301
        educationStatuses={educationStatuses}
302
        educationTypes={educationTypes}
303
        experienceEducation={experienceData as ExperienceEducation}
304
        experienceableId={experienceData?.experienceable_id ?? 0}
305
        experienceableType={
306
          experienceData?.experienceable_type ?? "application"
307
        }
308
        userSkills={skills}
309
        experienceSkills={experienceSkills}
310
        modalId={modalButtons.education.id}
311
        onModalCancel={closeModal}
312
        onModalConfirm={
313
          experienceData === null ? createExperience : updateExperience
314
        }
315
        parentElement={modalRoot}
316
        visible={
317
          isModalVisible.visible &&
318
          isModalVisible.id === modalButtons.education.id
319
        }
320
      />
321
      <ProfileWorkModal
322
        experienceWork={experienceData as ExperienceWork}
323
        experienceableId={experienceData?.experienceable_id ?? 0}
324
        experienceableType={
325
          experienceData?.experienceable_type ?? "application"
326
        }
327
        userSkills={skills}
328
        experienceSkills={experienceSkills}
329
        modalId={modalButtons.work.id}
330
        onModalCancel={closeModal}
331
        onModalConfirm={
332
          experienceData === null ? createExperience : updateExperience
333
        }
334
        parentElement={modalRoot}
335
        visible={
336
          isModalVisible.visible && isModalVisible.id === modalButtons.work.id
337
        }
338
      />
339
      <ProfileCommunityModal
340
        experienceCommunity={experienceData as ExperienceCommunity}
341
        experienceableId={experienceData?.experienceable_id ?? 0}
342
        experienceableType={
343
          experienceData?.experienceable_type ?? "application"
344
        }
345
        userSkills={skills}
346
        experienceSkills={experienceSkills}
347
        modalId={modalButtons.community.id}
348
        onModalCancel={closeModal}
349
        onModalConfirm={
350
          experienceData === null ? createExperience : updateExperience
351
        }
352
        parentElement={modalRoot}
353
        visible={
354
          isModalVisible.visible &&
355
          isModalVisible.id === modalButtons.community.id
356
        }
357
      />
358
      <ProfilePersonalModal
359
        experiencePersonal={experienceData as ExperiencePersonal}
360
        experienceableId={experienceData?.experienceable_id ?? 0}
361
        experienceableType={
362
          experienceData?.experienceable_type ?? "application"
363
        }
364
        userSkills={skills}
365
        experienceSkills={experienceSkills}
366
        modalId={modalButtons.personal.id}
367
        onModalCancel={closeModal}
368
        onModalConfirm={
369
          experienceData === null ? createExperience : updateExperience
370
        }
371
        parentElement={modalRoot}
372
        visible={
373
          isModalVisible.visible &&
374
          isModalVisible.id === modalButtons.personal.id
375
        }
376
      />
377
      <ProfileAwardModal
378
        experienceAward={experienceData as ExperienceAward}
379
        experienceableId={experienceData?.experienceable_id ?? 0}
380
        experienceableType={
381
          experienceData?.experienceable_type ?? "application"
382
        }
383
        userSkills={skills}
384
        experienceSkills={experienceSkills}
385
        modalId={modalButtons.award.id}
386
        onModalCancel={closeModal}
387
        onModalConfirm={
388
          experienceData === null ? createExperience : updateExperience
389
        }
390
        parentElement={modalRoot}
391
        recipientTypes={recipientTypes}
392
        recognitionTypes={recognitionTypes}
393
        visible={
394
          isModalVisible.visible && isModalVisible.id === modalButtons.award.id
395
        }
396
      />
397
    </>
398
  );
399
};
400
401
export default ProfileExperience;
402