placementt-core 11.0.533 → 11.0.892
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/constants.d.ts +13 -1
- package/lib/constants.js +86 -1
- package/lib/constants.js.map +1 -1
- package/lib/features/analytics/useAnalytics.d.ts +2 -0
- package/lib/features/analytics/useAnalytics.js +22 -19
- package/lib/features/analytics/useAnalytics.js.map +1 -1
- package/lib/features/global/downtime/useDowntime.d.ts +1 -0
- package/lib/features/global/downtime/useDowntime.js +9 -7
- package/lib/features/global/downtime/useDowntime.js.map +1 -1
- package/lib/features/global/users/useUserFunctions.js +1 -1
- package/lib/features/global/users/useUserFunctions.js.map +1 -1
- package/lib/features/jobs/jobsSlice.d.ts +10 -2
- package/lib/features/jobs/jobsSlice.js +5 -2
- package/lib/features/jobs/jobsSlice.js.map +1 -1
- package/lib/features/placements/studentPlacements/activePlacement.d.ts +5 -1
- package/lib/features/placements/studentPlacements/activePlacement.js +7 -3
- package/lib/features/placements/studentPlacements/activePlacement.js.map +1 -1
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.d.ts +3 -2
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.js +4 -1
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.js.map +1 -1
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.d.ts +2 -2
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.js +1 -1
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.js.map +1 -1
- package/lib/features/placements/studentPlacements/useStudentPlacements.d.ts +2 -12
- package/lib/features/placements/studentPlacements/useStudentPlacements.js +1 -26
- package/lib/features/placements/studentPlacements/useStudentPlacements.js.map +1 -1
- package/lib/features/updates/useUpdates.d.ts +1 -0
- package/lib/features/updates/useUpdates.js +13 -12
- package/lib/features/updates/useUpdates.js.map +1 -1
- package/lib/firebase/firebase.d.ts +5 -3
- package/lib/firebase/firebase.js +23 -12
- package/lib/firebase/firebase.js.map +1 -1
- package/lib/firebase/firebaseConfig.js.map +1 -1
- package/lib/firebase/firebaseQuery.d.ts +6 -2
- package/lib/firebase/firebaseQuery.js +11 -3
- package/lib/firebase/firebaseQuery.js.map +1 -1
- package/lib/firebase/readDatabase.d.ts +2 -4
- package/lib/firebase/readDatabase.js +30 -5
- package/lib/firebase/readDatabase.js.map +1 -1
- package/lib/firebase/writeDatabase.d.ts +6 -2
- package/lib/firebase/writeDatabase.js +2 -1
- package/lib/firebase/writeDatabase.js.map +1 -1
- package/lib/hooks.d.ts +277 -192
- package/lib/hooks.js +1437 -704
- package/lib/hooks.js.map +1 -1
- package/lib/reduxHooks.d.ts +122 -5
- package/lib/reduxHooks.js +132 -29
- package/lib/reduxHooks.js.map +1 -1
- package/lib/tasksAndTips.d.ts +19 -7
- package/lib/tasksAndTips.js +637 -164
- package/lib/tasksAndTips.js.map +1 -1
- package/lib/typeDefinitions.d.ts +321 -110
- package/lib/util.d.ts +15 -3
- package/lib/util.js +47 -10
- package/lib/util.js.map +1 -1
- package/package.json +7 -4
- package/src/constants.ts +91 -3
- package/src/features/analytics/useAnalytics.tsx +25 -17
- package/src/features/global/downtime/useDowntime.tsx +11 -7
- package/src/features/global/users/useUserFunctions.tsx +1 -1
- package/src/features/jobs/jobsSlice.ts +9 -3
- package/src/features/placements/studentPlacements/activePlacement.ts +8 -3
- package/src/features/placements/studentPlacements/completedStudentPlacementsSlice.ts +5 -2
- package/src/features/placements/studentPlacements/upcomingStudentPlacementsSlice.ts +2 -2
- package/src/features/placements/studentPlacements/useStudentPlacements.tsx +4 -28
- package/src/features/updates/useUpdates.tsx +14 -12
- package/src/firebase/firebase.tsx +26 -15
- package/src/firebase/firebaseConfig.tsx +1 -1
- package/src/firebase/firebaseQuery.tsx +11 -3
- package/src/firebase/readDatabase.tsx +34 -6
- package/src/firebase/writeDatabase.tsx +3 -1
- package/src/hooks.tsx +1804 -935
- package/src/reduxHooks.ts +144 -32
- package/src/tasksAndTips.ts +689 -166
- package/src/typeDefinitions.ts +373 -109
- package/src/util.ts +63 -18
package/src/hooks.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DocumentData, QueryConstraint, QueryDocumentSnapshot, QueryFieldFilterConstraint, QuerySnapshot, Unsubscribe, collection, documentId,
|
|
2
|
+
DocumentData, QueryConstraint, QueryDocumentSnapshot, QueryFieldFilterConstraint, QueryOrderByConstraint, QuerySnapshot, Unsubscribe, collection, documentId,
|
|
3
3
|
endAt, endBefore, getDocs, limit, limitToLast, onSnapshot, orderBy, query, startAfter, where
|
|
4
4
|
} from "firebase/firestore";
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -11,13 +11,13 @@ import { getFiles, getFormsFromId, getPlacementbyId, getPlacementsWhere, getUser
|
|
|
11
11
|
import { arrayUniqueValues, arraysEqual, convertDate, editNestedObject, getAccess, getDateDiff, getUniqueId, objectsEqual, validateEmail } from "./firebase/util";
|
|
12
12
|
import { addPlacement, editPlacementStage, uploadFiles } from "./firebase/writeDatabase";
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
AlumniConvoUser,
|
|
15
15
|
ApplicantStage,
|
|
16
|
-
ApplicantWorkflow,
|
|
17
16
|
Application,
|
|
18
|
-
ArrowObject, CohortData, FileItem, FlagCodes, InstituteData, OnboardingDocs, PlacementListing, Products,
|
|
17
|
+
ArrowObject, CohortData, CustomFormSchema, FileItem, FlagCodes, InstituteData, OnboardingDocs, PlacementListing, Products,
|
|
18
|
+
ProviderContactData,
|
|
19
19
|
ProviderData,
|
|
20
|
-
QueryObject,
|
|
20
|
+
QueryObject, QueryObjectConstraint, SchoolData, Sorts, StudentPlacementData, UserData, UserGroupData, WorkflowStage
|
|
21
21
|
} from "./typeDefinitions";
|
|
22
22
|
import algoliasearch from "algoliasearch";
|
|
23
23
|
import {getDownloadURL, ref} from "firebase/storage";
|
|
@@ -28,7 +28,7 @@ type StudentPlacementListParams = {
|
|
|
28
28
|
user: UserData,
|
|
29
29
|
student?: UserData,
|
|
30
30
|
queryConstraint?: QueryConstraint[],
|
|
31
|
-
ql?: number
|
|
31
|
+
ql?: number // query limit
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const DEFAULTQUERYLIMIT = 5;
|
|
@@ -60,20 +60,20 @@ export function useStudentPlacementList({user, student, queryConstraint, ql=DEFA
|
|
|
60
60
|
setStudentId(student.id);
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
-
if (!user.viewCohorts || !user.
|
|
63
|
+
if (!user.viewCohorts || !user.viewStudents || user.viewCohorts === "none") {
|
|
64
64
|
setStudentId(undefined);
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
-
if (user.viewCohorts === "all" && user.
|
|
67
|
+
if (user.viewCohorts === "all" && user.viewStudents == "all") {
|
|
68
68
|
setStudentId(student.id);
|
|
69
69
|
}
|
|
70
|
-
if (user.viewCohorts === "some" && user.visibleCohorts?.
|
|
71
|
-
if (user.
|
|
70
|
+
if (user.viewCohorts === "some" && user.visibleCohorts?.includes(student.cohort || "")) {
|
|
71
|
+
if (user.viewStudents === "all") {
|
|
72
72
|
setStudentId(student.id);
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
if (user.studentFilterValues?.
|
|
76
|
+
if (user.studentFilterValues?.includes(student.details[user.studentFilter || ""])) {
|
|
77
77
|
setStudentId(student.id);
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
@@ -152,7 +152,22 @@ type InstitutePlacementParams = {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
type NewInstitutePlacementParams = {
|
|
156
|
+
id?: string,
|
|
157
|
+
user: UserData,
|
|
158
|
+
cohort?: string,
|
|
159
|
+
queryConstraints?: QueryObjectConstraint,
|
|
160
|
+
ql?: number,
|
|
161
|
+
inProgress?: boolean,
|
|
162
|
+
view: "list"|"table",
|
|
163
|
+
filters?: FilterObject,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
export function useOldInstitutePlacementList({id, user, cohort, queryConstraint, ql=DEFAULTQUERYLIMIT, inProgress}:InstitutePlacementParams) {
|
|
156
171
|
const [loadMoreIcon, setLoadMoreIcon] = useState(true);
|
|
157
172
|
const [query, setQuery] = useState<string>();
|
|
158
173
|
const [initialQueryLimit, setInitialQueryLimit] = useState(ql);
|
|
@@ -165,7 +180,6 @@ export function useNewInstitutePlacementList({id, user, cohort, queryConstraint,
|
|
|
165
180
|
|
|
166
181
|
const algoliaClient = algoliasearch(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
|
|
167
182
|
const placementsIndex = algoliaClient.initIndex("placements");
|
|
168
|
-
const usersIndex = algoliaClient.initIndex("users");
|
|
169
183
|
|
|
170
184
|
useEffect(() => {
|
|
171
185
|
if (user.product !== "institutes" || user.userType !== "Staff") {
|
|
@@ -190,36 +204,6 @@ export function useNewInstitutePlacementList({id, user, cohort, queryConstraint,
|
|
|
190
204
|
student: UserData;
|
|
191
205
|
}} = {}
|
|
192
206
|
|
|
193
|
-
let userSearchString = `userType:Students AND status:active AND oId:${user.oId} AND product:${user.product}`
|
|
194
|
-
if (cohort) {
|
|
195
|
-
userSearchString = userSearchString + ` AND cohort:${cohort}`;
|
|
196
|
-
}
|
|
197
|
-
if (user.product === "institutes" && user.userType === "Staff") {
|
|
198
|
-
const searchStudentHits = await usersIndex.search<UserData>(query, {
|
|
199
|
-
filters: userSearchString
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
if (searchStudentHits) {
|
|
203
|
-
console.log("FOUND", searchStudentHits.hits.length, "students");
|
|
204
|
-
|
|
205
|
-
await Promise.all(searchStudentHits.hits.map(async (hit) => {
|
|
206
|
-
console.log("STUDENT", hit.objectID);
|
|
207
|
-
const constraints = [...(cohort ? [where("cohort", "==", cohort)] : [])]
|
|
208
|
-
if (inProgress !== undefined) {
|
|
209
|
-
constraints.push(inProgress ? where("inProgress", "==", true) : where("completed", "==", true));
|
|
210
|
-
}
|
|
211
|
-
const fPlacements = await getPlacementsWhere({w: constraints, uid: hit.objectID, oId: hit.oId}) as {[key: string]: StudentPlacementData};
|
|
212
|
-
|
|
213
|
-
console.log("PLACEMENTS", fPlacements)
|
|
214
|
-
Object.entries(fPlacements).forEach(([k, v]) => {
|
|
215
|
-
placementsFound[k] = {...v, student: hit};
|
|
216
|
-
})
|
|
217
|
-
}))
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
207
|
let placementSearchString = `oId:${user.oId} AND ` + (inProgress !== undefined ? (inProgress ? "inProgress:true" : "completed:true" ) : "")
|
|
224
208
|
if (cohort) {
|
|
225
209
|
placementSearchString = placementSearchString + ` AND cohort:${cohort}`;
|
|
@@ -248,7 +232,6 @@ export function useNewInstitutePlacementList({id, user, cohort, queryConstraint,
|
|
|
248
232
|
setPlacements(placementsFound);
|
|
249
233
|
setLoadMoreIcon(false);
|
|
250
234
|
}
|
|
251
|
-
|
|
252
235
|
searchPlacements()
|
|
253
236
|
}, [query]);
|
|
254
237
|
|
|
@@ -275,11 +258,13 @@ export function useNewInstitutePlacementList({id, user, cohort, queryConstraint,
|
|
|
275
258
|
return;
|
|
276
259
|
}
|
|
277
260
|
|
|
261
|
+
|
|
262
|
+
|
|
278
263
|
setLoadMoreIcon(true);
|
|
279
264
|
|
|
280
265
|
let fPlacements:{[key:string]: StudentPlacementData&{student:UserData}} = {};
|
|
281
266
|
|
|
282
|
-
if (((user.userGroup === "admin" || (cohort && user.viewCohorts === "some" && user?.visibleCohorts?.
|
|
267
|
+
if (((user.userGroup === "admin" || (cohort && user.viewCohorts === "some" && user?.visibleCohorts?.includes(cohort as string)) || (user.viewCohorts === "all" && user.viewStudents === "all")))) {
|
|
283
268
|
|
|
284
269
|
const queryConstraintOrdered = Boolean(queryConstraints && queryConstraints.find((v) => v.type === "orderBy"));
|
|
285
270
|
|
|
@@ -295,16 +280,17 @@ export function useNewInstitutePlacementList({id, user, cohort, queryConstraint,
|
|
|
295
280
|
|
|
296
281
|
|
|
297
282
|
const placementsQuery = await getPlacementsWhere({w: constraints, oId: oId, raw: true}) as QuerySnapshot;
|
|
298
|
-
|
|
283
|
+
console.log("PLACEMENTS RETRIEVED", placementsQuery.size);
|
|
299
284
|
|
|
300
285
|
const placementsWithStudentData:([string, StudentPlacementData&{student:UserData}]|false)[] = placementsQuery.empty ? [] : await Promise.all(placementsQuery.docs.map(async (placement) => {
|
|
301
286
|
const pData = placement.data() as StudentPlacementData;
|
|
302
287
|
const student = pData.uid === user.id ? user : (await getUserById(pData.uid).catch(() => false)) as UserData;
|
|
303
|
-
|
|
304
|
-
if (
|
|
288
|
+
console.log("STUDENT", student, "PLACEMENT", id);
|
|
289
|
+
// if (!student) return false;
|
|
290
|
+
if (user.viewStudents === "some") {
|
|
305
291
|
if (!(user.studentFilter && user.studentFilterValues)) return false;
|
|
306
292
|
|
|
307
|
-
if (!user.studentFilterValues.
|
|
293
|
+
if (!user.studentFilterValues.includes(student.details[user.studentFilter])) {
|
|
308
294
|
return false;
|
|
309
295
|
}
|
|
310
296
|
}
|
|
@@ -332,11 +318,251 @@ export function useNewInstitutePlacementList({id, user, cohort, queryConstraint,
|
|
|
332
318
|
}
|
|
333
319
|
};
|
|
334
320
|
|
|
321
|
+
|
|
335
322
|
return ({...{placements, loadMoreIcon, loadMorePlacements, setQuery, setInitialQueryLimit, reset, changeQueryConstraints}});
|
|
336
323
|
}
|
|
337
324
|
|
|
338
325
|
|
|
339
|
-
export function
|
|
326
|
+
export function useNewInstitutePlacementList({id, user, filters, view, cohort, queryConstraints, ql=DEFAULTQUERYLIMIT, inProgress}:NewInstitutePlacementParams) {
|
|
327
|
+
const [query, setQuery] = useState<QueryObject[]>();
|
|
328
|
+
|
|
329
|
+
const sorts:Sorts = {
|
|
330
|
+
["Student Forename - Asc"]: {
|
|
331
|
+
value: "studentForename",
|
|
332
|
+
direction: "asc",
|
|
333
|
+
},
|
|
334
|
+
["Student Forename - Desc"]: {
|
|
335
|
+
value: "studentForename",
|
|
336
|
+
direction: "desc",
|
|
337
|
+
},
|
|
338
|
+
["Student Surname - Asc"]: {
|
|
339
|
+
value: "studentSurname",
|
|
340
|
+
direction: "asc",
|
|
341
|
+
},
|
|
342
|
+
["Student Surname - Desc"]: {
|
|
343
|
+
value: "studentSurname",
|
|
344
|
+
direction: "desc",
|
|
345
|
+
},
|
|
346
|
+
["Student Email - Asc"]: {
|
|
347
|
+
value: "studentEmail",
|
|
348
|
+
direction: "asc",
|
|
349
|
+
},
|
|
350
|
+
["Student Email - Desc"]: {
|
|
351
|
+
value: "studentEmaile",
|
|
352
|
+
direction: "desc",
|
|
353
|
+
},
|
|
354
|
+
["Provider email - Asc"]: {
|
|
355
|
+
value: "providerEmail",
|
|
356
|
+
direction: "asc",
|
|
357
|
+
},
|
|
358
|
+
["Provider email - Desc"]: {
|
|
359
|
+
value: "providerEmail",
|
|
360
|
+
direction: "desc",
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const additionalProcessing = async (k, placement:StudentPlacementData) => {
|
|
365
|
+
if (user.viewStudents === "some") {
|
|
366
|
+
if (!(user.studentFilter && user.studentFilterValues)) return false;
|
|
367
|
+
|
|
368
|
+
const student = await getUserById(placement.uid).catch(() => false) as UserData|false;
|
|
369
|
+
|
|
370
|
+
if (!student) return;
|
|
371
|
+
|
|
372
|
+
if (!user.studentFilterValues.includes(student.details[user.studentFilter])) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {...placement, id: k};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
const {tableData, pageUp, pageDown, setFilters, page, setView, loading, updateSearch, updateSort, sort} = useDataViewerPaginator({view, filters, sorts, queryLimit: ql, data: query, additionalEntryProcessing: additionalProcessing, onSearch: async (s, sort, page, filters, limit) => await algoliaPlacementSearch(user, s, sort, page, filters, limit, cohort, inProgress)})
|
|
381
|
+
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
// Sets the query of for the DataViewerPaginator
|
|
384
|
+
if(user.product !== "institutes" || user.userType !== "Staff") {
|
|
385
|
+
setQuery(undefined);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (((user.userGroup === "admin" || (cohort && user.viewCohorts === "some" && user?.visibleCohorts?.includes(cohort as string)) || (user.viewCohorts === "all" && user.viewStudents === "all")))) {
|
|
389
|
+
const constraints:QueryObjectConstraint = [["oId", "==", user.oId], ["draft", "==", false]];
|
|
390
|
+
|
|
391
|
+
cohort && constraints.push(["cohort", "==", cohort]);
|
|
392
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
393
|
+
inProgress !== undefined && constraints.push(["inProgress", "==", inProgress]);
|
|
394
|
+
|
|
395
|
+
setQuery([{
|
|
396
|
+
path: ["placements"],
|
|
397
|
+
where: constraints
|
|
398
|
+
}])
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
setQuery(undefined)
|
|
402
|
+
}, [user, queryConstraints, cohort])
|
|
403
|
+
|
|
404
|
+
return {tableData, page, loading, updateSearch, setFilters, setView, pageUp, pageDown, sorts, updateSort, sort}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
type AlumniPaginatorParams = {
|
|
410
|
+
user?: UserData,
|
|
411
|
+
alumniConvoUser?: AlumniConvoUser,
|
|
412
|
+
school?: string,
|
|
413
|
+
queryConstraints?: QueryObjectConstraint,
|
|
414
|
+
ql?: number,
|
|
415
|
+
view: "list"|"table",
|
|
416
|
+
filters?: FilterObject,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
export function useAlumniPaginator({user, alumniConvoUser, filters, view, school, queryConstraints, ql=DEFAULTQUERYLIMIT}:AlumniPaginatorParams) {
|
|
422
|
+
const [query, setQuery] = useState<QueryObject[]>();
|
|
423
|
+
|
|
424
|
+
const sorts:Sorts = {}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
const {tableData, pageUp, pageDown, setFilters, page, setView, loading} = useDataViewerPaginator({view, filters, sorts, queryLimit: ql, data: query})
|
|
428
|
+
const firebaseQuery = new FirebaseQuery();
|
|
429
|
+
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
const createQuery = async () => {
|
|
432
|
+
// Sets the query of for the DataViewerPaginator
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
const getQueryAccess = async () => {
|
|
436
|
+
const constraints:QueryObjectConstraint = [];
|
|
437
|
+
|
|
438
|
+
if (user) {
|
|
439
|
+
constraints.push(["oId", "==", user.oId]);
|
|
440
|
+
|
|
441
|
+
if (user.userGroup === "admin" && user.userType === "Staff") return constraints;
|
|
442
|
+
|
|
443
|
+
if (user.userType === "Staff") {
|
|
444
|
+
if (user.viewSchools === "all") return constraints;
|
|
445
|
+
|
|
446
|
+
if (user.viewSchools === "none") return false;
|
|
447
|
+
|
|
448
|
+
if (user.viewSchools === "some") {
|
|
449
|
+
if (!school) return false;
|
|
450
|
+
|
|
451
|
+
if (user?.visibleSchools?.includes(school as string)) return constraints;
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (alumniConvoUser) {
|
|
459
|
+
constraints.push(["oId", "==", alumniConvoUser.oId]);
|
|
460
|
+
console.log("ALUMNI CONVO USER");
|
|
461
|
+
|
|
462
|
+
if ((school || alumniConvoUser.schoolId) && school === alumniConvoUser.schoolId) return constraints;
|
|
463
|
+
console.log("ALUMNI ACCESS TRUE");
|
|
464
|
+
if (alumniConvoUser.schoolId) {
|
|
465
|
+
const school = await firebaseQuery.getDocData(["schools", alumniConvoUser.schoolId]) as SchoolData;
|
|
466
|
+
console.log("SCHOOL ACCESS", school, school.alumniConversations);
|
|
467
|
+
return school.alumniConversations ? constraints : false;
|
|
468
|
+
|
|
469
|
+
} else {
|
|
470
|
+
const institute = await firebaseQuery.getDocData(["institutes", alumniConvoUser.oId]) as InstituteData;
|
|
471
|
+
return institute.alumniConversations ? constraints : false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
const constraints = await getQueryAccess();
|
|
477
|
+
console.log("CONSTRAINTS", constraints);
|
|
478
|
+
if (!constraints) return;
|
|
479
|
+
|
|
480
|
+
school && constraints.push(["schoolId", "==", school]);
|
|
481
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
482
|
+
|
|
483
|
+
return constraints;
|
|
484
|
+
}
|
|
485
|
+
console.log("Creating query");
|
|
486
|
+
createQuery().then((constraints) => {
|
|
487
|
+
setQuery([{
|
|
488
|
+
path: ["alumni"],
|
|
489
|
+
where: constraints
|
|
490
|
+
}])
|
|
491
|
+
})
|
|
492
|
+
}, [user, queryConstraints, school])
|
|
493
|
+
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
console.log("Alumni data", query, tableData);
|
|
496
|
+
}, [tableData]);
|
|
497
|
+
|
|
498
|
+
return {tableData, page, loading, setFilters, setView, pageUp, pageDown, sorts}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
const algoliaPlacementSearch = async (user: UserData, query?: string, sort?: [string, {value: string, direction: "asc"|"desc"}], page?: number, filters?: FilterObject, limit?: number, cohort?: string, inProgress?: boolean) => {
|
|
503
|
+
const algoliaClient = algoliasearch(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
|
|
504
|
+
const placementsIndex = algoliaClient.initIndex(sort ? Object.values(sort[1]).join("_") : "placements");
|
|
505
|
+
// const usersIndex = algoliaClient.initIndex("users");
|
|
506
|
+
|
|
507
|
+
// let userSearchString = `userType:Students AND status:active AND oId:${user.oId} AND product:${user.product}`
|
|
508
|
+
// if (cohort) {
|
|
509
|
+
// userSearchString = userSearchString + ` AND cohort:${cohort}`;
|
|
510
|
+
// }
|
|
511
|
+
|
|
512
|
+
// if (user.product === "institutes" && user.userType === "Staff") {
|
|
513
|
+
// const searchStudentHits = await usersIndex.search<UserData>(query, {
|
|
514
|
+
// filters: userSearchString,
|
|
515
|
+
// hitsPerPage: limit,
|
|
516
|
+
// page: page
|
|
517
|
+
// });
|
|
518
|
+
|
|
519
|
+
// if (searchStudentHits) {
|
|
520
|
+
// console.log("FOUND", searchStudentHits.hits.length, "students");
|
|
521
|
+
|
|
522
|
+
// await Promise.all(searchStudentHits.hits.map(async (hit) => {
|
|
523
|
+
// console.log("STUDENT", hit.objectID);
|
|
524
|
+
// const constraints = [...(cohort ? [where("cohort", "==", cohort)] : [])]
|
|
525
|
+
// if (inProgress !== undefined) {
|
|
526
|
+
// constraints.push(inProgress ? where("inProgress", "==", true) : where("completed", "==", true));
|
|
527
|
+
// }
|
|
528
|
+
// const fPlacements = await getPlacementsWhere({w: constraints, uid: hit.objectID, oId: hit.oId}) as {[key: string]: StudentPlacementData};
|
|
529
|
+
|
|
530
|
+
// console.log("PLACEMENTS", fPlacements)
|
|
531
|
+
// Object.entries(fPlacements).forEach(([k, v]) => {
|
|
532
|
+
// placementsFound[k] = {...v, student: hit};
|
|
533
|
+
// })
|
|
534
|
+
// }))
|
|
535
|
+
// }
|
|
536
|
+
// }
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
let placementSearchString = `oId:${user.oId} AND ` + (inProgress !== undefined ? (inProgress ? "inProgress:true" : "completed:true" ) : "")
|
|
540
|
+
if (cohort) {
|
|
541
|
+
placementSearchString = placementSearchString + ` AND cohort:${cohort}`;
|
|
542
|
+
}
|
|
543
|
+
filters && Object.entries(filters).filter(([, filter]) => filter.value).map(([id, filter]) => {
|
|
544
|
+
placementSearchString = placementSearchString + ` AND ${id}:${filter.value}`;
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const options = {
|
|
548
|
+
filters: placementSearchString,
|
|
549
|
+
hitsPerPage: limit,
|
|
550
|
+
page: page ? page - 1 : undefined,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const searchPlacementHits = await placementsIndex.search<StudentPlacementData>(query || "", options);
|
|
554
|
+
|
|
555
|
+
console.log(searchPlacementHits.hits);
|
|
556
|
+
|
|
557
|
+
const i = (searchPlacementHits ? (await Promise.all(searchPlacementHits.hits.map(async (hit) => {
|
|
558
|
+
return [hit.objectID, hit];
|
|
559
|
+
}))) : []).filter((e) => e !== undefined) as [string, StudentPlacementData][];
|
|
560
|
+
|
|
561
|
+
return Object.fromEntries(i)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
export function useVeryOldInstitutePlacementList({user, cohort, queryConstraint, ql=DEFAULTQUERYLIMIT}:InstitutePlacementParams) {
|
|
340
566
|
const [loadMoreIcon, setLoadMoreIcon] = useState(true);
|
|
341
567
|
const [query, setQuery] = useState("");
|
|
342
568
|
const [initialQueryLimit, setInitialQueryLimit] = useState(ql);
|
|
@@ -391,7 +617,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
391
617
|
|
|
392
618
|
let fPlacements:{[key:string]: StudentPlacementData&{student:UserData}} = {};
|
|
393
619
|
|
|
394
|
-
if (((user.userGroup === "admin" || (cohort && user.viewCohorts === "some" && user?.visibleCohorts?.
|
|
620
|
+
if (((user.userGroup === "admin" || (cohort && user.viewCohorts === "some" && user?.visibleCohorts?.includes(cohort as string)) || (user.viewCohorts === "all" && user.viewStudents === "all") || query) && (fStartPlacementAfter && fStartPlacementAfter[0] === "placement"))) {
|
|
395
621
|
const queryConstraintOrdered = Boolean(queryConstraints && queryConstraints.find((v) => v.type === "orderBy"));
|
|
396
622
|
|
|
397
623
|
const constraints:QueryConstraint[] = fStartPlacementAfter?.length === 4 && fStartPlacementAfter[1] ?
|
|
@@ -413,10 +639,10 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
413
639
|
const pData = placement.data() as StudentPlacementData;
|
|
414
640
|
const student = pData.uid === user.id ? user : (await getUserById(pData.uid).catch(() => false)) as UserData;
|
|
415
641
|
|
|
416
|
-
if (user.
|
|
642
|
+
if (user.viewStudents === "some") {
|
|
417
643
|
if (!(user.studentFilter && user.studentFilterValues)) return false;
|
|
418
644
|
|
|
419
|
-
if (!user.studentFilterValues.
|
|
645
|
+
if (!user.studentFilterValues.includes(student.details[user.studentFilter])) {
|
|
420
646
|
return false;
|
|
421
647
|
}
|
|
422
648
|
}
|
|
@@ -446,7 +672,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
446
672
|
}
|
|
447
673
|
}
|
|
448
674
|
|
|
449
|
-
if (!(user.studentFilterValues && user.studentFilter && user.
|
|
675
|
+
if (!(user.studentFilterValues && user.studentFilter && user.viewStudents === "some")) {
|
|
450
676
|
console.log("fenaibn");
|
|
451
677
|
return;
|
|
452
678
|
};
|
|
@@ -468,7 +694,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
468
694
|
);
|
|
469
695
|
|
|
470
696
|
if (ffStartPlacementAfter && ffStartPlacementAfter[3]) {
|
|
471
|
-
const currentFilter = user?.studentFilterValues?.
|
|
697
|
+
const currentFilter = user?.studentFilterValues?.[ffStartPlacementAfter[3]];
|
|
472
698
|
|
|
473
699
|
if (currentFilter && user.studentFilter) {
|
|
474
700
|
constraints.push(where(user.studentFilter, "==", currentFilter));
|
|
@@ -481,7 +707,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
481
707
|
// console.log("student", student);
|
|
482
708
|
|
|
483
709
|
// If there is no students but more filters
|
|
484
|
-
if (!student && ffStartPlacementAfter && ffStartPlacementAfter[3] + 1 < (user?.studentFilterValues
|
|
710
|
+
if (!student && ffStartPlacementAfter && ffStartPlacementAfter[3] + 1 < (user?.studentFilterValues || []).length) {
|
|
485
711
|
// console.log("No more students. Calling recursion");
|
|
486
712
|
return await getStudentPlacements(["student", undefined, undefined, ffStartPlacementAfter[3]+1], ffPlacements);
|
|
487
713
|
}
|
|
@@ -726,7 +952,7 @@ export function useFilterTablePaginator({data}:{data:{[key:string]:{[key:string]
|
|
|
726
952
|
const listenForUpdates = () => {
|
|
727
953
|
if (!Object.keys(itemList).length) return;
|
|
728
954
|
console.log("Fetching filter table updates")
|
|
729
|
-
const itemListUpdateQuery = query(collection(db,
|
|
955
|
+
const itemListUpdateQuery = query(collection(db, ...(querySchema.path as [any])), where(documentId(), "in", Object.keys(itemList)));
|
|
730
956
|
const itemUpdateSnapshot = onSnapshot(itemListUpdateQuery, (querySnapshot) => {
|
|
731
957
|
querySnapshot.docs.forEach((doc) => {
|
|
732
958
|
setTableData((data) => {
|
|
@@ -762,16 +988,120 @@ export function useFilterTablePaginator({data}:{data:{[key:string]:{[key:string]
|
|
|
762
988
|
return ({...{tableData, setPage, setFilters, page}});
|
|
763
989
|
}
|
|
764
990
|
|
|
765
|
-
export function
|
|
766
|
-
const [
|
|
767
|
-
const
|
|
991
|
+
export function useProviderContactPaginator({data, user, view, filters}:{data:QueryObject[], user: UserData, view: "list"|"table", filters?: FilterObject}) {
|
|
992
|
+
const [query, setQuery] = useState<QueryObject[]>();
|
|
993
|
+
const firebaseQuery = new FirebaseQuery();
|
|
994
|
+
|
|
995
|
+
const getAdditionalData = async (k: string, v: ProviderContactData) => {
|
|
996
|
+
if (v.savedBy?.[user.oId].activities?.includes("workExperience")) {
|
|
997
|
+
const placementsCount = await firebaseQuery.getCount("placementListings", [where(`savedBy.${user.oId}.exists`, "==", true), where("providerContactId", "==", k)]);
|
|
998
|
+
return {...v, plcaements: placementsCount};
|
|
999
|
+
}
|
|
1000
|
+
return v;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const {tableData, pageUp, pageDown, setFilters, page, setView, loading} = useDataViewerPaginator({view, filters, queryLimit: 20, data: query, additionalEntryProcessing: getAdditionalData})
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
useEffect(() => {
|
|
1007
|
+
const constraints:QueryObjectConstraint = [
|
|
1008
|
+
[`savedBy.${user.oId}.exists`, "==", true],
|
|
1009
|
+
]
|
|
1010
|
+
|
|
1011
|
+
if (user.userType === "Students") {
|
|
1012
|
+
constraints.push([`savedBy.${user.oId}.cohorts.${user.cohort}.listed`, "==", true]);
|
|
1013
|
+
}
|
|
1014
|
+
setQuery([{
|
|
1015
|
+
path: ["providerContacts"],
|
|
1016
|
+
where: constraints
|
|
1017
|
+
}])
|
|
1018
|
+
}, [user]);
|
|
1019
|
+
|
|
1020
|
+
return ({...{tableData, pageUp, pageDown, setFilters, page, setView, loading}});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
type UserPaginatorParams = {
|
|
1024
|
+
user: UserData,
|
|
1025
|
+
cohort?: string,
|
|
1026
|
+
data: QueryObject[],
|
|
1027
|
+
search?: string,
|
|
1028
|
+
userType: "Staff"|"Students",
|
|
1029
|
+
sort?: string
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
export function useCohortUserPaginator({user, cohort, data, search, userType, sort}:UserPaginatorParams) {
|
|
1033
|
+
const [tableData, setTableData] = useState<{[key:string]:{[key:string]: unknown}}>({});
|
|
1034
|
+
const [queryAnchor, setQueryAnchor] = useState<{startDoc?: QueryDocumentSnapshot, endDoc?: QueryDocumentSnapshot, startQueryPos: number, endQueryPos: number}>({startDoc: undefined, endDoc: undefined, startQueryPos: 0, endQueryPos: 0});
|
|
768
1035
|
const [filters, setFilters] = useState<{[key:string]: unknown}>();
|
|
769
|
-
const [
|
|
1036
|
+
const [sortResultsBy, setSortResultsBy] = useState<string|undefined>(sort);
|
|
770
1037
|
const [prevEntryIds, setPrevEntryIds] = useState<{[key:string]:number}>({});
|
|
771
|
-
const
|
|
1038
|
+
const [page, setPage] = useState([1, 0]);
|
|
1039
|
+
const [dataListenerUnsubscribe, setDataListenerUnsubscribe] = useState<Unsubscribe|undefined>();
|
|
1040
|
+
const [queries, setQueries] = useState<QueryConstraint[][]>();
|
|
1041
|
+
const [prevSearch, setPrevSearch] = useState<string>();
|
|
1042
|
+
|
|
1043
|
+
const algoliaClient = algoliasearch(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
|
|
1044
|
+
const usersIndex = algoliaClient.initIndex("users");
|
|
1045
|
+
|
|
1046
|
+
const sortOptions = {
|
|
1047
|
+
["Forename - Asc"]: ["details.forename", "asc"],
|
|
1048
|
+
["Forename - Desc"]: ["details.forename", "desc"],
|
|
1049
|
+
["Surname - Asc"]: ["details.surname", "desc"],
|
|
1050
|
+
["Surname - Desc"]: ["details.surname", "asc"],
|
|
1051
|
+
["Email - Asc"]: ["email", "asc"],
|
|
1052
|
+
["Email - Desc"]: ["email", "desc"],
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
useEffect(() => {
|
|
1056
|
+
if (user.userType !== "Staff") {
|
|
1057
|
+
console.log("Not a staff member", user)
|
|
1058
|
+
setQueries(undefined);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (
|
|
1062
|
+
(!user.viewCohorts && user.userGroup !== "admin") ||
|
|
1063
|
+
(user.viewCohorts === "none" && user.userGroup !== "admin") ||
|
|
1064
|
+
(user.viewCohorts === "some" && user.userGroup !== "admin" && cohort !== "all" && !user.visibleCohorts?.includes(cohort || ""))) {
|
|
1065
|
+
setQueries(undefined);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
setQueries(() => {
|
|
1069
|
+
const finalConstraints:QueryConstraint[][] = [];
|
|
1070
|
+
console.log("data", data, data.length)
|
|
1071
|
+
for (var i = 0; i < data.length; i++) {
|
|
1072
|
+
const item = data[i]
|
|
1073
|
+
console.log("item", item)
|
|
1074
|
+
const constraints:QueryConstraint[] = [];
|
|
1075
|
+
item.where && item.where.forEach((w) => {
|
|
1076
|
+
constraints.push(where(...w));
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
constraints.push(where("oId", "==", user.oId));
|
|
1080
|
+
cohort && cohort !== "all" && constraints.push(where("cohort", "==", cohort));
|
|
1081
|
+
|
|
1082
|
+
if (user.userGroup === "admin" || user.viewStudents === "all") {
|
|
1083
|
+
finalConstraints.push(constraints);
|
|
1084
|
+
continue
|
|
1085
|
+
}
|
|
1086
|
+
if (!user.studentFilter || !user.studentFilterValues) continue;
|
|
1087
|
+
user.studentFilterValues.forEach((filterValue) => {
|
|
1088
|
+
user.studentFilter && finalConstraints.push([...constraints, where("details."+user.studentFilter, "==", filterValue)])
|
|
1089
|
+
})
|
|
1090
|
+
};
|
|
1091
|
+
console.log("final", finalConstraints)
|
|
1092
|
+
return finalConstraints;
|
|
1093
|
+
});
|
|
1094
|
+
}, []);
|
|
1095
|
+
|
|
772
1096
|
|
|
773
1097
|
const getDataFromQuery = async (
|
|
774
|
-
itemList: {[key: string]:
|
|
1098
|
+
itemList: {[key: string]: QueryDocumentSnapshot<DocumentData>} = {}, currentQueryAnchor=queryAnchor, cursorDirection?:"increase"|"decrease"|undefined, prevEntries=prevEntryIds, loadMoreFromQuery=false):Promise<any> => {
|
|
1099
|
+
|
|
1100
|
+
if (!queries?.length) {
|
|
1101
|
+
setTableData({});
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
console.log("q", queries)
|
|
775
1105
|
let cursorPos:number;
|
|
776
1106
|
if (page[0] > page[1]) {
|
|
777
1107
|
cursorPos = currentQueryAnchor.endQueryPos;
|
|
@@ -779,41 +1109,49 @@ export function usePlacementListingPaginator({data, user}:{data:QueryObject[], u
|
|
|
779
1109
|
cursorPos = currentQueryAnchor.startQueryPos;
|
|
780
1110
|
}
|
|
781
1111
|
|
|
782
|
-
const querySchema
|
|
783
|
-
|
|
784
|
-
const createQuery = (
|
|
785
|
-
const constraints:any[] = [
|
|
786
|
-
where("savedById", "==", user.userType === "Staff" ? user.oId : user.id),
|
|
787
|
-
where("savedByProduct", "==", user.product),
|
|
788
|
-
where("savedByUserType", "==", user.userType === "Staff" ? "Organisation" : "Student"),
|
|
789
|
-
]
|
|
790
|
-
|
|
791
|
-
if (user.userType === "Students") {
|
|
792
|
-
constraints.push(where("listed", "==", true));
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
queryData?.where && queryData.where.forEach((w) => {
|
|
796
|
-
constraints.push(where(...w));
|
|
797
|
-
});
|
|
1112
|
+
const querySchema = queries[cursorPos];
|
|
1113
|
+
console.log("schema", querySchema)
|
|
1114
|
+
const createQuery = (mConstraints:QueryConstraint[]) => {
|
|
798
1115
|
|
|
1116
|
+
console.log("mConstraints", mConstraints);
|
|
1117
|
+
const fConstraints = [...mConstraints];
|
|
1118
|
+
|
|
1119
|
+
let addOrderBy = true;
|
|
799
1120
|
filters && Object.entries(filters).forEach(([key, value]) => {
|
|
800
|
-
|
|
1121
|
+
if (Array.isArray(value)) {
|
|
1122
|
+
value.forEach((v) => {
|
|
1123
|
+
if (typeof v === "object") {
|
|
1124
|
+
if (v instanceof QueryOrderByConstraint) {
|
|
1125
|
+
addOrderBy = false;
|
|
1126
|
+
}
|
|
1127
|
+
fConstraints.push(v as QueryFieldFilterConstraint|QueryOrderByConstraint);
|
|
1128
|
+
} else {
|
|
1129
|
+
fConstraints.push(where(key, "==", v));
|
|
1130
|
+
}
|
|
1131
|
+
})
|
|
1132
|
+
} else if (typeof value === "object") {
|
|
1133
|
+
if (value instanceof QueryOrderByConstraint) {
|
|
1134
|
+
addOrderBy = false;
|
|
1135
|
+
}
|
|
1136
|
+
fConstraints.push(value as QueryFieldFilterConstraint|QueryOrderByConstraint);
|
|
1137
|
+
} else {
|
|
1138
|
+
fConstraints.push(where(key, "==", value));
|
|
1139
|
+
}
|
|
801
1140
|
});
|
|
802
|
-
|
|
803
|
-
if (
|
|
804
|
-
if (
|
|
805
|
-
|
|
1141
|
+
console.log("Add order by", addOrderBy);
|
|
1142
|
+
if (addOrderBy) {
|
|
1143
|
+
if (sortResultsBy) {
|
|
1144
|
+
fConstraints.push(orderBy(sortOptions[sortResultsBy][0], sortOptions[sortResultsBy][1]));
|
|
806
1145
|
} else {
|
|
807
|
-
|
|
1146
|
+
fConstraints.push(orderBy(documentId()));
|
|
1147
|
+
|
|
808
1148
|
}
|
|
809
|
-
} else {
|
|
810
|
-
constraints.push(orderBy(documentId()));
|
|
811
1149
|
}
|
|
812
1150
|
|
|
813
1151
|
|
|
814
1152
|
if (page[0] > page[1] && !cursorDirection) { // Going up
|
|
815
|
-
currentQueryAnchor.
|
|
816
|
-
|
|
1153
|
+
currentQueryAnchor.endDoc && fConstraints.push(startAfter(currentQueryAnchor.endDoc));
|
|
1154
|
+
fConstraints.push(limit(10));
|
|
817
1155
|
if (!loadMoreFromQuery) {
|
|
818
1156
|
currentQueryAnchor = {...currentQueryAnchor, startQueryPos: currentQueryAnchor.endQueryPos};
|
|
819
1157
|
}
|
|
@@ -821,27 +1159,27 @@ export function usePlacementListingPaginator({data, user}:{data:QueryObject[], u
|
|
|
821
1159
|
if (!loadMoreFromQuery) {
|
|
822
1160
|
currentQueryAnchor = {...currentQueryAnchor, endQueryPos: currentQueryAnchor.startQueryPos};
|
|
823
1161
|
}
|
|
824
|
-
|
|
825
|
-
if (currentQueryAnchor.
|
|
826
|
-
currentQueryAnchor.
|
|
1162
|
+
fConstraints.push(limitToLast(10));
|
|
1163
|
+
if (currentQueryAnchor.startDoc) {
|
|
1164
|
+
currentQueryAnchor.startDoc && fConstraints.push(endBefore(currentQueryAnchor.startDoc));
|
|
827
1165
|
} else {
|
|
828
|
-
currentQueryAnchor.
|
|
1166
|
+
currentQueryAnchor.startDoc && fConstraints.push(endAt(currentQueryAnchor.startDoc));
|
|
829
1167
|
}
|
|
830
1168
|
} else {
|
|
831
1169
|
if (cursorDirection === "decrease") {
|
|
832
|
-
|
|
1170
|
+
fConstraints.push(limitToLast(10));
|
|
833
1171
|
} else {
|
|
834
|
-
|
|
1172
|
+
fConstraints.push(limit(10));
|
|
835
1173
|
}
|
|
836
1174
|
}
|
|
837
|
-
return
|
|
1175
|
+
return fConstraints;
|
|
838
1176
|
};
|
|
839
1177
|
|
|
840
1178
|
const constraints = createQuery(querySchema);
|
|
841
1179
|
// console.log(queryId, "constraints", constraints)
|
|
842
1180
|
|
|
843
|
-
const q = query(collection(db, "
|
|
844
|
-
const queryResults:{[key:string]:
|
|
1181
|
+
const q = query(collection(db, "users"), ...(constraints));
|
|
1182
|
+
const queryResults:{[key:string]: QueryDocumentSnapshot<DocumentData>} = {};
|
|
845
1183
|
|
|
846
1184
|
const queryData = await getDocs(q);
|
|
847
1185
|
|
|
@@ -856,7 +1194,7 @@ export function usePlacementListingPaginator({data, user}:{data:QueryObject[], u
|
|
|
856
1194
|
};
|
|
857
1195
|
|
|
858
1196
|
let index = 0;
|
|
859
|
-
reverseIfBack(queryData.docs).forEach(
|
|
1197
|
+
reverseIfBack(queryData.docs).forEach((doc: QueryDocumentSnapshot) => {
|
|
860
1198
|
if (Object.keys(queryResults).length + Object.keys(itemList).length === 10) {
|
|
861
1199
|
return;
|
|
862
1200
|
}
|
|
@@ -873,10 +1211,9 @@ export function usePlacementListingPaginator({data, user}:{data:QueryObject[], u
|
|
|
873
1211
|
return;
|
|
874
1212
|
}
|
|
875
1213
|
|
|
876
|
-
|
|
877
|
-
item.id = doc.id;
|
|
878
|
-
queryResults[doc.id] = item;
|
|
1214
|
+
queryResults[doc.id] = doc;
|
|
879
1215
|
index = index+1;
|
|
1216
|
+
|
|
880
1217
|
if (prevEntries[doc.id]) return;
|
|
881
1218
|
prevEntries[doc.id] = position;
|
|
882
1219
|
});
|
|
@@ -890,272 +1227,29 @@ export function usePlacementListingPaginator({data, user}:{data:QueryObject[], u
|
|
|
890
1227
|
setPrevEntryIds(prevEntries);
|
|
891
1228
|
|
|
892
1229
|
if (queryData.size < 10 && Object.keys(itemList).length < 10) {
|
|
893
|
-
if (page[0] > page[1] && cursorPos+1 <
|
|
894
|
-
console.log("Increase query index");
|
|
1230
|
+
if (page[0] > page[1] && cursorPos+1 < queries.length) {
|
|
895
1231
|
return getDataFromQuery(itemList, {...currentQueryAnchor, endQueryPos: currentQueryAnchor.endQueryPos+1}, "increase", prevEntries);
|
|
896
1232
|
} else if (page[0] < page[1] && cursorPos > 0) {
|
|
897
|
-
console.log("Decrease query index");
|
|
898
1233
|
return getDataFromQuery(itemList, {...currentQueryAnchor, startQueryPos: currentQueryAnchor.startQueryPos-1}, "decrease", prevEntries);
|
|
899
1234
|
}
|
|
900
1235
|
}
|
|
901
1236
|
|
|
902
1237
|
if (Object.keys(itemList).length < 10 && queryData.size === 10) {
|
|
903
|
-
|
|
904
|
-
// if(loadMoreFromQuery){return}
|
|
905
|
-
return getDataFromQuery(itemList, {...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0]}, undefined, prevEntries, true);
|
|
1238
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, startDoc: Object.values(itemList)[0], endDoc: Object.values(itemList).slice(-1)[0]}, undefined, prevEntries, true);
|
|
906
1239
|
}
|
|
907
1240
|
|
|
908
1241
|
if (queryData.size === 0 &&
|
|
909
1242
|
Object.keys(itemList).length === 0 &&
|
|
910
|
-
currentQueryAnchor.endQueryPos+1 ===
|
|
1243
|
+
currentQueryAnchor.endQueryPos+1 === queries.length &&
|
|
911
1244
|
page[0] > 1) {
|
|
912
1245
|
setTableData({});
|
|
913
1246
|
setQueryAnchor((a) => ({...a, startKey: ""}));
|
|
914
1247
|
return;
|
|
915
1248
|
}
|
|
916
1249
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const placement = await firebaseQuery.getDocData(["placementListings", v.placementId || ""]) as PlacementListing;
|
|
921
|
-
const address = await firebaseQuery.getDocData(["addresses", placement.addressId || ""]);
|
|
922
|
-
const provider = await firebaseQuery.getDocData(["providers", placement.providerId || ""]) as ProviderData;
|
|
923
|
-
const status = getPlacementStatus(provider, placement, v);
|
|
924
|
-
return [k, {...address, ...provider, ...placement, email: placement.providerEmail, status: status, savedPlacement: v}];
|
|
925
|
-
})));
|
|
926
|
-
setTableData(finalData);
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
const getPlacementStatus = (provider: ProviderData, placement: PlacementListing, savedPlacement: SavedPlacement):"active"|"requiresApproval"|"awaitingProviderApproval"|"public"|"blocked"|"notReviewed"|"notPublic"|"unknown" => {
|
|
930
|
-
if (savedPlacement.status === "Blocked") return "blocked";
|
|
931
|
-
if (savedPlacement.status === "Accepted") return "active";
|
|
932
|
-
|
|
933
|
-
if (user.product === "admin") {
|
|
934
|
-
if (placement.mapConsent === undefined) return "notReviewed";
|
|
935
|
-
if (placement.mapConsent === false) return "notPublic";
|
|
936
|
-
return "unknown";
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
if (!provider.insurance) return "awaitingProviderApproval";
|
|
940
|
-
|
|
941
|
-
return "requiresApproval";
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
useEffect(() => {
|
|
945
|
-
if (!filters) return;
|
|
946
|
-
|
|
947
|
-
setPage([1, 0]);
|
|
948
|
-
setTableData({});
|
|
949
|
-
setQueryAnchor({startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0});
|
|
950
|
-
setPrevEntryIds({});
|
|
951
|
-
}, [filters]);
|
|
952
|
-
|
|
953
|
-
// Fetch new data when queries or page change
|
|
954
|
-
useEffect(() => {
|
|
955
|
-
getDataFromQuery();
|
|
956
|
-
}, [page]);
|
|
957
|
-
|
|
958
|
-
return ({...{tableData, setPage, setFilters, page}});
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
type UserPaginatorParams = {
|
|
962
|
-
user: UserData,
|
|
963
|
-
cohort?: string,
|
|
964
|
-
data: QueryObject[],
|
|
965
|
-
search?: string,
|
|
966
|
-
userType: "Staff"|"Students"
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
export function useCohortUserPaginator({user, cohort, data, search, userType}:UserPaginatorParams) {
|
|
970
|
-
const [tableData, setTableData] = useState<{[key:string]:{[key:string]: unknown}}>({});
|
|
971
|
-
const [queryAnchor, setQueryAnchor] = useState<{startKey: string, endKey: string, startQueryPos: number, endQueryPos: number}>({startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0});
|
|
972
|
-
const [filters, setFilters] = useState<{[key:string]: unknown}>();
|
|
973
|
-
const [prevEntryIds, setPrevEntryIds] = useState<{[key:string]:number}>({});
|
|
974
|
-
const [page, setPage] = useState([1, 0]);
|
|
975
|
-
const [dataListenerUnsubscribe, setDataListenerUnsubscribe] = useState<Unsubscribe|undefined>();
|
|
976
|
-
const [queries, setQueries] = useState<QueryConstraint[][]>();
|
|
977
|
-
const [prevSearch, setPrevSearch] = useState<string>();
|
|
978
|
-
|
|
979
|
-
const algoliaClient = algoliasearch(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
|
|
980
|
-
const usersIndex = algoliaClient.initIndex("users");
|
|
981
|
-
|
|
982
|
-
useEffect(() => {
|
|
983
|
-
if (user.userType !== "Staff") {
|
|
984
|
-
console.log("Not a staff member", user)
|
|
985
|
-
setQueries(undefined);
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
if (
|
|
989
|
-
(!user.viewCohorts && user.userGroup !== "admin") ||
|
|
990
|
-
user.viewCohorts === "none" ||
|
|
991
|
-
(user.viewCohorts === "some" && cohort !== "all" && !user.visibleCohorts?.split(",").includes(cohort || ""))) {
|
|
992
|
-
setQueries(undefined);
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
setQueries(() => {
|
|
996
|
-
const finalConstraints:QueryConstraint[][] = [];
|
|
997
|
-
console.log("data", data, data.length)
|
|
998
|
-
for (var i = 0; i < data.length; i++) {
|
|
999
|
-
const item = data[i]
|
|
1000
|
-
console.log("item", item)
|
|
1001
|
-
const constraints:QueryConstraint[] = [];
|
|
1002
|
-
item.where && item.where.forEach((w) => {
|
|
1003
|
-
constraints.push(where(...w));
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
constraints.push(where("oId", "==", user.oId));
|
|
1007
|
-
cohort && cohort !== "all" && constraints.push(where("cohort", "==", cohort));
|
|
1008
|
-
|
|
1009
|
-
if (user.userGroup === "admin" || user.viewUsers === "all") {
|
|
1010
|
-
finalConstraints.push(constraints);
|
|
1011
|
-
continue
|
|
1012
|
-
}
|
|
1013
|
-
if (!user.studentFilter || !user.studentFilterValues) continue;
|
|
1014
|
-
user.studentFilterValues.split(", ").forEach((filterValue) => {
|
|
1015
|
-
user.studentFilter && finalConstraints.push([...constraints, where("details."+user.studentFilter, "==", filterValue)])
|
|
1016
|
-
})
|
|
1017
|
-
};
|
|
1018
|
-
console.log("final", finalConstraints)
|
|
1019
|
-
return finalConstraints;
|
|
1020
|
-
});
|
|
1021
|
-
}, []);
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const getDataFromQuery = async (
|
|
1025
|
-
itemList: {[key: string]: any} = {}, currentQueryAnchor=queryAnchor, cursorDirection?:"increase"|"decrease"|undefined, prevEntries=prevEntryIds, loadMoreFromQuery=false):Promise<any> => {
|
|
1026
|
-
|
|
1027
|
-
if (!queries) {
|
|
1028
|
-
setTableData({});
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
console.log("q", queries)
|
|
1032
|
-
let cursorPos:number;
|
|
1033
|
-
if (page[0] > page[1]) {
|
|
1034
|
-
cursorPos = currentQueryAnchor.endQueryPos;
|
|
1035
|
-
} else {
|
|
1036
|
-
cursorPos = currentQueryAnchor.startQueryPos;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
const querySchema = queries[cursorPos];
|
|
1040
|
-
console.log("schema", querySchema)
|
|
1041
|
-
const createQuery = (mConstraints:QueryConstraint[]) => {
|
|
1042
|
-
|
|
1043
|
-
console.log("mConstraints", mConstraints);
|
|
1044
|
-
const fConstraints = [...mConstraints];
|
|
1045
|
-
|
|
1046
|
-
filters && Object.entries(filters).forEach(([key, value]) => {
|
|
1047
|
-
if (typeof value === "object") {
|
|
1048
|
-
fConstraints.push(value as QueryFieldFilterConstraint);
|
|
1049
|
-
} else {
|
|
1050
|
-
fConstraints.push(where(key, "==", value));
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
fConstraints.push(orderBy(documentId()));
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (page[0] > page[1] && !cursorDirection) { // Going up
|
|
1057
|
-
currentQueryAnchor.endKey && fConstraints.push(startAfter(currentQueryAnchor.endKey));
|
|
1058
|
-
fConstraints.push(limit(10));
|
|
1059
|
-
if (!loadMoreFromQuery) {
|
|
1060
|
-
currentQueryAnchor = {...currentQueryAnchor, startQueryPos: currentQueryAnchor.endQueryPos};
|
|
1061
|
-
}
|
|
1062
|
-
} else if (page[0] < page[1] && !cursorDirection) { // Going down
|
|
1063
|
-
if (!loadMoreFromQuery) {
|
|
1064
|
-
currentQueryAnchor = {...currentQueryAnchor, endQueryPos: currentQueryAnchor.startQueryPos};
|
|
1065
|
-
}
|
|
1066
|
-
fConstraints.push(limitToLast(10));
|
|
1067
|
-
if (currentQueryAnchor.startKey) {
|
|
1068
|
-
currentQueryAnchor.startKey && fConstraints.push(endBefore(currentQueryAnchor.startKey));
|
|
1069
|
-
} else {
|
|
1070
|
-
currentQueryAnchor.startKey && fConstraints.push(endAt(currentQueryAnchor.startKey));
|
|
1071
|
-
}
|
|
1072
|
-
} else {
|
|
1073
|
-
if (cursorDirection === "decrease") {
|
|
1074
|
-
fConstraints.push(limitToLast(10));
|
|
1075
|
-
} else {
|
|
1076
|
-
fConstraints.push(limit(10));
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
return fConstraints;
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
const constraints = createQuery(querySchema);
|
|
1083
|
-
// console.log(queryId, "constraints", constraints)
|
|
1084
|
-
|
|
1085
|
-
const q = query(collection(db, "users"), ...(constraints));
|
|
1086
|
-
const queryResults:{[key:string]: unknown} = {};
|
|
1087
|
-
|
|
1088
|
-
const queryData = await getDocs(q);
|
|
1089
|
-
|
|
1090
|
-
// console.log("queryData.size", queryData.size)
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const reverseIfBack = (docs:QueryDocumentSnapshot<DocumentData>[]) => {
|
|
1094
|
-
if (page[0] < page[1]) {
|
|
1095
|
-
return docs.reverse();
|
|
1096
|
-
}
|
|
1097
|
-
return docs;
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
let index = 0;
|
|
1101
|
-
reverseIfBack(queryData.docs).forEach((doc: QueryDocumentSnapshot) => {
|
|
1102
|
-
if (Object.keys(queryResults).length + Object.keys(itemList).length === 10) {
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
let position = Object.keys(itemList).length+(page[0]-1)*10+index+1;
|
|
1107
|
-
if (page[0] < page[1]) {
|
|
1108
|
-
position = (page[0])*10-index-Object.keys(itemList).length;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// console.log(index, "doc.id", doc.id, position, "E", prevEntries[doc.id])
|
|
1112
|
-
|
|
1113
|
-
if (itemList[doc.id] || (prevEntries[doc.id] && prevEntries[doc.id] !== position)) {
|
|
1114
|
-
console.log("Removing ", doc.id, ": E=", prevEntries[doc.id], ", G=", position);
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
const item = doc.data();
|
|
1119
|
-
item.id = doc.id;
|
|
1120
|
-
queryResults[doc.id] = item;
|
|
1121
|
-
index = index+1;
|
|
1122
|
-
|
|
1123
|
-
if (prevEntries[doc.id]) return;
|
|
1124
|
-
prevEntries[doc.id] = position;
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
if (cursorDirection === "decrease" || page[0] < page[1]) {
|
|
1128
|
-
itemList = {...Object.fromEntries(Object.entries(queryResults).reverse()), ...itemList};
|
|
1129
|
-
} else {
|
|
1130
|
-
itemList = {...itemList, ...queryResults};
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
setPrevEntryIds(prevEntries);
|
|
1134
|
-
|
|
1135
|
-
if (queryData.size < 10 && Object.keys(itemList).length < 10) {
|
|
1136
|
-
if (page[0] > page[1] && cursorPos+1 < queries.length) {
|
|
1137
|
-
return getDataFromQuery(itemList, {...currentQueryAnchor, endQueryPos: currentQueryAnchor.endQueryPos+1}, "increase", prevEntries);
|
|
1138
|
-
} else if (page[0] < page[1] && cursorPos > 0) {
|
|
1139
|
-
return getDataFromQuery(itemList, {...currentQueryAnchor, startQueryPos: currentQueryAnchor.startQueryPos-1}, "decrease", prevEntries);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (Object.keys(itemList).length < 10 && queryData.size === 10) {
|
|
1144
|
-
return getDataFromQuery(itemList, {...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0]}, undefined, prevEntries, true);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
if (queryData.size === 0 &&
|
|
1148
|
-
Object.keys(itemList).length === 0 &&
|
|
1149
|
-
currentQueryAnchor.endQueryPos+1 === queries.length &&
|
|
1150
|
-
page[0] > 1) {
|
|
1151
|
-
setTableData({});
|
|
1152
|
-
setQueryAnchor((a) => ({...a, startKey: ""}));
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
const listenForUpdates = () => {
|
|
1157
|
-
if (!Object.keys(itemList).length) return;
|
|
1158
|
-
console.log("Fetching cohort user data")
|
|
1250
|
+
const listenForUpdates = () => {
|
|
1251
|
+
if (!Object.keys(itemList).length) return;
|
|
1252
|
+
console.log("Fetching cohort user data")
|
|
1159
1253
|
|
|
1160
1254
|
const itemListUpdateQuery = query(collection(db, "users"), where(documentId(), "in", Object.keys(itemList)));
|
|
1161
1255
|
const itemUpdateSnapshot = onSnapshot(itemListUpdateQuery, (querySnapshot) => {
|
|
@@ -1170,8 +1264,8 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1170
1264
|
};
|
|
1171
1265
|
listenForUpdates();
|
|
1172
1266
|
|
|
1173
|
-
setQueryAnchor({...currentQueryAnchor,
|
|
1174
|
-
setTableData(itemList);
|
|
1267
|
+
setQueryAnchor({...currentQueryAnchor, startDoc: Object.values(itemList)[0], endDoc: Object.values(itemList).slice(-1)[0]});
|
|
1268
|
+
setTableData(Object.fromEntries(Object.entries(itemList).map(([k, v]) => [k, {id: k, ...v.data()}])));
|
|
1175
1269
|
};
|
|
1176
1270
|
|
|
1177
1271
|
const searchUsers = async () => {
|
|
@@ -1215,10 +1309,10 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1215
1309
|
console.log("Set page");
|
|
1216
1310
|
|
|
1217
1311
|
setTableData({});
|
|
1218
|
-
setQueryAnchor({
|
|
1312
|
+
setQueryAnchor({startQueryPos: 0, endQueryPos: 0});
|
|
1219
1313
|
setPrevEntryIds({});
|
|
1220
1314
|
dataListenerUnsubscribe && dataListenerUnsubscribe();
|
|
1221
|
-
}, [filters, search]);
|
|
1315
|
+
}, [filters, search, sortResultsBy]);
|
|
1222
1316
|
|
|
1223
1317
|
// Fetch new data when queries or page change
|
|
1224
1318
|
useEffect(() => {
|
|
@@ -1231,7 +1325,10 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1231
1325
|
dataListenerUnsubscribe && dataListenerUnsubscribe();
|
|
1232
1326
|
}, [page, queries, prevSearch]);
|
|
1233
1327
|
|
|
1234
|
-
|
|
1328
|
+
|
|
1329
|
+
const setSort = (s: string) => setSortResultsBy(s);
|
|
1330
|
+
|
|
1331
|
+
return ({...{tableData, setPage, page, setFilters, setSort, sortOptions: Object.keys(sortOptions), sortBy: sortResultsBy}});
|
|
1235
1332
|
}
|
|
1236
1333
|
|
|
1237
1334
|
|
|
@@ -1309,480 +1406,514 @@ export function useLazyLoadQueryList({path, constraints, number, onItemFetched}:
|
|
|
1309
1406
|
}
|
|
1310
1407
|
|
|
1311
1408
|
|
|
1312
|
-
type PublicPlacementListingLoaderParams = {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
export function usePublicPlacementListingLoader({providerId, number=5}:PublicPlacementListingLoaderParams) {
|
|
1318
|
-
|
|
1319
|
-
|
|
1409
|
+
// type PublicPlacementListingLoaderParams = {
|
|
1410
|
+
// providerId?: string,
|
|
1411
|
+
// number: number,
|
|
1412
|
+
// }
|
|
1413
|
+
|
|
1414
|
+
// export function usePublicPlacementListingLoader({providerId, number=5}:PublicPlacementListingLoaderParams) {
|
|
1415
|
+
// const [items, setItems] = useState<{[key:string]:{[key:string]:unknown}}>({});
|
|
1416
|
+
// const [loadMoreIcon, setLoadMoreIcon] = useState<boolean>(false);
|
|
1417
|
+
|
|
1418
|
+
// const [lastItem, setLastItem] = useState<QueryDocumentSnapshot<DocumentData>>();
|
|
1419
|
+
// const firebaseQuery = new FirebaseQuery();
|
|
1420
|
+
|
|
1421
|
+
// const reset = () => {
|
|
1422
|
+
// setItems({});
|
|
1423
|
+
// setLastItem(undefined);
|
|
1424
|
+
// };
|
|
1425
|
+
|
|
1426
|
+
// const loadMore = async () => {
|
|
1427
|
+
// setLoadMoreIcon(true);
|
|
1428
|
+
// let formattedConstraints:QueryConstraint[] = [
|
|
1429
|
+
// where("status", "==", "listed"),
|
|
1430
|
+
// limit(number)
|
|
1431
|
+
// ];
|
|
1432
|
+
|
|
1433
|
+
// if (providerId) {
|
|
1434
|
+
// formattedConstraints.push(where("providerId", "==", providerId));
|
|
1435
|
+
// }
|
|
1436
|
+
// if (lastItem) {
|
|
1437
|
+
// formattedConstraints.push(startAfter(lastItem));
|
|
1438
|
+
// }
|
|
1439
|
+
// const documents = await firebaseQuery.getDocsWhere("placementListings", formattedConstraints, true) as QuerySnapshot<DocumentData>;
|
|
1440
|
+
|
|
1441
|
+
// console.log("docs", documents.docs);
|
|
1442
|
+
// setLastItem(documents.docs[documents.docs.length-1]);
|
|
1443
|
+
|
|
1444
|
+
// const processedItems = Object.fromEntries(await Promise.all(Object.values(documents.docs).map(async (doc) => {
|
|
1445
|
+
// let itemObj = {...doc.data(), id: doc.id} as PlacementListing;
|
|
1446
|
+
|
|
1447
|
+
// if (itemObj.addressId) {
|
|
1448
|
+
// const address = await firebaseQuery.getDocData(["addresses", itemObj.addressId]) as Address;
|
|
1449
|
+
// delete address.id;
|
|
1450
|
+
// itemObj = {...address, ...itemObj};
|
|
1451
|
+
// }
|
|
1452
|
+
// if (itemObj.applicantWorkflowId) {
|
|
1453
|
+
// const applicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", itemObj.applicantWorkflowId]) as ApplicantWorkflow).workflow.filter((i) => i.id === 1)[0];
|
|
1454
|
+
// const applicantFiles = applicantWorkflow.files ? Object.fromEntries(await Promise.all(applicantWorkflow.files?.map(async (fileId) => {
|
|
1455
|
+
// const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1456
|
+
// file.url = await getDownloadURL(ref(storage, `providers/${itemObj.providerId}/${file.fileName}`));
|
|
1457
|
+
|
|
1458
|
+
// return [fileId, file];
|
|
1459
|
+
// }))) : [];
|
|
1460
|
+
// const applicantForms = applicantWorkflow.forms ? Object.fromEntries(await Promise.all(applicantWorkflow.forms?.map(async (formId) => {
|
|
1461
|
+
// return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1462
|
+
// }))) : [];
|
|
1463
|
+
|
|
1464
|
+
// applicantWorkflow.viewableFiles = applicantFiles;
|
|
1465
|
+
// applicantWorkflow.formDetails = applicantForms;
|
|
1466
|
+
// itemObj = {...itemObj, applicantWorkflow: [applicantWorkflow]};
|
|
1467
|
+
// }
|
|
1468
|
+
// return [doc.id, itemObj];
|
|
1469
|
+
// })))
|
|
1470
|
+
|
|
1471
|
+
// setItems((i) => ({...i, ...processedItems}));
|
|
1472
|
+
// setLoadMoreIcon(false);
|
|
1473
|
+
// };
|
|
1474
|
+
// useEffect(() => {
|
|
1475
|
+
// loadMore();
|
|
1476
|
+
// }, []);
|
|
1477
|
+
|
|
1478
|
+
// return ({...{items, loadMore, loadMoreIcon, reset}});
|
|
1479
|
+
// }
|
|
1480
|
+
|
|
1481
|
+
// export type ApplicationHookParams = {
|
|
1482
|
+
// successText: {
|
|
1483
|
+
// submitted: {
|
|
1484
|
+
// title: string;
|
|
1485
|
+
// desc: string;
|
|
1486
|
+
// };
|
|
1487
|
+
// draftSaved: {
|
|
1488
|
+
// title: string;
|
|
1489
|
+
// desc: string;
|
|
1490
|
+
// };
|
|
1491
|
+
// stageComplete: {
|
|
1492
|
+
// title: string;
|
|
1493
|
+
// desc: string;
|
|
1494
|
+
// };
|
|
1495
|
+
// outcome: {
|
|
1496
|
+
// title: string;
|
|
1497
|
+
// desc: string;
|
|
1498
|
+
// };
|
|
1499
|
+
// };
|
|
1500
|
+
// setSuccessPopup: import("react").Dispatch<import("react").SetStateAction<"submitted" | "draftSaved" | "stageComplete" | "outcome" | undefined>>;
|
|
1501
|
+
// openSuccessPopup: (type: "submitted" | "draftSaved" | "stageComplete" | "outcome") => void;
|
|
1502
|
+
// setFApplication: import("react").Dispatch<import("react").SetStateAction<Partial<Application>>>;
|
|
1503
|
+
// fApplication: Partial<Application>;
|
|
1504
|
+
// draftSaved: boolean;
|
|
1505
|
+
// profileUrl: string | undefined;
|
|
1506
|
+
// successPopup: "submitted" | "draftSaved" | "stageComplete" | "outcome" | undefined;
|
|
1507
|
+
// fApplicationId: string | undefined;
|
|
1508
|
+
// fListing: PlacementListing | false | undefined;
|
|
1509
|
+
// student: UserData | undefined;
|
|
1510
|
+
// fProvider: {
|
|
1511
|
+
// details?: ProviderData;
|
|
1512
|
+
// profile?: string;
|
|
1513
|
+
// id?: string;
|
|
1514
|
+
// } | undefined;
|
|
1515
|
+
// onFApply: (draft?: boolean) => Promise<void>;
|
|
1516
|
+
// setFormComplete: (formId: string, e: {
|
|
1517
|
+
// [key: string]: unknown;
|
|
1518
|
+
// }) => void;
|
|
1519
|
+
// viewFile: (file: string, onOpen: (url: string) => void) => void
|
|
1520
|
+
// addFile: (files: string[], fileId: number) => void;
|
|
1521
|
+
// uploadedFiles?: {
|
|
1522
|
+
// [key: string]: FileItem;
|
|
1523
|
+
// };
|
|
1524
|
+
// getCurrentStage: (stage: number) => Promise<{
|
|
1525
|
+
// stage: ApplicantStage;
|
|
1526
|
+
// completedSections: {
|
|
1527
|
+
// submitted?: string | undefined;
|
|
1528
|
+
// filesViewed?: string[] | undefined;
|
|
1529
|
+
// formsCompleted?: {
|
|
1530
|
+
// [key: string]: unknown;
|
|
1531
|
+
// } | undefined;
|
|
1532
|
+
// filesUploaded?: {
|
|
1533
|
+
// [key: number]: string[];
|
|
1534
|
+
// } | undefined;
|
|
1535
|
+
// };
|
|
1536
|
+
// }>;
|
|
1537
|
+
// currentStageComplete?: boolean;
|
|
1538
|
+
// progressStage: (type: number | "accept" | "reject", e?: {
|
|
1539
|
+
// feedback?: string;
|
|
1540
|
+
// }) => Promise<void>;
|
|
1541
|
+
// };
|
|
1542
|
+
|
|
1543
|
+
// export function useCreateApplicationRenderer({user, listingId, listing, provider, application, applicationId, orgContext}:
|
|
1544
|
+
// {user:UserData, listingId:string, applicationId?: string, listing?: PlacementListing, application?: Partial<Application>,
|
|
1545
|
+
// orgContext?: {details: ProviderData, addresses: {[key: string]: OrganisationAddress}, applicantWorkflows: {[key: string]: ApplicantWorkflow}},
|
|
1546
|
+
// provider?: {details?: ProviderData, profile?: string, id?: string}}) {
|
|
1547
|
+
|
|
1548
|
+
// const firebaseQuery = new FirebaseQuery();
|
|
1549
|
+
|
|
1550
|
+
// let applicationWithoutAdditionalData = {...(application || {})} as any;
|
|
1551
|
+
// delete applicationWithoutAdditionalData.listing;
|
|
1552
|
+
// delete applicationWithoutAdditionalData.address;
|
|
1553
|
+
// delete applicationWithoutAdditionalData.provider;
|
|
1554
|
+
|
|
1555
|
+
// const [fApplication, setFApplication] = useState<Partial<Application>>(application ? applicationWithoutAdditionalData : {
|
|
1556
|
+
// uid: user.userType === "Students" ? user.id : undefined,
|
|
1557
|
+
// listingId: listingId,
|
|
1558
|
+
// addressId: listing?.addressId,
|
|
1559
|
+
// stage: 1,
|
|
1560
|
+
// reqUserType: "Students",
|
|
1561
|
+
// status: "draft"});
|
|
1562
|
+
// const [fApplicationId, setFApplicationId] = useState<string|undefined>(applicationId);
|
|
1563
|
+
// const [draftSaved, setDraftSaved] = useState(false);
|
|
1564
|
+
// const [fProvider, setFProvider] = useState<{details?: ProviderData, profile?: string, id?: string}|undefined>(provider);
|
|
1565
|
+
// const [fListing, setFListing] = useState<PlacementListing|false|undefined>(Object.keys(listing || {}).length > 5 ? listing : undefined);
|
|
1566
|
+
// const [student, setStudent] = useState<UserData|undefined>(user.userType === "Students" ? user : undefined);
|
|
1567
|
+
// const [profileUrl, setProfileUrl] = useState<string>();
|
|
1568
|
+
// const [successPopup, setSuccessPopup] = useState<"submitted"|"draftSaved"|"stageComplete"|"outcome">();
|
|
1569
|
+
// const [uploadedFiles, setUploadedFiles] = useState<{[key: string]: FileItem}>();
|
|
1570
|
+
// const [currentStageComplete, setCurrentStageComplete] = useState<boolean>();
|
|
1571
|
+
|
|
1572
|
+
// useEffect(() => {
|
|
1573
|
+
// const getListing = async () => {
|
|
1574
|
+
// console.log("Checking ID")
|
|
1575
|
+
// if (!listingId) return;
|
|
1576
|
+
// console.log("Getting listing")
|
|
1577
|
+
// console.log("LISTING PARAM", listing, Object.keys(listing || {}).length > 5);
|
|
1578
|
+
|
|
1579
|
+
// const listingData = (Object.keys(listing || {}).length > 5) ? listing : (await firebaseQuery.getDocData(["placementListings", listingId]).catch(() => false) as PlacementListing|false);
|
|
1580
|
+
|
|
1581
|
+
// console.log("LISTINGDATA", listingData, Object.keys(listing || {}).length > 5 ? {a: "string"} : "AAA");
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
|
|
1585
|
+
// const address = listingData ? (user.product === "providers" && orgContext) ? orgContext.addresses[listingData.addressId || ""] : await firebaseQuery.getDocData(["addresses", listingData.addressId as string]) as Address : undefined;
|
|
1586
|
+
// const workflow = listingData ? (user.product === "providers" && orgContext) ? orgContext.applicantWorkflows[listingData.applicantWorkflowId || ""] as ApplicantWorkflow : await firebaseQuery.getDocData(["applicantWorkflows", listingData.applicantWorkflowId as string]) as ApplicantWorkflow : undefined;
|
|
1587
|
+
|
|
1588
|
+
// if (workflow && listingData) {
|
|
1589
|
+
// workflow.workflow = await Promise.all(workflow.workflow.map(async (s) => {
|
|
1590
|
+
// const applicantFiles = s.files ? Object.fromEntries(await Promise.all(s.files?.map(async (fileId: string) => {
|
|
1591
|
+
// const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1592
|
+
// file.url = await getDownloadURL(ref(storage, `providers/${listingData?.providerId}/${file.fileName}`));
|
|
1593
|
+
|
|
1594
|
+
// return [fileId, file];
|
|
1595
|
+
// }))) : [];
|
|
1596
|
+
// const applicantForms = s.forms ? Object.fromEntries(await Promise.all(s.forms?.map(async (formId: string) => {
|
|
1597
|
+
// return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1598
|
+
// }))) : [];
|
|
1599
|
+
|
|
1600
|
+
// return {...s, viewableFiles: applicantFiles, formDetails: applicantForms};
|
|
1601
|
+
// }));
|
|
1602
|
+
// delete workflow.id;
|
|
1603
|
+
// }
|
|
1604
|
+
// if (address) {
|
|
1605
|
+
// delete address.id;
|
|
1606
|
+
// }
|
|
1607
|
+
|
|
1608
|
+
// console.log("Setting listing")
|
|
1609
|
+
// setFListing((listingData && workflow) ? {...listingData, ...address, applicantWorkflow: workflow.workflow} as PlacementListing : false);
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
// if ((fProvider?.id === application?.providerId) && user.product === "providers") {
|
|
1613
|
+
// setFProvider({details: orgContext?.details, id: user.oId});
|
|
1614
|
+
// } else if (listingData && listingData?.providerId) {
|
|
1615
|
+
// console.log("Getting provider from DB");
|
|
1616
|
+
// const provider = await firebaseQuery.getDocData(["providers", listingData.providerId]) as ProviderData;
|
|
1617
|
+
// console.log("Provider", provider);
|
|
1618
|
+
// setFProvider({details: provider, id: listingData.providerId});
|
|
1619
|
+
// }
|
|
1620
|
+
// }
|
|
1621
|
+
|
|
1622
|
+
// getListing();
|
|
1623
|
+
// }, [listingId, listing]);
|
|
1624
|
+
|
|
1625
|
+
// useEffect(() => {
|
|
1626
|
+
// if (student?.id !== application?.uid) {
|
|
1627
|
+
// if (user.userType === "Students") {
|
|
1628
|
+
// setStudent(user);
|
|
1629
|
+
// } else if (user.product === "providers" && application?.uid) {
|
|
1630
|
+
// firebaseQuery.getDocData(["users", application.uid]).then((s) => setStudent(s as UserData));
|
|
1631
|
+
// }
|
|
1632
|
+
// }
|
|
1633
|
+
// }, []);
|
|
1634
|
+
|
|
1635
|
+
// useEffect(() => {
|
|
1636
|
+
// setFListing(Object.keys(listing || {}).length > 5 ? listing : undefined);
|
|
1637
|
+
// }, [listing]);
|
|
1638
|
+
|
|
1639
|
+
// useEffect(() => {
|
|
1640
|
+
// if (provider?.profile) return;
|
|
1641
|
+
// if (!provider?.id) return;
|
|
1642
|
+
|
|
1643
|
+
// getDownloadURL(ref(storage, `providers/${provider?.id}/profilePic.png`)).then(setProfileUrl).catch(() => null);
|
|
1644
|
+
// }, [provider]);
|
|
1645
|
+
|
|
1646
|
+
|
|
1647
|
+
// useEffect(() => {
|
|
1648
|
+
// console.log("APPLICATIONID", applicationId);
|
|
1649
|
+
// if (!applicationId) {
|
|
1650
|
+
// if (!listingId) return;
|
|
1651
|
+
// firebaseQuery.getDocsWhere("applications", [where("status", "not-in", ["approved", "declined"]), where("uid", "==", user.id), where("listingId", "==", listingId)]).then((existingApplication) => {
|
|
1652
|
+
// console.log("EXISTING", existingApplication);
|
|
1653
|
+
// // get uploaded files
|
|
1654
|
+
// if (existingApplication && Object.keys(existingApplication).length) {
|
|
1655
|
+
// setFApplication(Object.values(existingApplication)[0]);
|
|
1656
|
+
// setFApplicationId(Object.keys(existingApplication)[0]);
|
|
1657
|
+
// }
|
|
1658
|
+
// });
|
|
1659
|
+
// return;
|
|
1660
|
+
// }
|
|
1661
|
+
// if (applicationId) {
|
|
1662
|
+
// setFApplicationId(applicationId);
|
|
1663
|
+
// if (application) {
|
|
1664
|
+
// let applicationWithoutAdditionalData = {...(application || {})} as any;
|
|
1665
|
+
// delete applicationWithoutAdditionalData.listing;
|
|
1666
|
+
// delete applicationWithoutAdditionalData.address;
|
|
1667
|
+
// delete applicationWithoutAdditionalData.provider;
|
|
1668
|
+
// setFApplication(applicationWithoutAdditionalData);
|
|
1669
|
+
// } else {
|
|
1670
|
+
// firebaseQuery.getDocData(["applications", applicationId]).then(setFApplication);
|
|
1671
|
+
// }
|
|
1672
|
+
// }
|
|
1673
|
+
// }, [application, applicationId]);
|
|
1674
|
+
|
|
1675
|
+
// const getCurrentStage = async (stage: number): Promise<{
|
|
1676
|
+
// stage: ApplicantStage; completedSections: {
|
|
1677
|
+
// submitted?: string | undefined;
|
|
1678
|
+
// filesViewed?: string[] | undefined;
|
|
1679
|
+
// formsCompleted?: {
|
|
1680
|
+
// [key: string]: unknown;
|
|
1681
|
+
// } | undefined;
|
|
1682
|
+
// filesUploaded?: {
|
|
1683
|
+
// [key: number]: string[];
|
|
1684
|
+
// } | undefined;
|
|
1685
|
+
// };
|
|
1686
|
+
// }> => {
|
|
1687
|
+
// console.log("fLSITING CURRENT STAGE", fListing);
|
|
1688
|
+
// if (!fListing) throw new Error("Listing deleted");
|
|
1689
|
+
// if (!fListing?.applicantWorkflowId) throw new Error("No workflow stage");
|
|
1690
|
+
|
|
1691
|
+
// const mApplicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", fListing.applicantWorkflowId]) as ApplicantWorkflow);
|
|
1692
|
+
|
|
1693
|
+
// const stageObj = mApplicantWorkflow?.workflow?.find((s) => s.id === stage);
|
|
1694
|
+
|
|
1695
|
+
// if (!stageObj) throw new Error("Can't find stage.");
|
|
1696
|
+
|
|
1697
|
+
// const applicantFiles = stageObj.files ? Object.fromEntries(await Promise.all(stageObj.files?.map(async (fileId) => {
|
|
1698
|
+
// const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1699
|
+
// file.url = await getDownloadURL(ref(storage, `providers/${mApplicantWorkflow?.oId}/${file.fileName}`));
|
|
1700
|
+
|
|
1701
|
+
// return [fileId, file];
|
|
1702
|
+
// }))) : [];
|
|
1703
|
+
// const applicantForms = stageObj.forms ? Object.fromEntries(await Promise.all(stageObj.forms?.map(async (formId) => {
|
|
1704
|
+
// return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1705
|
+
// }))) : [];
|
|
1706
|
+
|
|
1707
|
+
// stageObj.viewableFiles = applicantFiles;
|
|
1708
|
+
// stageObj.formDetails = applicantForms;
|
|
1709
|
+
|
|
1710
|
+
// return {stage: stageObj, completedSections: fApplication?.completedSections?.[stage] || {}};
|
|
1711
|
+
// };
|
|
1712
|
+
|
|
1713
|
+
// useEffect(() => {
|
|
1714
|
+
// const getUploadedFiles = async () => {
|
|
1715
|
+
// if (!fApplication.completedSections) {
|
|
1716
|
+
// setUploadedFiles({});
|
|
1717
|
+
// return
|
|
1718
|
+
// };
|
|
1719
|
+
|
|
1720
|
+
// const fileIds = Object.values(fApplication.completedSections)
|
|
1721
|
+
// .flatMap(stageData =>
|
|
1722
|
+
// Object.values(stageData.filesUploaded || {}).flatMap(fileIds => fileIds)
|
|
1723
|
+
// );
|
|
1724
|
+
|
|
1725
|
+
// const fileDataPromises = fileIds.map(async (fileId) => {
|
|
1726
|
+
// const fileData = await firebaseQuery.getDocData(["files", fileId]) as FileItem;
|
|
1727
|
+
// fileData.url = await getDownloadURL(ref(storage, `userFiles/${fileData.fileName}`));
|
|
1728
|
+
|
|
1729
|
+
// return [fileId, fileData] as [string, FileItem]
|
|
1730
|
+
// });
|
|
1731
|
+
|
|
1732
|
+
// const fileDataArray = await Promise.all(fileDataPromises);
|
|
1733
|
+
// const fileDataObj = Object.fromEntries(fileDataArray)
|
|
1734
|
+
// setUploadedFiles(fileDataObj)
|
|
1735
|
+
// }
|
|
1736
|
+
|
|
1737
|
+
// const addApplication = async () => {
|
|
1738
|
+
// console.log("ADDING APPLICATION");
|
|
1739
|
+
// if (!fListing || !fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1740
|
+
|
|
1741
|
+
// const applicationData = {
|
|
1742
|
+
// uid: student.id,
|
|
1743
|
+
// listingId: fListing?.id,
|
|
1744
|
+
// addressId: fListing.addressId,
|
|
1745
|
+
// applicantWorkflowId: fListing?.applicantWorkflowId,
|
|
1746
|
+
// providerId: fProvider.id,
|
|
1747
|
+
// stage: 1,
|
|
1748
|
+
// status: "draft",
|
|
1749
|
+
// created: (new Date()).toISOString(),
|
|
1750
|
+
// ...fApplication,
|
|
1751
|
+
// } as Partial<Application>;
|
|
1752
|
+
// const mApplicationId = (await firebaseQuery.add(["applications"], applicationData)).id;
|
|
1753
|
+
// console.log("APPLICATION ADDED");
|
|
1754
|
+
// setFApplicationId(mApplicationId);
|
|
1755
|
+
// setFApplication(applicationData);
|
|
1756
|
+
// setDraftSaved(true);
|
|
1757
|
+
// return;
|
|
1758
|
+
// };
|
|
1759
|
+
// getUploadedFiles();
|
|
1760
|
+
|
|
1761
|
+
// console.log("Checking IDs");
|
|
1762
|
+
|
|
1763
|
+
// if (!fListing || !fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1764
|
+
// if (user.product === "providers") return;
|
|
1765
|
+
|
|
1766
|
+
// console.log("Checking dates and sections");
|
|
1767
|
+
// if (!fApplication.completedSections && !fApplication.startDate && !fApplication.endDate) return;
|
|
1768
|
+
|
|
1769
|
+
// console.log("Application updated");
|
|
1770
|
+
// if (!fApplicationId && !applicationId) {
|
|
1771
|
+
// // save new
|
|
1772
|
+
// console.log("Add application")
|
|
1773
|
+
// addApplication();
|
|
1774
|
+
// return;
|
|
1775
|
+
// }
|
|
1776
|
+
// // update
|
|
1777
|
+
// firebaseQuery.update(["applications", (fApplicationId || applicationId) as string], fApplication);
|
|
1778
|
+
// setDraftSaved(true);
|
|
1779
|
+
// }, [fApplication]);
|
|
1780
|
+
|
|
1781
|
+
// const openSuccessPopup = (type: "submitted"|"draftSaved"|"stageComplete"|"outcome") => {
|
|
1782
|
+
// setSuccessPopup(type);
|
|
1783
|
+
// setTimeout(() => {
|
|
1784
|
+
// // onClose();
|
|
1785
|
+
// }, 1500);
|
|
1786
|
+
// };
|
|
1320
1787
|
|
|
1321
|
-
|
|
1322
|
-
const firebaseQuery = new FirebaseQuery();
|
|
1788
|
+
// useEffect(() => {
|
|
1323
1789
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1790
|
+
// const areStagesCompleted = async ():Promise<boolean|undefined> => {
|
|
1791
|
+
// if (!fListing) return;
|
|
1792
|
+
// if (fApplication.stage === undefined) return undefined;
|
|
1793
|
+
// const currentStage = await getCurrentStage(fApplication.stage);
|
|
1328
1794
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1795
|
+
// console.log("Checking current stage is complete");
|
|
1796
|
+
|
|
1797
|
+
// for (const fileViewed of currentStage.stage?.files || []) {
|
|
1798
|
+
// console.log("Checking file viewed", fileViewed, "in", currentStage?.completedSections?.filesViewed);
|
|
1799
|
+
// if (!currentStage?.completedSections?.filesViewed?.includes(fileViewed)) {
|
|
1800
|
+
// console.log("File not viewed");
|
|
1801
|
+
// return false;
|
|
1802
|
+
// } else {
|
|
1803
|
+
// console.log("File viewed");
|
|
1804
|
+
// }
|
|
1805
|
+
// }
|
|
1806
|
+
// for (const formCompleted of currentStage?.stage?.forms || []) {
|
|
1807
|
+
// console.log("Checking form completed", formCompleted, "in", currentStage?.completedSections?.formsCompleted);
|
|
1808
|
+
|
|
1809
|
+
// if (!Object.keys(currentStage?.completedSections?.formsCompleted || {}).includes(formCompleted)) {
|
|
1810
|
+
// console.log("Form not completed");
|
|
1811
|
+
// return false;
|
|
1812
|
+
// } else {
|
|
1813
|
+
// console.log("Form completed")
|
|
1814
|
+
// }
|
|
1815
|
+
// }
|
|
1816
|
+
// for (let i = 0; i++; i < (currentStage?.stage?.requiredFiles || []).length) {
|
|
1817
|
+
// if (!Object.keys(currentStage?.completedSections?.filesUploaded || {}).includes(i.toString())) {
|
|
1818
|
+
// console.log("Form not uploaded");
|
|
1819
|
+
// return false;
|
|
1820
|
+
// } else {
|
|
1821
|
+
// console.log("Form uploaded");
|
|
1822
|
+
// }
|
|
1823
|
+
// }
|
|
1824
|
+
|
|
1825
|
+
// return true;
|
|
1826
|
+
// };
|
|
1335
1827
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1339
|
-
if (lastItem) {
|
|
1340
|
-
formattedConstraints.push(startAfter(lastItem));
|
|
1341
|
-
}
|
|
1342
|
-
const documents = await firebaseQuery.getDocsWhere("placementListings", formattedConstraints, true) as QuerySnapshot<DocumentData>;
|
|
1828
|
+
// areStagesCompleted().then(setCurrentStageComplete);
|
|
1829
|
+
// }, [fApplication, fListing])
|
|
1343
1830
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1831
|
+
// const viewFile = (file: string, onOpen: (url: string) => void) => {
|
|
1832
|
+
// if (fApplication.reqUserType !== user.userType) return;
|
|
1833
|
+
// if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1834
|
+
// if (!fListing) throw new Error("No associated listing.");
|
|
1346
1835
|
|
|
1347
|
-
|
|
1348
|
-
let itemObj = {...doc.data(), id: doc.id} as PlacementListing;
|
|
1836
|
+
// const viewableFiles = fListing?.applicantWorkflow?.find((stage) => stage.id === fApplication.stage)?.viewableFiles;
|
|
1349
1837
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1838
|
+
// setFApplication((a) => {
|
|
1839
|
+
// const oldA = {...a};
|
|
1840
|
+
// const viewedFiles = a.completedSections?.[1]?.filesViewed || [];
|
|
1841
|
+
// if (viewedFiles?.includes(file)) return a;
|
|
1842
|
+
|
|
1843
|
+
// viewableFiles?.[file].url && onOpen(viewableFiles?.[file].url);
|
|
1844
|
+
// viewedFiles?.push(file);
|
|
1845
|
+
|
|
1846
|
+
// const newA = editNestedObject(["completedSections", 1, "filesViewed"], oldA, viewedFiles) as Partial<Application>;
|
|
1847
|
+
// return newA;
|
|
1848
|
+
// });
|
|
1849
|
+
// };
|
|
1850
|
+
|
|
1851
|
+
// const addFile = (files: string[], fileId: number) => {
|
|
1852
|
+
// if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1853
|
+
// if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1854
|
+
|
|
1855
|
+
// if (!files.length) throw new Error("No files to upload");
|
|
1856
|
+
// setFApplication((a) => editNestedObject(["completedSections", fApplication.stage as number, "filesUploaded", fileId], a, files) as Application);
|
|
1857
|
+
// };
|
|
1858
|
+
|
|
1859
|
+
// const setFormComplete = (formId: string, e: {[key: string]: unknown}) => {
|
|
1860
|
+
// if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1861
|
+
// if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1862
|
+
// setFApplication((a) => editNestedObject(["completedSections", fApplication.stage as number, "formsCompleted", formId], a, e) as Application);
|
|
1863
|
+
// };
|
|
1864
|
+
|
|
1865
|
+
// const successText = {
|
|
1866
|
+
// submitted: {
|
|
1867
|
+
// title: "Application submitted",
|
|
1868
|
+
// desc: "We've sent your application to the placement provider. You will hear what the next steps are soon.",
|
|
1869
|
+
// },
|
|
1870
|
+
// draftSaved: {
|
|
1871
|
+
// title: "Draft saved",
|
|
1872
|
+
// desc: "Your draft has been saved. Go to the 'Applications' section from your home screen to edit.",
|
|
1873
|
+
// },
|
|
1874
|
+
// stageComplete: {
|
|
1875
|
+
// title: "Stage complete",
|
|
1876
|
+
// desc: "We have sent this to the next stage in the application process. We will update you with any progress.",
|
|
1877
|
+
// },
|
|
1878
|
+
// outcome: {
|
|
1879
|
+
// title: "Outcome submitted",
|
|
1880
|
+
// desc: "We have sent the student an email with the outcome of their application.",
|
|
1881
|
+
// },
|
|
1882
|
+
// };
|
|
1883
|
+
|
|
1884
|
+
// const onFApply = async (draft?: boolean) => {
|
|
1885
|
+
// if (draft) {
|
|
1886
|
+
// openSuccessPopup("draftSaved")
|
|
1887
|
+
// return;
|
|
1888
|
+
// }
|
|
1889
|
+
|
|
1890
|
+
// if (!fApplicationId) return;
|
|
1891
|
+
|
|
1892
|
+
// // Check all items have been filled in.
|
|
1893
|
+
// if (!fApplication.startDate || !fApplication.endDate) throw new Error("Please select dates for your placement.");
|
|
1894
|
+
|
|
1895
|
+
// await executeCallable("applications-submit", {applicationId: fApplicationId});
|
|
1896
|
+
|
|
1897
|
+
// const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]) as Application;
|
|
1898
|
+
// setFApplication(newApplication);
|
|
1899
|
+
// openSuccessPopup("submitted")
|
|
1900
|
+
// };
|
|
1901
|
+
|
|
1902
|
+
// const progressStage = async (type: number|"accept"|"reject", e?: {feedback?: string}) => {
|
|
1903
|
+
// // Check all stages completed.
|
|
1904
|
+
// if (!fApplicationId) return;
|
|
1905
|
+
// if (!currentStageComplete) throw new Error("Complete all forms before submitting.");
|
|
1906
|
+
// if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1907
|
+
// if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1908
|
+
|
|
1909
|
+
// await executeCallable("applications-changeStage", {applicationId: fApplicationId, type: type, feedback: e?.feedback});
|
|
1910
|
+
// const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]) as Application;
|
|
1911
|
+
// setFApplication(newApplication);
|
|
1912
|
+
// };
|
|
1913
|
+
|
|
1360
1914
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const applicantForms = applicantWorkflow.forms ? Object.fromEntries(await Promise.all(applicantWorkflow.forms?.map(async (formId) => {
|
|
1364
|
-
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1365
|
-
}))) : [];
|
|
1366
|
-
|
|
1367
|
-
applicantWorkflow.viewableFiles = applicantFiles;
|
|
1368
|
-
applicantWorkflow.formDetails = applicantForms;
|
|
1369
|
-
itemObj = {...itemObj, applicantWorkflow: [applicantWorkflow]};
|
|
1370
|
-
}
|
|
1371
|
-
return [doc.id, itemObj];
|
|
1372
|
-
})))
|
|
1373
|
-
|
|
1374
|
-
setItems((i) => ({...i, ...processedItems}));
|
|
1375
|
-
setLoadMoreIcon(false);
|
|
1376
|
-
};
|
|
1377
|
-
useEffect(() => {
|
|
1378
|
-
loadMore();
|
|
1379
|
-
}, []);
|
|
1380
|
-
|
|
1381
|
-
return ({...{items, loadMore, loadMoreIcon, reset}});
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
export type ApplicationHookParams = {
|
|
1385
|
-
successText: {
|
|
1386
|
-
submitted: {
|
|
1387
|
-
title: string;
|
|
1388
|
-
desc: string;
|
|
1389
|
-
};
|
|
1390
|
-
draftSaved: {
|
|
1391
|
-
title: string;
|
|
1392
|
-
desc: string;
|
|
1393
|
-
};
|
|
1394
|
-
stageComplete: {
|
|
1395
|
-
title: string;
|
|
1396
|
-
desc: string;
|
|
1397
|
-
};
|
|
1398
|
-
outcome: {
|
|
1399
|
-
title: string;
|
|
1400
|
-
desc: string;
|
|
1401
|
-
};
|
|
1402
|
-
};
|
|
1403
|
-
setSuccessPopup: import("react").Dispatch<import("react").SetStateAction<"submitted" | "draftSaved" | "stageComplete" | "outcome" | undefined>>;
|
|
1404
|
-
openSuccessPopup: (type: "submitted" | "draftSaved" | "stageComplete" | "outcome") => void;
|
|
1405
|
-
setFApplication: import("react").Dispatch<import("react").SetStateAction<Partial<Application>>>;
|
|
1406
|
-
fApplication: Partial<Application>;
|
|
1407
|
-
draftSaved: boolean;
|
|
1408
|
-
profileUrl: string | undefined;
|
|
1409
|
-
successPopup: "submitted" | "draftSaved" | "stageComplete" | "outcome" | undefined;
|
|
1410
|
-
fApplicationId: string | undefined;
|
|
1411
|
-
fListing: PlacementListing | undefined;
|
|
1412
|
-
student: UserData | undefined;
|
|
1413
|
-
fProvider: {
|
|
1414
|
-
details?: ProviderData;
|
|
1415
|
-
profile?: string;
|
|
1416
|
-
id?: string;
|
|
1417
|
-
} | undefined;
|
|
1418
|
-
onFApply: (draft?: boolean) => Promise<void>;
|
|
1419
|
-
setFormComplete: (formId: string, e: {
|
|
1420
|
-
[key: string]: unknown;
|
|
1421
|
-
}) => void;
|
|
1422
|
-
viewFile: (file: string, onOpen: (url: string) => void) => void
|
|
1423
|
-
addFile: (files: string[], fileId: number) => void;
|
|
1424
|
-
uploadedFiles?: {
|
|
1425
|
-
[key: string]: FileItem;
|
|
1426
|
-
};
|
|
1427
|
-
getCurrentStage: (stage: number) => Promise<{
|
|
1428
|
-
stage: ApplicantStage;
|
|
1429
|
-
completedSections: {
|
|
1430
|
-
submitted?: string | undefined;
|
|
1431
|
-
filesViewed?: string[] | undefined;
|
|
1432
|
-
formsCompleted?: {
|
|
1433
|
-
[key: string]: unknown;
|
|
1434
|
-
} | undefined;
|
|
1435
|
-
filesUploaded?: {
|
|
1436
|
-
[key: number]: string[];
|
|
1437
|
-
} | undefined;
|
|
1438
|
-
};
|
|
1439
|
-
}>;
|
|
1440
|
-
currentStageComplete?: boolean;
|
|
1441
|
-
progressStage: (type: number | "accept" | "reject", e?: {
|
|
1442
|
-
feedback?: string;
|
|
1443
|
-
}) => Promise<void>;
|
|
1444
|
-
};
|
|
1445
|
-
|
|
1446
|
-
export function useCreateApplicationRenderer({user, listingId, listing, provider, application, applicationId, orgContext}:
|
|
1447
|
-
{user:UserData, listingId:string, applicationId?: string, listing?: PlacementListing, application?: Partial<Application>,
|
|
1448
|
-
orgContext?: {details: ProviderData, addresses: {[key: string]: OrganisationAddress}, applicantWorkflows: {[key: string]: ApplicantWorkflow}},
|
|
1449
|
-
provider?: {details?: ProviderData, profile?: string, id?: string}}) {
|
|
1450
|
-
|
|
1451
|
-
const firebaseQuery = new FirebaseQuery();
|
|
1452
|
-
|
|
1453
|
-
let applicationWithoutAdditionalData = {...(application || {})} as any;
|
|
1454
|
-
delete applicationWithoutAdditionalData.listing;
|
|
1455
|
-
delete applicationWithoutAdditionalData.address;
|
|
1456
|
-
delete applicationWithoutAdditionalData.provider;
|
|
1457
|
-
|
|
1458
|
-
const [fApplication, setFApplication] = useState<Partial<Application>>(application ? applicationWithoutAdditionalData : {
|
|
1459
|
-
uid: user.userType === "Students" ? user.id : undefined,
|
|
1460
|
-
listingId: listingId,
|
|
1461
|
-
stage: 1,
|
|
1462
|
-
reqUserType: "Students",
|
|
1463
|
-
status: "draft"});
|
|
1464
|
-
const [fApplicationId, setFApplicationId] = useState<string|undefined>(applicationId);
|
|
1465
|
-
const [draftSaved, setDraftSaved] = useState(false);
|
|
1466
|
-
const [fProvider, setFProvider] = useState<{details?: ProviderData, profile?: string, id?: string}|undefined>(provider);
|
|
1467
|
-
const [fListing, setFListing] = useState<PlacementListing|undefined>(listing);
|
|
1468
|
-
const [student, setStudent] = useState<UserData|undefined>(user.userType === "Students" ? user : undefined);
|
|
1469
|
-
const [profileUrl, setProfileUrl] = useState<string>();
|
|
1470
|
-
const [successPopup, setSuccessPopup] = useState<"submitted"|"draftSaved"|"stageComplete"|"outcome">();
|
|
1471
|
-
const [uploadedFiles, setUploadedFiles] = useState<{[key: string]: FileItem}>();
|
|
1472
|
-
const [currentStageComplete, setCurrentStageComplete] = useState<boolean>();
|
|
1473
|
-
|
|
1474
|
-
useEffect(() => {
|
|
1475
|
-
const getListing = async () => {
|
|
1476
|
-
console.log("Checking ID")
|
|
1477
|
-
if (!listingId) return;
|
|
1478
|
-
console.log("Getting listing")
|
|
1479
|
-
|
|
1480
|
-
const listingData = listing || await firebaseQuery.getDocData(["placementListings", listingId]) as PlacementListing;
|
|
1481
|
-
const address = (user.product === "providers" && orgContext) ? orgContext.addresses[listingData.addressId || ""] : await firebaseQuery.getDocData(["addresses", listingData.addressId as string]) as Address;
|
|
1482
|
-
const workflow = (user.product === "providers" && orgContext) ? orgContext.applicantWorkflows[listingData.applicantWorkflowId || ""] as ApplicantWorkflow : await firebaseQuery.getDocData(["applicantWorkflows", listingData.applicantWorkflowId as string]) as ApplicantWorkflow;
|
|
1483
|
-
|
|
1484
|
-
workflow.workflow = await Promise.all(workflow.workflow.map(async (s) => {
|
|
1485
|
-
const applicantFiles = s.files ? Object.fromEntries(await Promise.all(s.files?.map(async (fileId: string) => {
|
|
1486
|
-
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1487
|
-
file.url = await getDownloadURL(ref(storage, `userFiles/${file.fileName}`));
|
|
1488
|
-
|
|
1489
|
-
return [fileId, file];
|
|
1490
|
-
}))) : [];
|
|
1491
|
-
const applicantForms = s.forms ? Object.fromEntries(await Promise.all(s.forms?.map(async (formId: string) => {
|
|
1492
|
-
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1493
|
-
}))) : [];
|
|
1494
|
-
|
|
1495
|
-
return {...s, viewableFiles: applicantFiles, formDetails: applicantForms};
|
|
1496
|
-
}));
|
|
1497
|
-
|
|
1498
|
-
delete address.id;
|
|
1499
|
-
delete workflow.id;
|
|
1500
|
-
console.log("Setting listing")
|
|
1501
|
-
setFListing({...listingData, ...address, applicantWorkflow: workflow.workflow});
|
|
1502
|
-
|
|
1503
|
-
console.log("Getting provider", listingData?.providerId);
|
|
1504
|
-
if ((fProvider?.id === application?.providerId) && user.product === "providers") {
|
|
1505
|
-
setFProvider({details: orgContext?.details, id: user.oId});
|
|
1506
|
-
} else if (listingData?.providerId) {
|
|
1507
|
-
console.log("Getting provider from DB");
|
|
1508
|
-
const provider = await firebaseQuery.getDocData(["providers", listingData.providerId]) as ProviderData;
|
|
1509
|
-
console.log("Provider", provider);
|
|
1510
|
-
setFProvider({details: provider, id: listingData.providerId});
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
getListing();
|
|
1515
|
-
}, [listingId, listing]);
|
|
1516
|
-
|
|
1517
|
-
useEffect(() => {
|
|
1518
|
-
if (student?.id !== application?.uid) {
|
|
1519
|
-
if (user.userType === "Students") {
|
|
1520
|
-
setStudent(user);
|
|
1521
|
-
} else if (user.product === "providers" && application?.uid) {
|
|
1522
|
-
firebaseQuery.getDocData(["users", application.uid]).then((s) => setStudent(s as UserData));
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}, []);
|
|
1526
|
-
|
|
1527
|
-
useEffect(() => {
|
|
1528
|
-
setFListing(listing);
|
|
1529
|
-
}, [listing]);
|
|
1530
|
-
|
|
1531
|
-
useEffect(() => {
|
|
1532
|
-
if (provider?.profile) return;
|
|
1533
|
-
if (!provider?.id) return;
|
|
1534
|
-
|
|
1535
|
-
getDownloadURL(ref(storage, `providers/${provider?.id}/profilePic.png`)).then(setProfileUrl).catch(() => null);
|
|
1536
|
-
}, [provider]);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
useEffect(() => {
|
|
1540
|
-
console.log("APPLICATIONID", applicationId);
|
|
1541
|
-
if (!applicationId) {
|
|
1542
|
-
if (!listingId) return;
|
|
1543
|
-
firebaseQuery.getDocsWhere("applications", [where("status", "not-in", ["approved", "declined"]), where("uid", "==", user.id), where("listingId", "==", listingId)]).then((existingApplication) => {
|
|
1544
|
-
console.log("EXISTING", existingApplication);
|
|
1545
|
-
// get uploaded files
|
|
1546
|
-
if (existingApplication && Object.keys(existingApplication).length) {
|
|
1547
|
-
setFApplication(Object.values(existingApplication)[0]);
|
|
1548
|
-
setFApplicationId(Object.keys(existingApplication)[0]);
|
|
1549
|
-
}
|
|
1550
|
-
});
|
|
1551
|
-
return;
|
|
1552
|
-
}
|
|
1553
|
-
if (applicationId) setFApplicationId(applicationId);
|
|
1554
|
-
}, [application, applicationId]);
|
|
1555
|
-
|
|
1556
|
-
const getCurrentStage = async (stage: number): Promise<{
|
|
1557
|
-
stage: ApplicantStage; completedSections: {
|
|
1558
|
-
submitted?: string | undefined;
|
|
1559
|
-
filesViewed?: string[] | undefined;
|
|
1560
|
-
formsCompleted?: {
|
|
1561
|
-
[key: string]: unknown;
|
|
1562
|
-
} | undefined;
|
|
1563
|
-
filesUploaded?: {
|
|
1564
|
-
[key: number]: string[];
|
|
1565
|
-
} | undefined;
|
|
1566
|
-
};
|
|
1567
|
-
}> => {
|
|
1568
|
-
if (!fListing?.applicantWorkflowId) throw new Error("No workflow stage");
|
|
1569
|
-
|
|
1570
|
-
const mApplicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", fListing.applicantWorkflowId]) as ApplicantWorkflow);
|
|
1571
|
-
|
|
1572
|
-
const stageObj = mApplicantWorkflow?.workflow?.find((s) => s.id === stage);
|
|
1573
|
-
|
|
1574
|
-
if (!stageObj) throw new Error("Can't find stage.");
|
|
1575
|
-
|
|
1576
|
-
const applicantFiles = stageObj.files ? Object.fromEntries(await Promise.all(stageObj.files?.map(async (fileId) => {
|
|
1577
|
-
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1578
|
-
file.url = await getDownloadURL(ref(storage, `userFiles/${file.fileName}`));
|
|
1579
|
-
|
|
1580
|
-
return [fileId, file];
|
|
1581
|
-
}))) : [];
|
|
1582
|
-
const applicantForms = stageObj.forms ? Object.fromEntries(await Promise.all(stageObj.forms?.map(async (formId) => {
|
|
1583
|
-
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1584
|
-
}))) : [];
|
|
1585
|
-
|
|
1586
|
-
stageObj.viewableFiles = applicantFiles;
|
|
1587
|
-
stageObj.formDetails = applicantForms;
|
|
1588
|
-
|
|
1589
|
-
return {stage: stageObj, completedSections: fApplication?.completedSections?.[stage] || {}};
|
|
1590
|
-
};
|
|
1591
|
-
|
|
1592
|
-
useEffect(() => {
|
|
1593
|
-
const getUploadedFiles = async () => {
|
|
1594
|
-
if (!fApplication.completedSections) {
|
|
1595
|
-
setUploadedFiles({});
|
|
1596
|
-
return
|
|
1597
|
-
};
|
|
1598
|
-
|
|
1599
|
-
const fileIds = Object.values(fApplication.completedSections)
|
|
1600
|
-
.flatMap(stageData =>
|
|
1601
|
-
Object.values(stageData.filesUploaded || {}).flatMap(fileIds => fileIds)
|
|
1602
|
-
);
|
|
1603
|
-
|
|
1604
|
-
const fileDataPromises = fileIds.map(async (fileId) => {
|
|
1605
|
-
const fileData = await firebaseQuery.getDocData(["files", fileId]) as FileItem;
|
|
1606
|
-
fileData.url = await getDownloadURL(ref(storage, `userFiles/${fileData.fileName}`));
|
|
1607
|
-
|
|
1608
|
-
return [fileId, fileData] as [string, FileItem]
|
|
1609
|
-
});
|
|
1610
|
-
|
|
1611
|
-
const fileDataArray = await Promise.all(fileDataPromises);
|
|
1612
|
-
const fileDataObj = Object.fromEntries(fileDataArray)
|
|
1613
|
-
setUploadedFiles(fileDataObj)
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
const addApplication = async () => {
|
|
1617
|
-
console.log("ADDING APPLICATION");
|
|
1618
|
-
if (!fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1619
|
-
|
|
1620
|
-
const applicationData = {
|
|
1621
|
-
uid: student.id,
|
|
1622
|
-
listingId: fListing?.id,
|
|
1623
|
-
applicantWorkflowId: fListing?.applicantWorkflowId,
|
|
1624
|
-
providerId: fProvider.id,
|
|
1625
|
-
stage: 1,
|
|
1626
|
-
status: "draft",
|
|
1627
|
-
created: (new Date()).toISOString(),
|
|
1628
|
-
...fApplication,
|
|
1629
|
-
} as Partial<Application>;
|
|
1630
|
-
const mApplicationId = (await firebaseQuery.add(["applications"], applicationData)).id;
|
|
1631
|
-
console.log("APPLICATION ADDED");
|
|
1632
|
-
setFApplicationId(mApplicationId);
|
|
1633
|
-
setFApplication(applicationData);
|
|
1634
|
-
setDraftSaved(true);
|
|
1635
|
-
return;
|
|
1636
|
-
};
|
|
1637
|
-
getUploadedFiles();
|
|
1638
|
-
|
|
1639
|
-
console.log("Checking IDs");
|
|
1640
|
-
console.log(fListing?.id, fProvider?.id, student?.id);
|
|
1641
|
-
if (!fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1642
|
-
if (user.product === "providers") throw new Error("Providers cannot create applications");
|
|
1643
|
-
|
|
1644
|
-
console.log("Checking dates and sections");
|
|
1645
|
-
if (!fApplication.completedSections && !fApplication.startDate && !fApplication.endDate) return;
|
|
1646
|
-
|
|
1647
|
-
console.log("Application updated");
|
|
1648
|
-
if (!fApplicationId && !applicationId) {
|
|
1649
|
-
// save new
|
|
1650
|
-
console.log("Add application")
|
|
1651
|
-
addApplication();
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
// update
|
|
1655
|
-
firebaseQuery.update(["applications", (fApplicationId || applicationId) as string], fApplication);
|
|
1656
|
-
setDraftSaved(true);
|
|
1657
|
-
}, [fApplication]);
|
|
1658
|
-
|
|
1659
|
-
const openSuccessPopup = (type: "submitted"|"draftSaved"|"stageComplete"|"outcome") => {
|
|
1660
|
-
setSuccessPopup(type);
|
|
1661
|
-
setTimeout(() => {
|
|
1662
|
-
// onClose();
|
|
1663
|
-
}, 1500);
|
|
1664
|
-
};
|
|
1665
|
-
|
|
1666
|
-
useEffect(() => {
|
|
1667
|
-
|
|
1668
|
-
const areStagesCompleted = async ():Promise<boolean|undefined> => {
|
|
1669
|
-
if (!fListing) return;
|
|
1670
|
-
if (fApplication.stage === undefined) return undefined;
|
|
1671
|
-
const currentStage = await getCurrentStage(fApplication.stage);
|
|
1672
|
-
|
|
1673
|
-
console.log("Checking current stage is complete");
|
|
1674
|
-
|
|
1675
|
-
for (const fileViewed of currentStage.stage?.files || []) {
|
|
1676
|
-
console.log("Checking file viewed", fileViewed, "in", currentStage?.completedSections?.filesViewed);
|
|
1677
|
-
if (!currentStage?.completedSections?.filesViewed?.includes(fileViewed)) {
|
|
1678
|
-
console.log("File not viewed");
|
|
1679
|
-
return false;
|
|
1680
|
-
} else {
|
|
1681
|
-
console.log("File viewed");
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
for (const formCompleted of currentStage?.stage?.forms || []) {
|
|
1685
|
-
console.log("Checking form completed", formCompleted, "in", currentStage?.completedSections?.formsCompleted);
|
|
1686
|
-
|
|
1687
|
-
if (!Object.keys(currentStage?.completedSections?.formsCompleted || {}).includes(formCompleted)) {
|
|
1688
|
-
console.log("Form not completed");
|
|
1689
|
-
return false;
|
|
1690
|
-
} else {
|
|
1691
|
-
console.log("Form completed")
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
for (let i = 0; i++; i < (currentStage?.stage?.requiredFiles || []).length) {
|
|
1695
|
-
if (!Object.keys(currentStage?.completedSections?.filesUploaded || {}).includes(i.toString())) {
|
|
1696
|
-
console.log("Form not uploaded");
|
|
1697
|
-
return false;
|
|
1698
|
-
} else {
|
|
1699
|
-
console.log("Form uploaded");
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
return true;
|
|
1704
|
-
};
|
|
1705
|
-
|
|
1706
|
-
areStagesCompleted().then(setCurrentStageComplete);
|
|
1707
|
-
}, [fApplication, fListing])
|
|
1708
|
-
|
|
1709
|
-
const viewFile = (file: string, onOpen: (url: string) => void) => {
|
|
1710
|
-
if (fApplication.reqUserType !== user.userType) return;
|
|
1711
|
-
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1712
|
-
|
|
1713
|
-
const viewableFiles = fListing?.applicantWorkflow?.find((stage) => stage.id === fApplication.stage)?.viewableFiles;
|
|
1714
|
-
|
|
1715
|
-
setFApplication((a) => {
|
|
1716
|
-
const oldA = {...a};
|
|
1717
|
-
const viewedFiles = a.completedSections?.[1]?.filesViewed || [];
|
|
1718
|
-
if (viewedFiles?.includes(file)) return a;
|
|
1719
|
-
|
|
1720
|
-
viewableFiles?.[file].url && onOpen(viewableFiles?.[file].url);
|
|
1721
|
-
viewedFiles?.push(file);
|
|
1722
|
-
|
|
1723
|
-
const newA = editNestedObject(["completedSections", 1, "filesViewed"], oldA, viewedFiles) as Partial<Application>;
|
|
1724
|
-
return newA;
|
|
1725
|
-
});
|
|
1726
|
-
};
|
|
1727
|
-
|
|
1728
|
-
const addFile = (files: string[], fileId: number) => {
|
|
1729
|
-
if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1730
|
-
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1731
|
-
|
|
1732
|
-
if (!files.length) throw new Error("No files to upload");
|
|
1733
|
-
setFApplication((a) => editNestedObject(["completedSections", fApplication.stage as number, "filesUploaded", fileId], a, files) as Application);
|
|
1734
|
-
};
|
|
1735
|
-
|
|
1736
|
-
const setFormComplete = (formId: string, e: {[key: string]: unknown}) => {
|
|
1737
|
-
if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1738
|
-
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1739
|
-
setFApplication((a) => editNestedObject(["completedSections", fApplication.stage as number, "formsCompleted", formId], a, e) as Application);
|
|
1740
|
-
};
|
|
1741
|
-
|
|
1742
|
-
const successText = {
|
|
1743
|
-
submitted: {
|
|
1744
|
-
title: "Application submitted",
|
|
1745
|
-
desc: "We've sent your application to the placement provider. You will hear what the next steps are soon.",
|
|
1746
|
-
},
|
|
1747
|
-
draftSaved: {
|
|
1748
|
-
title: "Draft saved",
|
|
1749
|
-
desc: "Your draft has been saved. Go to the 'Applications' section from your home screen to edit.",
|
|
1750
|
-
},
|
|
1751
|
-
stageComplete: {
|
|
1752
|
-
title: "Stage complete",
|
|
1753
|
-
desc: "We have sent this to the next stage in the application process. We will update you with any progress.",
|
|
1754
|
-
},
|
|
1755
|
-
outcome: {
|
|
1756
|
-
title: "Outcome submitted",
|
|
1757
|
-
desc: "We have sent the student an email with the outcome of their application.",
|
|
1758
|
-
},
|
|
1759
|
-
};
|
|
1760
|
-
|
|
1761
|
-
const onFApply = async (draft?: boolean) => {
|
|
1762
|
-
if (draft) {
|
|
1763
|
-
openSuccessPopup("draftSaved")
|
|
1764
|
-
return;
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
// Check all items have been filled in.
|
|
1768
|
-
if (!fApplication.startDate || !fApplication.endDate) throw new Error("Please select dates for your placement.");
|
|
1769
|
-
|
|
1770
|
-
await executeCallable("applications-submit", {applicationId: fApplicationId});
|
|
1771
|
-
openSuccessPopup("submitted")
|
|
1772
|
-
};
|
|
1773
|
-
|
|
1774
|
-
const progressStage = async (type: number|"accept"|"reject", e?: {feedback?: string}) => {
|
|
1775
|
-
// Check all stages completed.
|
|
1776
|
-
if (!currentStageComplete) throw new Error("Complete all forms before submitting.");
|
|
1777
|
-
if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1778
|
-
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1779
|
-
|
|
1780
|
-
await executeCallable("applications-changeStage", {applicationId: fApplicationId, type: type, feedback: e?.feedback});
|
|
1781
|
-
};
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
return {successText, onFApply, progressStage, currentStageComplete, uploadedFiles, getCurrentStage, setFormComplete, viewFile, addFile, setSuccessPopup, openSuccessPopup, setFApplication, fApplication, draftSaved, profileUrl, successPopup, fApplicationId, fListing, student, fProvider}
|
|
1785
|
-
}
|
|
1915
|
+
// return {successText, onFApply, progressStage, currentStageComplete, uploadedFiles, getCurrentStage, setFormComplete, viewFile, addFile, setSuccessPopup, openSuccessPopup, setFApplication, fApplication, draftSaved, profileUrl, successPopup, fApplicationId, fListing, student, fProvider}
|
|
1916
|
+
// }
|
|
1786
1917
|
|
|
1787
1918
|
export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
1788
1919
|
{user:UserData, orgContext?: {details: InstituteData, userGroups: {[key:string]: UserGroupData}, forms: {[key:string]: unknown}, cohorts: {[key:string]: CohortData}},
|
|
@@ -1795,8 +1926,6 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1795
1926
|
const [student, setStudent] = useState<UserData|undefined>((user && (user?.userType === "Students")) ? user : undefined);
|
|
1796
1927
|
const [cohort, setCohort] = useState<CohortData>();
|
|
1797
1928
|
const [complete, setComplete] = useState(false);
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
1929
|
const [stage, setStage] = useState<"provider"|"address"|"dates"|"review">("dates");
|
|
1801
1930
|
|
|
1802
1931
|
const sections:Array<"dates"|"provider"|"address"|"review"> = ["dates", "provider", "address", "review"];
|
|
@@ -1847,11 +1976,9 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1847
1976
|
|
|
1848
1977
|
const submitSection = (type:"dates"|"provider"|"address", data:{[key:string]:unknown}) => {
|
|
1849
1978
|
setFormData((f) => {
|
|
1850
|
-
const { id, ...values } = data
|
|
1851
1979
|
return {
|
|
1852
1980
|
...f,
|
|
1853
|
-
...
|
|
1854
|
-
id: (placement?.placementId || formData?.id) as string
|
|
1981
|
+
...data
|
|
1855
1982
|
} as StudentPlacementData;
|
|
1856
1983
|
});
|
|
1857
1984
|
if (placement?.placementId && type === "dates") {
|
|
@@ -1865,16 +1992,33 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1865
1992
|
setFormData(undefined);
|
|
1866
1993
|
};
|
|
1867
1994
|
|
|
1868
|
-
const proposePlacement = async (draft=false) => {
|
|
1995
|
+
const proposePlacement = async (draft = false) => {
|
|
1996
|
+
const getPlacementStatus = (startDate: Date, endDate: Date) => {
|
|
1997
|
+
const today = new Date()
|
|
1998
|
+
if (startDate <= today && endDate >= today) return { active: !draft ? true : false, inProgress: true, completed: false};
|
|
1999
|
+
else if (endDate <= today) return { completed: true, inProgress: false, active: false};
|
|
2000
|
+
return { completed: false, inProgress: true, active: false };
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1869
2003
|
if (!formData || !student) {
|
|
1870
2004
|
throw new Error("Cannot find placement details.");
|
|
1871
2005
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2006
|
+
|
|
2007
|
+
try {
|
|
2008
|
+
console.log("formData", formData);
|
|
2009
|
+
const status = getPlacementStatus(new Date(formData.startDate), new Date(formData.endDate));
|
|
2010
|
+
const newFormData = {...formData, ...status}
|
|
2011
|
+
|
|
2012
|
+
if (newFormData.id && newFormData.uid && draft === newFormData.draft) await firebaseQuery.update(["placements", newFormData.id], newFormData);
|
|
2013
|
+
else await addPlacement(newFormData, student.id, draft)
|
|
2014
|
+
setComplete(true);
|
|
2015
|
+
return {status}
|
|
2016
|
+
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
console.log("Error:", error);
|
|
2019
|
+
throw error;
|
|
1876
2020
|
}
|
|
1877
|
-
|
|
2021
|
+
/*
|
|
1878
2022
|
console.log("STUDENTID", student.id);
|
|
1879
2023
|
|
|
1880
2024
|
return await addPlacement(formData, student.id, draft).catch((e) => {
|
|
@@ -1885,9 +2029,9 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1885
2029
|
setComplete(true);
|
|
1886
2030
|
return e;
|
|
1887
2031
|
});
|
|
1888
|
-
|
|
1889
|
-
|
|
2032
|
+
*/
|
|
1890
2033
|
};
|
|
2034
|
+
|
|
1891
2035
|
|
|
1892
2036
|
const deletePlacement = async (id:string) => {
|
|
1893
2037
|
return await firebaseQuery.delete(["placements", id]);
|
|
@@ -1989,7 +2133,7 @@ export function useCreateCohortRenderer({oId, product, initialData=defaultCohort
|
|
|
1989
2133
|
type UserUploadParams = {
|
|
1990
2134
|
userType: "Staff"|"Students",
|
|
1991
2135
|
user: UserData,
|
|
1992
|
-
onComplete?: () => void,
|
|
2136
|
+
onComplete?: (e: string) => void,
|
|
1993
2137
|
cohortId?: string,
|
|
1994
2138
|
userGroupId?: string,
|
|
1995
2139
|
shareNameWithReferralLeaderboardConsent?: string,
|
|
@@ -2004,9 +2148,6 @@ export function useUserUploadHandler({product, oId, userType, user, onComplete,
|
|
|
2004
2148
|
const {execute} = useExecuteCallableJob({user: user});
|
|
2005
2149
|
|
|
2006
2150
|
const requiredFields = ["forename", "surname", "email"];
|
|
2007
|
-
if (userType === "Students") {
|
|
2008
|
-
requiredFields.push("year");
|
|
2009
|
-
}
|
|
2010
2151
|
|
|
2011
2152
|
const checkData = (userData: {email?: string, parentEmail?: string, year?: number, [key:string]: unknown}[]) => {
|
|
2012
2153
|
setAlert(undefined);
|
|
@@ -2061,11 +2202,6 @@ export function useUserUploadHandler({product, oId, userType, user, onComplete,
|
|
|
2061
2202
|
}
|
|
2062
2203
|
}
|
|
2063
2204
|
// Add more test cases
|
|
2064
|
-
|
|
2065
|
-
if (userType === "Students" && (!user.year || !((user.year | 0) > 0 && user.year % 1 === 0))) {
|
|
2066
|
-
setAlert({msg: "'Year' must be a number, e.g. 10.", severity: "error"});
|
|
2067
|
-
return false;
|
|
2068
|
-
}
|
|
2069
2205
|
}
|
|
2070
2206
|
|
|
2071
2207
|
if (emptyRequiredCell) {
|
|
@@ -2111,10 +2247,10 @@ export function useUserUploadHandler({product, oId, userType, user, onComplete,
|
|
|
2111
2247
|
setAlert(undefined);
|
|
2112
2248
|
if (fUsers.length) {
|
|
2113
2249
|
console.log("fUsers", fUsers);
|
|
2114
|
-
execute("userManagement-addUsers", {product: product, oId: oId, users: fUsers, userType: userType, userGroupId: userGroupId, cohortId: cohortId});
|
|
2250
|
+
const jobId = await execute("userManagement-addUsers", {product: product, oId: oId, users: fUsers, userType: userType, userGroupId: userGroupId, cohortId: cohortId});
|
|
2251
|
+
onComplete && onComplete(jobId);
|
|
2115
2252
|
}
|
|
2116
2253
|
console.log("Complete", onComplete);
|
|
2117
|
-
onComplete && onComplete();
|
|
2118
2254
|
console.log("Finished");
|
|
2119
2255
|
};
|
|
2120
2256
|
|
|
@@ -2831,41 +2967,42 @@ function useGenericWorkflowEditor({user, initialData, defaultData, onSubmit}: Ge
|
|
|
2831
2967
|
onDeleteArrow, fWorkflowNodes, containerRef, setError, setArrows, setFWorkflowNodes}});
|
|
2832
2968
|
}
|
|
2833
2969
|
|
|
2834
|
-
type
|
|
2970
|
+
type InstituteProviderContactUploadParams = {
|
|
2835
2971
|
user: UserData,
|
|
2836
2972
|
onComplete?: () => void
|
|
2837
2973
|
}
|
|
2838
2974
|
|
|
2839
|
-
export type
|
|
2840
|
-
|
|
2841
|
-
|
|
2975
|
+
export type InstituteProviderContactUpload = {
|
|
2976
|
+
business: string;
|
|
2977
|
+
forename?: string;
|
|
2978
|
+
surname?: string;
|
|
2842
2979
|
email: string;
|
|
2843
2980
|
phone?: string;
|
|
2844
|
-
addressOne
|
|
2845
|
-
addressTwo
|
|
2846
|
-
city
|
|
2847
|
-
postcode
|
|
2848
|
-
country
|
|
2981
|
+
addressOne?: string;
|
|
2982
|
+
addressTwo?: string;
|
|
2983
|
+
city?: string;
|
|
2984
|
+
postcode?: string;
|
|
2985
|
+
country?: string;
|
|
2849
2986
|
};
|
|
2850
2987
|
|
|
2851
|
-
export function
|
|
2988
|
+
export function useInstituteProviderContactsHandler({user}: InstituteProviderContactUploadParams) {
|
|
2852
2989
|
const [emptyCellsWarning, setEmptyCellsWarning] = useState(false);
|
|
2853
2990
|
const [alert, setAlert] = useState<{severity: "warning"|"error"|"success"|"info", msg: string}>();
|
|
2854
2991
|
|
|
2855
|
-
const requiredFields = ["
|
|
2992
|
+
const requiredFields = ["business", "forename", "email"];
|
|
2856
2993
|
const {execute} = useExecuteCallableJob({user: user});
|
|
2857
2994
|
|
|
2858
|
-
const checkData = (
|
|
2995
|
+
const checkData = (providerContacts: InstituteProviderContactUpload[]) => {
|
|
2859
2996
|
setAlert(undefined);
|
|
2860
2997
|
|
|
2861
|
-
|
|
2998
|
+
providerContacts = providerContacts.filter((u) => Object.entries(u).some(([, v]) => v));
|
|
2862
2999
|
|
|
2863
|
-
if (!Object.entries(
|
|
3000
|
+
if (!Object.entries(providerContacts)) {
|
|
2864
3001
|
return [];
|
|
2865
3002
|
}
|
|
2866
|
-
console.log("P",
|
|
3003
|
+
console.log("P", providerContacts);
|
|
2867
3004
|
|
|
2868
|
-
if (
|
|
3005
|
+
if (providerContacts.filter((u) => u.email).length < providerContacts.length) {
|
|
2869
3006
|
setAlert({msg: "Your data contains missing email addresses.", severity: "error"});
|
|
2870
3007
|
return false;
|
|
2871
3008
|
}
|
|
@@ -2873,18 +3010,18 @@ export function useInstitutePlacementListingHandler({user}: InstitutePlacementLi
|
|
|
2873
3010
|
let emptyOptionalCell = false;
|
|
2874
3011
|
let emptyRequiredCell = false;
|
|
2875
3012
|
|
|
2876
|
-
for (const
|
|
2877
|
-
if (!checkEmailValidity(
|
|
3013
|
+
for (const providerContact of providerContacts ) {
|
|
3014
|
+
if (!checkEmailValidity(providerContact)) {
|
|
2878
3015
|
return false;
|
|
2879
3016
|
}
|
|
2880
3017
|
|
|
2881
|
-
if (Object.keys(
|
|
3018
|
+
if (Object.keys(providerContact).includes("")) {
|
|
2882
3019
|
setAlert({msg: "Data cannot be uploaded in unnamed columns.", severity: "error"});
|
|
2883
3020
|
return false;
|
|
2884
3021
|
}
|
|
2885
3022
|
|
|
2886
|
-
for (const field in
|
|
2887
|
-
if (!
|
|
3023
|
+
for (const field in providerContact) {
|
|
3024
|
+
if (!providerContact[field]) {
|
|
2888
3025
|
if (requiredFields.includes(field)) {
|
|
2889
3026
|
setAlert({msg: "All users must contain "+requiredFields.join(", "), severity: "error"});
|
|
2890
3027
|
emptyRequiredCell = true;
|
|
@@ -2905,44 +3042,30 @@ export function useInstitutePlacementListingHandler({user}: InstitutePlacementLi
|
|
|
2905
3042
|
} else {
|
|
2906
3043
|
setEmptyCellsWarning(false);
|
|
2907
3044
|
}
|
|
2908
|
-
return
|
|
3045
|
+
return providerContacts;
|
|
2909
3046
|
};
|
|
2910
3047
|
|
|
2911
|
-
const checkEmailValidity = (
|
|
2912
|
-
if (!validateEmail(
|
|
2913
|
-
setAlert({msg: `Error in email formatting: ${
|
|
3048
|
+
const checkEmailValidity = (providerContact: InstituteProviderContactUpload) => {
|
|
3049
|
+
if (!validateEmail(providerContact.email as string)) {
|
|
3050
|
+
setAlert({msg: `Error in email formatting: ${providerContact.email}. Amend errors and reupload.`, severity: "error"});
|
|
2914
3051
|
return false;
|
|
2915
3052
|
}
|
|
2916
3053
|
return true;
|
|
2917
3054
|
};
|
|
2918
3055
|
|
|
2919
|
-
const
|
|
2920
|
-
let
|
|
2921
|
-
console.log("PP",
|
|
3056
|
+
const uploadProviderContacts = async (providers: InstituteProviderContactUpload[], schoolId?: string) => {
|
|
3057
|
+
let fProviders:InstituteProviderContactUpload[] = [];
|
|
3058
|
+
console.log("PP", providers);
|
|
2922
3059
|
|
|
2923
|
-
const cleanUpload = checkData(
|
|
3060
|
+
const cleanUpload = checkData(providers);
|
|
2924
3061
|
if (!cleanUpload) {
|
|
2925
3062
|
return false;
|
|
2926
3063
|
}
|
|
2927
|
-
|
|
3064
|
+
fProviders = cleanUpload;
|
|
2928
3065
|
|
|
2929
3066
|
setAlert(undefined);
|
|
2930
|
-
if (
|
|
2931
|
-
|
|
2932
|
-
({
|
|
2933
|
-
name: v.provider,
|
|
2934
|
-
title: v.jobTitle,
|
|
2935
|
-
providerEmail: v.email,
|
|
2936
|
-
providerPhone: v.phone,
|
|
2937
|
-
["address-line1"]: v.addressOne,
|
|
2938
|
-
["address-line2"]: v.addressTwo,
|
|
2939
|
-
locality: v.city,
|
|
2940
|
-
postal_code: v.postcode,
|
|
2941
|
-
country: v.country,
|
|
2942
|
-
}));
|
|
2943
|
-
console.log("P", placements);
|
|
2944
|
-
console.log("callable params", {placements: formattedPlacements, instituteId: user.product === "admin" ? "admin" : user.oId});
|
|
2945
|
-
execute("placementListing-add", {data: formattedPlacements, instituteId: user.product === "admin" ? "admin" : user.oId});
|
|
3067
|
+
if (fProviders.length) {
|
|
3068
|
+
execute("providerContacts-add", {providerContacts: fProviders, instituteId: user.product === "admin" ? "admin" : user.oId, schoolId: schoolId});
|
|
2946
3069
|
}
|
|
2947
3070
|
return true;
|
|
2948
3071
|
};
|
|
@@ -2952,7 +3075,7 @@ export function useInstitutePlacementListingHandler({user}: InstitutePlacementLi
|
|
|
2952
3075
|
setEmptyCellsWarning(false);
|
|
2953
3076
|
};
|
|
2954
3077
|
|
|
2955
|
-
return ({...{
|
|
3078
|
+
return ({...{uploadProviderContacts, alert, onChange}});
|
|
2956
3079
|
}
|
|
2957
3080
|
|
|
2958
3081
|
|
|
@@ -2962,12 +3085,12 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
2962
3085
|
const [workflow, setWorkflow] = useState<WorkflowStage[]>();
|
|
2963
3086
|
const [cohort, setCohort] = useState<CohortData>();
|
|
2964
3087
|
const [student, setStudent] = useState<UserData>();
|
|
2965
|
-
const [wStage, setWStage] = useState<WorkflowStage>();
|
|
3088
|
+
const [wStage, setWStage] = useState<WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}}>();
|
|
2966
3089
|
const [snackbar, setSnackbar] = useState<{ open: boolean; message?: string}>({open: false});
|
|
2967
3090
|
const [disableEmail, setDisableEmail] = useState({parent: false, provider: false});
|
|
2968
3091
|
const [rejectELIPopup, setRejectELIPopup] = useState(false);
|
|
2969
3092
|
const [eliPopupOpen, setEliPopupOpen] = useState(false);
|
|
2970
|
-
const [
|
|
3093
|
+
const [eliData, setELIData] = useState<string>();
|
|
2971
3094
|
|
|
2972
3095
|
const [rejectExternalDocPopup, setRejectExternalDocPopup] = useState<"riskAssessment"|"dbsCheck"|false>(false);
|
|
2973
3096
|
const [externalDocPopupOpen, setExternalDocPopupOpen] = useState<"riskAssessment"|"dbsCheck"|false>(false);
|
|
@@ -2982,6 +3105,10 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
2982
3105
|
const [uploadRA, setUploadRA] = useState(false);
|
|
2983
3106
|
const [uploadDBS, setUploadDBS] = useState(false);
|
|
2984
3107
|
const [onboardingPopup, setOnboardingPopup] = useState(false);
|
|
3108
|
+
const [dismissOnboardingPopup, setDismissOnboardingPopup] = useState(false);
|
|
3109
|
+
const [addOnboardingDocsPopup, setAddOnboardingDocsPopup] = useState(false);
|
|
3110
|
+
const [shareStudentRequestPopup, setShareStudentRequestPopup] = useState(false);
|
|
3111
|
+
|
|
2985
3112
|
const [editable, setEditable] = useState(false);
|
|
2986
3113
|
|
|
2987
3114
|
const [withdrawFromPlacementPopup, setWithdrawFromPlacementPopup] = useState(false);
|
|
@@ -3031,7 +3158,17 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3031
3158
|
if (user.userType === "Students") {
|
|
3032
3159
|
setStudent(user);
|
|
3033
3160
|
} else {
|
|
3034
|
-
|
|
3161
|
+
if (placement.uid) {
|
|
3162
|
+
getUserById(placement.uid, undefined, false).then(setStudent);
|
|
3163
|
+
} else {
|
|
3164
|
+
setStudent({
|
|
3165
|
+
details: {
|
|
3166
|
+
forename: placement.studentForename || "",
|
|
3167
|
+
surname: placement.studentSurname || "",
|
|
3168
|
+
},
|
|
3169
|
+
email: placement.studentEmail || ""
|
|
3170
|
+
} as UserData)
|
|
3171
|
+
}
|
|
3035
3172
|
}
|
|
3036
3173
|
|
|
3037
3174
|
}, [placement]);
|
|
@@ -3041,19 +3178,29 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3041
3178
|
useEffect(() => {
|
|
3042
3179
|
if (!workflow || !placement) return;
|
|
3043
3180
|
|
|
3044
|
-
const
|
|
3045
|
-
// console.log("currentWorkflowStage", currentWorkflowStage)
|
|
3046
|
-
currentWorkflowStage.id = placement.status;
|
|
3181
|
+
const getAdditionalStageData = async ():Promise<WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}}> => {
|
|
3047
3182
|
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3183
|
+
const currentWorkflowStage = {...workflow.find((obj) => obj.id === placement.status) as WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}}};
|
|
3184
|
+
// console.log("currentWorkflowStage", currentWorkflowStage)
|
|
3185
|
+
currentWorkflowStage.id = placement.status;
|
|
3186
|
+
|
|
3187
|
+
// Get form data for current stage
|
|
3188
|
+
if (currentWorkflowStage.forms) {
|
|
3189
|
+
getFormsFromId(["forms"], currentWorkflowStage.forms).then((details) => {
|
|
3190
|
+
currentWorkflowStage.formDetails = details as [{name:string}];
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
if (currentWorkflowStage.files) {
|
|
3194
|
+
currentWorkflowStage.files = Object.fromEntries(await Promise.all(currentWorkflowStage.files.map(async (file) => {
|
|
3195
|
+
const fileItem = await firebaseQuery.getDocData(["files", file]) as FileItem;
|
|
3196
|
+
const url = await getDownloadURL(ref(storage, `institutes/${placement.oId}/${fileItem.fileName}`))
|
|
3197
|
+
return [file, {name: fileItem.name, url: url}];
|
|
3198
|
+
})));
|
|
3199
|
+
}
|
|
3200
|
+
return currentWorkflowStage;
|
|
3056
3201
|
}
|
|
3202
|
+
|
|
3203
|
+
getAdditionalStageData().then(setWStage);
|
|
3057
3204
|
}, [placement, workflow]);
|
|
3058
3205
|
|
|
3059
3206
|
|
|
@@ -3097,15 +3244,14 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3097
3244
|
};
|
|
3098
3245
|
|
|
3099
3246
|
|
|
3100
|
-
const onboardingStatus:"Add onboarding documents"|"Onboarding sent"|"Onboarding docs completed"|"Onboarding docs approved"|"Complete onboarding" =
|
|
3247
|
+
const onboardingStatus:"Add onboarding documents"|"Onboarding sent"|"Onboarding docs completed"|"Onboarding docs approved"|"Complete onboarding" =
|
|
3248
|
+
placement?.onboarding ? placement.onboarding.completed?.submitted ? placement.onboarding.completed.accepted ? "Onboarding docs approved" : "Onboarding docs completed" : (user.userType === "Staff" ? "Onboarding sent" : "Complete onboarding") : "Add onboarding documents";
|
|
3101
3249
|
|
|
3102
3250
|
const signOffPlacements = getAccess(user, "signOffPlacements");
|
|
3103
3251
|
|
|
3104
3252
|
let canEdit = false;
|
|
3105
3253
|
|
|
3106
|
-
if ((wStage?.userType === "Staff" && user.product === "
|
|
3107
|
-
canEdit = true;
|
|
3108
|
-
} else if ((wStage?.userType === "Staff" && user.userType === "Staff" && user.product === "institutes") || (user.product === "providers" && wStage?.userType === "Provider") || user.userType === "Students" && wStage?.userType === "Students") {
|
|
3254
|
+
if ((wStage?.userType === "Staff" && user.userType === "Staff" && user.product === "institutes") || (user.product === "providers" && wStage?.userType === "Provider") || user.userType === "Students" && wStage?.userType === "Students") {
|
|
3109
3255
|
console.log("ALMOST CAN EDIT");
|
|
3110
3256
|
if (user.userType === "Staff" && !signOffPlacements) {
|
|
3111
3257
|
canEdit = false;
|
|
@@ -3116,16 +3262,19 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3116
3262
|
|
|
3117
3263
|
|
|
3118
3264
|
|
|
3119
|
-
const onFlagClick = async (e:FlagCodes) => {
|
|
3265
|
+
const onFlagClick = async (e:FlagCodes, onClose?: boolean) => {
|
|
3120
3266
|
if (!placement) return;
|
|
3121
3267
|
if (e === "completeOnboarding" || e === "reviewOnboarding") {
|
|
3122
3268
|
setOnboardingPopup(true);
|
|
3123
3269
|
}
|
|
3270
|
+
if (e === "studentNotAccepted") {
|
|
3271
|
+
setShareStudentRequestPopup(true);
|
|
3272
|
+
}
|
|
3124
3273
|
if (e === "noInsurance") {
|
|
3125
|
-
if (!
|
|
3274
|
+
if (!eliData) {
|
|
3126
3275
|
const storageRef = ref(storage, `insurance/${placement.providerId}.pdf`);
|
|
3127
|
-
const file = await getDownloadURL(storageRef);
|
|
3128
|
-
|
|
3276
|
+
const file = await getDownloadURL(storageRef).catch(() => placement.insuranceSkippedReason);
|
|
3277
|
+
setELIData(file);
|
|
3129
3278
|
}
|
|
3130
3279
|
setEliPopupOpen(true);
|
|
3131
3280
|
}
|
|
@@ -3146,19 +3295,26 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3146
3295
|
}
|
|
3147
3296
|
setExternalDocPopupOpen("dbsCheck");
|
|
3148
3297
|
}
|
|
3298
|
+
if (e === "addOnboarding") {
|
|
3299
|
+
if (onClose) {
|
|
3300
|
+
setDismissOnboardingPopup(true);
|
|
3301
|
+
} else {
|
|
3302
|
+
setAddOnboardingDocsPopup(true);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3149
3305
|
};
|
|
3150
3306
|
|
|
3151
3307
|
const approveELI = async () => {
|
|
3152
3308
|
if (!placement) return;
|
|
3153
|
-
await executeCallable("insurance-approve", {oId: user.oId,
|
|
3309
|
+
await executeCallable("insurance-approve", {oId: user.oId, providerContactId: placement.providerContactId});
|
|
3154
3310
|
setEliPopupOpen(false);
|
|
3155
3311
|
};
|
|
3156
3312
|
|
|
3157
3313
|
const rejectELI = async ({reason}:{reason: string}) => {
|
|
3158
3314
|
if (!placement || !institute) return;
|
|
3159
3315
|
|
|
3160
|
-
console.log("Reject", {reason: reason, placement: placement, instituteName: institute.name
|
|
3161
|
-
await executeCallable("insurance-reject", {reason: reason, placementId: placementId, instituteName: institute.name
|
|
3316
|
+
console.log("Reject", {reason: reason, placement: placement, instituteName: institute.name});
|
|
3317
|
+
await executeCallable("insurance-reject", {reason: reason, placementId: placementId, instituteName: institute.name});
|
|
3162
3318
|
setRejectELIPopup(false);
|
|
3163
3319
|
setEliPopupOpen(false);
|
|
3164
3320
|
};
|
|
@@ -3179,15 +3335,17 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3179
3335
|
const manuallyConfigureProvider = async () => {
|
|
3180
3336
|
if (!placement) return;
|
|
3181
3337
|
if (!placement.providerId) {
|
|
3182
|
-
const res = await executeCallable("
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3338
|
+
const res = await executeCallable("providerContacts-uploadProviderDetails", {
|
|
3339
|
+
placement: {
|
|
3340
|
+
id: placementId,
|
|
3341
|
+
data: Object.fromEntries(Object.entries(placement).filter(([k]) => k !== "contactId")),
|
|
3342
|
+
stage: wStage,
|
|
3343
|
+
},
|
|
3186
3344
|
skipSearch: true}).catch((e) => {
|
|
3187
3345
|
throw e;
|
|
3188
3346
|
});
|
|
3189
3347
|
console.log("RETURN", res.data);
|
|
3190
|
-
setPlacement((p) => ({...p, ...res.data}));
|
|
3348
|
+
setPlacement((p) => ({...p, ...res.data as any}));
|
|
3191
3349
|
if (uploadProviderDocPopup === "insurance") {
|
|
3192
3350
|
setUploadProviderDocPopup(undefined);
|
|
3193
3351
|
setUploadInsurance(true);
|
|
@@ -3206,26 +3364,28 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3206
3364
|
await executeCallable("placement-withdraw", {placementId: placementId});
|
|
3207
3365
|
}
|
|
3208
3366
|
|
|
3209
|
-
return {placement, wStage, student, workflow, editable, withdrawFromPlacementPopup, setWithdrawFromPlacementPopup, withdrawFromPlacement, onFlagClick, setUploadInsurance, setUploadProviderDocPopup, setUploadRA, setUploadDBS, onboardingStatus, setSkipStagePopup, onboardingPopup, setViewExternalLinkPopup, setOnboardingPopup, setRejectELIPopup,
|
|
3367
|
+
return {placement, wStage, student, workflow, editable, withdrawFromPlacementPopup, addOnboardingDocsPopup, setAddOnboardingDocsPopup, dismissOnboardingPopup, setDismissOnboardingPopup, setWithdrawFromPlacementPopup, withdrawFromPlacement, onFlagClick, setUploadInsurance, setUploadProviderDocPopup, setUploadRA, setUploadDBS, onboardingStatus, setSkipStagePopup, onboardingPopup, setViewExternalLinkPopup, setOnboardingPopup, setRejectELIPopup, eliData, riskAssessmentURL, dbsCheckURL, setExternalLinkCopied, skipStagePopup, snackbar, setSnackbar, cohort, disableEmail, rejectELIPopup, eliPopupOpen, rejectExternalDocPopup, externalDocPopupOpen, viewExternalLinkPopup, externalLinkCopied, uploadInsurance, uploadRA, uploadDBS, editStage, sendEmail, canEdit, approveELI, setEliPopupOpen, uploadProviderDocPopup, rejectELI, setRejectExternalDocPopup, setExternalDocPopupOpen, approveProviderDoc, rejectProviderDoc, manuallyConfigureProvider, institute, shareStudentRequestPopup, setShareStudentRequestPopup}
|
|
3210
3368
|
}
|
|
3211
3369
|
|
|
3212
|
-
export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onboarding: (
|
|
3370
|
+
export function useOnboardingPopup({onboarding, providerId, placementId, user, onClose}:{onboarding: (
|
|
3213
3371
|
OnboardingDocs&{
|
|
3214
3372
|
completed: {
|
|
3215
|
-
submitted:
|
|
3373
|
+
submitted: boolean,
|
|
3374
|
+
submittedDate?: string,
|
|
3216
3375
|
accepted?: boolean,
|
|
3217
3376
|
filesViewed?: string[],
|
|
3218
3377
|
formsCompleted?: {[key: string]: unknown},
|
|
3219
3378
|
filesUploaded?: {[key: number]: string[]},
|
|
3220
3379
|
}}
|
|
3221
|
-
), placementId: string, user: UserData, onClose: () => void}) {
|
|
3380
|
+
), placementId: string, user: UserData, providerId: string, onClose: () => void}) {
|
|
3222
3381
|
|
|
3223
3382
|
const [fileUploadPopup, setFileUploadPopup] = useState<false|number>(false);
|
|
3224
3383
|
const [form, setForm] = useState<{id: string, name: string, [key:string]: unknown}>();
|
|
3225
3384
|
const [rejectPopup, setRejectPopup] = useState(false);
|
|
3226
3385
|
const [mOnboarding, setMOnboarding] = useState(onboarding);
|
|
3227
3386
|
const [completedSections, setCompletedSections] = useState<{
|
|
3228
|
-
submitted:
|
|
3387
|
+
submitted: boolean,
|
|
3388
|
+
submittedDate?: string,
|
|
3229
3389
|
filesViewed?: string[] | undefined;
|
|
3230
3390
|
formsCompleted?: {
|
|
3231
3391
|
[key: string]: unknown;
|
|
@@ -3234,7 +3394,19 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3234
3394
|
[key: number]: string[];
|
|
3235
3395
|
} | undefined;
|
|
3236
3396
|
}>();
|
|
3397
|
+
|
|
3237
3398
|
const [uploadedFiles, setUploadedFiles] = useState<{[key: string]: FileItem}>({});
|
|
3399
|
+
const [viewableFiles, setViewableFiles] = useState<{[key: string]: FileItem}>();
|
|
3400
|
+
const [formDetails, setFormDetails] = useState<{
|
|
3401
|
+
[key: string]: {
|
|
3402
|
+
name: string;
|
|
3403
|
+
id: string;
|
|
3404
|
+
description?: string;
|
|
3405
|
+
product: Products;
|
|
3406
|
+
oId: string;
|
|
3407
|
+
form: CustomFormSchema;
|
|
3408
|
+
};
|
|
3409
|
+
}>({});
|
|
3238
3410
|
|
|
3239
3411
|
const firebaseQuery = new FirebaseQuery();
|
|
3240
3412
|
|
|
@@ -3252,7 +3424,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3252
3424
|
const viewedFiles = a.completed ? a.completed?.filesViewed || [] : [];
|
|
3253
3425
|
if (viewedFiles?.includes(file)) return a;
|
|
3254
3426
|
|
|
3255
|
-
|
|
3427
|
+
viewableFiles?.[file].url && onOpen(viewableFiles?.[file].url);
|
|
3256
3428
|
viewedFiles?.push(file);
|
|
3257
3429
|
|
|
3258
3430
|
const newA = editNestedObject(["completed", "filesViewed"], oldA, viewedFiles) as any;
|
|
@@ -3275,7 +3447,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3275
3447
|
|
|
3276
3448
|
const onboardingFiles = onboarding.files ? Object.fromEntries(await Promise.all(onboarding.files?.map(async (fileId) => {
|
|
3277
3449
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
3278
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
3450
|
+
file.url = await getDownloadURL(ref(storage, `providers/${providerId}/${file.fileName}`));
|
|
3279
3451
|
|
|
3280
3452
|
return [fileId, file];
|
|
3281
3453
|
}))) : [];
|
|
@@ -3283,8 +3455,8 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3283
3455
|
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
3284
3456
|
}))) : [];
|
|
3285
3457
|
|
|
3286
|
-
|
|
3287
|
-
|
|
3458
|
+
setViewableFiles(onboardingFiles);
|
|
3459
|
+
setFormDetails(onboardingForms);
|
|
3288
3460
|
return onboardingNew;
|
|
3289
3461
|
};
|
|
3290
3462
|
getOnboardingData().then(setMOnboarding);
|
|
@@ -3346,7 +3518,8 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3346
3518
|
// Check all stages completed.
|
|
3347
3519
|
if (!placementId) return;
|
|
3348
3520
|
if (!stagesCompleted()) throw new Error("Complete all forms before submitting.");
|
|
3349
|
-
await firebaseQuery.update(["placements", placementId], {["onboarding.completed.submitted"]: convertDate(new Date(), "dbstring") as string});
|
|
3521
|
+
await firebaseQuery.update(["placements", placementId], {["onboarding.completed.accepted"]: false, ["onboarding.completed.submitted"]: true, ["onboarding.completed.submittedDate"]: convertDate(new Date(), "dbstring") as string});
|
|
3522
|
+
//executeCallable("sendOnboardingSubmittedEmail", {});
|
|
3350
3523
|
};
|
|
3351
3524
|
|
|
3352
3525
|
useEffect(() => {
|
|
@@ -3391,5 +3564,701 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3391
3564
|
addCompletedSectionURLs();
|
|
3392
3565
|
}, [mOnboarding]);
|
|
3393
3566
|
|
|
3394
|
-
return {addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup};
|
|
3395
|
-
}
|
|
3567
|
+
return {addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup, viewableFiles, formDetails};
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
|
|
3571
|
+
|
|
3572
|
+
|
|
3573
|
+
|
|
3574
|
+
export function useLoadAddresses(user: UserData, limitItems?: number, queryConstraint?: QueryConstraint[], request?: boolean) {
|
|
3575
|
+
const [addresses, setAddresses] = useState<{[key: string]: OrganisationAddress&{listings: number}}>({});
|
|
3576
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3577
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3578
|
+
|
|
3579
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3580
|
+
|
|
3581
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3582
|
+
|
|
3583
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3584
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3585
|
+
};
|
|
3586
|
+
|
|
3587
|
+
const loadAddresses = () => {
|
|
3588
|
+
const constraints:QueryConstraint[] = [where("oId", "==", user.oId), where("product", "==", user.product), orderBy(documentId())]
|
|
3589
|
+
|
|
3590
|
+
if (limitItems) {
|
|
3591
|
+
constraints.push(limit(limitItems));
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
if (user.viewAddresses === "all" || user.userGroup === "admin" || request) {
|
|
3595
|
+
if (lastDoc?.id) {
|
|
3596
|
+
constraints.push(startAfter(lastDoc?.id));
|
|
3597
|
+
}
|
|
3598
|
+
} else if (user.viewAddresses === "request") {
|
|
3599
|
+
if (!user.visibleAddresses?.length) return;
|
|
3600
|
+
|
|
3601
|
+
constraints.push(where(documentId(), "in", user.visibleAddresses))
|
|
3602
|
+
if (lastDoc?.id) {
|
|
3603
|
+
constraints.push(startAfter(lastDoc?.id));
|
|
3604
|
+
}
|
|
3605
|
+
} else {
|
|
3606
|
+
setLoading(false);
|
|
3607
|
+
return; // viewAddresses === "none", no need to load anything
|
|
3608
|
+
}
|
|
3609
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3610
|
+
|
|
3611
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3612
|
+
const deletedAddresses = snapshot.docChanges().map((change) => {
|
|
3613
|
+
if (change.type === "removed") {
|
|
3614
|
+
return change.doc.id;
|
|
3615
|
+
}
|
|
3616
|
+
return;
|
|
3617
|
+
})
|
|
3618
|
+
|
|
3619
|
+
setAddresses((prev) => Object.fromEntries(Object.entries(prev).filter(([k]) => !deletedAddresses.includes(k))))
|
|
3620
|
+
|
|
3621
|
+
if (!snapshot.empty) {
|
|
3622
|
+
|
|
3623
|
+
|
|
3624
|
+
const newAddresses:[string, OrganisationAddress][] = snapshot.docs.map(doc => ([doc.id, {id: doc.id, ...doc.data() as OrganisationAddress}]));
|
|
3625
|
+
|
|
3626
|
+
const withListings:{[key: string]: OrganisationAddress&{listings: number}} = Object.fromEntries(await Promise.all(newAddresses.map(async ([k, address]) => {
|
|
3627
|
+
const listings = await firebaseQuery.getCount("placementListings", [where("providerId", "==", user.oId), where("addressId", "==", k)]);
|
|
3628
|
+
return [k, {...address, listings: listings}];
|
|
3629
|
+
})));
|
|
3630
|
+
|
|
3631
|
+
setAddresses(prev => ({...prev, ...withListings}));
|
|
3632
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3633
|
+
}
|
|
3634
|
+
setLoading(false);
|
|
3635
|
+
}), "addresses", constraints, undefined, true);
|
|
3636
|
+
};
|
|
3637
|
+
|
|
3638
|
+
useEffect(() => {
|
|
3639
|
+
const unsubscribe = loadAddresses();
|
|
3640
|
+
|
|
3641
|
+
return () => {
|
|
3642
|
+
if (unsubscribe) {
|
|
3643
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3644
|
+
}
|
|
3645
|
+
};
|
|
3646
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3647
|
+
}, [queryConstraints]);
|
|
3648
|
+
|
|
3649
|
+
const onScrollBottom = () => {
|
|
3650
|
+
if (!limitItems) return;
|
|
3651
|
+
if (!loading) {
|
|
3652
|
+
setLoading(true);
|
|
3653
|
+
loadAddresses();
|
|
3654
|
+
}
|
|
3655
|
+
};
|
|
3656
|
+
|
|
3657
|
+
return { addresses, onScrollBottom, loading, changeQueryConstraints };
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
|
|
3661
|
+
|
|
3662
|
+
|
|
3663
|
+
export function useLoadListings(user: UserData, queryConstraint?: QueryConstraint[], request?: boolean) {
|
|
3664
|
+
const [listings, setListings] = useState<[string, PlacementListing&{applicants?: number, scheduled?: number, active?: number}][]>([]);
|
|
3665
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3666
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3667
|
+
|
|
3668
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3669
|
+
|
|
3670
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3671
|
+
|
|
3672
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3673
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3674
|
+
};
|
|
3675
|
+
|
|
3676
|
+
const loadListings = () => {
|
|
3677
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3678
|
+
|
|
3679
|
+
if (user.viewPlacementListings === "all" || user.userGroup === "admin" || request) {
|
|
3680
|
+
if (lastDoc?.id) {
|
|
3681
|
+
constraints.push(startAfter(lastDoc));
|
|
3682
|
+
}
|
|
3683
|
+
} else {
|
|
3684
|
+
if (!user.visibleListings?.length) return;
|
|
3685
|
+
constraints.push(where(documentId(), 'in', user.visibleListings));
|
|
3686
|
+
if (lastDoc?.id) {
|
|
3687
|
+
constraints.push(startAfter(lastDoc));
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
if (user.viewAddresses !== "all" && user.viewPlacementListings === "all" && user.userGroup !== "admin") {
|
|
3692
|
+
if (!user.visibleAddresses?.length) return;
|
|
3693
|
+
|
|
3694
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3695
|
+
}
|
|
3696
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3697
|
+
|
|
3698
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3699
|
+
const deletedListings = snapshot.docChanges().map((change) => {
|
|
3700
|
+
if (change.type === "removed") {
|
|
3701
|
+
return change.doc.id;
|
|
3702
|
+
}
|
|
3703
|
+
return;
|
|
3704
|
+
})
|
|
3705
|
+
|
|
3706
|
+
setListings((prev) => prev.filter(([k]) => !deletedListings.includes(k)))
|
|
3707
|
+
|
|
3708
|
+
if (!snapshot.empty) {
|
|
3709
|
+
const newListings:[string, PlacementListing][] = snapshot.docs.map(doc => ([doc.id, {...doc.data() as PlacementListing, id: doc.id}]));
|
|
3710
|
+
|
|
3711
|
+
const listingsWithAdditionalData:[string, PlacementListing&{applicants?: number, scheduled?: number, active?: number}][] = await Promise.all(newListings.map(async ([id, listing]) => {
|
|
3712
|
+
const listingWithAdditionalData = {...listing} as PlacementListing&{applicants?: number, scheduled?: number, active?: number};
|
|
3713
|
+
|
|
3714
|
+
if (listingWithAdditionalData.applicants !== undefined) return [id, listingWithAdditionalData];
|
|
3715
|
+
|
|
3716
|
+
listingWithAdditionalData.applicants = await firebaseQuery.getCount("applications", [where("providerId", "==", user.oId), where("placementId", "==", id), where("status", "==", "submitted")]);
|
|
3717
|
+
listingWithAdditionalData.scheduled = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("placementId", "==", id), where("inProgress", "==", true), where("startDate", ">", convertDate(new Date(), "dbstring"))]);
|
|
3718
|
+
listingWithAdditionalData.active = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("placementId", "==", id), where("active", "==", true)]);
|
|
3719
|
+
|
|
3720
|
+
return [id, listingWithAdditionalData];
|
|
3721
|
+
}));
|
|
3722
|
+
setListings(prev => (Object.entries({...Object.fromEntries(prev), ...Object.fromEntries(listingsWithAdditionalData)})));
|
|
3723
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3724
|
+
}
|
|
3725
|
+
setLoading(false);
|
|
3726
|
+
}), "placementListings", constraints, undefined, true);
|
|
3727
|
+
};
|
|
3728
|
+
|
|
3729
|
+
useEffect(() => {
|
|
3730
|
+
|
|
3731
|
+
loadListings();
|
|
3732
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3733
|
+
}, [queryConstraints]);
|
|
3734
|
+
|
|
3735
|
+
const onScrollBottom = () => {
|
|
3736
|
+
if (!loading) {
|
|
3737
|
+
setLoading(true);
|
|
3738
|
+
loadListings();
|
|
3739
|
+
}
|
|
3740
|
+
};
|
|
3741
|
+
|
|
3742
|
+
return { listings, onScrollBottom, loading, changeQueryConstraints };
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
|
|
3746
|
+
|
|
3747
|
+
export function useLoadProviderPlacements(user: UserData, queryConstraint?: QueryConstraint[], placementId?: string) {
|
|
3748
|
+
const [placements, setPlacements] = useState<[string, StudentPlacementData&{applicants?: number, scheduled?: number, active?: number}][]>([]);
|
|
3749
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3750
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3751
|
+
|
|
3752
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3753
|
+
|
|
3754
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3755
|
+
|
|
3756
|
+
if (user.product !== "providers") throw new Error("Only providers can use this hook.");
|
|
3757
|
+
|
|
3758
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3759
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3760
|
+
};
|
|
3761
|
+
|
|
3762
|
+
const loadListings = () => {
|
|
3763
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3764
|
+
|
|
3765
|
+
if (placementId) {
|
|
3766
|
+
constraints.push(where("placementId", "==", placementId));
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
if (user.viewPlacementListings === "all" || user.userGroup === "admin") {
|
|
3770
|
+
if (lastDoc?.id) {
|
|
3771
|
+
constraints.push(startAfter(lastDoc));
|
|
3772
|
+
}
|
|
3773
|
+
} else {
|
|
3774
|
+
if (!user.visibleListings?.length) return;
|
|
3775
|
+
constraints.push(where("placementId", 'in', user.visibleListings));
|
|
3776
|
+
if (lastDoc?.id) {
|
|
3777
|
+
constraints.push(startAfter(lastDoc));
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
if (user.viewAddresses !== "all" && user.viewPlacementListings === "all" && user.userGroup !== "admin") {
|
|
3782
|
+
if (!user.visibleAddresses?.length) return;
|
|
3783
|
+
|
|
3784
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3785
|
+
}
|
|
3786
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3787
|
+
|
|
3788
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3789
|
+
const deletedListings = snapshot.docChanges().map((change) => {
|
|
3790
|
+
if (change.type === "removed") {
|
|
3791
|
+
return change.doc.id;
|
|
3792
|
+
}
|
|
3793
|
+
return;
|
|
3794
|
+
})
|
|
3795
|
+
|
|
3796
|
+
setPlacements((prev) => prev.filter(([k]) => !deletedListings.includes(k)))
|
|
3797
|
+
|
|
3798
|
+
if (!snapshot.empty) {
|
|
3799
|
+
const newPlacements:[string, StudentPlacementData][] = snapshot.docs.map(doc => ([doc.id, {...doc.data() as StudentPlacementData, id: doc.id}]));
|
|
3800
|
+
|
|
3801
|
+
const withAdditionalData = await Promise.all(newPlacements.map(async ([id, placement]) => {
|
|
3802
|
+
const student = placement.uid ? await firebaseQuery.getDocData(["users", placement.uid || ""]).catch(() => false) as UserData|false :
|
|
3803
|
+
{
|
|
3804
|
+
details: {
|
|
3805
|
+
forename: placement.studentForename,
|
|
3806
|
+
surname: placement.studentSurname,
|
|
3807
|
+
},
|
|
3808
|
+
email: placement.studentEmail
|
|
3809
|
+
};
|
|
3810
|
+
const listing = await firebaseQuery.getDocData(["placementListings", placement.placementId || ""]).catch(() => false) as PlacementListing;
|
|
3811
|
+
|
|
3812
|
+
return [id, {...placement, student: student, listing: listing}];
|
|
3813
|
+
}));
|
|
3814
|
+
setPlacements(prev => (Object.entries({...Object.fromEntries(prev), ...Object.fromEntries(withAdditionalData)})));
|
|
3815
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3816
|
+
}
|
|
3817
|
+
setLoading(false);
|
|
3818
|
+
}), "placements", constraints, undefined, true);
|
|
3819
|
+
};
|
|
3820
|
+
|
|
3821
|
+
useEffect(() => {
|
|
3822
|
+
loadListings();
|
|
3823
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3824
|
+
}, [queryConstraints]);
|
|
3825
|
+
|
|
3826
|
+
const onScrollBottom = () => {
|
|
3827
|
+
if (!loading) {
|
|
3828
|
+
setLoading(true);
|
|
3829
|
+
loadListings();
|
|
3830
|
+
}
|
|
3831
|
+
};
|
|
3832
|
+
|
|
3833
|
+
return { placements: Object.fromEntries(placements), onScrollBottom, loading, changeQueryConstraints };
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
|
|
3837
|
+
|
|
3838
|
+
export function useLoadApplications({user, applicationType, listingId, queryConstraint}:{user: UserData, applicationType?: "all"|"actionRequired"|"awaitingStudent"|"closed", listingId?: string, queryConstraint?: QueryConstraint[]}) {
|
|
3839
|
+
const [applications, setApplications] = useState<[string, Application][]>([]);
|
|
3840
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3841
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3842
|
+
const [type, setType] = useState<"actionRequired"|"awaitingStudent"|"closed"|"all">(applicationType || "all");
|
|
3843
|
+
|
|
3844
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3845
|
+
|
|
3846
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3847
|
+
|
|
3848
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3849
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3850
|
+
};
|
|
3851
|
+
|
|
3852
|
+
const loadApplications = () => {
|
|
3853
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3854
|
+
|
|
3855
|
+
|
|
3856
|
+
if (lastDoc?.id) {
|
|
3857
|
+
constraints.push(startAfter(lastDoc));
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
switch (type) {
|
|
3861
|
+
case "actionRequired":
|
|
3862
|
+
constraints.push(where("status", "==", "submitted"), where("reqUserType", "==", "Staff"));
|
|
3863
|
+
break;
|
|
3864
|
+
case "awaitingStudent":
|
|
3865
|
+
constraints.push(where("status", "==", "submitted"), where("reqUserType", "==", "Students"));
|
|
3866
|
+
break;
|
|
3867
|
+
case "closed":
|
|
3868
|
+
constraints.push(where("status", "in", ["approved", "declined"]));
|
|
3869
|
+
break;
|
|
3870
|
+
default:
|
|
3871
|
+
constraints.push(where("status", "==", "submitted"));
|
|
3872
|
+
}
|
|
3873
|
+
if (listingId) {
|
|
3874
|
+
constraints.push(where("listingId", "==", listingId));
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
console.log("Constraints before user group check", constraints);
|
|
3878
|
+
if (user.viewAddresses !== "all" && user.userGroup !== "admin") {
|
|
3879
|
+
if (user.viewPlacementListings === "all") {
|
|
3880
|
+
if (!user.visibleAddresses?.length) return;
|
|
3881
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3882
|
+
} else {
|
|
3883
|
+
if (!user.visibleListings?.length) return;
|
|
3884
|
+
constraints.push(where('placementId', 'in', user.visibleListings));
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3889
|
+
console.log("Constraints after user group check", constraints);
|
|
3890
|
+
|
|
3891
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3892
|
+
const deletedApplications = snapshot.docChanges().map((change) => {
|
|
3893
|
+
if (change.type === "removed") {
|
|
3894
|
+
return change.doc.id;
|
|
3895
|
+
}
|
|
3896
|
+
return;
|
|
3897
|
+
})
|
|
3898
|
+
|
|
3899
|
+
console.log("applicantCount", snapshot.size);
|
|
3900
|
+
|
|
3901
|
+
setApplications((prev) => prev.filter(([k]) => !deletedApplications.includes(k)))
|
|
3902
|
+
|
|
3903
|
+
if (!snapshot.empty) {
|
|
3904
|
+
const newApplications:[string, Application][] = snapshot.docs.map(doc => ([doc.id, {id: doc.id, ...doc.data() as Application}])).filter(([, v]) => (v as Application).status !== "draft") as [string, Application][];
|
|
3905
|
+
|
|
3906
|
+
setApplications(prev => ([...prev, ...newApplications]));
|
|
3907
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3908
|
+
} else {
|
|
3909
|
+
setApplications([]);
|
|
3910
|
+
setLastDoc(null);
|
|
3911
|
+
}
|
|
3912
|
+
setLoading(false);
|
|
3913
|
+
}), "applications", constraints, undefined, true);
|
|
3914
|
+
};
|
|
3915
|
+
|
|
3916
|
+
useEffect(() => {
|
|
3917
|
+
loadApplications();
|
|
3918
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3919
|
+
}, [type, queryConstraints]);
|
|
3920
|
+
|
|
3921
|
+
const onScrollBottom = () => {
|
|
3922
|
+
if (!loading) {
|
|
3923
|
+
setLoading(true);
|
|
3924
|
+
loadApplications();
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3927
|
+
|
|
3928
|
+
return { applications, type, setType, onScrollBottom, loading, changeQueryConstraints };
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
|
|
3932
|
+
|
|
3933
|
+
export type FilterObject = {
|
|
3934
|
+
[key: string]: {
|
|
3935
|
+
label: string,
|
|
3936
|
+
value?: unknown,
|
|
3937
|
+
values?: {[key:string|number]: string|{label: string, test: QueryFieldFilterConstraint}},
|
|
3938
|
+
type: "dropdown"|"string"|"number"
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
export type DataViewerPaginater = {
|
|
3943
|
+
view: "list"|"table";
|
|
3944
|
+
queryLimit?: number;
|
|
3945
|
+
formatItems?: (key: string, item: any) => Promise<{ key: string, item: any }> | { key: string, item: any };
|
|
3946
|
+
snapshot?: boolean;
|
|
3947
|
+
filters?: FilterObject;
|
|
3948
|
+
onSearch?: boolean | ((search?: string, sort?: [string, {value: string, direction: "asc"|"desc"}], page?: number, filters?:FilterObject, limit?: number) => Promise<{ [key: string]: any }>);
|
|
3949
|
+
data?: {[key:string]:{[key:string]: unknown}}|QueryObject[];
|
|
3950
|
+
additionalEntryProcessing?: (k: string, v: any) => Promise<any> | any,
|
|
3951
|
+
sorts?: Sorts
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
export function useDataViewerPaginator({view: initialView, sorts, queryLimit=10, additionalEntryProcessing, formatItems, snapshot, filters: initialFilters, onSearch, data}:DataViewerPaginater) {
|
|
3955
|
+
const [tableData, setTableData] = useState<{[key:string]:{[key:string]: unknown}}>(Array.isArray(data) ? Object.fromEntries(Object.entries(data).slice(0, queryLimit)) : {});
|
|
3956
|
+
const [page, setPage] = useState([1, 0]);
|
|
3957
|
+
const [view, setView] = useState(initialView);
|
|
3958
|
+
const [filters, setFilters] = useState<FilterObject|undefined>(initialFilters);
|
|
3959
|
+
const [queryAnchor, setQueryAnchor] = useState<{startKey: string, endKey: string, startQueryPos: number, endQueryPos: number}>({startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0});
|
|
3960
|
+
const [prevEntryIds, setPrevEntryIds] = useState<{[key:string]:number}>({});
|
|
3961
|
+
const [dataListenerUnsubscribe, setDataListenerUnsubscribe] = useState<{[key: string] : Unsubscribe}>();
|
|
3962
|
+
const [loading, setLoading] = useState<boolean|"loaded">(true);
|
|
3963
|
+
const [searchString, setSearchString] = useState<string>();
|
|
3964
|
+
const [sort, setSort] = useState<[string, {value: string, direction: "asc"|"desc"}]>();
|
|
3965
|
+
|
|
3966
|
+
|
|
3967
|
+
|
|
3968
|
+
const processedData = async (k: string, v: any) => additionalEntryProcessing ? await additionalEntryProcessing(k, v) : v;
|
|
3969
|
+
|
|
3970
|
+
const setTableDataFromDefinedData = async () => {
|
|
3971
|
+
if (!data || Array.isArray(data)) return;
|
|
3972
|
+
|
|
3973
|
+
const dataWithAdditionalProcessingPossibleNulls: [string, any][] = (await Promise.all(Object.entries(data).map(async ([k, v]) => [k, await processedData(k, v)])))
|
|
3974
|
+
const dataWithAdditionalProcessing = dataWithAdditionalProcessingPossibleNulls.filter(([k, v]) => v);
|
|
3975
|
+
|
|
3976
|
+
const searchedData: [string, any][] = searchString ? dataWithAdditionalProcessing.filter(([, v]) => {
|
|
3977
|
+
const values = Object.values(v).join(", ");
|
|
3978
|
+
console.log("VALUESTRING", v);
|
|
3979
|
+
return values.includes(searchString);
|
|
3980
|
+
}) : dataWithAdditionalProcessing;
|
|
3981
|
+
|
|
3982
|
+
const filteredData:[string, any][] = filters && Object.keys(filters).length > 0 ? searchedData.filter(([, dataValue]) => Object.entries(filters).every(([filterKey, filterValue]) => {
|
|
3983
|
+
const value = dataValue[filterKey];
|
|
3984
|
+
if ((typeof value === "number") && value === parseInt(filterValue.value as string)) return true;
|
|
3985
|
+
if ((typeof value === "boolean") && value === (filterValue.value === "true")) return true;
|
|
3986
|
+
if ((typeof value === "boolean") && value === (filterValue.value === "false")) return true;
|
|
3987
|
+
|
|
3988
|
+
if ((typeof value === "string" || Array.isArray(value)) && value.includes(filterValue.value as string)) return true;
|
|
3989
|
+
return false;
|
|
3990
|
+
})) : searchedData;
|
|
3991
|
+
if (view === "table") {
|
|
3992
|
+
if (!queryLimit) throw new Error("Tables must have a limit defined.");
|
|
3993
|
+
const newData:[string, any][] = filteredData.slice((page[0] - 1)*queryLimit, page[0]*queryLimit);
|
|
3994
|
+
setTableData(Object.fromEntries(newData));
|
|
3995
|
+
if (Object.keys(Object.fromEntries(newData)).pop() === Object.keys(data).pop()) {
|
|
3996
|
+
setLoading("loaded");
|
|
3997
|
+
} else {
|
|
3998
|
+
setLoading(false);
|
|
3999
|
+
}
|
|
4000
|
+
return;
|
|
4001
|
+
}
|
|
4002
|
+
if (view === "list") {
|
|
4003
|
+
setTableData(Object.fromEntries(filteredData));
|
|
4004
|
+
setLoading("loaded");
|
|
4005
|
+
}
|
|
4006
|
+
};
|
|
4007
|
+
|
|
4008
|
+
const getDataFromQuery = async (
|
|
4009
|
+
itemList: {[key: string]: any} = {}, currentQueryAnchor=queryAnchor, cursorDirection?:"increase"|"decrease"|undefined, prevEntries=prevEntryIds, loadMoreFromQuery=false):Promise<any> => {
|
|
4010
|
+
|
|
4011
|
+
// if (!filters) return;
|
|
4012
|
+
setLoading(true);
|
|
4013
|
+
if (!Array.isArray(data)) {
|
|
4014
|
+
setTableDataFromDefinedData();
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
if (!queryLimit) throw new Error("Firestore queries must have a limit defined.");
|
|
4018
|
+
|
|
4019
|
+
if (onSearch && (searchString || sort)) {
|
|
4020
|
+
if (typeof onSearch === "boolean") throw new Error("When using Firestore queries, an onSearch function should be passed to retrieve data externally. Additional processing is however completed in this hook.");
|
|
4021
|
+
const data = await onSearch(searchString, sort, page[0], filters, queryLimit);
|
|
4022
|
+
const dataWithAdditionalProcessing: [string, any][] = await Promise.all(Object.entries(data).map(async ([k, v]) => [k, await processedData(k, v)]));
|
|
4023
|
+
|
|
4024
|
+
setTableData((old) => {
|
|
4025
|
+
if (view === "table") {
|
|
4026
|
+
return {...Object.fromEntries(dataWithAdditionalProcessing)};
|
|
4027
|
+
}
|
|
4028
|
+
return {...old, ...Object.fromEntries(dataWithAdditionalProcessing)};
|
|
4029
|
+
});
|
|
4030
|
+
|
|
4031
|
+
if (dataWithAdditionalProcessing.length < queryLimit) {
|
|
4032
|
+
setLoading("loaded");
|
|
4033
|
+
} else {
|
|
4034
|
+
setLoading(false);
|
|
4035
|
+
}
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
|
|
4040
|
+
|
|
4041
|
+
let cursorPos:number;
|
|
4042
|
+
if (page[0] > page[1]) {
|
|
4043
|
+
cursorPos = currentQueryAnchor.endQueryPos;
|
|
4044
|
+
} else {
|
|
4045
|
+
cursorPos = currentQueryAnchor.startQueryPos;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
const querySchema:QueryObject = data[cursorPos];
|
|
4049
|
+
|
|
4050
|
+
const createQuery = (queryData:QueryObject) => {
|
|
4051
|
+
const constraints:any[] = [];
|
|
4052
|
+
queryData.where && queryData.where.forEach((w) => {
|
|
4053
|
+
constraints.push(where(...w));
|
|
4054
|
+
});
|
|
4055
|
+
|
|
4056
|
+
filters && Object.entries(filters).filter(([, value]) => value.value).forEach(([key, value]) => {
|
|
4057
|
+
const filterValue = (value.type === "number" || value.type === "dropdown") ? parseInt(value.value as string) || value.value : value.value;
|
|
4058
|
+
constraints.push(where(key, "==", filterValue));
|
|
4059
|
+
});
|
|
4060
|
+
|
|
4061
|
+
constraints.push(orderBy(queryData.orderBy ? queryData.orderBy === "documentId" ? documentId() : queryData.orderBy : documentId()));
|
|
4062
|
+
|
|
4063
|
+
if (page[0] > page[1] && !cursorDirection) { // Going up
|
|
4064
|
+
currentQueryAnchor.endKey && constraints.push(startAfter(currentQueryAnchor.endKey));
|
|
4065
|
+
constraints.push(limit(queryLimit));
|
|
4066
|
+
if (!loadMoreFromQuery) {
|
|
4067
|
+
currentQueryAnchor = {...currentQueryAnchor, startQueryPos: currentQueryAnchor.endQueryPos};
|
|
4068
|
+
}
|
|
4069
|
+
} else if (page[0] < page[1] && !cursorDirection) { // Going down
|
|
4070
|
+
if (!loadMoreFromQuery) {
|
|
4071
|
+
currentQueryAnchor = {...currentQueryAnchor, endQueryPos: currentQueryAnchor.startQueryPos};
|
|
4072
|
+
}
|
|
4073
|
+
constraints.push(limitToLast(queryLimit));
|
|
4074
|
+
if (currentQueryAnchor.startKey) {
|
|
4075
|
+
currentQueryAnchor.startKey && constraints.push(endBefore(currentQueryAnchor.startKey));
|
|
4076
|
+
} else {
|
|
4077
|
+
currentQueryAnchor.startKey && constraints.push(endAt(currentQueryAnchor.startKey));
|
|
4078
|
+
}
|
|
4079
|
+
} else {
|
|
4080
|
+
if (cursorDirection === "decrease") {
|
|
4081
|
+
constraints.push(limitToLast(queryLimit));
|
|
4082
|
+
} else {
|
|
4083
|
+
constraints.push(limit(queryLimit));
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
return constraints;
|
|
4087
|
+
};
|
|
4088
|
+
|
|
4089
|
+
const constraints = createQuery(querySchema);
|
|
4090
|
+
const q = query(collection(db, ...(querySchema.path as [any])), ...(constraints));
|
|
4091
|
+
|
|
4092
|
+
console.log("Fetching docs", constraints);
|
|
4093
|
+
|
|
4094
|
+
if (snapshot) {
|
|
4095
|
+
// Use onSnapshot to get real-time updates
|
|
4096
|
+
const unsubscribe = onSnapshot(q, (querySnapshot) => {
|
|
4097
|
+
handleQuerySnapshot(querySnapshot);
|
|
4098
|
+
});
|
|
4099
|
+
// Save the unsubscribe function to state so we can dispose of it later
|
|
4100
|
+
setDataListenerUnsubscribe((d) => ({...(d || {}), [cursorPos]: unsubscribe}));
|
|
4101
|
+
return;
|
|
4102
|
+
} else {
|
|
4103
|
+
// Just get the docs without setting up a listener
|
|
4104
|
+
const queryData = await getDocs(q);
|
|
4105
|
+
handleQuerySnapshot(queryData);
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
// Function to handle query snapshot
|
|
4109
|
+
async function handleQuerySnapshot(querySnapshot: QuerySnapshot) {
|
|
4110
|
+
if (!Array.isArray(data)) throw new Error("Called querySnapshot but data is defined.");
|
|
4111
|
+
if (!queryLimit) throw new Error("Firestore queries must have a limit defined.");
|
|
4112
|
+
|
|
4113
|
+
const queryResults: { [key: string]: { [key: string]: unknown } } = {};
|
|
4114
|
+
let index = 0; // Declare the index variable
|
|
4115
|
+
|
|
4116
|
+
const reverseIfBack = (docs: QueryDocumentSnapshot<DocumentData>[]) =>
|
|
4117
|
+
page[0] < page[1] ? docs.reverse() : docs;
|
|
4118
|
+
|
|
4119
|
+
// Process each document in the querySnapshot
|
|
4120
|
+
for (const doc of reverseIfBack(querySnapshot.docs)) {
|
|
4121
|
+
if ((Object.keys(queryResults).length + Object.keys(itemList).length) === queryLimit) {
|
|
4122
|
+
break;
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
let position = Object.keys(itemList).length+(page[0]-1)*queryLimit+index+1;
|
|
4126
|
+
if (page[0] < page[1]) {
|
|
4127
|
+
position = (page[0])*queryLimit-index-Object.keys(itemList).length;
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
|
|
4131
|
+
if (itemList[doc.id] || (prevEntries[doc.id] && prevEntries[doc.id] !== position)) {
|
|
4132
|
+
console.log("Removing ", doc.id, ": E=", prevEntries[doc.id], ", G=", position);
|
|
4133
|
+
continue;
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
let item: {[key: string]: unknown}|false = doc.data();
|
|
4137
|
+
item.id = doc.id;
|
|
4138
|
+
|
|
4139
|
+
// Apply additionalEntryProcessing if provided
|
|
4140
|
+
if (additionalEntryProcessing) {
|
|
4141
|
+
item = await additionalEntryProcessing(doc.id, item);
|
|
4142
|
+
}
|
|
4143
|
+
if (!item) continue;
|
|
4144
|
+
|
|
4145
|
+
queryResults[doc.id] = item;
|
|
4146
|
+
index += 1;
|
|
4147
|
+
|
|
4148
|
+
if (prevEntries[doc.id]) continue;
|
|
4149
|
+
prevEntries[doc.id] = position;
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
if (cursorDirection === "decrease" || page[0] < page[1]) {
|
|
4153
|
+
itemList = {...Object.fromEntries(Object.entries(queryResults).reverse()), ...itemList};
|
|
4154
|
+
} else {
|
|
4155
|
+
itemList = {...itemList, ...queryResults};
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
// Updating state with the new data and query anchors
|
|
4159
|
+
|
|
4160
|
+
|
|
4161
|
+
setPrevEntryIds(prevEntries);
|
|
4162
|
+
|
|
4163
|
+
if (querySnapshot.size < queryLimit && Object.keys(itemList).length < queryLimit) {
|
|
4164
|
+
// If we have ran out of entries, increase or decrease the index.
|
|
4165
|
+
if (page[0] > page[1] && cursorPos+1 < data.length) {
|
|
4166
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, endQueryPos: currentQueryAnchor.endQueryPos+1}, "increase", prevEntries);
|
|
4167
|
+
} else if (page[0] < page[1] && cursorPos > 0) {
|
|
4168
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, startQueryPos: currentQueryAnchor.startQueryPos-1}, "decrease", prevEntries);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
if (Object.keys(itemList).length < queryLimit && querySnapshot.size === queryLimit) {
|
|
4173
|
+
console.log("Shorter than ten");
|
|
4174
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0]}, undefined, prevEntries, true);
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
if (querySnapshot.size === 0 &&
|
|
4178
|
+
Object.keys(itemList).length === 0 &&
|
|
4179
|
+
currentQueryAnchor.endQueryPos+1 === data.length &&
|
|
4180
|
+
page[0] > 1) {
|
|
4181
|
+
if (view === "table") {
|
|
4182
|
+
setTableData({});
|
|
4183
|
+
}
|
|
4184
|
+
setQueryAnchor((a) => ({...a, startKey: ""}));
|
|
4185
|
+
setLoading("loaded");
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4189
|
+
if (querySnapshot.size < queryLimit) {
|
|
4190
|
+
setLoading("loaded");
|
|
4191
|
+
} else {
|
|
4192
|
+
setLoading(false);
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
setQueryAnchor({...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0]});
|
|
4196
|
+
|
|
4197
|
+
setTableData((old) => {
|
|
4198
|
+
if (view === "table") {
|
|
4199
|
+
return {...itemList};
|
|
4200
|
+
}
|
|
4201
|
+
return {...old, ...itemList};
|
|
4202
|
+
});
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4205
|
+
|
|
4206
|
+
const reset = () => {
|
|
4207
|
+
console.log("Resetting after filters?");
|
|
4208
|
+
setPage([1, 0]);
|
|
4209
|
+
setTableData({});
|
|
4210
|
+
setQueryAnchor({startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0});
|
|
4211
|
+
setPrevEntryIds({});
|
|
4212
|
+
dataListenerUnsubscribe && Object.values(dataListenerUnsubscribe).map((u) => u());
|
|
4213
|
+
setDataListenerUnsubscribe(undefined);
|
|
4214
|
+
};
|
|
4215
|
+
|
|
4216
|
+
useEffect(() => {
|
|
4217
|
+
console.log("Filters updates", filters);
|
|
4218
|
+
if (!filters) return;
|
|
4219
|
+
console.log("Resetting cus filters");
|
|
4220
|
+
reset();
|
|
4221
|
+
}, [filters]);
|
|
4222
|
+
|
|
4223
|
+
useEffect(() => {
|
|
4224
|
+
console.log("View reset.")
|
|
4225
|
+
reset();
|
|
4226
|
+
}, [view]);
|
|
4227
|
+
|
|
4228
|
+
useEffect(() => {
|
|
4229
|
+
console.log("search reset.")
|
|
4230
|
+
reset();
|
|
4231
|
+
}, [searchString]);
|
|
4232
|
+
|
|
4233
|
+
useEffect(() => {
|
|
4234
|
+
console.log("data reset.")
|
|
4235
|
+
reset();
|
|
4236
|
+
}, [data]);
|
|
4237
|
+
|
|
4238
|
+
useEffect(() => {
|
|
4239
|
+
console.log("sort reset.")
|
|
4240
|
+
reset();
|
|
4241
|
+
}, [sort]);
|
|
4242
|
+
|
|
4243
|
+
|
|
4244
|
+
// Fetch new data when queries or page change
|
|
4245
|
+
useEffect(() => {
|
|
4246
|
+
getDataFromQuery();
|
|
4247
|
+
dataListenerUnsubscribe && Object.values(dataListenerUnsubscribe).map((u) => u());
|
|
4248
|
+
}, [page]);
|
|
4249
|
+
|
|
4250
|
+
const pageUp = () => {
|
|
4251
|
+
setPage((p) => ([p[0]+1, p[0]]));
|
|
4252
|
+
};
|
|
4253
|
+
|
|
4254
|
+
const pageDown = () => {
|
|
4255
|
+
setPage((p) => ([p[0]-1, p[0]]));
|
|
4256
|
+
};
|
|
4257
|
+
|
|
4258
|
+
const updateSort = (sortLabel: string) => {
|
|
4259
|
+
if (!sorts || !sorts[sortLabel]) return;
|
|
4260
|
+
setSort([sortLabel, sorts[sortLabel]]);
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
return ({...{tableData, pageUp, pageDown, setFilters, page: page[0], sorts, loading, sort, updateSort: updateSort, setView, updateSearch: setSearchString}});
|
|
4264
|
+
};
|