Passed
Push — feature/redux-applications ( b47837 )
by Tristan
05:21
created

resources/assets/js/store/Experience/experienceReducer.ts   A

Complexity

Total Complexity 21
Complexity/F 2.33

Size

Lines of Code 395
Function Count 9

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 21
eloc 316
mnd 12
bc 12
fnc 9
dl 0
loc 395
rs 10
bpm 1.3333
cpm 2.3333
noi 0
c 0
b 0
f 0

9 Functions

Rating   Name   Duplication   Size   Complexity  
A experienceReducer.ts ➔ isWork 0 5 1
A experienceReducer.ts ➔ isEducation 0 4 1
A experienceReducer.ts ➔ isAward 0 4 1
A experienceReducer.ts ➔ fetchExperienceByApplicant 0 20 2
A experienceReducer.ts ➔ deleteExperience 0 20 2
A experienceReducer.ts ➔ fetchExperienceByApplication 0 21 2
B experienceReducer.ts ➔ setExperience 0 45 8
A experienceReducer.ts ➔ isCommunity 0 4 1
A experienceReducer.ts ➔ isPersonal 0 4 1
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(
131
  experience: Experience,
132
): experience is ExperienceWork & { type: "work" } {
133
  return experience.type === "work";
134
}
135
function isEducation(
136
  experience: Experience,
137
): experience is ExperienceEducation & { type: "education" } {
138
  return experience.type === "education";
139
}
140
function isCommunity(
141
  experience: Experience,
142
): experience is ExperienceCommunity & { type: "community" } {
143
  return experience.type === "community";
144
}
145
function isAward(
146
  experience: Experience,
147
): experience is ExperienceAward & { type: "award" } {
148
  return experience.type === "award";
149
}
150
function isPersonal(
151
  experience: Experience,
152
): experience is ExperiencePersonal & { type: "personal" } {
153
  return experience.type === "personal";
154
}
155
156
const experienceTypeGuards = {
157
  work: isWork,
158
  education: isEducation,
159
  community: isCommunity,
160
  award: isAward,
161
  personal: isPersonal,
162
};
163
164
function fetchExperienceByApplication<T extends keyof EntityState>(
165
  state: EntityState,
166
  action: ExperienceAction,
167
  type: T,
168
): EntityState[T] {
169
  const subState = state[type];
170
  if (action.type !== FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED) {
171
    return subState;
172
  }
173
  const typeFilter = experienceTypeGuards[type];
174
  const experiences = action.payload.filter(typeFilter);
175
  return {
176
    ...subState,
177
    byId: {
178
      ...subState.byId,
179
      ...mapToObject(experiences, getId),
180
    },
181
    idsByApplication: {
182
      ...subState.idsByApplicant,
183
      [action.meta.applicationId]: experiences.map(getId),
184
    },
185
  };
186
}
187
function fetchExperienceByApplicant<T extends keyof EntityState>(
188
  state: EntityState,
189
  action: ExperienceAction,
190
  type: T,
191
): EntityState[T] {
192
  const subState = state[type];
193
  if (action.type !== FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED) {
194
    return subState;
195
  }
196
  const typeFilter = experienceTypeGuards[type];
197
  const experiences = action.payload.filter(typeFilter);
198
  return {
199
    ...subState,
200
    byId: {
201
      ...subState.byId,
202
      ...mapToObject(experiences, getId),
203
    },
204
    idsByApplicant: {
205
      ...subState.idsByApplicant,
206
      [action.meta.applicantId]: experiences.map(getId),
207
    },
208
  };
209
}
210
211
function setExperience<T extends keyof EntityState>(
212
  state: EntityState,
213
  action: ExperienceAction,
214
  type: T,
215
): EntityState[T] {
216
  const subState = state[type];
217
  if (
218
    (action.type !== CREATE_EXPERIENCE_SUCCEEDED &&
219
      action.type !== UPDATE_EXPERIENCE_SUCCEEDED) ||
220
    action.meta.type !== type
221
  ) {
222
    return subState;
223
  }
224
  const experience = action.payload;
225
  const ownerId = experience.experienceable_id;
226
  const idsByApplicant =
227
    experience.experienceable_type === "applicant"
228
      ? {
229
          ...subState.idsByApplicant,
230
          [ownerId]: uniq([
231
            ...(subState.idsByApplicant[ownerId] ?? []),
232
            experience.id,
233
          ]),
234
        }
235
      : subState.idsByApplicant;
236
  const idsByApplication =
237
    experience.experienceable_type === "application"
238
      ? {
239
          ...subState.idsByApplication,
240
          [ownerId]: uniq([
241
            ...(subState.idsByApplication[ownerId] ?? []),
242
            experience.id,
243
          ]),
244
        }
245
      : subState.idsByApplication;
246
247
  return {
248
    ...subState,
249
    byId: {
250
      ...subState.byId,
251
      [action.payload.id]: action.payload,
252
    },
253
    idsByApplicant,
254
    idsByApplication,
255
  };
256
}
257
258
function deleteExperience<T extends keyof EntityState>(
259
  state: EntityState,
260
  action: ExperienceAction,
261
  type: T,
262
): EntityState[T] {
263
  const subState = state[type];
264
  if (
265
    action.type !== DELETE_EXPERIENCE_SUCCEEDED ||
266
    action.meta.type !== type
267
  ) {
268
    return subState;
269
  }
270
  const dropId = (ids: number[]): number[] =>
271
    ids.filter((id) => id !== action.meta.id);
272
  return {
273
    ...subState,
274
    byId: deleteProperty(subState.byId, action.meta.id),
275
    idsByApplicant: mapObjectValues(subState.idsByApplicant, dropId),
276
    idsByApplication: mapObjectValues(subState.idsByApplication, dropId),
277
  };
278
}
279
280
export const entitiesReducer = (
281
  state = initEntities(),
282
  action: ExperienceAction,
283
): EntityState => {
284
  switch (action.type) {
285
    case FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED:
286
      return {
287
        ...state,
288
        work: fetchExperienceByApplicant(state, action, "work"),
289
        education: fetchExperienceByApplicant(state, action, "education"),
290
        community: fetchExperienceByApplicant(state, action, "community"),
291
        award: fetchExperienceByApplicant(state, action, "award"),
292
        personal: fetchExperienceByApplicant(state, action, "personal"),
293
      };
294
    case FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED:
295
      return {
296
        ...state,
297
        work: fetchExperienceByApplication(state, action, "work"),
298
        education: fetchExperienceByApplication(state, action, "education"),
299
        community: fetchExperienceByApplication(state, action, "community"),
300
        award: fetchExperienceByApplication(state, action, "award"),
301
        personal: fetchExperienceByApplication(state, action, "personal"),
302
      };
303
    case CREATE_EXPERIENCE_SUCCEEDED:
304
    case UPDATE_EXPERIENCE_SUCCEEDED:
305
      return {
306
        ...state,
307
        [action.meta.type]: setExperience(state, action, action.meta.type),
308
      };
309
    case DELETE_EXPERIENCE_SUCCEEDED:
310
      return {
311
        ...state,
312
        [action.meta.type]: deleteExperience(state, action, action.meta.type),
313
      };
314
    default:
315
      return state;
316
  }
317
};
318
319
export const uiReducer = (
320
  state = initUi(),
321
  action: ExperienceAction,
322
): UiState => {
323
  switch (action.type) {
324
    case FETCH_EXPERIENCE_BY_APPLICANT_STARTED:
325
      return {
326
        ...state,
327
        updatingByApplicant: {
328
          ...state.updatingByApplicant,
329
          [action.meta.applicantId]: true,
330
        },
331
      };
332
    case FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED:
333
    case FETCH_EXPERIENCE_BY_APPLICANT_FAILED:
334
      return {
335
        ...state,
336
        updatingByApplicant: {
337
          ...state.updatingByApplicant,
338
          [action.meta.applicantId]: false,
339
        },
340
      };
341
    case FETCH_EXPERIENCE_BY_APPLICATION_STARTED:
342
      return {
343
        ...state,
344
        updatingByApplication: {
345
          ...state.updatingByApplication,
346
          [action.meta.applicationId]: true,
347
        },
348
      };
349
    case FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED:
350
    case FETCH_EXPERIENCE_BY_APPLICATION_FAILED:
351
      return {
352
        ...state,
353
        updatingByApplication: {
354
          ...state.updatingByApplication,
355
          [action.meta.applicationId]: false,
356
        },
357
      };
358
    case UPDATE_EXPERIENCE_STARTED:
359
    case DELETE_EXPERIENCE_STARTED:
360
      return {
361
        ...state,
362
        updatingByTypeAndId: {
363
          ...state.updatingByTypeAndId,
364
          [action.meta.type]: {
365
            ...state.updatingByTypeAndId[action.meta.type],
366
            [action.meta.id]: true,
367
          },
368
        },
369
      };
370
    case UPDATE_EXPERIENCE_SUCCEEDED:
371
    case DELETE_EXPERIENCE_SUCCEEDED:
372
    case UPDATE_EXPERIENCE_FAILED:
373
    case DELETE_EXPERIENCE_FAILED:
374
      return {
375
        ...state,
376
        updatingByTypeAndId: {
377
          ...state.updatingByTypeAndId,
378
          [action.meta.type]: {
379
            ...state.updatingByTypeAndId[action.meta.type],
380
            [action.meta.id]: false,
381
          },
382
        },
383
      };
384
    default:
385
      return state;
386
  }
387
};
388
389
export const experienceReducer = combineReducers({
390
  entities: entitiesReducer,
391
  ui: uiReducer,
392
});
393
394
export default experienceReducer;
395