Passed
Push — feature/experience-skills-api ( f678ab...a72ddc )
by Chris
09:19 queued 02:59
created

experienceReducer.ts ➔ setExperience   B

Complexity

Conditions 8

Size

Total Lines 45
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 45
rs 7.0773
c 0
b 0
f 0
cc 8
1
import { combineReducers } from "redux";
2
import {
3
  ExperienceWork,
4
  ExperienceEducation,
5
  ExperienceCommunity,
6
  ExperienceAward,
7
  ExperiencePersonal,
8
  Experience,
9
} from "../../models/types";
10
import {
11
  ExperienceAction,
12
  FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED,
13
  FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED,
14
  CREATE_EXPERIENCE_SUCCEEDED,
15
  FETCH_EXPERIENCE_BY_APPLICANT_STARTED,
16
  FETCH_EXPERIENCE_BY_APPLICANT_FAILED,
17
  FETCH_EXPERIENCE_BY_APPLICATION_STARTED,
18
  FETCH_EXPERIENCE_BY_APPLICATION_FAILED,
19
  UPDATE_EXPERIENCE_STARTED,
20
  UPDATE_EXPERIENCE_SUCCEEDED,
21
  UPDATE_EXPERIENCE_FAILED,
22
  DELETE_EXPERIENCE_STARTED,
23
  DELETE_EXPERIENCE_SUCCEEDED,
24
  DELETE_EXPERIENCE_FAILED,
25
} from "./experienceActions";
26
import {
27
  mapToObject,
28
  getId,
29
  uniq,
30
  deleteProperty,
31
  mapObjectValues,
32
} from "../../helpers/queries";
33
34
export interface ExperienceSection<T> {
35
  byId: {
36
    [id: number]: T;
37
  };
38
  idsByApplicant: {
39
    [applicantId: number]: number[];
40
  };
41
  idsByApplication: {
42
    [applicationId: number]: number[];
43
  };
44
}
45
46
export interface EntityState {
47
  work: ExperienceSection<ExperienceWork>;
48
  education: ExperienceSection<ExperienceEducation>;
49
  community: ExperienceSection<ExperienceCommunity>;
50
  award: ExperienceSection<ExperienceAward>;
51
  personal: ExperienceSection<ExperiencePersonal>;
52
}
53
54
export interface UiState {
55
  updatingByApplicant: {
56
    [id: number]: boolean;
57
  };
58
  updatingByApplication: {
59
    [id: number]: boolean;
60
  };
61
  updatingByTypeAndId: {
62
    work: {
63
      [id: number]: boolean;
64
    };
65
    education: {
66
      [id: number]: boolean;
67
    };
68
    community: {
69
      [id: number]: boolean;
70
    };
71
    award: {
72
      [id: number]: boolean;
73
    };
74
    personal: {
75
      [id: number]: boolean;
76
    };
77
  };
78
}
79
80
export interface ExperienceState {
81
  entities: EntityState;
82
  ui: UiState;
83
}
84
85
export const initEntities = (): EntityState => ({
86
  work: {
87
    byId: {},
88
    idsByApplicant: {},
89
    idsByApplication: {},
90
  },
91
  education: {
92
    byId: {},
93
    idsByApplicant: {},
94
    idsByApplication: {},
95
  },
96
  community: {
97
    byId: {},
98
    idsByApplicant: {},
99
    idsByApplication: {},
100
  },
101
  award: {
102
    byId: {},
103
    idsByApplicant: {},
104
    idsByApplication: {},
105
  },
106
  personal: {
107
    byId: {},
108
    idsByApplicant: {},
109
    idsByApplication: {},
110
  },
111
});
112
113
export const initUi = (): UiState => ({
114
  updatingByApplicant: {},
115
  updatingByApplication: {},
116
  updatingByTypeAndId: {
117
    work: {},
118
    education: {},
119
    community: {},
120
    award: {},
121
    personal: {},
122
  },
123
});
124
125
export const initExperienceState = (): ExperienceState => ({
126
  entities: initEntities(),
127
  ui: initUi(),
128
});
129
130
function isWork(experience: Experience): experience is ExperienceWork {
131
  return experience.type === "experience_work";
132
}
133
function isEducation(
134
  experience: Experience,
135
): experience is ExperienceEducation {
136
  return experience.type === "experience_education";
137
}
138
function isCommunity(
139
  experience: Experience,
140
): experience is ExperienceCommunity {
141
  return experience.type === "experience_community";
142
}
143
function isAward(experience: Experience): experience is ExperienceAward {
144
  return experience.type === "experience_award";
145
}
146
function isPersonal(experience: Experience): experience is ExperiencePersonal {
147
  return experience.type === "experience_personal";
148
}
149
150
const experienceTypeGuards = {
151
  work: isWork,
152
  education: isEducation,
153
  community: isCommunity,
154
  award: isAward,
155
  personal: isPersonal,
156
};
157
158
function massageType(experienceType: Experience["type"]): keyof EntityState {
159
  /* eslint-disable @typescript-eslint/camelcase */
160
  const mapping: { [key in Experience["type"]]: keyof EntityState } = {
161
    experience_work: "work",
162
    experience_education: "education",
163
    experience_community: "community",
164
    experience_award: "award",
165
    experience_personal: "personal",
166
  };
167
  /* eslint-enable @typescript-eslint/camelcase */
168
  return mapping[experienceType];
169
}
170
171
function fetchExperienceByApplication<T extends keyof EntityState>(
172
  state: EntityState,
173
  action: ExperienceAction,
174
  type: T,
175
): EntityState[T] {
176
  const subState = state[type];
177
  if (action.type !== FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED) {
178
    return subState;
179
  }
180
  const typeFilter = experienceTypeGuards[type];
181
  const experiences = action.payload.filter(typeFilter);
182
  return {
183
    ...subState,
184
    byId: {
185
      ...subState.byId,
186
      ...mapToObject(experiences, getId),
187
    },
188
    idsByApplication: {
189
      ...subState.idsByApplicant,
190
      [action.meta.applicationId]: experiences.map(getId),
191
    },
192
  };
193
}
194
function fetchExperienceByApplicant<T extends keyof EntityState>(
195
  state: EntityState,
196
  action: ExperienceAction,
197
  type: T,
198
): EntityState[T] {
199
  const subState = state[type];
200
  if (action.type !== FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED) {
201
    return subState;
202
  }
203
  const typeFilter = experienceTypeGuards[type];
204
  const experiences = action.payload.filter(typeFilter);
205
  return {
206
    ...subState,
207
    byId: {
208
      ...subState.byId,
209
      ...mapToObject(experiences, getId),
210
    },
211
    idsByApplicant: {
212
      ...subState.idsByApplicant,
213
      [action.meta.applicantId]: experiences.map(getId),
214
    },
215
  };
216
}
217
218
function setExperience<T extends keyof EntityState>(
219
  state: EntityState,
220
  action: ExperienceAction,
221
  type: T,
222
): EntityState[T] {
223
  const subState = state[type];
224
  if (
225
    (action.type !== CREATE_EXPERIENCE_SUCCEEDED &&
226
      action.type !== UPDATE_EXPERIENCE_SUCCEEDED) ||
227
    massageType(action.meta.type) !== type
228
  ) {
229
    return subState;
230
  }
231
  const experience = action.payload;
232
  const ownerId = experience.experienceable_id;
233
  const idsByApplicant =
234
    experience.experienceable_type === "applicant"
235
      ? {
236
          ...subState.idsByApplicant,
237
          [ownerId]: uniq([
238
            ...(subState.idsByApplicant[ownerId] ?? []),
239
            experience.id,
240
          ]),
241
        }
242
      : subState.idsByApplicant;
243
  const idsByApplication =
244
    experience.experienceable_type === "application"
245
      ? {
246
          ...subState.idsByApplication,
247
          [ownerId]: uniq([
248
            ...(subState.idsByApplication[ownerId] ?? []),
249
            experience.id,
250
          ]),
251
        }
252
      : subState.idsByApplication;
253
254
  return {
255
    ...subState,
256
    byId: {
257
      ...subState.byId,
258
      [action.payload.id]: action.payload,
259
    },
260
    idsByApplicant,
261
    idsByApplication,
262
  };
263
}
264
265
function deleteExperience<T extends keyof EntityState>(
266
  state: EntityState,
267
  action: ExperienceAction,
268
  type: T,
269
): EntityState[T] {
270
  const subState = state[type];
271
  if (
272
    action.type !== DELETE_EXPERIENCE_SUCCEEDED ||
273
    massageType(action.meta.type) !== type
274
  ) {
275
    return subState;
276
  }
277
  const dropId = (ids: number[]): number[] =>
278
    ids.filter((id) => id !== action.meta.id);
279
  return {
280
    ...subState,
281
    byId: deleteProperty(subState.byId, action.meta.id),
282
    idsByApplicant: mapObjectValues(subState.idsByApplicant, dropId),
283
    idsByApplication: mapObjectValues(subState.idsByApplication, dropId),
284
  };
285
}
286
287
export const entitiesReducer = (
288
  state = initEntities(),
289
  action: ExperienceAction,
290
): EntityState => {
291
  switch (action.type) {
292
    case FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED:
293
      return {
294
        ...state,
295
        work: fetchExperienceByApplicant(state, action, "work"),
296
        education: fetchExperienceByApplicant(state, action, "education"),
297
        community: fetchExperienceByApplicant(state, action, "community"),
298
        award: fetchExperienceByApplicant(state, action, "award"),
299
        personal: fetchExperienceByApplicant(state, action, "personal"),
300
      };
301
    case FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED:
302
      return {
303
        ...state,
304
        work: fetchExperienceByApplication(state, action, "work"),
305
        education: fetchExperienceByApplication(state, action, "education"),
306
        community: fetchExperienceByApplication(state, action, "community"),
307
        award: fetchExperienceByApplication(state, action, "award"),
308
        personal: fetchExperienceByApplication(state, action, "personal"),
309
      };
310
    case CREATE_EXPERIENCE_SUCCEEDED:
311
    case UPDATE_EXPERIENCE_SUCCEEDED:
312
      return {
313
        ...state,
314
        [massageType(action.meta.type)]: setExperience(
315
          state,
316
          action,
317
          massageType(action.meta.type),
318
        ),
319
      };
320
    case DELETE_EXPERIENCE_SUCCEEDED:
321
      return {
322
        ...state,
323
        [massageType(action.meta.type)]: deleteExperience(
324
          state,
325
          action,
326
          massageType(action.meta.type),
327
        ),
328
      };
329
    default:
330
      return state;
331
  }
332
};
333
334
export const uiReducer = (
335
  state = initUi(),
336
  action: ExperienceAction,
337
): UiState => {
338
  switch (action.type) {
339
    case FETCH_EXPERIENCE_BY_APPLICANT_STARTED:
340
      return {
341
        ...state,
342
        updatingByApplicant: {
343
          ...state.updatingByApplicant,
344
          [action.meta.applicantId]: true,
345
        },
346
      };
347
    case FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED:
348
    case FETCH_EXPERIENCE_BY_APPLICANT_FAILED:
349
      return {
350
        ...state,
351
        updatingByApplicant: {
352
          ...state.updatingByApplicant,
353
          [action.meta.applicantId]: false,
354
        },
355
      };
356
    case FETCH_EXPERIENCE_BY_APPLICATION_STARTED:
357
      return {
358
        ...state,
359
        updatingByApplication: {
360
          ...state.updatingByApplication,
361
          [action.meta.applicationId]: true,
362
        },
363
      };
364
    case FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED:
365
    case FETCH_EXPERIENCE_BY_APPLICATION_FAILED:
366
      return {
367
        ...state,
368
        updatingByApplication: {
369
          ...state.updatingByApplication,
370
          [action.meta.applicationId]: false,
371
        },
372
      };
373
    case UPDATE_EXPERIENCE_STARTED:
374
    case DELETE_EXPERIENCE_STARTED:
375
      return {
376
        ...state,
377
        updatingByTypeAndId: {
378
          ...state.updatingByTypeAndId,
379
          [massageType(action.meta.type)]: {
380
            ...state.updatingByTypeAndId[massageType(action.meta.type)],
381
            [action.meta.id]: true,
382
          },
383
        },
384
      };
385
    case UPDATE_EXPERIENCE_SUCCEEDED:
386
    case DELETE_EXPERIENCE_SUCCEEDED:
387
    case UPDATE_EXPERIENCE_FAILED:
388
    case DELETE_EXPERIENCE_FAILED:
389
      return {
390
        ...state,
391
        updatingByTypeAndId: {
392
          ...state.updatingByTypeAndId,
393
          [massageType(action.meta.type)]: {
394
            ...state.updatingByTypeAndId[massageType(action.meta.type)],
395
            [action.meta.id]: false,
396
          },
397
        },
398
      };
399
    default:
400
      return state;
401
  }
402
};
403
404
export const experienceReducer = combineReducers({
405
  entities: entitiesReducer,
406
  ui: uiReducer,
407
});
408
409
export default experienceReducer;
410