placementt-core 11.0.533 → 11.0.803
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 +15 -9
- 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 +226 -16
- package/lib/hooks.js +938 -146
- package/lib/hooks.js.map +1 -1
- package/lib/reduxHooks.d.ts +56 -4
- package/lib/reduxHooks.js +63 -19
- package/lib/reduxHooks.js.map +1 -1
- package/lib/tasksAndTips.d.ts +7 -5
- package/lib/tasksAndTips.js +487 -23
- package/lib/tasksAndTips.js.map +1 -1
- package/lib/typeDefinitions.d.ts +149 -30
- package/lib/util.d.ts +7 -2
- package/lib/util.js +32 -9
- package/lib/util.js.map +1 -1
- package/package.json +1 -1
- 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 +19 -12
- 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 +1124 -166
- package/src/reduxHooks.ts +71 -22
- package/src/tasksAndTips.ts +495 -30
- package/src/typeDefinitions.ts +140 -35
- package/src/util.ts +45 -17
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";
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
ApplicantStage,
|
|
16
16
|
ApplicantWorkflow,
|
|
17
17
|
Application,
|
|
18
|
-
ArrowObject, CohortData, FileItem, FlagCodes, InstituteData, OnboardingDocs, PlacementListing, Products,
|
|
18
|
+
ArrowObject, CohortData, CustomFormSchema, FileItem, FlagCodes, InstituteData, OnboardingDocs, PlacementListing, Products,
|
|
19
19
|
ProviderData,
|
|
20
|
-
QueryObject, SavedPlacement, StudentPlacementData, UserData, UserGroupData, WorkflowStage
|
|
20
|
+
QueryObject, QueryObjectConstraint, SavedPlacement, 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,157 @@ 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
|
+
const algoliaPlacementSearch = async (user: UserData, query?: string, sort?: [string, {value: string, direction: "asc"|"desc"}], page?: number, filters?: FilterObject, limit?: number, cohort?: string, inProgress?: boolean) => {
|
|
409
|
+
const algoliaClient = algoliasearch(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
|
|
410
|
+
const placementsIndex = algoliaClient.initIndex(sort ? Object.values(sort[1]).join("_") : "placements");
|
|
411
|
+
// const usersIndex = algoliaClient.initIndex("users");
|
|
412
|
+
|
|
413
|
+
// let userSearchString = `userType:Students AND status:active AND oId:${user.oId} AND product:${user.product}`
|
|
414
|
+
// if (cohort) {
|
|
415
|
+
// userSearchString = userSearchString + ` AND cohort:${cohort}`;
|
|
416
|
+
// }
|
|
417
|
+
|
|
418
|
+
// if (user.product === "institutes" && user.userType === "Staff") {
|
|
419
|
+
// const searchStudentHits = await usersIndex.search<UserData>(query, {
|
|
420
|
+
// filters: userSearchString,
|
|
421
|
+
// hitsPerPage: limit,
|
|
422
|
+
// page: page
|
|
423
|
+
// });
|
|
424
|
+
|
|
425
|
+
// if (searchStudentHits) {
|
|
426
|
+
// console.log("FOUND", searchStudentHits.hits.length, "students");
|
|
427
|
+
|
|
428
|
+
// await Promise.all(searchStudentHits.hits.map(async (hit) => {
|
|
429
|
+
// console.log("STUDENT", hit.objectID);
|
|
430
|
+
// const constraints = [...(cohort ? [where("cohort", "==", cohort)] : [])]
|
|
431
|
+
// if (inProgress !== undefined) {
|
|
432
|
+
// constraints.push(inProgress ? where("inProgress", "==", true) : where("completed", "==", true));
|
|
433
|
+
// }
|
|
434
|
+
// const fPlacements = await getPlacementsWhere({w: constraints, uid: hit.objectID, oId: hit.oId}) as {[key: string]: StudentPlacementData};
|
|
435
|
+
|
|
436
|
+
// console.log("PLACEMENTS", fPlacements)
|
|
437
|
+
// Object.entries(fPlacements).forEach(([k, v]) => {
|
|
438
|
+
// placementsFound[k] = {...v, student: hit};
|
|
439
|
+
// })
|
|
440
|
+
// }))
|
|
441
|
+
// }
|
|
442
|
+
// }
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
let placementSearchString = `oId:${user.oId} AND ` + (inProgress !== undefined ? (inProgress ? "inProgress:true" : "completed:true" ) : "")
|
|
446
|
+
if (cohort) {
|
|
447
|
+
placementSearchString = placementSearchString + ` AND cohort:${cohort}`;
|
|
448
|
+
}
|
|
449
|
+
filters && Object.entries(filters).filter(([, filter]) => filter.value).map(([id, filter]) => {
|
|
450
|
+
placementSearchString = placementSearchString + ` AND ${id}:${filter.value}`;
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const options = {
|
|
454
|
+
filters: placementSearchString,
|
|
455
|
+
hitsPerPage: limit,
|
|
456
|
+
page: page ? page - 1 : undefined,
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const searchPlacementHits = await placementsIndex.search<StudentPlacementData>(query || "", options);
|
|
460
|
+
|
|
461
|
+
console.log(searchPlacementHits.hits);
|
|
462
|
+
|
|
463
|
+
const i = (searchPlacementHits ? (await Promise.all(searchPlacementHits.hits.map(async (hit) => {
|
|
464
|
+
return [hit.objectID, hit];
|
|
465
|
+
}))) : []).filter((e) => e !== undefined) as [string, StudentPlacementData][];
|
|
466
|
+
|
|
467
|
+
return Object.fromEntries(i)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
export function useVeryOldInstitutePlacementList({user, cohort, queryConstraint, ql=DEFAULTQUERYLIMIT}:InstitutePlacementParams) {
|
|
340
472
|
const [loadMoreIcon, setLoadMoreIcon] = useState(true);
|
|
341
473
|
const [query, setQuery] = useState("");
|
|
342
474
|
const [initialQueryLimit, setInitialQueryLimit] = useState(ql);
|
|
@@ -391,7 +523,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
391
523
|
|
|
392
524
|
let fPlacements:{[key:string]: StudentPlacementData&{student:UserData}} = {};
|
|
393
525
|
|
|
394
|
-
if (((user.userGroup === "admin" || (cohort && user.viewCohorts === "some" && user?.visibleCohorts?.
|
|
526
|
+
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
527
|
const queryConstraintOrdered = Boolean(queryConstraints && queryConstraints.find((v) => v.type === "orderBy"));
|
|
396
528
|
|
|
397
529
|
const constraints:QueryConstraint[] = fStartPlacementAfter?.length === 4 && fStartPlacementAfter[1] ?
|
|
@@ -413,10 +545,10 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
413
545
|
const pData = placement.data() as StudentPlacementData;
|
|
414
546
|
const student = pData.uid === user.id ? user : (await getUserById(pData.uid).catch(() => false)) as UserData;
|
|
415
547
|
|
|
416
|
-
if (user.
|
|
548
|
+
if (user.viewStudents === "some") {
|
|
417
549
|
if (!(user.studentFilter && user.studentFilterValues)) return false;
|
|
418
550
|
|
|
419
|
-
if (!user.studentFilterValues.
|
|
551
|
+
if (!user.studentFilterValues.includes(student.details[user.studentFilter])) {
|
|
420
552
|
return false;
|
|
421
553
|
}
|
|
422
554
|
}
|
|
@@ -446,7 +578,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
446
578
|
}
|
|
447
579
|
}
|
|
448
580
|
|
|
449
|
-
if (!(user.studentFilterValues && user.studentFilter && user.
|
|
581
|
+
if (!(user.studentFilterValues && user.studentFilter && user.viewStudents === "some")) {
|
|
450
582
|
console.log("fenaibn");
|
|
451
583
|
return;
|
|
452
584
|
};
|
|
@@ -468,7 +600,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
468
600
|
);
|
|
469
601
|
|
|
470
602
|
if (ffStartPlacementAfter && ffStartPlacementAfter[3]) {
|
|
471
|
-
const currentFilter = user?.studentFilterValues?.
|
|
603
|
+
const currentFilter = user?.studentFilterValues?.[ffStartPlacementAfter[3]];
|
|
472
604
|
|
|
473
605
|
if (currentFilter && user.studentFilter) {
|
|
474
606
|
constraints.push(where(user.studentFilter, "==", currentFilter));
|
|
@@ -481,7 +613,7 @@ export function useOldInstitutePlacementList({user, cohort, queryConstraint, ql=
|
|
|
481
613
|
// console.log("student", student);
|
|
482
614
|
|
|
483
615
|
// If there is no students but more filters
|
|
484
|
-
if (!student && ffStartPlacementAfter && ffStartPlacementAfter[3] + 1 < (user?.studentFilterValues
|
|
616
|
+
if (!student && ffStartPlacementAfter && ffStartPlacementAfter[3] + 1 < (user?.studentFilterValues || []).length) {
|
|
485
617
|
// console.log("No more students. Calling recursion");
|
|
486
618
|
return await getStudentPlacements(["student", undefined, undefined, ffStartPlacementAfter[3]+1], ffPlacements);
|
|
487
619
|
}
|
|
@@ -726,7 +858,7 @@ export function useFilterTablePaginator({data}:{data:{[key:string]:{[key:string]
|
|
|
726
858
|
const listenForUpdates = () => {
|
|
727
859
|
if (!Object.keys(itemList).length) return;
|
|
728
860
|
console.log("Fetching filter table updates")
|
|
729
|
-
const itemListUpdateQuery = query(collection(db,
|
|
861
|
+
const itemListUpdateQuery = query(collection(db, ...(querySchema.path as [any])), where(documentId(), "in", Object.keys(itemList)));
|
|
730
862
|
const itemUpdateSnapshot = onSnapshot(itemListUpdateQuery, (querySnapshot) => {
|
|
731
863
|
querySnapshot.docs.forEach((doc) => {
|
|
732
864
|
setTableData((data) => {
|
|
@@ -918,9 +1050,9 @@ export function usePlacementListingPaginator({data, user}:{data:QueryObject[], u
|
|
|
918
1050
|
|
|
919
1051
|
const finalData = Object.fromEntries(await Promise.all(Object.entries(itemList).map(async ([k, v]:[string, SavedPlacement]) => {
|
|
920
1052
|
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);
|
|
1053
|
+
const address = await firebaseQuery.getDocData(["addresses", placement.addressId || ""]).catch(() => ({["address-line1"]: "Unknown address"}));
|
|
1054
|
+
const provider = await firebaseQuery.getDocData(["providers", placement.providerId || ""]).catch(() => ({name: "Unknown"})) as ProviderData;
|
|
1055
|
+
const status = provider.name !== "Unknown" ? getPlacementStatus(provider, placement, v) : "Deleted";
|
|
924
1056
|
return [k, {...address, ...provider, ...placement, email: placement.providerEmail, status: status, savedPlacement: v}];
|
|
925
1057
|
})));
|
|
926
1058
|
setTableData(finalData);
|
|
@@ -963,13 +1095,15 @@ type UserPaginatorParams = {
|
|
|
963
1095
|
cohort?: string,
|
|
964
1096
|
data: QueryObject[],
|
|
965
1097
|
search?: string,
|
|
966
|
-
userType: "Staff"|"Students"
|
|
1098
|
+
userType: "Staff"|"Students",
|
|
1099
|
+
sort?: string
|
|
967
1100
|
}
|
|
968
1101
|
|
|
969
|
-
export function useCohortUserPaginator({user, cohort, data, search, userType}:UserPaginatorParams) {
|
|
1102
|
+
export function useCohortUserPaginator({user, cohort, data, search, userType, sort}:UserPaginatorParams) {
|
|
970
1103
|
const [tableData, setTableData] = useState<{[key:string]:{[key:string]: unknown}}>({});
|
|
971
|
-
const [queryAnchor, setQueryAnchor] = useState<{
|
|
1104
|
+
const [queryAnchor, setQueryAnchor] = useState<{startDoc?: QueryDocumentSnapshot, endDoc?: QueryDocumentSnapshot, startQueryPos: number, endQueryPos: number}>({startDoc: undefined, endDoc: undefined, startQueryPos: 0, endQueryPos: 0});
|
|
972
1105
|
const [filters, setFilters] = useState<{[key:string]: unknown}>();
|
|
1106
|
+
const [sortResultsBy, setSortResultsBy] = useState<string|undefined>(sort);
|
|
973
1107
|
const [prevEntryIds, setPrevEntryIds] = useState<{[key:string]:number}>({});
|
|
974
1108
|
const [page, setPage] = useState([1, 0]);
|
|
975
1109
|
const [dataListenerUnsubscribe, setDataListenerUnsubscribe] = useState<Unsubscribe|undefined>();
|
|
@@ -978,6 +1112,15 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
978
1112
|
|
|
979
1113
|
const algoliaClient = algoliasearch(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
|
|
980
1114
|
const usersIndex = algoliaClient.initIndex("users");
|
|
1115
|
+
|
|
1116
|
+
const sortOptions = {
|
|
1117
|
+
["Forename - Asc"]: ["details.forename", "asc"],
|
|
1118
|
+
["Forename - Desc"]: ["details.forename", "desc"],
|
|
1119
|
+
["Surname - Asc"]: ["details.surname", "desc"],
|
|
1120
|
+
["Surname - Desc"]: ["details.surname", "asc"],
|
|
1121
|
+
["Email - Asc"]: ["email", "asc"],
|
|
1122
|
+
["Email - Desc"]: ["email", "desc"],
|
|
1123
|
+
}
|
|
981
1124
|
|
|
982
1125
|
useEffect(() => {
|
|
983
1126
|
if (user.userType !== "Staff") {
|
|
@@ -988,7 +1131,7 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
988
1131
|
if (
|
|
989
1132
|
(!user.viewCohorts && user.userGroup !== "admin") ||
|
|
990
1133
|
user.viewCohorts === "none" ||
|
|
991
|
-
(user.viewCohorts === "some" && cohort !== "all" && !user.visibleCohorts?.
|
|
1134
|
+
(user.viewCohorts === "some" && cohort !== "all" && !user.visibleCohorts?.includes(cohort || ""))) {
|
|
992
1135
|
setQueries(undefined);
|
|
993
1136
|
return;
|
|
994
1137
|
}
|
|
@@ -1006,12 +1149,12 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1006
1149
|
constraints.push(where("oId", "==", user.oId));
|
|
1007
1150
|
cohort && cohort !== "all" && constraints.push(where("cohort", "==", cohort));
|
|
1008
1151
|
|
|
1009
|
-
if (user.userGroup === "admin" || user.
|
|
1152
|
+
if (user.userGroup === "admin" || user.viewStudents === "all") {
|
|
1010
1153
|
finalConstraints.push(constraints);
|
|
1011
1154
|
continue
|
|
1012
1155
|
}
|
|
1013
1156
|
if (!user.studentFilter || !user.studentFilterValues) continue;
|
|
1014
|
-
user.studentFilterValues.
|
|
1157
|
+
user.studentFilterValues.forEach((filterValue) => {
|
|
1015
1158
|
user.studentFilter && finalConstraints.push([...constraints, where("details."+user.studentFilter, "==", filterValue)])
|
|
1016
1159
|
})
|
|
1017
1160
|
};
|
|
@@ -1022,9 +1165,9 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1022
1165
|
|
|
1023
1166
|
|
|
1024
1167
|
const getDataFromQuery = async (
|
|
1025
|
-
itemList: {[key: string]:
|
|
1168
|
+
itemList: {[key: string]: QueryDocumentSnapshot<DocumentData>} = {}, currentQueryAnchor=queryAnchor, cursorDirection?:"increase"|"decrease"|undefined, prevEntries=prevEntryIds, loadMoreFromQuery=false):Promise<any> => {
|
|
1026
1169
|
|
|
1027
|
-
if (!queries) {
|
|
1170
|
+
if (!queries?.length) {
|
|
1028
1171
|
setTableData({});
|
|
1029
1172
|
return;
|
|
1030
1173
|
}
|
|
@@ -1043,18 +1186,41 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1043
1186
|
console.log("mConstraints", mConstraints);
|
|
1044
1187
|
const fConstraints = [...mConstraints];
|
|
1045
1188
|
|
|
1189
|
+
let addOrderBy = true;
|
|
1046
1190
|
filters && Object.entries(filters).forEach(([key, value]) => {
|
|
1047
|
-
if (
|
|
1048
|
-
|
|
1191
|
+
if (Array.isArray(value)) {
|
|
1192
|
+
value.forEach((v) => {
|
|
1193
|
+
if (typeof v === "object") {
|
|
1194
|
+
if (v instanceof QueryOrderByConstraint) {
|
|
1195
|
+
addOrderBy = false;
|
|
1196
|
+
}
|
|
1197
|
+
fConstraints.push(v as QueryFieldFilterConstraint|QueryOrderByConstraint);
|
|
1198
|
+
} else {
|
|
1199
|
+
fConstraints.push(where(key, "==", v));
|
|
1200
|
+
}
|
|
1201
|
+
})
|
|
1202
|
+
} else if (typeof value === "object") {
|
|
1203
|
+
if (value instanceof QueryOrderByConstraint) {
|
|
1204
|
+
addOrderBy = false;
|
|
1205
|
+
}
|
|
1206
|
+
fConstraints.push(value as QueryFieldFilterConstraint|QueryOrderByConstraint);
|
|
1049
1207
|
} else {
|
|
1050
1208
|
fConstraints.push(where(key, "==", value));
|
|
1051
1209
|
}
|
|
1052
1210
|
});
|
|
1053
|
-
|
|
1211
|
+
console.log("Add order by", addOrderBy);
|
|
1212
|
+
if (addOrderBy) {
|
|
1213
|
+
if (sortResultsBy) {
|
|
1214
|
+
fConstraints.push(orderBy(sortOptions[sortResultsBy][0], sortOptions[sortResultsBy][1]));
|
|
1215
|
+
} else {
|
|
1216
|
+
fConstraints.push(orderBy(documentId()));
|
|
1217
|
+
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1054
1220
|
|
|
1055
1221
|
|
|
1056
1222
|
if (page[0] > page[1] && !cursorDirection) { // Going up
|
|
1057
|
-
currentQueryAnchor.
|
|
1223
|
+
currentQueryAnchor.endDoc && fConstraints.push(startAfter(currentQueryAnchor.endDoc));
|
|
1058
1224
|
fConstraints.push(limit(10));
|
|
1059
1225
|
if (!loadMoreFromQuery) {
|
|
1060
1226
|
currentQueryAnchor = {...currentQueryAnchor, startQueryPos: currentQueryAnchor.endQueryPos};
|
|
@@ -1064,10 +1230,10 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1064
1230
|
currentQueryAnchor = {...currentQueryAnchor, endQueryPos: currentQueryAnchor.startQueryPos};
|
|
1065
1231
|
}
|
|
1066
1232
|
fConstraints.push(limitToLast(10));
|
|
1067
|
-
if (currentQueryAnchor.
|
|
1068
|
-
currentQueryAnchor.
|
|
1233
|
+
if (currentQueryAnchor.startDoc) {
|
|
1234
|
+
currentQueryAnchor.startDoc && fConstraints.push(endBefore(currentQueryAnchor.startDoc));
|
|
1069
1235
|
} else {
|
|
1070
|
-
currentQueryAnchor.
|
|
1236
|
+
currentQueryAnchor.startDoc && fConstraints.push(endAt(currentQueryAnchor.startDoc));
|
|
1071
1237
|
}
|
|
1072
1238
|
} else {
|
|
1073
1239
|
if (cursorDirection === "decrease") {
|
|
@@ -1083,7 +1249,7 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1083
1249
|
// console.log(queryId, "constraints", constraints)
|
|
1084
1250
|
|
|
1085
1251
|
const q = query(collection(db, "users"), ...(constraints));
|
|
1086
|
-
const queryResults:{[key:string]:
|
|
1252
|
+
const queryResults:{[key:string]: QueryDocumentSnapshot<DocumentData>} = {};
|
|
1087
1253
|
|
|
1088
1254
|
const queryData = await getDocs(q);
|
|
1089
1255
|
|
|
@@ -1115,9 +1281,7 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1115
1281
|
return;
|
|
1116
1282
|
}
|
|
1117
1283
|
|
|
1118
|
-
|
|
1119
|
-
item.id = doc.id;
|
|
1120
|
-
queryResults[doc.id] = item;
|
|
1284
|
+
queryResults[doc.id] = doc;
|
|
1121
1285
|
index = index+1;
|
|
1122
1286
|
|
|
1123
1287
|
if (prevEntries[doc.id]) return;
|
|
@@ -1141,7 +1305,7 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1141
1305
|
}
|
|
1142
1306
|
|
|
1143
1307
|
if (Object.keys(itemList).length < 10 && queryData.size === 10) {
|
|
1144
|
-
return getDataFromQuery(itemList, {...currentQueryAnchor,
|
|
1308
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, startDoc: Object.values(itemList)[0], endDoc: Object.values(itemList).slice(-1)[0]}, undefined, prevEntries, true);
|
|
1145
1309
|
}
|
|
1146
1310
|
|
|
1147
1311
|
if (queryData.size === 0 &&
|
|
@@ -1170,8 +1334,8 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1170
1334
|
};
|
|
1171
1335
|
listenForUpdates();
|
|
1172
1336
|
|
|
1173
|
-
setQueryAnchor({...currentQueryAnchor,
|
|
1174
|
-
setTableData(itemList);
|
|
1337
|
+
setQueryAnchor({...currentQueryAnchor, startDoc: Object.values(itemList)[0], endDoc: Object.values(itemList).slice(-1)[0]});
|
|
1338
|
+
setTableData(Object.fromEntries(Object.entries(itemList).map(([k, v]) => [k, {id: k, ...v.data()}])));
|
|
1175
1339
|
};
|
|
1176
1340
|
|
|
1177
1341
|
const searchUsers = async () => {
|
|
@@ -1215,10 +1379,10 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1215
1379
|
console.log("Set page");
|
|
1216
1380
|
|
|
1217
1381
|
setTableData({});
|
|
1218
|
-
setQueryAnchor({
|
|
1382
|
+
setQueryAnchor({startQueryPos: 0, endQueryPos: 0});
|
|
1219
1383
|
setPrevEntryIds({});
|
|
1220
1384
|
dataListenerUnsubscribe && dataListenerUnsubscribe();
|
|
1221
|
-
}, [filters, search]);
|
|
1385
|
+
}, [filters, search, sortResultsBy]);
|
|
1222
1386
|
|
|
1223
1387
|
// Fetch new data when queries or page change
|
|
1224
1388
|
useEffect(() => {
|
|
@@ -1231,7 +1395,10 @@ export function useCohortUserPaginator({user, cohort, data, search, userType}:Us
|
|
|
1231
1395
|
dataListenerUnsubscribe && dataListenerUnsubscribe();
|
|
1232
1396
|
}, [page, queries, prevSearch]);
|
|
1233
1397
|
|
|
1234
|
-
|
|
1398
|
+
|
|
1399
|
+
const setSort = (s: string) => setSortResultsBy(s);
|
|
1400
|
+
|
|
1401
|
+
return ({...{tableData, setPage, page, setFilters, setSort, sortOptions: Object.keys(sortOptions), sortBy: sortResultsBy}});
|
|
1235
1402
|
}
|
|
1236
1403
|
|
|
1237
1404
|
|
|
@@ -1356,7 +1523,7 @@ export function usePublicPlacementListingLoader({providerId, number=5}:PublicPla
|
|
|
1356
1523
|
const applicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", itemObj.applicantWorkflowId]) as ApplicantWorkflow).workflow.filter((i) => i.id === 1)[0];
|
|
1357
1524
|
const applicantFiles = applicantWorkflow.files ? Object.fromEntries(await Promise.all(applicantWorkflow.files?.map(async (fileId) => {
|
|
1358
1525
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1359
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
1526
|
+
file.url = await getDownloadURL(ref(storage, `providers/${itemObj.providerId}/${file.fileName}`));
|
|
1360
1527
|
|
|
1361
1528
|
return [fileId, file];
|
|
1362
1529
|
}))) : [];
|
|
@@ -1408,7 +1575,7 @@ export type ApplicationHookParams = {
|
|
|
1408
1575
|
profileUrl: string | undefined;
|
|
1409
1576
|
successPopup: "submitted" | "draftSaved" | "stageComplete" | "outcome" | undefined;
|
|
1410
1577
|
fApplicationId: string | undefined;
|
|
1411
|
-
fListing: PlacementListing | undefined;
|
|
1578
|
+
fListing: PlacementListing | false | undefined;
|
|
1412
1579
|
student: UserData | undefined;
|
|
1413
1580
|
fProvider: {
|
|
1414
1581
|
details?: ProviderData;
|
|
@@ -1458,13 +1625,14 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1458
1625
|
const [fApplication, setFApplication] = useState<Partial<Application>>(application ? applicationWithoutAdditionalData : {
|
|
1459
1626
|
uid: user.userType === "Students" ? user.id : undefined,
|
|
1460
1627
|
listingId: listingId,
|
|
1628
|
+
addressId: listing?.addressId,
|
|
1461
1629
|
stage: 1,
|
|
1462
1630
|
reqUserType: "Students",
|
|
1463
1631
|
status: "draft"});
|
|
1464
1632
|
const [fApplicationId, setFApplicationId] = useState<string|undefined>(applicationId);
|
|
1465
1633
|
const [draftSaved, setDraftSaved] = useState(false);
|
|
1466
1634
|
const [fProvider, setFProvider] = useState<{details?: ProviderData, profile?: string, id?: string}|undefined>(provider);
|
|
1467
|
-
const [fListing, setFListing] = useState<PlacementListing|undefined>(listing);
|
|
1635
|
+
const [fListing, setFListing] = useState<PlacementListing|false|undefined>(Object.keys(listing || {}).length > 5 ? listing : undefined);
|
|
1468
1636
|
const [student, setStudent] = useState<UserData|undefined>(user.userType === "Students" ? user : undefined);
|
|
1469
1637
|
const [profileUrl, setProfileUrl] = useState<string>();
|
|
1470
1638
|
const [successPopup, setSuccessPopup] = useState<"submitted"|"draftSaved"|"stageComplete"|"outcome">();
|
|
@@ -1476,34 +1644,44 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1476
1644
|
console.log("Checking ID")
|
|
1477
1645
|
if (!listingId) return;
|
|
1478
1646
|
console.log("Getting listing")
|
|
1647
|
+
console.log("LISTING PARAM", listing, Object.keys(listing || {}).length > 5);
|
|
1479
1648
|
|
|
1480
|
-
const listingData = listing || await firebaseQuery.getDocData(["placementListings", listingId]) as PlacementListing;
|
|
1481
|
-
|
|
1482
|
-
|
|
1649
|
+
const listingData = (Object.keys(listing || {}).length > 5) ? listing : (await firebaseQuery.getDocData(["placementListings", listingId]).catch(() => false) as PlacementListing|false);
|
|
1650
|
+
|
|
1651
|
+
console.log("LISTINGDATA", listingData, Object.keys(listing || {}).length > 5 ? {a: "string"} : "AAA");
|
|
1652
|
+
|
|
1653
|
+
|
|
1654
|
+
|
|
1655
|
+
const address = listingData ? (user.product === "providers" && orgContext) ? orgContext.addresses[listingData.addressId || ""] : await firebaseQuery.getDocData(["addresses", listingData.addressId as string]) as Address : undefined;
|
|
1656
|
+
const workflow = listingData ? (user.product === "providers" && orgContext) ? orgContext.applicantWorkflows[listingData.applicantWorkflowId || ""] as ApplicantWorkflow : await firebaseQuery.getDocData(["applicantWorkflows", listingData.applicantWorkflowId as string]) as ApplicantWorkflow : undefined;
|
|
1483
1657
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1658
|
+
if (workflow && listingData) {
|
|
1659
|
+
workflow.workflow = await Promise.all(workflow.workflow.map(async (s) => {
|
|
1660
|
+
const applicantFiles = s.files ? Object.fromEntries(await Promise.all(s.files?.map(async (fileId: string) => {
|
|
1661
|
+
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1662
|
+
file.url = await getDownloadURL(ref(storage, `providers/${listingData?.providerId}/${file.fileName}`));
|
|
1663
|
+
|
|
1664
|
+
return [fileId, file];
|
|
1665
|
+
}))) : [];
|
|
1666
|
+
const applicantForms = s.forms ? Object.fromEntries(await Promise.all(s.forms?.map(async (formId: string) => {
|
|
1667
|
+
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1668
|
+
}))) : [];
|
|
1669
|
+
|
|
1670
|
+
return {...s, viewableFiles: applicantFiles, formDetails: applicantForms};
|
|
1671
|
+
}));
|
|
1672
|
+
delete workflow.id;
|
|
1673
|
+
}
|
|
1674
|
+
if (address) {
|
|
1675
|
+
delete address.id;
|
|
1676
|
+
}
|
|
1497
1677
|
|
|
1498
|
-
delete address.id;
|
|
1499
|
-
delete workflow.id;
|
|
1500
1678
|
console.log("Setting listing")
|
|
1501
|
-
setFListing({...listingData, ...address, applicantWorkflow: workflow.workflow});
|
|
1679
|
+
setFListing((listingData && workflow) ? {...listingData, ...address, applicantWorkflow: workflow.workflow} as PlacementListing : false);
|
|
1680
|
+
|
|
1502
1681
|
|
|
1503
|
-
console.log("Getting provider", listingData?.providerId);
|
|
1504
1682
|
if ((fProvider?.id === application?.providerId) && user.product === "providers") {
|
|
1505
1683
|
setFProvider({details: orgContext?.details, id: user.oId});
|
|
1506
|
-
} else if (listingData?.providerId) {
|
|
1684
|
+
} else if (listingData && listingData?.providerId) {
|
|
1507
1685
|
console.log("Getting provider from DB");
|
|
1508
1686
|
const provider = await firebaseQuery.getDocData(["providers", listingData.providerId]) as ProviderData;
|
|
1509
1687
|
console.log("Provider", provider);
|
|
@@ -1525,7 +1703,7 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1525
1703
|
}, []);
|
|
1526
1704
|
|
|
1527
1705
|
useEffect(() => {
|
|
1528
|
-
setFListing(listing);
|
|
1706
|
+
setFListing(Object.keys(listing || {}).length > 5 ? listing : undefined);
|
|
1529
1707
|
}, [listing]);
|
|
1530
1708
|
|
|
1531
1709
|
useEffect(() => {
|
|
@@ -1550,7 +1728,18 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1550
1728
|
});
|
|
1551
1729
|
return;
|
|
1552
1730
|
}
|
|
1553
|
-
if (applicationId)
|
|
1731
|
+
if (applicationId) {
|
|
1732
|
+
setFApplicationId(applicationId);
|
|
1733
|
+
if (application) {
|
|
1734
|
+
let applicationWithoutAdditionalData = {...(application || {})} as any;
|
|
1735
|
+
delete applicationWithoutAdditionalData.listing;
|
|
1736
|
+
delete applicationWithoutAdditionalData.address;
|
|
1737
|
+
delete applicationWithoutAdditionalData.provider;
|
|
1738
|
+
setFApplication(applicationWithoutAdditionalData);
|
|
1739
|
+
} else {
|
|
1740
|
+
firebaseQuery.getDocData(["applications", applicationId]).then(setFApplication);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1554
1743
|
}, [application, applicationId]);
|
|
1555
1744
|
|
|
1556
1745
|
const getCurrentStage = async (stage: number): Promise<{
|
|
@@ -1565,6 +1754,8 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1565
1754
|
} | undefined;
|
|
1566
1755
|
};
|
|
1567
1756
|
}> => {
|
|
1757
|
+
console.log("fLSITING CURRENT STAGE", fListing);
|
|
1758
|
+
if (!fListing) throw new Error("Listing deleted");
|
|
1568
1759
|
if (!fListing?.applicantWorkflowId) throw new Error("No workflow stage");
|
|
1569
1760
|
|
|
1570
1761
|
const mApplicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", fListing.applicantWorkflowId]) as ApplicantWorkflow);
|
|
@@ -1575,7 +1766,7 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1575
1766
|
|
|
1576
1767
|
const applicantFiles = stageObj.files ? Object.fromEntries(await Promise.all(stageObj.files?.map(async (fileId) => {
|
|
1577
1768
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1578
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
1769
|
+
file.url = await getDownloadURL(ref(storage, `providers/${mApplicantWorkflow?.oId}/${file.fileName}`));
|
|
1579
1770
|
|
|
1580
1771
|
return [fileId, file];
|
|
1581
1772
|
}))) : [];
|
|
@@ -1615,11 +1806,12 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1615
1806
|
|
|
1616
1807
|
const addApplication = async () => {
|
|
1617
1808
|
console.log("ADDING APPLICATION");
|
|
1618
|
-
if (!fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1809
|
+
if (!fListing || !fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1619
1810
|
|
|
1620
1811
|
const applicationData = {
|
|
1621
1812
|
uid: student.id,
|
|
1622
1813
|
listingId: fListing?.id,
|
|
1814
|
+
addressId: fListing.addressId,
|
|
1623
1815
|
applicantWorkflowId: fListing?.applicantWorkflowId,
|
|
1624
1816
|
providerId: fProvider.id,
|
|
1625
1817
|
stage: 1,
|
|
@@ -1637,9 +1829,9 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1637
1829
|
getUploadedFiles();
|
|
1638
1830
|
|
|
1639
1831
|
console.log("Checking IDs");
|
|
1640
|
-
|
|
1641
|
-
if (!fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1642
|
-
if (user.product === "providers")
|
|
1832
|
+
|
|
1833
|
+
if (!fListing || !fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1834
|
+
if (user.product === "providers") return;
|
|
1643
1835
|
|
|
1644
1836
|
console.log("Checking dates and sections");
|
|
1645
1837
|
if (!fApplication.completedSections && !fApplication.startDate && !fApplication.endDate) return;
|
|
@@ -1709,6 +1901,7 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1709
1901
|
const viewFile = (file: string, onOpen: (url: string) => void) => {
|
|
1710
1902
|
if (fApplication.reqUserType !== user.userType) return;
|
|
1711
1903
|
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1904
|
+
if (!fListing) throw new Error("No associated listing.");
|
|
1712
1905
|
|
|
1713
1906
|
const viewableFiles = fListing?.applicantWorkflow?.find((stage) => stage.id === fApplication.stage)?.viewableFiles;
|
|
1714
1907
|
|
|
@@ -1763,21 +1956,29 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1763
1956
|
openSuccessPopup("draftSaved")
|
|
1764
1957
|
return;
|
|
1765
1958
|
}
|
|
1959
|
+
|
|
1960
|
+
if (!fApplicationId) return;
|
|
1766
1961
|
|
|
1767
1962
|
// Check all items have been filled in.
|
|
1768
1963
|
if (!fApplication.startDate || !fApplication.endDate) throw new Error("Please select dates for your placement.");
|
|
1769
1964
|
|
|
1770
1965
|
await executeCallable("applications-submit", {applicationId: fApplicationId});
|
|
1966
|
+
|
|
1967
|
+
const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]) as Application;
|
|
1968
|
+
setFApplication(newApplication);
|
|
1771
1969
|
openSuccessPopup("submitted")
|
|
1772
1970
|
};
|
|
1773
1971
|
|
|
1774
1972
|
const progressStage = async (type: number|"accept"|"reject", e?: {feedback?: string}) => {
|
|
1775
1973
|
// Check all stages completed.
|
|
1974
|
+
if (!fApplicationId) return;
|
|
1776
1975
|
if (!currentStageComplete) throw new Error("Complete all forms before submitting.");
|
|
1777
1976
|
if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1778
1977
|
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1779
1978
|
|
|
1780
1979
|
await executeCallable("applications-changeStage", {applicationId: fApplicationId, type: type, feedback: e?.feedback});
|
|
1980
|
+
const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]) as Application;
|
|
1981
|
+
setFApplication(newApplication);
|
|
1781
1982
|
};
|
|
1782
1983
|
|
|
1783
1984
|
|
|
@@ -1795,8 +1996,6 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1795
1996
|
const [student, setStudent] = useState<UserData|undefined>((user && (user?.userType === "Students")) ? user : undefined);
|
|
1796
1997
|
const [cohort, setCohort] = useState<CohortData>();
|
|
1797
1998
|
const [complete, setComplete] = useState(false);
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
1999
|
const [stage, setStage] = useState<"provider"|"address"|"dates"|"review">("dates");
|
|
1801
2000
|
|
|
1802
2001
|
const sections:Array<"dates"|"provider"|"address"|"review"> = ["dates", "provider", "address", "review"];
|
|
@@ -1865,16 +2064,33 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1865
2064
|
setFormData(undefined);
|
|
1866
2065
|
};
|
|
1867
2066
|
|
|
1868
|
-
const proposePlacement = async (draft=false) => {
|
|
2067
|
+
const proposePlacement = async (draft = false) => {
|
|
2068
|
+
const getPlacementStatus = (startDate: Date, endDate: Date) => {
|
|
2069
|
+
const today = new Date()
|
|
2070
|
+
if (startDate <= today && endDate >= today) return { active: !draft ? true : false, inProgress: true, completed: false};
|
|
2071
|
+
else if (endDate <= today) return { completed: true, inProgress: false, active: false};
|
|
2072
|
+
return { completed: false, inProgress: true, active: false };
|
|
2073
|
+
}
|
|
2074
|
+
|
|
1869
2075
|
if (!formData || !student) {
|
|
1870
2076
|
throw new Error("Cannot find placement details.");
|
|
1871
2077
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2078
|
+
|
|
2079
|
+
try {
|
|
2080
|
+
console.log("formData", formData);
|
|
2081
|
+
const status = getPlacementStatus(new Date(formData.startDate), new Date(formData.endDate));
|
|
2082
|
+
const newFormData = {...formData, ...status}
|
|
2083
|
+
|
|
2084
|
+
if (newFormData.id && newFormData.uid && draft === newFormData.draft) await firebaseQuery.update(["placements", newFormData.id], newFormData);
|
|
2085
|
+
else await addPlacement(newFormData, student.id, draft)
|
|
2086
|
+
setComplete(true);
|
|
2087
|
+
return {status}
|
|
2088
|
+
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
console.log("Error:", error);
|
|
2091
|
+
throw error;
|
|
1876
2092
|
}
|
|
1877
|
-
|
|
2093
|
+
/*
|
|
1878
2094
|
console.log("STUDENTID", student.id);
|
|
1879
2095
|
|
|
1880
2096
|
return await addPlacement(formData, student.id, draft).catch((e) => {
|
|
@@ -1885,9 +2101,9 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1885
2101
|
setComplete(true);
|
|
1886
2102
|
return e;
|
|
1887
2103
|
});
|
|
1888
|
-
|
|
1889
|
-
|
|
2104
|
+
*/
|
|
1890
2105
|
};
|
|
2106
|
+
|
|
1891
2107
|
|
|
1892
2108
|
const deletePlacement = async (id:string) => {
|
|
1893
2109
|
return await firebaseQuery.delete(["placements", id]);
|
|
@@ -1989,7 +2205,7 @@ export function useCreateCohortRenderer({oId, product, initialData=defaultCohort
|
|
|
1989
2205
|
type UserUploadParams = {
|
|
1990
2206
|
userType: "Staff"|"Students",
|
|
1991
2207
|
user: UserData,
|
|
1992
|
-
onComplete?: () => void,
|
|
2208
|
+
onComplete?: (e: string) => void,
|
|
1993
2209
|
cohortId?: string,
|
|
1994
2210
|
userGroupId?: string,
|
|
1995
2211
|
shareNameWithReferralLeaderboardConsent?: string,
|
|
@@ -2004,9 +2220,6 @@ export function useUserUploadHandler({product, oId, userType, user, onComplete,
|
|
|
2004
2220
|
const {execute} = useExecuteCallableJob({user: user});
|
|
2005
2221
|
|
|
2006
2222
|
const requiredFields = ["forename", "surname", "email"];
|
|
2007
|
-
if (userType === "Students") {
|
|
2008
|
-
requiredFields.push("year");
|
|
2009
|
-
}
|
|
2010
2223
|
|
|
2011
2224
|
const checkData = (userData: {email?: string, parentEmail?: string, year?: number, [key:string]: unknown}[]) => {
|
|
2012
2225
|
setAlert(undefined);
|
|
@@ -2061,11 +2274,6 @@ export function useUserUploadHandler({product, oId, userType, user, onComplete,
|
|
|
2061
2274
|
}
|
|
2062
2275
|
}
|
|
2063
2276
|
// 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
2277
|
}
|
|
2070
2278
|
|
|
2071
2279
|
if (emptyRequiredCell) {
|
|
@@ -2111,10 +2319,10 @@ export function useUserUploadHandler({product, oId, userType, user, onComplete,
|
|
|
2111
2319
|
setAlert(undefined);
|
|
2112
2320
|
if (fUsers.length) {
|
|
2113
2321
|
console.log("fUsers", fUsers);
|
|
2114
|
-
execute("userManagement-addUsers", {product: product, oId: oId, users: fUsers, userType: userType, userGroupId: userGroupId, cohortId: cohortId});
|
|
2322
|
+
const jobId = await execute("userManagement-addUsers", {product: product, oId: oId, users: fUsers, userType: userType, userGroupId: userGroupId, cohortId: cohortId});
|
|
2323
|
+
onComplete && onComplete(jobId);
|
|
2115
2324
|
}
|
|
2116
2325
|
console.log("Complete", onComplete);
|
|
2117
|
-
onComplete && onComplete();
|
|
2118
2326
|
console.log("Finished");
|
|
2119
2327
|
};
|
|
2120
2328
|
|
|
@@ -2962,7 +3170,7 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
2962
3170
|
const [workflow, setWorkflow] = useState<WorkflowStage[]>();
|
|
2963
3171
|
const [cohort, setCohort] = useState<CohortData>();
|
|
2964
3172
|
const [student, setStudent] = useState<UserData>();
|
|
2965
|
-
const [wStage, setWStage] = useState<WorkflowStage>();
|
|
3173
|
+
const [wStage, setWStage] = useState<WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}}>();
|
|
2966
3174
|
const [snackbar, setSnackbar] = useState<{ open: boolean; message?: string}>({open: false});
|
|
2967
3175
|
const [disableEmail, setDisableEmail] = useState({parent: false, provider: false});
|
|
2968
3176
|
const [rejectELIPopup, setRejectELIPopup] = useState(false);
|
|
@@ -2982,6 +3190,10 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
2982
3190
|
const [uploadRA, setUploadRA] = useState(false);
|
|
2983
3191
|
const [uploadDBS, setUploadDBS] = useState(false);
|
|
2984
3192
|
const [onboardingPopup, setOnboardingPopup] = useState(false);
|
|
3193
|
+
const [dismissOnboardingPopup, setDismissOnboardingPopup] = useState(false);
|
|
3194
|
+
const [addOnboardingDocsPopup, setAddOnboardingDocsPopup] = useState(false);
|
|
3195
|
+
const [shareStudentRequestPopup, setShareStudentRequestPopup] = useState(false);
|
|
3196
|
+
|
|
2985
3197
|
const [editable, setEditable] = useState(false);
|
|
2986
3198
|
|
|
2987
3199
|
const [withdrawFromPlacementPopup, setWithdrawFromPlacementPopup] = useState(false);
|
|
@@ -3031,7 +3243,17 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3031
3243
|
if (user.userType === "Students") {
|
|
3032
3244
|
setStudent(user);
|
|
3033
3245
|
} else {
|
|
3034
|
-
|
|
3246
|
+
if (placement.uid) {
|
|
3247
|
+
getUserById(placement.uid, undefined, false).then(setStudent);
|
|
3248
|
+
} else {
|
|
3249
|
+
setStudent({
|
|
3250
|
+
details: {
|
|
3251
|
+
forename: placement.studentForename || "",
|
|
3252
|
+
surname: placement.studentSurname || "",
|
|
3253
|
+
},
|
|
3254
|
+
email: placement.studentEmail || ""
|
|
3255
|
+
} as UserData)
|
|
3256
|
+
}
|
|
3035
3257
|
}
|
|
3036
3258
|
|
|
3037
3259
|
}, [placement]);
|
|
@@ -3041,19 +3263,29 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3041
3263
|
useEffect(() => {
|
|
3042
3264
|
if (!workflow || !placement) return;
|
|
3043
3265
|
|
|
3044
|
-
const
|
|
3045
|
-
// console.log("currentWorkflowStage", currentWorkflowStage)
|
|
3046
|
-
currentWorkflowStage.id = placement.status;
|
|
3266
|
+
const getAdditionalStageData = async ():Promise<WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}}> => {
|
|
3047
3267
|
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3268
|
+
const currentWorkflowStage = {...workflow.find((obj) => obj.id === placement.status) as WorkflowStage&{files: {[fileId: string]: {name: string, url: string}}}};
|
|
3269
|
+
// console.log("currentWorkflowStage", currentWorkflowStage)
|
|
3270
|
+
currentWorkflowStage.id = placement.status;
|
|
3271
|
+
|
|
3272
|
+
// Get form data for current stage
|
|
3273
|
+
if (currentWorkflowStage.forms) {
|
|
3274
|
+
getFormsFromId(["forms"], currentWorkflowStage.forms).then((details) => {
|
|
3275
|
+
currentWorkflowStage.formDetails = details as [{name:string}];
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
if (currentWorkflowStage.files) {
|
|
3279
|
+
currentWorkflowStage.files = Object.fromEntries(await Promise.all(currentWorkflowStage.files.map(async (file) => {
|
|
3280
|
+
const fileItem = await firebaseQuery.getDocData(["files", file]) as FileItem;
|
|
3281
|
+
const url = await getDownloadURL(ref(storage, `institutes/${placement.oId}/${fileItem.fileName}`))
|
|
3282
|
+
return [file, {name: fileItem.name, url: url}];
|
|
3283
|
+
})));
|
|
3284
|
+
}
|
|
3285
|
+
return currentWorkflowStage;
|
|
3056
3286
|
}
|
|
3287
|
+
|
|
3288
|
+
getAdditionalStageData().then(setWStage);
|
|
3057
3289
|
}, [placement, workflow]);
|
|
3058
3290
|
|
|
3059
3291
|
|
|
@@ -3097,15 +3329,14 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3097
3329
|
};
|
|
3098
3330
|
|
|
3099
3331
|
|
|
3100
|
-
const onboardingStatus:"Add onboarding documents"|"Onboarding sent"|"Onboarding docs completed"|"Onboarding docs approved"|"Complete onboarding" =
|
|
3332
|
+
const onboardingStatus:"Add onboarding documents"|"Onboarding sent"|"Onboarding docs completed"|"Onboarding docs approved"|"Complete onboarding" =
|
|
3333
|
+
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
3334
|
|
|
3102
3335
|
const signOffPlacements = getAccess(user, "signOffPlacements");
|
|
3103
3336
|
|
|
3104
3337
|
let canEdit = false;
|
|
3105
3338
|
|
|
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") {
|
|
3339
|
+
if ((wStage?.userType === "Staff" && user.userType === "Staff" && user.product === "institutes") || (user.product === "providers" && wStage?.userType === "Provider") || user.userType === "Students" && wStage?.userType === "Students") {
|
|
3109
3340
|
console.log("ALMOST CAN EDIT");
|
|
3110
3341
|
if (user.userType === "Staff" && !signOffPlacements) {
|
|
3111
3342
|
canEdit = false;
|
|
@@ -3116,11 +3347,14 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3116
3347
|
|
|
3117
3348
|
|
|
3118
3349
|
|
|
3119
|
-
const onFlagClick = async (e:FlagCodes) => {
|
|
3350
|
+
const onFlagClick = async (e:FlagCodes, onClose?: boolean) => {
|
|
3120
3351
|
if (!placement) return;
|
|
3121
3352
|
if (e === "completeOnboarding" || e === "reviewOnboarding") {
|
|
3122
3353
|
setOnboardingPopup(true);
|
|
3123
3354
|
}
|
|
3355
|
+
if (e === "studentNotAccepted") {
|
|
3356
|
+
setShareStudentRequestPopup(true);
|
|
3357
|
+
}
|
|
3124
3358
|
if (e === "noInsurance") {
|
|
3125
3359
|
if (!eliURL) {
|
|
3126
3360
|
const storageRef = ref(storage, `insurance/${placement.providerId}.pdf`);
|
|
@@ -3146,6 +3380,13 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3146
3380
|
}
|
|
3147
3381
|
setExternalDocPopupOpen("dbsCheck");
|
|
3148
3382
|
}
|
|
3383
|
+
if (e === "addOnboarding") {
|
|
3384
|
+
if (onClose) {
|
|
3385
|
+
setDismissOnboardingPopup(true);
|
|
3386
|
+
} else {
|
|
3387
|
+
setAddOnboardingDocsPopup(true);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3149
3390
|
};
|
|
3150
3391
|
|
|
3151
3392
|
const approveELI = async () => {
|
|
@@ -3187,7 +3428,7 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3187
3428
|
throw e;
|
|
3188
3429
|
});
|
|
3189
3430
|
console.log("RETURN", res.data);
|
|
3190
|
-
setPlacement((p) => ({...p, ...res.data}));
|
|
3431
|
+
setPlacement((p) => ({...p, ...res.data as any}));
|
|
3191
3432
|
if (uploadProviderDocPopup === "insurance") {
|
|
3192
3433
|
setUploadProviderDocPopup(undefined);
|
|
3193
3434
|
setUploadInsurance(true);
|
|
@@ -3206,26 +3447,28 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3206
3447
|
await executeCallable("placement-withdraw", {placementId: placementId});
|
|
3207
3448
|
}
|
|
3208
3449
|
|
|
3209
|
-
return {placement, wStage, student, workflow, editable, withdrawFromPlacementPopup, setWithdrawFromPlacementPopup, withdrawFromPlacement, onFlagClick, setUploadInsurance, setUploadProviderDocPopup, setUploadRA, setUploadDBS, onboardingStatus, setSkipStagePopup, onboardingPopup, setViewExternalLinkPopup, setOnboardingPopup, setRejectELIPopup, eliURL, 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}
|
|
3450
|
+
return {placement, wStage, student, workflow, editable, withdrawFromPlacementPopup, addOnboardingDocsPopup, setAddOnboardingDocsPopup, dismissOnboardingPopup, setDismissOnboardingPopup, setWithdrawFromPlacementPopup, withdrawFromPlacement, onFlagClick, setUploadInsurance, setUploadProviderDocPopup, setUploadRA, setUploadDBS, onboardingStatus, setSkipStagePopup, onboardingPopup, setViewExternalLinkPopup, setOnboardingPopup, setRejectELIPopup, eliURL, 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
3451
|
}
|
|
3211
3452
|
|
|
3212
|
-
export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onboarding: (
|
|
3453
|
+
export function useOnboardingPopup({onboarding, providerId, placementId, user, onClose}:{onboarding: (
|
|
3213
3454
|
OnboardingDocs&{
|
|
3214
3455
|
completed: {
|
|
3215
|
-
submitted:
|
|
3456
|
+
submitted: boolean,
|
|
3457
|
+
submittedDate?: string,
|
|
3216
3458
|
accepted?: boolean,
|
|
3217
3459
|
filesViewed?: string[],
|
|
3218
3460
|
formsCompleted?: {[key: string]: unknown},
|
|
3219
3461
|
filesUploaded?: {[key: number]: string[]},
|
|
3220
3462
|
}}
|
|
3221
|
-
), placementId: string, user: UserData, onClose: () => void}) {
|
|
3463
|
+
), placementId: string, user: UserData, providerId: string, onClose: () => void}) {
|
|
3222
3464
|
|
|
3223
3465
|
const [fileUploadPopup, setFileUploadPopup] = useState<false|number>(false);
|
|
3224
3466
|
const [form, setForm] = useState<{id: string, name: string, [key:string]: unknown}>();
|
|
3225
3467
|
const [rejectPopup, setRejectPopup] = useState(false);
|
|
3226
3468
|
const [mOnboarding, setMOnboarding] = useState(onboarding);
|
|
3227
3469
|
const [completedSections, setCompletedSections] = useState<{
|
|
3228
|
-
submitted:
|
|
3470
|
+
submitted: boolean,
|
|
3471
|
+
submittedDate?: string,
|
|
3229
3472
|
filesViewed?: string[] | undefined;
|
|
3230
3473
|
formsCompleted?: {
|
|
3231
3474
|
[key: string]: unknown;
|
|
@@ -3234,7 +3477,19 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3234
3477
|
[key: number]: string[];
|
|
3235
3478
|
} | undefined;
|
|
3236
3479
|
}>();
|
|
3480
|
+
|
|
3237
3481
|
const [uploadedFiles, setUploadedFiles] = useState<{[key: string]: FileItem}>({});
|
|
3482
|
+
const [viewableFiles, setViewableFiles] = useState<{[key: string]: FileItem}>();
|
|
3483
|
+
const [formDetails, setFormDetails] = useState<{
|
|
3484
|
+
[key: string]: {
|
|
3485
|
+
name: string;
|
|
3486
|
+
id: string;
|
|
3487
|
+
description?: string;
|
|
3488
|
+
product: Products;
|
|
3489
|
+
oId: string;
|
|
3490
|
+
form: CustomFormSchema;
|
|
3491
|
+
};
|
|
3492
|
+
}>({});
|
|
3238
3493
|
|
|
3239
3494
|
const firebaseQuery = new FirebaseQuery();
|
|
3240
3495
|
|
|
@@ -3252,7 +3507,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3252
3507
|
const viewedFiles = a.completed ? a.completed?.filesViewed || [] : [];
|
|
3253
3508
|
if (viewedFiles?.includes(file)) return a;
|
|
3254
3509
|
|
|
3255
|
-
|
|
3510
|
+
viewableFiles?.[file].url && onOpen(viewableFiles?.[file].url);
|
|
3256
3511
|
viewedFiles?.push(file);
|
|
3257
3512
|
|
|
3258
3513
|
const newA = editNestedObject(["completed", "filesViewed"], oldA, viewedFiles) as any;
|
|
@@ -3275,7 +3530,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3275
3530
|
|
|
3276
3531
|
const onboardingFiles = onboarding.files ? Object.fromEntries(await Promise.all(onboarding.files?.map(async (fileId) => {
|
|
3277
3532
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
3278
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
3533
|
+
file.url = await getDownloadURL(ref(storage, `providers/${providerId}/${file.fileName}`));
|
|
3279
3534
|
|
|
3280
3535
|
return [fileId, file];
|
|
3281
3536
|
}))) : [];
|
|
@@ -3283,8 +3538,8 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3283
3538
|
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
3284
3539
|
}))) : [];
|
|
3285
3540
|
|
|
3286
|
-
|
|
3287
|
-
|
|
3541
|
+
setViewableFiles(onboardingFiles);
|
|
3542
|
+
setFormDetails(onboardingForms);
|
|
3288
3543
|
return onboardingNew;
|
|
3289
3544
|
};
|
|
3290
3545
|
getOnboardingData().then(setMOnboarding);
|
|
@@ -3346,7 +3601,8 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3346
3601
|
// Check all stages completed.
|
|
3347
3602
|
if (!placementId) return;
|
|
3348
3603
|
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});
|
|
3604
|
+
await firebaseQuery.update(["placements", placementId], {["onboarding.completed.accepted"]: false, ["onboarding.completed.submitted"]: true, ["onboarding.completed.submittedDate"]: convertDate(new Date(), "dbstring") as string});
|
|
3605
|
+
//executeCallable("sendOnboardingSubmittedEmail", {});
|
|
3350
3606
|
};
|
|
3351
3607
|
|
|
3352
3608
|
useEffect(() => {
|
|
@@ -3391,5 +3647,707 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3391
3647
|
addCompletedSectionURLs();
|
|
3392
3648
|
}, [mOnboarding]);
|
|
3393
3649
|
|
|
3394
|
-
return {addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup};
|
|
3395
|
-
}
|
|
3650
|
+
return {addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup, viewableFiles, formDetails};
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
|
|
3654
|
+
|
|
3655
|
+
|
|
3656
|
+
|
|
3657
|
+
export function useLoadAddresses(user: UserData, limitItems?: number, queryConstraint?: QueryConstraint[], request?: boolean) {
|
|
3658
|
+
const [addresses, setAddresses] = useState<{[key: string]: OrganisationAddress&{listings: number}}>({});
|
|
3659
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3660
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3661
|
+
|
|
3662
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3663
|
+
|
|
3664
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3665
|
+
|
|
3666
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3667
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3668
|
+
};
|
|
3669
|
+
|
|
3670
|
+
const loadAddresses = () => {
|
|
3671
|
+
const constraints:QueryConstraint[] = [where("oId", "==", user.oId), where("product", "==", user.product), orderBy(documentId())]
|
|
3672
|
+
|
|
3673
|
+
if (limitItems) {
|
|
3674
|
+
constraints.push(limit(limitItems));
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
if (user.viewAddresses === "all" || user.userGroup === "admin" || request) {
|
|
3678
|
+
if (lastDoc?.id) {
|
|
3679
|
+
constraints.push(startAfter(lastDoc?.id));
|
|
3680
|
+
}
|
|
3681
|
+
} else if (user.viewAddresses === "request") {
|
|
3682
|
+
if (!user.visibleAddresses?.length) return;
|
|
3683
|
+
|
|
3684
|
+
constraints.push(where(documentId(), "in", user.visibleAddresses))
|
|
3685
|
+
if (lastDoc?.id) {
|
|
3686
|
+
constraints.push(startAfter(lastDoc?.id));
|
|
3687
|
+
}
|
|
3688
|
+
} else {
|
|
3689
|
+
setLoading(false);
|
|
3690
|
+
return; // viewAddresses === "none", no need to load anything
|
|
3691
|
+
}
|
|
3692
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3693
|
+
|
|
3694
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3695
|
+
const deletedAddresses = snapshot.docChanges().map((change) => {
|
|
3696
|
+
if (change.type === "removed") {
|
|
3697
|
+
return change.doc.id;
|
|
3698
|
+
}
|
|
3699
|
+
return;
|
|
3700
|
+
})
|
|
3701
|
+
|
|
3702
|
+
setAddresses((prev) => Object.fromEntries(Object.entries(prev).filter(([k]) => !deletedAddresses.includes(k))))
|
|
3703
|
+
|
|
3704
|
+
if (!snapshot.empty) {
|
|
3705
|
+
|
|
3706
|
+
|
|
3707
|
+
const newAddresses:[string, OrganisationAddress][] = snapshot.docs.map(doc => ([doc.id, {id: doc.id, ...doc.data() as OrganisationAddress}]));
|
|
3708
|
+
|
|
3709
|
+
const withListings:{[key: string]: OrganisationAddress&{listings: number}} = Object.fromEntries(await Promise.all(newAddresses.map(async ([k, address]) => {
|
|
3710
|
+
const listings = await firebaseQuery.getCount("placementListings", [where("providerId", "==", user.oId), where("addressId", "==", k)]);
|
|
3711
|
+
return [k, {...address, listings: listings}];
|
|
3712
|
+
})));
|
|
3713
|
+
|
|
3714
|
+
setAddresses(prev => ({...prev, ...withListings}));
|
|
3715
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3716
|
+
}
|
|
3717
|
+
setLoading(false);
|
|
3718
|
+
}), "addresses", constraints, undefined, true);
|
|
3719
|
+
};
|
|
3720
|
+
|
|
3721
|
+
useEffect(() => {
|
|
3722
|
+
const unsubscribe = loadAddresses();
|
|
3723
|
+
|
|
3724
|
+
return () => {
|
|
3725
|
+
if (unsubscribe) {
|
|
3726
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3727
|
+
}
|
|
3728
|
+
};
|
|
3729
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3730
|
+
}, [queryConstraints]);
|
|
3731
|
+
|
|
3732
|
+
const onScrollBottom = () => {
|
|
3733
|
+
if (!limitItems) return;
|
|
3734
|
+
if (!loading) {
|
|
3735
|
+
setLoading(true);
|
|
3736
|
+
loadAddresses();
|
|
3737
|
+
}
|
|
3738
|
+
};
|
|
3739
|
+
|
|
3740
|
+
return { addresses, onScrollBottom, loading, changeQueryConstraints };
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
|
|
3744
|
+
|
|
3745
|
+
|
|
3746
|
+
export function useLoadListings(user: UserData, queryConstraint?: QueryConstraint[], request?: boolean) {
|
|
3747
|
+
const [listings, setListings] = useState<[string, PlacementListing&{applicants?: number, scheduled?: number, active?: number}][]>([]);
|
|
3748
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3749
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3750
|
+
|
|
3751
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3752
|
+
|
|
3753
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3754
|
+
|
|
3755
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3756
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3757
|
+
};
|
|
3758
|
+
|
|
3759
|
+
const loadListings = () => {
|
|
3760
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3761
|
+
|
|
3762
|
+
if (user.viewPlacementListings === "all" || user.userGroup === "admin" || request) {
|
|
3763
|
+
if (lastDoc?.id) {
|
|
3764
|
+
constraints.push(startAfter(lastDoc));
|
|
3765
|
+
}
|
|
3766
|
+
} else {
|
|
3767
|
+
if (!user.visibleListings?.length) return;
|
|
3768
|
+
constraints.push(where(documentId(), 'in', user.visibleListings));
|
|
3769
|
+
if (lastDoc?.id) {
|
|
3770
|
+
constraints.push(startAfter(lastDoc));
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
if (user.viewAddresses !== "all" && user.viewPlacementListings === "all" && user.userGroup !== "admin") {
|
|
3775
|
+
if (!user.visibleAddresses?.length) return;
|
|
3776
|
+
|
|
3777
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3778
|
+
}
|
|
3779
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3780
|
+
|
|
3781
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3782
|
+
const deletedListings = snapshot.docChanges().map((change) => {
|
|
3783
|
+
if (change.type === "removed") {
|
|
3784
|
+
return change.doc.id;
|
|
3785
|
+
}
|
|
3786
|
+
return;
|
|
3787
|
+
})
|
|
3788
|
+
|
|
3789
|
+
setListings((prev) => prev.filter(([k]) => !deletedListings.includes(k)))
|
|
3790
|
+
|
|
3791
|
+
if (!snapshot.empty) {
|
|
3792
|
+
const newListings:[string, PlacementListing][] = snapshot.docs.map(doc => ([doc.id, {...doc.data() as PlacementListing, id: doc.id}]));
|
|
3793
|
+
|
|
3794
|
+
const listingsWithAdditionalData:[string, PlacementListing&{applicants?: number, scheduled?: number, active?: number}][] = await Promise.all(newListings.map(async ([id, listing]) => {
|
|
3795
|
+
const listingWithAdditionalData = {...listing} as PlacementListing&{applicants?: number, scheduled?: number, active?: number};
|
|
3796
|
+
|
|
3797
|
+
if (listingWithAdditionalData.applicants !== undefined) return [id, listingWithAdditionalData];
|
|
3798
|
+
|
|
3799
|
+
listingWithAdditionalData.applicants = await firebaseQuery.getCount("applications", [where("providerId", "==", user.oId), where("placementId", "==", id), where("status", "==", "submitted")]);
|
|
3800
|
+
listingWithAdditionalData.scheduled = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("placementId", "==", id), where("inProgress", "==", true), where("startDate", ">", convertDate(new Date(), "dbstring"))]);
|
|
3801
|
+
listingWithAdditionalData.active = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("placementId", "==", id), where("active", "==", true)]);
|
|
3802
|
+
|
|
3803
|
+
return [id, listingWithAdditionalData];
|
|
3804
|
+
}));
|
|
3805
|
+
setListings(prev => (Object.entries({...Object.fromEntries(prev), ...Object.fromEntries(listingsWithAdditionalData)})));
|
|
3806
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3807
|
+
}
|
|
3808
|
+
setLoading(false);
|
|
3809
|
+
}), "placementListings", constraints, undefined, true);
|
|
3810
|
+
};
|
|
3811
|
+
|
|
3812
|
+
useEffect(() => {
|
|
3813
|
+
let unsubscribe: () => void;
|
|
3814
|
+
|
|
3815
|
+
|
|
3816
|
+
|
|
3817
|
+
loadListings();
|
|
3818
|
+
|
|
3819
|
+
return () => {
|
|
3820
|
+
if (unsubscribe) {
|
|
3821
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3822
|
+
}
|
|
3823
|
+
};
|
|
3824
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3825
|
+
}, [queryConstraints]);
|
|
3826
|
+
|
|
3827
|
+
const onScrollBottom = () => {
|
|
3828
|
+
if (!loading) {
|
|
3829
|
+
setLoading(true);
|
|
3830
|
+
loadListings();
|
|
3831
|
+
}
|
|
3832
|
+
};
|
|
3833
|
+
|
|
3834
|
+
return { listings, onScrollBottom, loading, changeQueryConstraints };
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
|
|
3838
|
+
|
|
3839
|
+
export function useLoadProviderPlacements(user: UserData, queryConstraint?: QueryConstraint[], placementId?: string) {
|
|
3840
|
+
const [placements, setPlacements] = useState<[string, StudentPlacementData&{applicants?: number, scheduled?: number, active?: number}][]>([]);
|
|
3841
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3842
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3843
|
+
|
|
3844
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3845
|
+
|
|
3846
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3847
|
+
|
|
3848
|
+
if (user.product !== "providers") throw new Error("Only providers can use this hook.");
|
|
3849
|
+
|
|
3850
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3851
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3852
|
+
};
|
|
3853
|
+
|
|
3854
|
+
const loadListings = () => {
|
|
3855
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3856
|
+
|
|
3857
|
+
if (placementId) {
|
|
3858
|
+
constraints.push(where("placementId", "==", placementId));
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
if (user.viewPlacementListings === "all" || user.userGroup === "admin") {
|
|
3862
|
+
if (lastDoc?.id) {
|
|
3863
|
+
constraints.push(startAfter(lastDoc));
|
|
3864
|
+
}
|
|
3865
|
+
} else {
|
|
3866
|
+
if (!user.visibleListings?.length) return;
|
|
3867
|
+
constraints.push(where("placementId", 'in', user.visibleListings));
|
|
3868
|
+
if (lastDoc?.id) {
|
|
3869
|
+
constraints.push(startAfter(lastDoc));
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
if (user.viewAddresses !== "all" && user.viewPlacementListings === "all" && user.userGroup !== "admin") {
|
|
3874
|
+
if (!user.visibleAddresses?.length) return;
|
|
3875
|
+
|
|
3876
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3877
|
+
}
|
|
3878
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3879
|
+
|
|
3880
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3881
|
+
const deletedListings = snapshot.docChanges().map((change) => {
|
|
3882
|
+
if (change.type === "removed") {
|
|
3883
|
+
return change.doc.id;
|
|
3884
|
+
}
|
|
3885
|
+
return;
|
|
3886
|
+
})
|
|
3887
|
+
|
|
3888
|
+
setPlacements((prev) => prev.filter(([k]) => !deletedListings.includes(k)))
|
|
3889
|
+
|
|
3890
|
+
if (!snapshot.empty) {
|
|
3891
|
+
const newPlacements:[string, StudentPlacementData][] = snapshot.docs.map(doc => ([doc.id, {...doc.data() as StudentPlacementData, id: doc.id}]));
|
|
3892
|
+
|
|
3893
|
+
const withAdditionalData = await Promise.all(newPlacements.map(async ([id, placement]) => {
|
|
3894
|
+
const student = placement.uid ? await firebaseQuery.getDocData(["users", placement.uid || ""]).catch(() => false) as UserData|false :
|
|
3895
|
+
{
|
|
3896
|
+
details: {
|
|
3897
|
+
forename: placement.studentForename,
|
|
3898
|
+
surname: placement.studentSurname,
|
|
3899
|
+
},
|
|
3900
|
+
email: placement.studentEmail
|
|
3901
|
+
};
|
|
3902
|
+
const listing = await firebaseQuery.getDocData(["placementListings", placement.placementId || ""]).catch(() => false) as PlacementListing;
|
|
3903
|
+
|
|
3904
|
+
return [id, {...placement, student: student, listing: listing}];
|
|
3905
|
+
}));
|
|
3906
|
+
setPlacements(prev => (Object.entries({...Object.fromEntries(prev), ...Object.fromEntries(withAdditionalData)})));
|
|
3907
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3908
|
+
}
|
|
3909
|
+
setLoading(false);
|
|
3910
|
+
}), "placements", constraints, undefined, true);
|
|
3911
|
+
};
|
|
3912
|
+
|
|
3913
|
+
useEffect(() => {
|
|
3914
|
+
let unsubscribe: () => void;
|
|
3915
|
+
|
|
3916
|
+
|
|
3917
|
+
|
|
3918
|
+
loadListings();
|
|
3919
|
+
|
|
3920
|
+
return () => {
|
|
3921
|
+
if (unsubscribe) {
|
|
3922
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3923
|
+
}
|
|
3924
|
+
};
|
|
3925
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3926
|
+
}, [queryConstraints]);
|
|
3927
|
+
|
|
3928
|
+
const onScrollBottom = () => {
|
|
3929
|
+
if (!loading) {
|
|
3930
|
+
setLoading(true);
|
|
3931
|
+
loadListings();
|
|
3932
|
+
}
|
|
3933
|
+
};
|
|
3934
|
+
|
|
3935
|
+
return { placements: Object.fromEntries(placements), onScrollBottom, loading, changeQueryConstraints };
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3938
|
+
|
|
3939
|
+
|
|
3940
|
+
export function useLoadApplications({user, applicationType, listingId, queryConstraint}:{user: UserData, applicationType?: "all"|"actionRequired"|"awaitingStudent"|"closed", listingId?: string, queryConstraint?: QueryConstraint[]}) {
|
|
3941
|
+
const [applications, setApplications] = useState<[string, Application][]>([]);
|
|
3942
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3943
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3944
|
+
const [type, setType] = useState<"actionRequired"|"awaitingStudent"|"closed"|"all">(applicationType || "all");
|
|
3945
|
+
|
|
3946
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3947
|
+
|
|
3948
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3949
|
+
|
|
3950
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3951
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3952
|
+
};
|
|
3953
|
+
|
|
3954
|
+
const loadApplications = () => {
|
|
3955
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3956
|
+
|
|
3957
|
+
|
|
3958
|
+
if (lastDoc?.id) {
|
|
3959
|
+
constraints.push(startAfter(lastDoc));
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
switch (type) {
|
|
3963
|
+
case "actionRequired":
|
|
3964
|
+
constraints.push(where("status", "==", "submitted"), where("reqUserType", "==", "Staff"));
|
|
3965
|
+
break;
|
|
3966
|
+
case "awaitingStudent":
|
|
3967
|
+
constraints.push(where("status", "==", "submitted"), where("reqUserType", "==", "Students"));
|
|
3968
|
+
break;
|
|
3969
|
+
case "closed":
|
|
3970
|
+
constraints.push(where("status", "in", ["approved", "declined"]));
|
|
3971
|
+
break;
|
|
3972
|
+
default:
|
|
3973
|
+
constraints.push(where("status", "==", "submitted"));
|
|
3974
|
+
}
|
|
3975
|
+
if (listingId) {
|
|
3976
|
+
constraints.push(where("listingId", "==", listingId));
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
console.log("Constraints before user group check", constraints);
|
|
3980
|
+
if (user.viewAddresses !== "all" && user.userGroup !== "admin") {
|
|
3981
|
+
if (user.viewPlacementListings === "all") {
|
|
3982
|
+
if (!user.visibleAddresses?.length) return;
|
|
3983
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3984
|
+
} else {
|
|
3985
|
+
if (!user.visibleListings?.length) return;
|
|
3986
|
+
constraints.push(where('placementId', 'in', user.visibleListings));
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3991
|
+
console.log("Constraints after user group check", constraints);
|
|
3992
|
+
|
|
3993
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3994
|
+
const deletedApplications = snapshot.docChanges().map((change) => {
|
|
3995
|
+
if (change.type === "removed") {
|
|
3996
|
+
return change.doc.id;
|
|
3997
|
+
}
|
|
3998
|
+
return;
|
|
3999
|
+
})
|
|
4000
|
+
|
|
4001
|
+
console.log("applicantCount", snapshot.size);
|
|
4002
|
+
|
|
4003
|
+
setApplications((prev) => prev.filter(([k]) => !deletedApplications.includes(k)))
|
|
4004
|
+
|
|
4005
|
+
if (!snapshot.empty) {
|
|
4006
|
+
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][];
|
|
4007
|
+
|
|
4008
|
+
setApplications(prev => ([...prev, ...newApplications]));
|
|
4009
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
4010
|
+
} else {
|
|
4011
|
+
setApplications([]);
|
|
4012
|
+
setLastDoc(null);
|
|
4013
|
+
}
|
|
4014
|
+
setLoading(false);
|
|
4015
|
+
}), "applications", constraints, undefined, true);
|
|
4016
|
+
};
|
|
4017
|
+
|
|
4018
|
+
useEffect(() => {
|
|
4019
|
+
let unsubscribe: () => void;
|
|
4020
|
+
|
|
4021
|
+
loadApplications();
|
|
4022
|
+
return () => {
|
|
4023
|
+
if (unsubscribe) {
|
|
4024
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
4025
|
+
}
|
|
4026
|
+
};
|
|
4027
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4028
|
+
}, [type, queryConstraints]);
|
|
4029
|
+
|
|
4030
|
+
const onScrollBottom = () => {
|
|
4031
|
+
if (!loading) {
|
|
4032
|
+
setLoading(true);
|
|
4033
|
+
loadApplications();
|
|
4034
|
+
}
|
|
4035
|
+
};
|
|
4036
|
+
|
|
4037
|
+
return { applications, type, setType, onScrollBottom, loading, changeQueryConstraints };
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
|
|
4041
|
+
|
|
4042
|
+
export type FilterObject = {
|
|
4043
|
+
[key: string]: {
|
|
4044
|
+
label: string,
|
|
4045
|
+
value?: unknown,
|
|
4046
|
+
values?: {[key:string|number]: string|{label: string, test: QueryFieldFilterConstraint}},
|
|
4047
|
+
type: "dropdown"|"string"|"number"
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
export type DataViewerPaginater = {
|
|
4052
|
+
view: "list"|"table";
|
|
4053
|
+
queryLimit?: number;
|
|
4054
|
+
formatItems?: (key: string, item: any) => Promise<{ key: string, item: any }> | { key: string, item: any };
|
|
4055
|
+
snapshot?: boolean;
|
|
4056
|
+
filters?: FilterObject;
|
|
4057
|
+
onSearch?: boolean | ((search?: string, sort?: [string, {value: string, direction: "asc"|"desc"}], page?: number, filters?:FilterObject, limit?: number) => Promise<{ [key: string]: any }>);
|
|
4058
|
+
data?: {[key:string]:{[key:string]: unknown}}|QueryObject[];
|
|
4059
|
+
additionalEntryProcessing?: (k: string, v: any) => Promise<any> | any,
|
|
4060
|
+
sorts?: Sorts
|
|
4061
|
+
}
|
|
4062
|
+
|
|
4063
|
+
export function useDataViewerPaginator({view: initialView, sorts, queryLimit, additionalEntryProcessing, formatItems, snapshot, filters: initialFilters, onSearch, data}:DataViewerPaginater) {
|
|
4064
|
+
const [tableData, setTableData] = useState<{[key:string]:{[key:string]: unknown}}>(Array.isArray(data) ? Object.fromEntries(Object.entries(data).slice(0, queryLimit)) : {});
|
|
4065
|
+
const [page, setPage] = useState([1, 0]);
|
|
4066
|
+
const [view, setView] = useState(initialView);
|
|
4067
|
+
const [filters, setFilters] = useState<FilterObject|undefined>(initialFilters);
|
|
4068
|
+
const [queryAnchor, setQueryAnchor] = useState<{startKey: string, endKey: string, startQueryPos: number, endQueryPos: number}>({startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0});
|
|
4069
|
+
const [prevEntryIds, setPrevEntryIds] = useState<{[key:string]:number}>({});
|
|
4070
|
+
const [dataListenerUnsubscribe, setDataListenerUnsubscribe] = useState<{[key: string] : Unsubscribe}>();
|
|
4071
|
+
const [loading, setLoading] = useState<boolean|"loaded">(true);
|
|
4072
|
+
const [searchString, setSearchString] = useState<string>();
|
|
4073
|
+
const [sort, setSort] = useState<[string, {value: string, direction: "asc"|"desc"}]>();
|
|
4074
|
+
|
|
4075
|
+
|
|
4076
|
+
|
|
4077
|
+
const processedData = async (k: string, v: any) => additionalEntryProcessing ? await additionalEntryProcessing(k, v) : v;
|
|
4078
|
+
|
|
4079
|
+
const setTableDataFromDefinedData = async () => {
|
|
4080
|
+
if (!data || Array.isArray(data)) return;
|
|
4081
|
+
|
|
4082
|
+
const dataWithAdditionalProcessingPossibleNulls: [string, any][] = (await Promise.all(Object.entries(data).map(async ([k, v]) => [k, await processedData(k, v)])))
|
|
4083
|
+
const dataWithAdditionalProcessing = dataWithAdditionalProcessingPossibleNulls.filter(([k, v]) => v);
|
|
4084
|
+
|
|
4085
|
+
const searchedData: [string, any][] = searchString ? dataWithAdditionalProcessing.filter(([, v]) => {
|
|
4086
|
+
const values = Object.values(v).join(", ");
|
|
4087
|
+
console.log("VALUESTRING", v);
|
|
4088
|
+
return values.includes(searchString);
|
|
4089
|
+
}) : dataWithAdditionalProcessing;
|
|
4090
|
+
|
|
4091
|
+
const filteredData:[string, any][] = filters && Object.keys(filters).length > 0 ? searchedData.filter(([, dataValue]) => Object.entries(filters).every(([filterKey, filterValue]) => {
|
|
4092
|
+
const value = dataValue[filterKey];
|
|
4093
|
+
if ((typeof value === "number") && value === parseInt(filterValue.value as string)) return true;
|
|
4094
|
+
if ((typeof value === "boolean") && value === (filterValue.value === "true")) return true;
|
|
4095
|
+
if ((typeof value === "boolean") && value === (filterValue.value === "false")) return true;
|
|
4096
|
+
|
|
4097
|
+
if ((typeof value === "string" || Array.isArray(value)) && value.includes(filterValue.value as string)) return true;
|
|
4098
|
+
return false;
|
|
4099
|
+
})) : searchedData;
|
|
4100
|
+
if (view === "table") {
|
|
4101
|
+
if (!queryLimit) throw new Error("Tables must have a limit defined.");
|
|
4102
|
+
const newData:[string, any][] = filteredData.slice((page[0] - 1)*queryLimit, page[0]*queryLimit);
|
|
4103
|
+
setTableData(Object.fromEntries(newData));
|
|
4104
|
+
if (Object.keys(Object.fromEntries(newData)).pop() === Object.keys(data).pop()) {
|
|
4105
|
+
setLoading("loaded");
|
|
4106
|
+
} else {
|
|
4107
|
+
setLoading(false);
|
|
4108
|
+
}
|
|
4109
|
+
return;
|
|
4110
|
+
}
|
|
4111
|
+
if (view === "list") {
|
|
4112
|
+
setTableData(Object.fromEntries(filteredData));
|
|
4113
|
+
setLoading("loaded");
|
|
4114
|
+
}
|
|
4115
|
+
};
|
|
4116
|
+
|
|
4117
|
+
const getDataFromQuery = async (
|
|
4118
|
+
itemList: {[key: string]: any} = {}, currentQueryAnchor=queryAnchor, cursorDirection?:"increase"|"decrease"|undefined, prevEntries=prevEntryIds, loadMoreFromQuery=false):Promise<any> => {
|
|
4119
|
+
|
|
4120
|
+
if (!filters) return;
|
|
4121
|
+
setLoading(true);
|
|
4122
|
+
if (!Array.isArray(data)) {
|
|
4123
|
+
setTableDataFromDefinedData();
|
|
4124
|
+
return;
|
|
4125
|
+
}
|
|
4126
|
+
if (!queryLimit) throw new Error("Firestore queries must have a limit defined.");
|
|
4127
|
+
|
|
4128
|
+
if (onSearch && (searchString || sort)) {
|
|
4129
|
+
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.");
|
|
4130
|
+
const data = await onSearch(searchString, sort, page[0], filters, queryLimit);
|
|
4131
|
+
const dataWithAdditionalProcessing: [string, any][] = await Promise.all(Object.entries(data).map(async ([k, v]) => [k, await processedData(k, v)]));
|
|
4132
|
+
|
|
4133
|
+
setTableData((old) => {
|
|
4134
|
+
if (view === "table") {
|
|
4135
|
+
return {...Object.fromEntries(dataWithAdditionalProcessing)};
|
|
4136
|
+
}
|
|
4137
|
+
return {...old, ...Object.fromEntries(dataWithAdditionalProcessing)};
|
|
4138
|
+
});
|
|
4139
|
+
|
|
4140
|
+
if (dataWithAdditionalProcessing.length < queryLimit) {
|
|
4141
|
+
setLoading("loaded");
|
|
4142
|
+
} else {
|
|
4143
|
+
setLoading(false);
|
|
4144
|
+
}
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
|
|
4149
|
+
|
|
4150
|
+
let cursorPos:number;
|
|
4151
|
+
if (page[0] > page[1]) {
|
|
4152
|
+
cursorPos = currentQueryAnchor.endQueryPos;
|
|
4153
|
+
} else {
|
|
4154
|
+
cursorPos = currentQueryAnchor.startQueryPos;
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
const querySchema:QueryObject = data[cursorPos];
|
|
4158
|
+
|
|
4159
|
+
const createQuery = (queryData:QueryObject) => {
|
|
4160
|
+
const constraints:any[] = [];
|
|
4161
|
+
queryData.where && queryData.where.forEach((w) => {
|
|
4162
|
+
constraints.push(where(...w));
|
|
4163
|
+
});
|
|
4164
|
+
|
|
4165
|
+
filters && Object.entries(filters).filter(([, value]) => value.value).forEach(([key, value]) => {
|
|
4166
|
+
const filterValue = (value.type === "number" || value.type === "dropdown") ? parseInt(value.value as string) || value.value : value.value;
|
|
4167
|
+
constraints.push(where(key, "==", filterValue));
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4170
|
+
constraints.push(orderBy(queryData.orderBy ? queryData.orderBy === "documentId" ? documentId() : queryData.orderBy : documentId()));
|
|
4171
|
+
|
|
4172
|
+
if (page[0] > page[1] && !cursorDirection) { // Going up
|
|
4173
|
+
currentQueryAnchor.endKey && constraints.push(startAfter(currentQueryAnchor.endKey));
|
|
4174
|
+
constraints.push(limit(queryLimit));
|
|
4175
|
+
if (!loadMoreFromQuery) {
|
|
4176
|
+
currentQueryAnchor = {...currentQueryAnchor, startQueryPos: currentQueryAnchor.endQueryPos};
|
|
4177
|
+
}
|
|
4178
|
+
} else if (page[0] < page[1] && !cursorDirection) { // Going down
|
|
4179
|
+
if (!loadMoreFromQuery) {
|
|
4180
|
+
currentQueryAnchor = {...currentQueryAnchor, endQueryPos: currentQueryAnchor.startQueryPos};
|
|
4181
|
+
}
|
|
4182
|
+
constraints.push(limitToLast(queryLimit));
|
|
4183
|
+
if (currentQueryAnchor.startKey) {
|
|
4184
|
+
currentQueryAnchor.startKey && constraints.push(endBefore(currentQueryAnchor.startKey));
|
|
4185
|
+
} else {
|
|
4186
|
+
currentQueryAnchor.startKey && constraints.push(endAt(currentQueryAnchor.startKey));
|
|
4187
|
+
}
|
|
4188
|
+
} else {
|
|
4189
|
+
if (cursorDirection === "decrease") {
|
|
4190
|
+
constraints.push(limitToLast(queryLimit));
|
|
4191
|
+
} else {
|
|
4192
|
+
constraints.push(limit(queryLimit));
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
return constraints;
|
|
4196
|
+
};
|
|
4197
|
+
|
|
4198
|
+
const constraints = createQuery(querySchema);
|
|
4199
|
+
const q = query(collection(db, ...(querySchema.path as [any])), ...(constraints));
|
|
4200
|
+
|
|
4201
|
+
console.log("Fetching docs", constraints);
|
|
4202
|
+
|
|
4203
|
+
if (snapshot) {
|
|
4204
|
+
// Use onSnapshot to get real-time updates
|
|
4205
|
+
const unsubscribe = onSnapshot(q, (querySnapshot) => {
|
|
4206
|
+
handleQuerySnapshot(querySnapshot);
|
|
4207
|
+
});
|
|
4208
|
+
// Save the unsubscribe function to state so we can dispose of it later
|
|
4209
|
+
setDataListenerUnsubscribe((d) => ({...(d || {}), [cursorPos]: unsubscribe}));
|
|
4210
|
+
return;
|
|
4211
|
+
} else {
|
|
4212
|
+
// Just get the docs without setting up a listener
|
|
4213
|
+
const queryData = await getDocs(q);
|
|
4214
|
+
handleQuerySnapshot(queryData);
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
// Function to handle query snapshot
|
|
4218
|
+
async function handleQuerySnapshot(querySnapshot: QuerySnapshot) {
|
|
4219
|
+
if (!Array.isArray(data)) throw new Error("Called querySnapshot but data is defined.");
|
|
4220
|
+
if (!queryLimit) throw new Error("Firestore queries must have a limit defined.");
|
|
4221
|
+
|
|
4222
|
+
const queryResults: { [key: string]: { [key: string]: unknown } } = {};
|
|
4223
|
+
let index = 0; // Declare the index variable
|
|
4224
|
+
|
|
4225
|
+
const reverseIfBack = (docs: QueryDocumentSnapshot<DocumentData>[]) =>
|
|
4226
|
+
page[0] < page[1] ? docs.reverse() : docs;
|
|
4227
|
+
|
|
4228
|
+
// Process each document in the querySnapshot
|
|
4229
|
+
for (const doc of reverseIfBack(querySnapshot.docs)) {
|
|
4230
|
+
if ((Object.keys(queryResults).length + Object.keys(itemList).length) === queryLimit) {
|
|
4231
|
+
break;
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
let position = Object.keys(itemList).length+(page[0]-1)*queryLimit+index+1;
|
|
4235
|
+
if (page[0] < page[1]) {
|
|
4236
|
+
position = (page[0])*queryLimit-index-Object.keys(itemList).length;
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
|
|
4240
|
+
if (itemList[doc.id] || (prevEntries[doc.id] && prevEntries[doc.id] !== position)) {
|
|
4241
|
+
console.log("Removing ", doc.id, ": E=", prevEntries[doc.id], ", G=", position);
|
|
4242
|
+
continue;
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
let item: {[key: string]: unknown}|false = doc.data();
|
|
4246
|
+
item.id = doc.id;
|
|
4247
|
+
|
|
4248
|
+
// Apply additionalEntryProcessing if provided
|
|
4249
|
+
if (additionalEntryProcessing) {
|
|
4250
|
+
item = await additionalEntryProcessing(doc.id, item);
|
|
4251
|
+
}
|
|
4252
|
+
if (!item) continue;
|
|
4253
|
+
|
|
4254
|
+
queryResults[doc.id] = item;
|
|
4255
|
+
index += 1;
|
|
4256
|
+
|
|
4257
|
+
if (prevEntries[doc.id]) continue;
|
|
4258
|
+
prevEntries[doc.id] = position;
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
if (cursorDirection === "decrease" || page[0] < page[1]) {
|
|
4262
|
+
itemList = {...Object.fromEntries(Object.entries(queryResults).reverse()), ...itemList};
|
|
4263
|
+
} else {
|
|
4264
|
+
itemList = {...itemList, ...queryResults};
|
|
4265
|
+
}
|
|
4266
|
+
|
|
4267
|
+
// Updating state with the new data and query anchors
|
|
4268
|
+
|
|
4269
|
+
|
|
4270
|
+
setPrevEntryIds(prevEntries);
|
|
4271
|
+
|
|
4272
|
+
if (querySnapshot.size < queryLimit && Object.keys(itemList).length < queryLimit) {
|
|
4273
|
+
// If we have ran out of entries, increase or decrease the index.
|
|
4274
|
+
if (page[0] > page[1] && cursorPos+1 < data.length) {
|
|
4275
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, endQueryPos: currentQueryAnchor.endQueryPos+1}, "increase", prevEntries);
|
|
4276
|
+
} else if (page[0] < page[1] && cursorPos > 0) {
|
|
4277
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, startQueryPos: currentQueryAnchor.startQueryPos-1}, "decrease", prevEntries);
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
if (Object.keys(itemList).length < queryLimit && querySnapshot.size === queryLimit) {
|
|
4282
|
+
console.log("Shorter than ten");
|
|
4283
|
+
return getDataFromQuery(itemList, {...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0]}, undefined, prevEntries, true);
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
if (querySnapshot.size === 0 &&
|
|
4287
|
+
Object.keys(itemList).length === 0 &&
|
|
4288
|
+
currentQueryAnchor.endQueryPos+1 === data.length &&
|
|
4289
|
+
page[0] > 1) {
|
|
4290
|
+
setTableData({});
|
|
4291
|
+
setQueryAnchor((a) => ({...a, startKey: ""}));
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
if (querySnapshot.size < queryLimit) {
|
|
4296
|
+
setLoading("loaded");
|
|
4297
|
+
} else {
|
|
4298
|
+
setLoading(false);
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
setQueryAnchor({...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0]});
|
|
4302
|
+
|
|
4303
|
+
setTableData((old) => {
|
|
4304
|
+
if (view === "table") {
|
|
4305
|
+
return {...itemList};
|
|
4306
|
+
}
|
|
4307
|
+
return {...old, ...itemList};
|
|
4308
|
+
});
|
|
4309
|
+
}
|
|
4310
|
+
};
|
|
4311
|
+
|
|
4312
|
+
const reset = () => {
|
|
4313
|
+
console.log("Resetting after filters?");
|
|
4314
|
+
setPage([1, 0]);
|
|
4315
|
+
setTableData({});
|
|
4316
|
+
setQueryAnchor({startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0});
|
|
4317
|
+
setPrevEntryIds({});
|
|
4318
|
+
dataListenerUnsubscribe && Object.values(dataListenerUnsubscribe).map((u) => u());
|
|
4319
|
+
setDataListenerUnsubscribe(undefined);
|
|
4320
|
+
};
|
|
4321
|
+
|
|
4322
|
+
useEffect(() => {
|
|
4323
|
+
console.log("Filters updates", filters);
|
|
4324
|
+
if (!filters) return;
|
|
4325
|
+
console.log("Resetting cus filters");
|
|
4326
|
+
reset();
|
|
4327
|
+
}, [filters]);
|
|
4328
|
+
|
|
4329
|
+
useEffect(() => {
|
|
4330
|
+
reset();
|
|
4331
|
+
}, [view, searchString, data, sort]);
|
|
4332
|
+
|
|
4333
|
+
// Fetch new data when queries or page change
|
|
4334
|
+
useEffect(() => {
|
|
4335
|
+
getDataFromQuery();
|
|
4336
|
+
dataListenerUnsubscribe && Object.values(dataListenerUnsubscribe).map((u) => u());
|
|
4337
|
+
}, [page]);
|
|
4338
|
+
|
|
4339
|
+
const pageUp = () => {
|
|
4340
|
+
setPage((p) => ([p[0]+1, p[0]]));
|
|
4341
|
+
};
|
|
4342
|
+
|
|
4343
|
+
const pageDown = () => {
|
|
4344
|
+
setPage((p) => ([p[0]-1, p[0]]));
|
|
4345
|
+
};
|
|
4346
|
+
|
|
4347
|
+
const updateSort = (sortLabel: string) => {
|
|
4348
|
+
if (!sorts || !sorts[sortLabel]) return;
|
|
4349
|
+
setSort([sortLabel, sorts[sortLabel]]);
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
return ({...{tableData, pageUp, pageDown, setFilters, page: page[0], sorts, loading, sort, updateSort: updateSort, setView, updateSearch: setSearchString}});
|
|
4353
|
+
};
|