Passed
Push — task/update-profile-experience ( 9cba2e )
by Tristan
05:53
created

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

Complexity

Total Complexity 42
Complexity/F 0

Size

Lines of Code 392
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 42
eloc 326
mnd 42
bc 42
fnc 0
dl 0
loc 392
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/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 {
19
  AwardRecipientType,
20
  AwardRecognitionType,
21
  ProfileAwardModal,
22
} from "../Application/ExperienceModals/AwardExperienceModal";
23
import {
24
  EducationStatus,
25
  EducationType,
26
  ProfileEducationModal,
27
} from "../Application/ExperienceModals/EducationExperienceModal";
28
import { mapToObject, getId } from "../../helpers/queries";
29
import { experienceMessages } from "../Application/applicationMessages";
30
import { toggleAccordion } from "../../helpers/forms";
31
import { useUrlHash } from "../../helpers/router";
32
33
import { getExperienceSkillsOfExperience } from "../Application/helpers";
34
import { ProfileWorkModal } from "../Application/ExperienceModals/WorkExperienceModal";
35
import { ProfileCommunityModal } from "../Application/ExperienceModals/CommunityExperienceModal";
36
import { ProfilePersonalModal } from "../Application/ExperienceModals/PersonalExperienceModal";
37
import { ProfileEducationAccordion } from "../Application/ExperienceAccordions/ExperienceEducationAccordion";
38
import { ProfileWorkAccordion } from "../Application/ExperienceAccordions/ExperienceWorkAccordion";
39
import { ProfileCommunityAccordion } from "../Application/ExperienceAccordions/ExperienceCommunityAccordion";
40
import { ProfilePersonalAccordion } from "../Application/ExperienceAccordions/ExperiencePersonalAccordion";
41
import { ProfileAwardAccordion } from "../Application/ExperienceAccordions/ExperienceAwardAccordion";
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: EducationStatus[];
113
  educationTypes: EducationType[];
114
  experienceSkills: ExperienceSkill[];
115
  skills: Skill[];
116
  recipientTypes: AwardRecipientType[];
117
  recognitionTypes: AwardRecognitionType[];
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
        modalId={modalButtons.education.id}
309
        onModalCancel={closeModal}
310
        onModalConfirm={
311
          experienceData === null ? createExperience : updateExperience
312
        }
313
        parentElement={modalRoot}
314
        visible={
315
          isModalVisible.visible &&
316
          isModalVisible.id === modalButtons.education.id
317
        }
318
      />
319
      <ProfileWorkModal
320
        experienceWork={experienceData as ExperienceWork}
321
        experienceableId={experienceData?.experienceable_id ?? 0}
322
        experienceableType={
323
          experienceData?.experienceable_type ?? "application"
324
        }
325
        modalId={modalButtons.work.id}
326
        onModalCancel={closeModal}
327
        onModalConfirm={
328
          experienceData === null ? createExperience : updateExperience
329
        }
330
        parentElement={modalRoot}
331
        visible={
332
          isModalVisible.visible && isModalVisible.id === modalButtons.work.id
333
        }
334
      />
335
      <ProfileCommunityModal
336
        experienceCommunity={experienceData as ExperienceCommunity}
337
        experienceableId={experienceData?.experienceable_id ?? 0}
338
        experienceableType={
339
          experienceData?.experienceable_type ?? "application"
340
        }
341
        modalId={modalButtons.community.id}
342
        onModalCancel={closeModal}
343
        onModalConfirm={
344
          experienceData === null ? createExperience : updateExperience
345
        }
346
        parentElement={modalRoot}
347
        visible={
348
          isModalVisible.visible &&
349
          isModalVisible.id === modalButtons.community.id
350
        }
351
      />
352
      <ProfilePersonalModal
353
        experiencePersonal={experienceData as ExperiencePersonal}
354
        experienceableId={experienceData?.experienceable_id ?? 0}
355
        experienceableType={
356
          experienceData?.experienceable_type ?? "application"
357
        }
358
        modalId={modalButtons.personal.id}
359
        onModalCancel={closeModal}
360
        onModalConfirm={
361
          experienceData === null ? createExperience : updateExperience
362
        }
363
        parentElement={modalRoot}
364
        visible={
365
          isModalVisible.visible &&
366
          isModalVisible.id === modalButtons.personal.id
367
        }
368
      />
369
      <ProfileAwardModal
370
        experienceAward={experienceData as ExperienceAward}
371
        experienceableId={experienceData?.experienceable_id ?? 0}
372
        experienceableType={
373
          experienceData?.experienceable_type ?? "application"
374
        }
375
        modalId={modalButtons.award.id}
376
        onModalCancel={closeModal}
377
        onModalConfirm={
378
          experienceData === null ? createExperience : updateExperience
379
        }
380
        parentElement={modalRoot}
381
        recipientTypes={recipientTypes}
382
        recognitionTypes={recognitionTypes}
383
        visible={
384
          isModalVisible.visible && isModalVisible.id === modalButtons.award.id
385
        }
386
      />
387
    </>
388
  );
389
};
390
391
export default ProfileExperience;
392