placementt-core 11.0.533 → 11.0.892

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