Passed
Push — task/refactor-application-data... ( 44cf03...30e714 )
by Tristan
05:41 queued 01:11
created

resources/assets/js/hooks/applicationHooks.tsx   A

Complexity

Total Complexity 38
Complexity/F 2.92

Size

Lines of Code 375
Function Count 13

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 38
eloc 285
mnd 25
bc 25
fnc 13
dl 0
loc 375
rs 9.36
bpm 1.923
cpm 2.923
noi 0
c 0
b 0
f 0

13 Functions

Rating   Name   Duplication   Size   Complexity  
A applicationHooks.tsx ➔ useJob 0 4 2
B applicationHooks.tsx ➔ useFetchExperienceConstants 0 58 5
A applicationHooks.tsx ➔ useFetchJob 0 21 3
A applicationHooks.tsx ➔ useExperienceConstants 0 16 1
A applicationHooks.tsx ➔ useFetchApplication 0 26 2
A applicationHooks.tsx ➔ useFetchAllApplicationData 0 41 2
A applicationHooks.tsx ➔ useCriteria 0 4 2
B applicationHooks.tsx ➔ useFetchExperience 0 64 7
A applicationHooks.tsx ➔ useApplication 0 6 1
A applicationHooks.tsx ➔ useExperienceSkills 0 16 5
B applicationHooks.tsx ➔ useExperiences 0 30 5
A applicationHooks.tsx ➔ useSkills 0 3 1
A applicationHooks.tsx ➔ useFetchSkills 0 14 2
1
/* eslint-disable camelcase */
2
import { useCallback, useEffect, useState } from "react";
3
import { useSelector } from "react-redux";
4
import { DispatchType } from "../configureStore";
5
import { RootState } from "../store/store";
6
import { getAwardRecipientTypes as fetchAwardRecipientTypes } from "../store/AwardRecipientType/awardRecipientTypeActions";
7
import { getAwardRecipientTypes } from "../store/AwardRecipientType/awardRecipientTypeSelector";
8
import { getAwardRecognitionTypes as fetchAwardRecognitionTypes } from "../store/AwardRecognitionType/awardRecognitionTypeActions";
9
import { getAwardRecognitionTypes } from "../store/AwardRecognitionType/awardRecognitionTypeSelector";
10
import { getEducationTypes as fetchEducationTypes } from "../store/EducationType/educationTypeActions";
11
import { getEducationTypes } from "../store/EducationType/educationTypeSelector";
12
import { getEducationStatuses as fetchEducationStatuses } from "../store/EducationStatus/educationStatusActions";
13
import { getEducationStatuses } from "../store/EducationStatus/educationStatusSelector";
14
import {
15
  AwardRecipientType,
16
  AwardRecognitionType,
17
  EducationType,
18
  EducationStatus,
19
  Job,
20
  Experience as ExperienceType,
21
  Skill,
22
  ExperienceSkill,
23
  ApplicationNormalized,
24
  Criteria,
25
} from "../models/types";
26
import {
27
  getApplicationIsUpdating,
28
  getApplicationNormalized,
29
} from "../store/Application/applicationSelector";
30
import { fetchApplicationNormalized } from "../store/Application/applicationActions";
31
import {
32
  getCriteriaByJob,
33
  getJob,
34
  getJobIsUpdating,
35
} from "../store/Job/jobSelector";
36
import { fetchJob } from "../store/Job/jobActions";
37
import { ApplicationStatusId } from "../models/lookupConstants";
38
import {
39
  getExperienceByApplicant,
40
  getExperienceByApplication,
41
  getExperienceSkillsByApplicant,
42
  getExperienceSkillsByApplication,
43
  getUpdatingByApplicant,
44
  getUpdatingByApplication,
45
} from "../store/Experience/experienceSelector";
46
import {
47
  fetchExperienceByApplicant,
48
  fetchExperienceByApplication,
49
} from "../store/Experience/experienceActions";
50
import { getSkills, getSkillsUpdating } from "../store/Skill/skillSelector";
51
import { fetchSkills } from "../store/Skill/skillActions";
52
53
export function useApplication(
54
  applicationId: number,
55
): ApplicationNormalized | null {
56
  return useSelector((state: RootState) =>
57
    getApplicationNormalized(state, { applicationId }),
58
  );
59
}
60
61
export function useJob(jobId: number | undefined): Job | null {
62
  return useSelector((state: RootState) =>
63
    jobId ? getJob(state, { jobId }) : null,
64
  );
65
}
66
67
export function useExperienceConstants(): {
68
  awardRecipientTypes: AwardRecipientType[];
69
  awardRecognitionTypes: AwardRecognitionType[];
70
  educationTypes: EducationType[];
71
  educationStatuses: EducationStatus[];
72
} {
73
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
74
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
75
  const educationTypes = useSelector(getEducationTypes);
76
  const educationStatuses = useSelector(getEducationStatuses);
77
  return {
78
    awardRecipientTypes,
79
    awardRecognitionTypes,
80
    educationTypes,
81
    educationStatuses,
82
  };
83
}
84
85
export function useSkills(): Skill[] {
86
  return useSelector(getSkills);
87
}
88
89
export function useCriteria(jobId: number | undefined): Criteria[] {
90
  return useSelector((state: RootState) =>
91
    jobId ? getCriteriaByJob(state, { jobId }) : [],
92
  );
93
}
94
95
export function useExperiences(
96
  applicationId: number,
97
  application: ApplicationNormalized | null,
98
): ExperienceType[] {
99
  const applicantId = application?.applicant_id ?? 0;
100
101
  // When an Application is still a draft, use Experiences associated with the applicant profile.
102
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
103
  const useProfileExperience =
104
    application === null ||
105
    application.application_status_id === ApplicationStatusId.draft;
106
107
  // This selector must be memoized because getExperienceByApplicant/Application uses reselect, and not re-reselect, so it needs to preserve its state.
108
  const experienceSelector = useCallback(
109
    (state: RootState) =>
110
      useProfileExperience
111
        ? getExperienceByApplicant(state, { applicantId })
112
        : getExperienceByApplication(state, { applicationId }),
113
    [applicationId, applicantId, useProfileExperience],
114
  );
115
  const experiencesByType = useSelector(experienceSelector);
116
  const experiences: ExperienceType[] = [
117
    ...experiencesByType.award,
118
    ...experiencesByType.community,
119
    ...experiencesByType.education,
120
    ...experiencesByType.personal,
121
    ...experiencesByType.work,
122
  ];
123
  return experiences;
124
}
125
126
export function useExperienceSkills(
127
  applicationId: number,
128
  application: ApplicationNormalized | null,
129
): ExperienceSkill[] {
130
  // ExperienceSkills don't need to be fetched because they are returned in the Experiences API calls.
131
  const applicantId = application?.applicant_id ?? 0;
132
  const useProfileExperience =
133
    application === null ||
134
    application.application_status_id === ApplicationStatusId.draft;
135
  const expSkillSelector = (state: RootState) =>
136
    useProfileExperience
137
      ? getExperienceSkillsByApplicant(state, { applicantId })
138
      : getExperienceSkillsByApplication(state, { applicationId });
139
  const experienceSkills = useSelector(expSkillSelector);
140
  return experienceSkills;
141
}
142
143
/**
144
 * Return all skills from the redux store, and fetch the skills from backend if they are not yet in the store.
145
 * @param dispatch
146
 */
147
export function useFetchSkills(dispatch: DispatchType): Skill[] {
148
  const skills = useSelector(getSkills);
149
  const skillsUpdating = useSelector(getSkillsUpdating);
150
  useEffect(() => {
151
    if (skills.length === 0 && !skillsUpdating) {
152
      dispatch(fetchSkills());
153
    }
154
  }, [skills.length, skillsUpdating, dispatch]);
155
  return skills;
156
}
157
158
/**
159
 * Return all Experience constants from the redux store, and fetch them from backend if they are not yet in the store.
160
 * @param dispatch
161
 */
162
export function useFetchExperienceConstants(
163
  dispatch: DispatchType,
164
): {
165
  awardRecipientTypes: AwardRecipientType[];
166
  awardRecognitionTypes: AwardRecognitionType[];
167
  educationTypes: EducationType[];
168
  educationStatuses: EducationStatus[];
169
} {
170
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
171
  const awardRecipientTypesLoading = useSelector(
172
    (state: RootState) => state.awardRecipientType.loading,
173
  );
174
  useEffect(() => {
175
    if (awardRecipientTypes.length === 0 && !awardRecipientTypesLoading) {
176
      dispatch(fetchAwardRecipientTypes());
177
    }
178
  }, [awardRecipientTypes, awardRecipientTypesLoading, dispatch]);
179
180
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
181
  const awardRecognitionTypesLoading = useSelector(
182
    (state: RootState) => state.awardRecognitionType.loading,
183
  );
184
  useEffect(() => {
185
    if (awardRecognitionTypes.length === 0 && !awardRecognitionTypesLoading) {
186
      dispatch(fetchAwardRecognitionTypes());
187
    }
188
  }, [awardRecognitionTypes, awardRecognitionTypesLoading, dispatch]);
189
190
  const educationTypes = useSelector(getEducationTypes);
191
  const educationTypesLoading = useSelector(
192
    (state: RootState) => state.educationType.loading,
193
  );
194
  useEffect(() => {
195
    if (educationTypes.length === 0 && !educationTypesLoading) {
196
      dispatch(fetchEducationTypes());
197
    }
198
  }, [educationTypes, educationTypesLoading, dispatch]);
199
200
  const educationStatuses = useSelector(getEducationStatuses);
201
  const educationStatusesLoading = useSelector(
202
    (state: RootState) => state.educationStatus.loading,
203
  );
204
  useEffect(() => {
205
    if (educationStatuses.length === 0 && !educationStatusesLoading) {
206
      dispatch(fetchEducationStatuses());
207
    }
208
  }, [educationStatuses, educationStatusesLoading, dispatch]);
209
210
  return {
211
    awardRecipientTypes,
212
    awardRecognitionTypes,
213
    educationTypes,
214
    educationStatuses,
215
  };
216
}
217
218
/**
219
 * Return an Application (normalized, ie without Review) from the redux store, and fetch it from backend if it is not yet in the store.
220
 * @param applicationId
221
 * @param dispatch
222
 */
223
export function useFetchApplication(
224
  applicationId: number,
225
  dispatch: DispatchType,
226
): ApplicationNormalized | null {
227
  const applicationSelector = (
228
    state: RootState,
229
  ): ApplicationNormalized | null =>
230
    getApplicationNormalized(state, { applicationId });
231
  const application: ApplicationNormalized | null = useSelector(
232
    applicationSelector,
233
  );
234
  const applicationIsUpdating = useSelector((state: RootState) =>
235
    getApplicationIsUpdating(state, { applicationId }),
236
  );
237
  useEffect(() => {
238
    if (application === null && !applicationIsUpdating) {
239
      dispatch(fetchApplicationNormalized(applicationId));
240
    }
241
  }, [application, applicationId, applicationIsUpdating, dispatch]);
242
  return application;
243
}
244
245
/**
246
 * Return an Job from the redux store, and fetch it from backend if it is not yet in the store.
247
 * @param jobId
248
 * @param dispatch
249
 */
250
export function useFetchJob(
251
  jobId: number | undefined,
252
  dispatch: DispatchType,
253
): Job | null {
254
  const job = useJob(jobId);
255
  const jobUpdatingSelector = (state: RootState) =>
256
    jobId ? getJobIsUpdating(state, jobId) : false;
257
  const jobIsUpdating = useSelector(jobUpdatingSelector);
258
  useEffect(() => {
259
    // If job is null and not already updating, fetch it.
260
    if (jobId && job === null && !jobIsUpdating) {
261
      dispatch(fetchJob(jobId));
262
    }
263
  }, [jobId, job, jobIsUpdating, dispatch]);
264
  return job;
265
}
266
267
/**
268
 * Return all Experience relavant to an Application from the redux store, and fetch it from backend if it is not yet in the store.
269
 * @param applicationId
270
 * @param application
271
 * @param dispatch
272
 */
273
export function useFetchExperience(
274
  applicationId: number,
275
  application: ApplicationNormalized | null,
276
  dispatch: DispatchType,
277
): {
278
  experiences: ExperienceType[];
279
  experiencesUpdating: boolean;
280
  experiencesFetched: boolean;
281
} {
282
  const applicantId = application?.applicant_id ?? 0;
283
284
  // When an Application is still a draft, use Experiences associated with the applicant profile.
285
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
286
  const applicationLoaded = application !== null;
287
  const useProfileExperience =
288
    application === null ||
289
    application.application_status_id === ApplicationStatusId.draft;
290
291
  const experiences = useExperiences(applicationId, application);
292
  const experiencesUpdating = useSelector((state: RootState) =>
293
    useProfileExperience
294
      ? getUpdatingByApplicant(state, { applicantId })
295
      : getUpdatingByApplication(state, { applicationId }),
296
  );
297
  const [experiencesFetched, setExperiencesFetched] = useState(false);
298
  useEffect(() => {
299
    // Only load experiences if they have never been fetched by this component (!experiencesFetched),
300
    //  have never been fetched by another component (length === 0),
301
    //  and are not currently being fetched (!experiencesUpdating).
302
    // Also, wait until application has been loaded so the correct source can be determined.
303
    if (
304
      applicationLoaded &&
305
      !experiencesFetched &&
306
      !experiencesUpdating &&
307
      experiences.length === 0
308
    ) {
309
      setExperiencesFetched(true);
310
      if (useProfileExperience) {
311
        dispatch(fetchExperienceByApplicant(applicantId));
312
      } else {
313
        dispatch(fetchExperienceByApplication(applicationId));
314
      }
315
    }
316
  }, [
317
    applicantId,
318
    applicationId,
319
    applicationLoaded,
320
    dispatch,
321
    experiences.length,
322
    experiencesFetched,
323
    experiencesUpdating,
324
    useProfileExperience,
325
  ]);
326
  return {
327
    experiences,
328
    experiencesUpdating,
329
    experiencesFetched,
330
  };
331
}
332
333
/**
334
 * Trigger fetches for all data needed for the Application process which is not yet in the redux store, or in the process of loading.
335
 * @param applicationId
336
 */
337
export function useFetchAllApplicationData(
338
  applicationId: number,
339
  dispatch: DispatchType,
340
): {
341
  applicationLoaded: boolean;
342
  jobLoaded: boolean;
343
  experiencesLoaded: boolean;
344
  experienceConstantsLoaded: boolean;
345
  skillsLoaded: boolean;
346
} {
347
  const application = useFetchApplication(applicationId, dispatch);
348
  const jobId = application?.job_poster_id;
349
  const job = useFetchJob(jobId, dispatch);
350
  const { experiences, experiencesUpdating } = useFetchExperience(
351
    applicationId,
352
    application,
353
    dispatch,
354
  );
355
  const {
356
    awardRecipientTypes,
357
    awardRecognitionTypes,
358
    educationTypes,
359
    educationStatuses,
360
  } = useFetchExperienceConstants(dispatch);
361
  const skills = useFetchSkills(dispatch);
362
363
  return {
364
    applicationLoaded: application !== null,
365
    jobLoaded: job !== null,
366
    experiencesLoaded: !experiencesUpdating || experiences.length > 0,
367
    experienceConstantsLoaded:
368
      awardRecipientTypes.length > 0 &&
369
      awardRecognitionTypes.length > 0 &&
370
      educationTypes.length > 0 &&
371
      educationStatuses.length > 0,
372
    skillsLoaded: skills.length > 0,
373
  };
374
}
375