placementt-core 1.20.211 → 11.10.151

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 (130) hide show
  1. package/.eslintrc.js +40 -40
  2. package/.gitattributes +2 -2
  3. package/lib/config.d.ts +0 -1
  4. package/lib/constants.d.ts +6 -2
  5. package/lib/constants.js +136 -136
  6. package/lib/constants.js.map +1 -1
  7. package/lib/features/analytics/useAnalytics.d.ts +0 -1
  8. package/lib/features/analytics/useAnalytics.js +3 -4
  9. package/lib/features/analytics/useAnalytics.js.map +1 -1
  10. package/lib/features/config.d.ts +133 -133
  11. package/lib/features/config.js +35 -35
  12. package/lib/features/contacts/contacts.d.ts +75 -75
  13. package/lib/features/contacts/contacts.js +105 -105
  14. package/lib/features/contacts/contactsSlice.d.ts +5 -5
  15. package/lib/features/contacts/useContacts.js +1 -2
  16. package/lib/features/contacts/useContacts.js.map +1 -1
  17. package/lib/features/downtime/useDowntime.d.ts +11 -11
  18. package/lib/features/downtime/useDowntime.js +22 -22
  19. package/lib/features/dropdown/useDropdown.d.ts +2 -3
  20. package/lib/features/dropdown/useDropdown.js +1 -2
  21. package/lib/features/dropdown/useDropdown.js.map +1 -1
  22. package/lib/features/global/downtime/useDowntime.d.ts +0 -1
  23. package/lib/features/global/downtime/useDowntime.js +1 -2
  24. package/lib/features/global/downtime/useDowntime.js.map +1 -1
  25. package/lib/features/global/users/useUserFunctions.d.ts +0 -1
  26. package/lib/features/global/users/useUserFunctions.js +7 -8
  27. package/lib/features/global/users/useUserFunctions.js.map +1 -1
  28. package/lib/features/placements/studentPlacements/activePlacement.d.ts +1 -1
  29. package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.d.ts +2 -2
  30. package/lib/features/placements/studentPlacements/studentPlacementsSlice.d.ts +63 -63
  31. package/lib/features/placements/studentPlacements/studentPlacementsSlice.js +81 -81
  32. package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.d.ts +2 -2
  33. package/lib/features/placements/studentPlacements/useStudentPlacements.d.ts +0 -1
  34. package/lib/features/placements/studentPlacements/useStudentPlacements.js +1 -2
  35. package/lib/features/placements/studentPlacements/useStudentPlacements.js.map +1 -1
  36. package/lib/features/providerPlacements/providerPlacementsSlice.d.ts +19 -19
  37. package/lib/features/providerPlacements/providerPlacementsSlice.js +24 -24
  38. package/lib/features/referrals/useReferrals.d.ts +0 -1
  39. package/lib/features/referrals/useReferrals.js +1 -2
  40. package/lib/features/referrals/useReferrals.js.map +1 -1
  41. package/lib/features/studentPlacements/studentPlacementsSlice.d.ts +62 -62
  42. package/lib/features/studentPlacements/studentPlacementsSlice.js +87 -87
  43. package/lib/features/studentPlacements/useStudentPlacements.d.ts +6 -6
  44. package/lib/features/studentPlacements/useStudentPlacements.js +18 -18
  45. package/lib/features/updates/useUpdates.js +1 -2
  46. package/lib/features/updates/useUpdates.js.map +1 -1
  47. package/lib/features/userSlice.d.ts +26 -26
  48. package/lib/features/userSlice.js +23 -23
  49. package/lib/features/users/useUserFunctions.d.ts +25 -25
  50. package/lib/features/users/useUserFunctions.js +124 -124
  51. package/lib/features/users/userSlice.d.ts +46 -46
  52. package/lib/features/users/userSlice.js +48 -48
  53. package/lib/firebase/firebase.d.ts +3 -1
  54. package/lib/firebase/firebase.js +9 -3
  55. package/lib/firebase/firebase.js.map +1 -1
  56. package/lib/firebase/firebaseConfig.js +3 -0
  57. package/lib/firebase/firebaseConfig.js.map +1 -1
  58. package/lib/firebase/firebaseQuery.d.ts +3 -1
  59. package/lib/firebase/firebaseQuery.js +11 -1
  60. package/lib/firebase/firebaseQuery.js.map +1 -1
  61. package/lib/firebase/persistence.js +2 -2
  62. package/lib/firebase/persistence.js.map +1 -1
  63. package/lib/firebase/readDatabase.d.ts +8 -7
  64. package/lib/firebase/readDatabase.js +41 -8
  65. package/lib/firebase/readDatabase.js.map +1 -1
  66. package/lib/firebase/util.d.ts +3 -4
  67. package/lib/firebase/util.js +49 -4
  68. package/lib/firebase/util.js.map +1 -1
  69. package/lib/firebase/writeDatabase.d.ts +7 -3
  70. package/lib/firebase/writeDatabase.js +9 -2
  71. package/lib/firebase/writeDatabase.js.map +1 -1
  72. package/lib/hooks.d.ts +476 -20
  73. package/lib/hooks.js +1855 -237
  74. package/lib/hooks.js.map +1 -1
  75. package/lib/images/GatsbyBenchmarks.d.ts +0 -1
  76. package/lib/images/GatsbyBenchmarks.js +1 -1
  77. package/lib/images/GatsbyBenchmarks.js.map +1 -1
  78. package/lib/reduxHooks.d.ts +9 -2
  79. package/lib/reduxHooks.js +36 -9
  80. package/lib/reduxHooks.js.map +1 -1
  81. package/lib/tasksAndTips.d.ts +25 -5
  82. package/lib/tasksAndTips.js +517 -48
  83. package/lib/tasksAndTips.js.map +1 -1
  84. package/lib/typeDefinitions.d.ts +472 -55
  85. package/lib/util.d.ts +1 -0
  86. package/lib/util.js +85 -7
  87. package/lib/util.js.map +1 -1
  88. package/package.json +52 -49
  89. package/src/DatabaseDefinitions.ts +18 -18
  90. package/src/apiCalls.ts +128 -128
  91. package/src/config.ts +50 -50
  92. package/src/constants.ts +714 -707
  93. package/src/databaseTypes.ts +42 -42
  94. package/src/features/analytics/useAnalytics.tsx +64 -64
  95. package/src/features/contacts/contactsSlice.ts +147 -147
  96. package/src/features/contacts/useContacts.tsx +73 -73
  97. package/src/features/dropdown/useDropdown.tsx +52 -52
  98. package/src/features/global/downtime/useDowntime.tsx +23 -23
  99. package/src/features/global/users/useUserFunctions.tsx +132 -132
  100. package/src/features/jobs/jobsSlice.ts +65 -65
  101. package/src/features/placements/studentPlacements/activePlacement.ts +68 -68
  102. package/src/features/placements/studentPlacements/completedStudentPlacementsSlice.ts +97 -97
  103. package/src/features/placements/studentPlacements/upcomingStudentPlacementsSlice.ts +108 -108
  104. package/src/features/placements/studentPlacements/useStudentPlacements.tsx +9 -9
  105. package/src/features/placements/types.ts +10 -10
  106. package/src/features/referrals/useReferrals.tsx +56 -56
  107. package/src/features/updates/useUpdates.tsx +38 -38
  108. package/src/firebase/firebase.tsx +144 -138
  109. package/src/firebase/firebaseConfig.tsx +45 -42
  110. package/src/firebase/firebaseQuery.tsx +150 -140
  111. package/src/firebase/persistence.ts +84 -84
  112. package/src/firebase/readDatabase.tsx +235 -197
  113. package/src/firebase/util.tsx +352 -308
  114. package/src/firebase/writeDatabase.tsx +77 -68
  115. package/src/hooks.tsx +4029 -1928
  116. package/src/images/GatsbyBenchmarks.tsx +711 -711
  117. package/src/images/LogFuturePlacement.jsx +64 -64
  118. package/src/images/LogPreviousPlacement.jsx +228 -228
  119. package/src/images/gatsby_benchmarks.svg +466 -466
  120. package/src/images/log_future_placement.svg +114 -114
  121. package/src/images/log_previous_placement.svg +199 -199
  122. package/src/index.ts +34 -34
  123. package/src/readDatabase.tsx +3 -3
  124. package/src/reduxHooks.ts +231 -200
  125. package/src/tasksAndTips.ts +917 -410
  126. package/src/tutorialTips.ts +58 -58
  127. package/src/typeDefinitions.ts +893 -503
  128. package/src/util.ts +137 -47
  129. package/tsconfig.dev.json +5 -5
  130. package/tsconfig.json +21 -21
package/lib/hooks.js CHANGED
@@ -3,7 +3,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.useInstitutePlacementListingHandler = exports.useWorkflowEditor = exports.useUserUploadHandler = exports.useCreateCohortRenderer = exports.useRefDimensions = exports.useProposePlacementRenderer = exports.useLazyLoadQueryList = exports.useAdmissionsPaginator = exports.useCohortUserPaginator = exports.usePlacementListingPaginator = exports.useFilterTablePaginator = exports.useOldInstitutePlacementList = exports.useNewInstitutePlacementList = exports.useStudentPlacementList = void 0;
6
+ exports.useFirestoreQuery = exports.useRefDimensions = void 0;
7
+ exports.useStudentPlacementList = useStudentPlacementList;
8
+ exports.useNewInstitutePlacementList = useNewInstitutePlacementList;
9
+ exports.useOldInstitutePlacementList = useOldInstitutePlacementList;
10
+ exports.useFilterTablePaginator = useFilterTablePaginator;
11
+ exports.usePlacementListingPaginator = usePlacementListingPaginator;
12
+ exports.useCohortUserPaginator = useCohortUserPaginator;
13
+ exports.useAdmissionsPaginator = useAdmissionsPaginator;
14
+ exports.useLazyLoadQueryList = useLazyLoadQueryList;
15
+ exports.usePublicPlacementListingLoader = usePublicPlacementListingLoader;
16
+ exports.useCreateApplicationRenderer = useCreateApplicationRenderer;
17
+ exports.useProposePlacementRenderer = useProposePlacementRenderer;
18
+ exports.useCreateCohortRenderer = useCreateCohortRenderer;
19
+ exports.useUserUploadHandler = useUserUploadHandler;
20
+ exports.useWorkflowEditor = useWorkflowEditor;
21
+ exports.useApplicantWorkflowEditor = useApplicantWorkflowEditor;
22
+ exports.useInstitutePlacementListingHandler = useInstitutePlacementListingHandler;
23
+ exports.useGetIndividualPlacementForPlacementPage = useGetIndividualPlacementForPlacementPage;
24
+ exports.useOnboardingPopup = useOnboardingPopup;
25
+ exports.useLoadAddresses = useLoadAddresses;
26
+ exports.useLoadListings = useLoadListings;
27
+ exports.useLoadApplications = useLoadApplications;
7
28
  const firestore_1 = require("firebase/firestore");
8
29
  const react_1 = require("react");
9
30
  const constants_1 = require("./constants");
@@ -13,6 +34,8 @@ const firebaseQuery_1 = __importDefault(require("./firebase/firebaseQuery"));
13
34
  const readDatabase_1 = require("./firebase/readDatabase");
14
35
  const util_1 = require("./firebase/util");
15
36
  const writeDatabase_1 = require("./firebase/writeDatabase");
37
+ const algoliasearch_1 = __importDefault(require("algoliasearch"));
38
+ const storage_1 = require("firebase/storage");
16
39
  const DEFAULTQUERYLIMIT = 5;
17
40
  function useStudentPlacementList({ user, student, queryConstraint, ql = DEFAULTQUERYLIMIT }) {
18
41
  const [loadMoreIcon, setLoadMoreIcon] = (0, react_1.useState)(true);
@@ -101,21 +124,81 @@ function useStudentPlacementList({ user, student, queryConstraint, ql = DEFAULTQ
101
124
  };
102
125
  return ({ ...{ placements, loadMoreIcon, loadMorePlacements, setQuery, setInitialQueryLimit, reset, changeQueryConstraints } });
103
126
  }
104
- exports.useStudentPlacementList = useStudentPlacementList;
105
- function useNewInstitutePlacementList({ id, user, cohort, queryConstraint, ql = DEFAULTQUERYLIMIT }) {
127
+ function useNewInstitutePlacementList({ id, user, cohort, queryConstraint, ql = DEFAULTQUERYLIMIT, inProgress }) {
106
128
  const [loadMoreIcon, setLoadMoreIcon] = (0, react_1.useState)(true);
107
- const [query, setQuery] = (0, react_1.useState)("");
129
+ const [query, setQuery] = (0, react_1.useState)();
108
130
  const [initialQueryLimit, setInitialQueryLimit] = (0, react_1.useState)(ql);
109
131
  const [queryConstraints, setQueryConstraints] = (0, react_1.useState)(queryConstraint || []);
110
132
  const [oId, setOId] = (0, react_1.useState)();
111
133
  const [placements, setPlacements] = (0, react_1.useState)();
112
134
  const [startPlacementAfter, setStartPlacementAfter] = (0, react_1.useState)(); // uid, pId
135
+ const algoliaClient = (0, algoliasearch_1.default)(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
136
+ const placementsIndex = algoliaClient.initIndex("placements");
137
+ const usersIndex = algoliaClient.initIndex("users");
113
138
  (0, react_1.useEffect)(() => {
114
139
  if (user.product !== "institutes" || user.userType !== "Staff") {
115
140
  setOId(undefined);
116
141
  }
117
142
  setOId(user.oId);
118
143
  }, [user]);
144
+ (0, react_1.useEffect)(() => {
145
+ if (query === undefined)
146
+ return;
147
+ if (!query) {
148
+ reset();
149
+ loadMorePlacements([undefined, 0]);
150
+ }
151
+ const searchPlacements = async () => {
152
+ let placementsFound = {};
153
+ let userSearchString = `userType:Students AND status:active AND oId:${user.oId} AND product:${user.product}`;
154
+ if (cohort) {
155
+ userSearchString = userSearchString + ` AND cohort:${cohort}`;
156
+ }
157
+ if (user.product === "institutes" && user.userType === "Staff") {
158
+ const searchStudentHits = await usersIndex.search(query, {
159
+ filters: userSearchString
160
+ });
161
+ if (searchStudentHits) {
162
+ console.log("FOUND", searchStudentHits.hits.length, "students");
163
+ await Promise.all(searchStudentHits.hits.map(async (hit) => {
164
+ console.log("STUDENT", hit.objectID);
165
+ const constraints = [...(cohort ? [(0, firestore_1.where)("cohort", "==", cohort)] : [])];
166
+ if (inProgress !== undefined) {
167
+ constraints.push(inProgress ? (0, firestore_1.where)("inProgress", "==", true) : (0, firestore_1.where)("completed", "==", true));
168
+ }
169
+ const fPlacements = await (0, readDatabase_1.getPlacementsWhere)({ w: constraints, uid: hit.objectID, oId: hit.oId });
170
+ console.log("PLACEMENTS", fPlacements);
171
+ Object.entries(fPlacements).forEach(([k, v]) => {
172
+ placementsFound[k] = { ...v, student: hit };
173
+ });
174
+ }));
175
+ }
176
+ }
177
+ let placementSearchString = `oId:${user.oId} AND ` + (inProgress !== undefined ? (inProgress ? "inProgress:true" : "completed:true") : "");
178
+ if (cohort) {
179
+ placementSearchString = placementSearchString + ` AND cohort:${cohort}`;
180
+ }
181
+ const searchPlacementHits = await placementsIndex.search(query, {
182
+ filters: placementSearchString
183
+ });
184
+ const i = (searchPlacementHits ? (await Promise.all(searchPlacementHits.hits.map(async (hit) => {
185
+ const student = hit.uid === user.id ?
186
+ user : (await (0, readDatabase_1.getUserById)(hit.uid)
187
+ .catch(() => false));
188
+ if (!student)
189
+ return;
190
+ console.log("STUDENNT", hit.objectID, student);
191
+ const finalData = { ...hit, student: student };
192
+ return [hit.objectID, finalData];
193
+ }))) : []).filter((e) => e !== undefined);
194
+ console.log("FOUND", i.length, "placement");
195
+ placementsFound = { ...Object.fromEntries(i), ...placementsFound };
196
+ console.log("found", placementsFound);
197
+ setPlacements(placementsFound);
198
+ setLoadMoreIcon(false);
199
+ };
200
+ searchPlacements();
201
+ }, [query]);
119
202
  const reset = () => {
120
203
  setPlacements(undefined);
121
204
  setStartPlacementAfter([undefined, 0]);
@@ -127,10 +210,10 @@ function useNewInstitutePlacementList({ id, user, cohort, queryConstraint, ql =
127
210
  setPlacements(undefined);
128
211
  setStartPlacementAfter([undefined, 0]);
129
212
  loadMorePlacements([undefined, 0]);
130
- }, [query, queryConstraints]);
213
+ }, [queryConstraints]);
131
214
  const loadMorePlacements = async (fStartPlacementAfter = startPlacementAfter) => {
132
215
  var _a;
133
- if (user.viewCohorts === "none" || !oId) {
216
+ if (query || user.viewCohorts === "none" || !oId) {
134
217
  setLoadMoreIcon(false);
135
218
  return;
136
219
  }
@@ -144,7 +227,7 @@ function useNewInstitutePlacementList({ id, user, cohort, queryConstraint, ql =
144
227
  (0, firestore_1.startAfter)(fStartPlacementAfter[0])] :
145
228
  [(0, firestore_1.limit)(placements ? DEFAULTQUERYLIMIT : initialQueryLimit), ...(queryConstraintOrdered ? [] : [(0, firestore_1.orderBy)((0, firestore_1.documentId)())])];
146
229
  queryConstraints && constraints.unshift(...queryConstraints);
147
- query !== "" && constraints.unshift((0, firestore_1.where)("name", "==", query));
230
+ // query && constraints.unshift(where("name", "==", query));
148
231
  cohort && constraints.unshift((0, firestore_1.where)("cohort", "==", cohort));
149
232
  const placementsQuery = await (0, readDatabase_1.getPlacementsWhere)({ w: constraints, oId: oId, raw: true });
150
233
  const placementsWithStudentData = placementsQuery.empty ? [] : await Promise.all(placementsQuery.docs.map(async (placement) => {
@@ -178,7 +261,6 @@ function useNewInstitutePlacementList({ id, user, cohort, queryConstraint, ql =
178
261
  };
179
262
  return ({ ...{ placements, loadMoreIcon, loadMorePlacements, setQuery, setInitialQueryLimit, reset, changeQueryConstraints } });
180
263
  }
181
- exports.useNewInstitutePlacementList = useNewInstitutePlacementList;
182
264
  function useOldInstitutePlacementList({ user, cohort, queryConstraint, ql = DEFAULTQUERYLIMIT }) {
183
265
  const [loadMoreIcon, setLoadMoreIcon] = (0, react_1.useState)(true);
184
266
  const [query, setQuery] = (0, react_1.useState)("");
@@ -372,7 +454,6 @@ function useOldInstitutePlacementList({ user, cohort, queryConstraint, ql = DEFA
372
454
  };
373
455
  return ({ ...{ placements, loadMoreIcon, loadMorePlacements, setQuery, setInitialQueryLimit, reset, changeQueryConstraints } });
374
456
  }
375
- exports.useOldInstitutePlacementList = useOldInstitutePlacementList;
376
457
  function useFilterTablePaginator({ data }) {
377
458
  const [tableData, setTableData] = (0, react_1.useState)(Array.isArray(data) ? Object.fromEntries(Object.entries(data).slice(0, 10)) : {});
378
459
  const [page, setPage] = (0, react_1.useState)([1, 0]);
@@ -401,14 +482,7 @@ function useFilterTablePaginator({ data }) {
401
482
  filters && Object.entries(filters).forEach(([key, value]) => {
402
483
  constraints.push((0, firestore_1.where)(key, "==", value));
403
484
  });
404
- if (queryData.orderBy) {
405
- if (queryData.orderBy === "documentId") {
406
- constraints.push((0, firestore_1.orderBy)((0, firestore_1.documentId)()));
407
- }
408
- else {
409
- constraints.push((0, firestore_1.orderBy)(queryData.orderBy));
410
- }
411
- }
485
+ constraints.push((0, firestore_1.orderBy)(queryData.orderBy ? queryData.orderBy === "documentId" ? (0, firestore_1.documentId)() : queryData.orderBy : (0, firestore_1.documentId)()));
412
486
  if (page[0] > page[1] && !cursorDirection) { // Going up
413
487
  currentQueryAnchor.endKey && constraints.push((0, firestore_1.startAfter)(currentQueryAnchor.endKey));
414
488
  constraints.push((0, firestore_1.limit)(10));
@@ -505,6 +579,7 @@ function useFilterTablePaginator({ data }) {
505
579
  const listenForUpdates = () => {
506
580
  if (!Object.keys(itemList).length)
507
581
  return;
582
+ console.log("Fetching filter table updates");
508
583
  const itemListUpdateQuery = (0, firestore_1.query)((0, firestore_1.collection)(firebaseConfig_1.db, "users"), (0, firestore_1.where)((0, firestore_1.documentId)(), "in", Object.keys(itemList)));
509
584
  const itemUpdateSnapshot = (0, firestore_1.onSnapshot)(itemListUpdateQuery, (querySnapshot) => {
510
585
  querySnapshot.docs.forEach((doc) => {
@@ -535,9 +610,8 @@ function useFilterTablePaginator({ data }) {
535
610
  getDataFromQuery();
536
611
  dataListenerUnsubscribe && dataListenerUnsubscribe();
537
612
  }, [page]);
538
- return ({ ...{ tableData, setPage, setFilters } });
613
+ return ({ ...{ tableData, setPage, setFilters, page } });
539
614
  }
540
- exports.useFilterTablePaginator = useFilterTablePaginator;
541
615
  function usePlacementListingPaginator({ data, user }) {
542
616
  const [tableData, setTableData] = (0, react_1.useState)({});
543
617
  const [page, setPage] = (0, react_1.useState)([1, 0]);
@@ -553,16 +627,23 @@ function usePlacementListingPaginator({ data, user }) {
553
627
  else {
554
628
  cursorPos = currentQueryAnchor.startQueryPos;
555
629
  }
556
- const querySchema = data[cursorPos];
630
+ const querySchema = data === null || data === void 0 ? void 0 : data[cursorPos];
557
631
  const createQuery = (queryData) => {
558
- const constraints = [];
559
- queryData.where && queryData.where.forEach((w) => {
632
+ const constraints = [
633
+ (0, firestore_1.where)("savedById", "==", user.userType === "Staff" ? user.oId : user.id),
634
+ (0, firestore_1.where)("savedByProduct", "==", user.product),
635
+ (0, firestore_1.where)("savedByUserType", "==", user.userType === "Staff" ? "Organisation" : "Student"),
636
+ ];
637
+ if (user.userType === "Students") {
638
+ constraints.push((0, firestore_1.where)("listed", "==", true));
639
+ }
640
+ (queryData === null || queryData === void 0 ? void 0 : queryData.where) && queryData.where.forEach((w) => {
560
641
  constraints.push((0, firestore_1.where)(...w));
561
642
  });
562
643
  filters && Object.entries(filters).forEach(([key, value]) => {
563
644
  constraints.push((0, firestore_1.where)(key, "==", value));
564
645
  });
565
- if (queryData.orderBy) {
646
+ if (queryData === null || queryData === void 0 ? void 0 : queryData.orderBy) {
566
647
  if (queryData.orderBy === "documentId") {
567
648
  constraints.push((0, firestore_1.orderBy)((0, firestore_1.documentId)()));
568
649
  }
@@ -570,6 +651,9 @@ function usePlacementListingPaginator({ data, user }) {
570
651
  constraints.push((0, firestore_1.orderBy)(queryData.orderBy));
571
652
  }
572
653
  }
654
+ else {
655
+ constraints.push((0, firestore_1.orderBy)((0, firestore_1.documentId)()));
656
+ }
573
657
  if (page[0] > page[1] && !cursorDirection) { // Going up
574
658
  currentQueryAnchor.endKey && constraints.push((0, firestore_1.startAfter)(currentQueryAnchor.endKey));
575
659
  constraints.push((0, firestore_1.limit)(10));
@@ -601,7 +685,7 @@ function usePlacementListingPaginator({ data, user }) {
601
685
  };
602
686
  const constraints = createQuery(querySchema);
603
687
  // console.log(queryId, "constraints", constraints)
604
- const q = (0, firestore_1.query)((0, firestore_1.collection)(firebaseConfig_1.db, ...querySchema.path), ...(constraints));
688
+ const q = (0, firestore_1.query)((0, firestore_1.collection)(firebaseConfig_1.db, "savedPlacements"), ...(constraints));
605
689
  const queryResults = {};
606
690
  const queryData = await (0, firestore_1.getDocs)(q);
607
691
  // console.log("queryData.size", queryData.size)
@@ -665,17 +749,19 @@ function usePlacementListingPaginator({ data, user }) {
665
749
  }
666
750
  setQueryAnchor({ ...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0] });
667
751
  const finalData = Object.fromEntries(await Promise.all(Object.entries(itemList).map(async ([k, v]) => {
668
- const address = await firebaseQuery.getDocData(["addresses", v.addressId || ""]);
669
- const provider = await firebaseQuery.getDocData(["providers", v.providerId || ""]);
670
- const status = getPlacementStatus(v);
671
- return [k, { ...address, ...provider, ...v, email: v.providerEmail, status: status }];
752
+ const placement = await firebaseQuery.getDocData(["placementListings", v.placementId || ""]);
753
+ const address = await firebaseQuery.getDocData(["addresses", placement.addressId || ""]);
754
+ const provider = await firebaseQuery.getDocData(["providers", placement.providerId || ""]);
755
+ const status = getPlacementStatus(provider, placement, v);
756
+ return [k, { ...address, ...provider, ...placement, email: placement.providerEmail, status: status, savedPlacement: v }];
672
757
  })));
673
758
  setTableData(finalData);
674
759
  };
675
- const getPlacementStatus = (placement) => {
676
- var _a;
677
- if (placement.mapConsent === true)
678
- return "public";
760
+ const getPlacementStatus = (provider, placement, savedPlacement) => {
761
+ if (savedPlacement.status === "Blocked")
762
+ return "blocked";
763
+ if (savedPlacement.status === "Accepted")
764
+ return "active";
679
765
  if (user.product === "admin") {
680
766
  if (placement.mapConsent === undefined)
681
767
  return "notReviewed";
@@ -684,13 +770,7 @@ function usePlacementListingPaginator({ data, user }) {
684
770
  return "unknown";
685
771
  }
686
772
  ;
687
- if (!((_a = placement === null || placement === void 0 ? void 0 : placement.savedBy) === null || _a === void 0 ? void 0 : _a[user.oId].providerConsent))
688
- return "awaitingProviderApproval";
689
- if (placement.savedBy[user.oId].status === "approved")
690
- return "active";
691
- if (placement.savedBy[user.oId].status === "blocked")
692
- return "blocked";
693
- if (placement.savedBy[user.oId].status === "rejected")
773
+ if (!provider.insurance)
694
774
  return "awaitingProviderApproval";
695
775
  return "requiresApproval";
696
776
  };
@@ -706,10 +786,9 @@ function usePlacementListingPaginator({ data, user }) {
706
786
  (0, react_1.useEffect)(() => {
707
787
  getDataFromQuery();
708
788
  }, [page]);
709
- return ({ ...{ tableData, setPage, setFilters } });
789
+ return ({ ...{ tableData, setPage, setFilters, page } });
710
790
  }
711
- exports.usePlacementListingPaginator = usePlacementListingPaginator;
712
- function useCohortUserPaginator({ user, cohort, data }) {
791
+ function useCohortUserPaginator({ user, cohort, data, search, userType }) {
713
792
  const [tableData, setTableData] = (0, react_1.useState)({});
714
793
  const [queryAnchor, setQueryAnchor] = (0, react_1.useState)({ startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0 });
715
794
  const [filters, setFilters] = (0, react_1.useState)();
@@ -717,13 +796,19 @@ function useCohortUserPaginator({ user, cohort, data }) {
717
796
  const [page, setPage] = (0, react_1.useState)([1, 0]);
718
797
  const [dataListenerUnsubscribe, setDataListenerUnsubscribe] = (0, react_1.useState)();
719
798
  const [queries, setQueries] = (0, react_1.useState)();
799
+ const [prevSearch, setPrevSearch] = (0, react_1.useState)();
800
+ const algoliaClient = (0, algoliasearch_1.default)(process.env.NODE_ENV === "development" ? "A0ZB50I7VS" : "XMPXCMUUOJ", user.algoliaKey);
801
+ const usersIndex = algoliaClient.initIndex("users");
720
802
  (0, react_1.useEffect)(() => {
721
803
  var _a;
722
804
  if (user.userType !== "Staff") {
805
+ console.log("Not a staff member", user);
723
806
  setQueries(undefined);
724
807
  return;
725
808
  }
726
- if ((!user.viewCohorts && user.userGroup !== "admin") || user.viewCohorts === "none" || (user.viewCohorts === "some" && cohort !== "all" && !((_a = user.visibleCohorts) === null || _a === void 0 ? void 0 : _a.split(",").includes(cohort || "")))) {
809
+ if ((!user.viewCohorts && user.userGroup !== "admin") ||
810
+ user.viewCohorts === "none" ||
811
+ (user.viewCohorts === "some" && cohort !== "all" && !((_a = user.visibleCohorts) === null || _a === void 0 ? void 0 : _a.split(",").includes(cohort || "")))) {
727
812
  setQueries(undefined);
728
813
  return;
729
814
  }
@@ -754,9 +839,6 @@ function useCohortUserPaginator({ user, cohort, data }) {
754
839
  return finalConstraints;
755
840
  });
756
841
  }, []);
757
- (0, react_1.useEffect)(() => {
758
- console.log("TD", tableData);
759
- }, [tableData]);
760
842
  const getDataFromQuery = async (itemList = {}, currentQueryAnchor = queryAnchor, cursorDirection, prevEntries = prevEntryIds, loadMoreFromQuery = false) => {
761
843
  if (!queries) {
762
844
  setTableData({});
@@ -776,7 +858,12 @@ function useCohortUserPaginator({ user, cohort, data }) {
776
858
  console.log("mConstraints", mConstraints);
777
859
  const fConstraints = [...mConstraints];
778
860
  filters && Object.entries(filters).forEach(([key, value]) => {
779
- fConstraints.push((0, firestore_1.where)(key, "==", value));
861
+ if (typeof value === "object") {
862
+ fConstraints.push(value);
863
+ }
864
+ else {
865
+ fConstraints.push((0, firestore_1.where)(key, "==", value));
866
+ }
780
867
  });
781
868
  fConstraints.push((0, firestore_1.orderBy)((0, firestore_1.documentId)()));
782
869
  if (page[0] > page[1] && !cursorDirection) { // Going up
@@ -871,6 +958,7 @@ function useCohortUserPaginator({ user, cohort, data }) {
871
958
  const listenForUpdates = () => {
872
959
  if (!Object.keys(itemList).length)
873
960
  return;
961
+ console.log("Fetching cohort user data");
874
962
  const itemListUpdateQuery = (0, firestore_1.query)((0, firestore_1.collection)(firebaseConfig_1.db, "users"), (0, firestore_1.where)((0, firestore_1.documentId)(), "in", Object.keys(itemList)));
875
963
  const itemUpdateSnapshot = (0, firestore_1.onSnapshot)(itemListUpdateQuery, (querySnapshot) => {
876
964
  querySnapshot.docs.forEach((doc) => {
@@ -886,23 +974,57 @@ function useCohortUserPaginator({ user, cohort, data }) {
886
974
  setQueryAnchor({ ...currentQueryAnchor, startKey: Object.keys(itemList)[0], endKey: Object.keys(itemList).slice(-1)[0] });
887
975
  setTableData(itemList);
888
976
  };
977
+ const searchUsers = async () => {
978
+ if (!search)
979
+ return;
980
+ let userSearchString = `userType:${userType} AND oId:${user.oId} AND product:${user.product}`;
981
+ if (cohort && cohort !== "all") {
982
+ userSearchString = userSearchString + ` AND cohort:${cohort}`;
983
+ }
984
+ Object.entries(filters || {}).forEach(([field, value]) => {
985
+ userSearchString = userSearchString + ` AND ${field}:${value}`;
986
+ });
987
+ console.log("Going", page[0] > page[1] ? "up" : "down");
988
+ console.log("Start at", page[0] > page[1] ? page[1] : page[1] - 1);
989
+ const searchStudentHits = await usersIndex.search(search, {
990
+ filters: userSearchString,
991
+ length: 10,
992
+ offset: 10 * (page[0] > page[1] ? page[1] : (page[0] - 1))
993
+ });
994
+ const results = Object.fromEntries(await Promise.all(searchStudentHits.hits.map(async (hit) => {
995
+ return [hit.objectID, hit];
996
+ })));
997
+ setPrevSearch(search);
998
+ setTableData(results);
999
+ };
889
1000
  (0, react_1.useEffect)(() => {
890
- if (!filters)
1001
+ if (!filters && !prevSearch && !search)
891
1002
  return;
1003
+ if (search && prevSearch) {
1004
+ console.log("New search");
1005
+ setPrevSearch(search);
1006
+ return;
1007
+ }
1008
+ console.log("Setting page");
892
1009
  setPage([1, 0]);
1010
+ console.log("Set page");
893
1011
  setTableData({});
894
1012
  setQueryAnchor({ startKey: "", endKey: "", startQueryPos: 0, endQueryPos: 0 });
895
1013
  setPrevEntryIds({});
896
1014
  dataListenerUnsubscribe && dataListenerUnsubscribe();
897
- }, [filters]);
1015
+ }, [filters, search]);
898
1016
  // Fetch new data when queries or page change
899
1017
  (0, react_1.useEffect)(() => {
1018
+ if (search) {
1019
+ console.log("PAGE", page);
1020
+ searchUsers();
1021
+ return;
1022
+ }
900
1023
  getDataFromQuery();
901
1024
  dataListenerUnsubscribe && dataListenerUnsubscribe();
902
- }, [page]);
903
- return ({ ...{ tableData, setPage, setFilters } });
1025
+ }, [page, queries, prevSearch]);
1026
+ return ({ ...{ tableData, setPage, page, setFilters } });
904
1027
  }
905
- exports.useCohortUserPaginator = useCohortUserPaginator;
906
1028
  function useAdmissionsPaginator({ data }) {
907
1029
  const [tableData, setTableData] = (0, react_1.useState)({});
908
1030
  const [page, setPage] = (0, react_1.useState)([1, 0]);
@@ -916,8 +1038,7 @@ function useAdmissionsPaginator({ data }) {
916
1038
  }, [filters]);
917
1039
  return ({ ...{ tableData, setPage, setFilters } });
918
1040
  }
919
- exports.useAdmissionsPaginator = useAdmissionsPaginator;
920
- function useLazyLoadQueryList({ path, constraints, number }) {
1041
+ function useLazyLoadQueryList({ path, constraints, number, onItemFetched }) {
921
1042
  const [items, setItems] = (0, react_1.useState)({});
922
1043
  const [loadMoreIcon, setLoadMoreIcon] = (0, react_1.useState)(false);
923
1044
  const [lastItem, setLastItem] = (0, react_1.useState)();
@@ -927,6 +1048,7 @@ function useLazyLoadQueryList({ path, constraints, number }) {
927
1048
  setLastItem(undefined);
928
1049
  };
929
1050
  const loadMore = async () => {
1051
+ var _a;
930
1052
  setLoadMoreIcon(true);
931
1053
  let formattedConstraints = [];
932
1054
  if (constraints) {
@@ -936,8 +1058,73 @@ function useLazyLoadQueryList({ path, constraints, number }) {
936
1058
  formattedConstraints.push((0, firestore_1.startAfter)(lastItem));
937
1059
  }
938
1060
  const documents = await firebaseQuery.getDocsWhere(path, formattedConstraints, true);
1061
+ console.log("docs", documents);
1062
+ if (documents === null || documents === void 0 ? void 0 : documents.empty) {
1063
+ setLoadMoreIcon(false);
1064
+ return;
1065
+ }
1066
+ setLastItem(documents === null || documents === void 0 ? void 0 : documents.docs[((_a = documents === null || documents === void 0 ? void 0 : documents.docs) === null || _a === void 0 ? void 0 : _a.length) - 1]);
1067
+ const processedItems = Object.fromEntries(await Promise.all(Object.values((documents === null || documents === void 0 ? void 0 : documents.docs) || {}).map(async (doc) => {
1068
+ const itemObj = { ...doc.data(), id: doc.id, docPath: doc.ref };
1069
+ return [doc.id, onItemFetched ? await onItemFetched(itemObj) : itemObj];
1070
+ })));
1071
+ setItems((i) => ({ ...i, ...processedItems }));
1072
+ setLoadMoreIcon(false);
1073
+ };
1074
+ (0, react_1.useEffect)(() => {
1075
+ loadMore();
1076
+ }, []);
1077
+ return ({ ...{ items, loadMore, loadMoreIcon, reset } });
1078
+ }
1079
+ function usePublicPlacementListingLoader({ providerId, number = 5 }) {
1080
+ const [items, setItems] = (0, react_1.useState)({});
1081
+ const [loadMoreIcon, setLoadMoreIcon] = (0, react_1.useState)(false);
1082
+ const [lastItem, setLastItem] = (0, react_1.useState)();
1083
+ const firebaseQuery = new firebaseQuery_1.default();
1084
+ const reset = () => {
1085
+ setItems({});
1086
+ setLastItem(undefined);
1087
+ };
1088
+ const loadMore = async () => {
1089
+ setLoadMoreIcon(true);
1090
+ let formattedConstraints = [
1091
+ (0, firestore_1.where)("status", "==", "listed"),
1092
+ (0, firestore_1.limit)(number)
1093
+ ];
1094
+ if (providerId) {
1095
+ formattedConstraints.push((0, firestore_1.where)("providerId", "==", providerId));
1096
+ }
1097
+ if (lastItem) {
1098
+ formattedConstraints.push((0, firestore_1.startAfter)(lastItem));
1099
+ }
1100
+ const documents = await firebaseQuery.getDocsWhere("placementListings", formattedConstraints, true);
1101
+ console.log("docs", documents.docs);
939
1102
  setLastItem(documents.docs[documents.docs.length - 1]);
940
- setItems((i) => ({ ...i, ...(Object.fromEntries(Object.entries(documents.docs).map(([, doc]) => [doc.id, { ...doc.data(), docPath: doc.ref }]))) }));
1103
+ const processedItems = Object.fromEntries(await Promise.all(Object.values(documents.docs).map(async (doc) => {
1104
+ var _a, _b;
1105
+ let itemObj = { ...doc.data(), id: doc.id };
1106
+ if (itemObj.addressId) {
1107
+ const address = await firebaseQuery.getDocData(["addresses", itemObj.addressId]);
1108
+ delete address.id;
1109
+ itemObj = { ...address, ...itemObj };
1110
+ }
1111
+ if (itemObj.applicantWorkflowId) {
1112
+ const applicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", itemObj.applicantWorkflowId])).workflow.filter((i) => i.id === 1)[0];
1113
+ const applicantFiles = applicantWorkflow.files ? Object.fromEntries(await Promise.all((_a = applicantWorkflow.files) === null || _a === void 0 ? void 0 : _a.map(async (fileId) => {
1114
+ const file = await firebaseQuery.getDocData(["files", fileId]);
1115
+ file.url = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `providers/${itemObj.providerId}/${file.fileName}`));
1116
+ return [fileId, file];
1117
+ }))) : [];
1118
+ const applicantForms = applicantWorkflow.forms ? Object.fromEntries(await Promise.all((_b = applicantWorkflow.forms) === null || _b === void 0 ? void 0 : _b.map(async (formId) => {
1119
+ return [formId, await firebaseQuery.getDocData(["forms", formId])];
1120
+ }))) : [];
1121
+ applicantWorkflow.viewableFiles = applicantFiles;
1122
+ applicantWorkflow.formDetails = applicantForms;
1123
+ itemObj = { ...itemObj, applicantWorkflow: [applicantWorkflow] };
1124
+ }
1125
+ return [doc.id, itemObj];
1126
+ })));
1127
+ setItems((i) => ({ ...i, ...processedItems }));
941
1128
  setLoadMoreIcon(false);
942
1129
  };
943
1130
  (0, react_1.useEffect)(() => {
@@ -945,7 +1132,338 @@ function useLazyLoadQueryList({ path, constraints, number }) {
945
1132
  }, []);
946
1133
  return ({ ...{ items, loadMore, loadMoreIcon, reset } });
947
1134
  }
948
- exports.useLazyLoadQueryList = useLazyLoadQueryList;
1135
+ function useCreateApplicationRenderer({ user, listingId, listing, provider, application, applicationId, orgContext }) {
1136
+ const firebaseQuery = new firebaseQuery_1.default();
1137
+ let applicationWithoutAdditionalData = { ...(application || {}) };
1138
+ delete applicationWithoutAdditionalData.listing;
1139
+ delete applicationWithoutAdditionalData.address;
1140
+ delete applicationWithoutAdditionalData.provider;
1141
+ const [fApplication, setFApplication] = (0, react_1.useState)(application ? applicationWithoutAdditionalData : {
1142
+ uid: user.userType === "Students" ? user.id : undefined,
1143
+ listingId: listingId,
1144
+ addressId: listing === null || listing === void 0 ? void 0 : listing.addressId,
1145
+ stage: 1,
1146
+ reqUserType: "Students",
1147
+ status: "draft"
1148
+ });
1149
+ const [fApplicationId, setFApplicationId] = (0, react_1.useState)(applicationId);
1150
+ const [draftSaved, setDraftSaved] = (0, react_1.useState)(false);
1151
+ const [fProvider, setFProvider] = (0, react_1.useState)(provider);
1152
+ const [fListing, setFListing] = (0, react_1.useState)(Object.keys(listing || {}).length > 5 ? listing : undefined);
1153
+ const [student, setStudent] = (0, react_1.useState)(user.userType === "Students" ? user : undefined);
1154
+ const [profileUrl, setProfileUrl] = (0, react_1.useState)();
1155
+ const [successPopup, setSuccessPopup] = (0, react_1.useState)();
1156
+ const [uploadedFiles, setUploadedFiles] = (0, react_1.useState)();
1157
+ const [currentStageComplete, setCurrentStageComplete] = (0, react_1.useState)();
1158
+ (0, react_1.useEffect)(() => {
1159
+ const getListing = async () => {
1160
+ console.log("Checking ID");
1161
+ if (!listingId)
1162
+ return;
1163
+ console.log("Getting listing");
1164
+ console.log("LISTING PARAM", listing, Object.keys(listing || {}).length > 5);
1165
+ const listingData = (Object.keys(listing || {}).length > 5) ? listing : await firebaseQuery.getDocData(["placementListings", listingId]).catch(() => false);
1166
+ console.log("LISTINGDATA", listingData, Object.keys(listing || {}).length > 5 ? { a: "string" } : "AAA");
1167
+ const address = listingData ? (user.product === "providers" && orgContext) ? orgContext.addresses[listingData.addressId || ""] : await firebaseQuery.getDocData(["addresses", listingData.addressId]) : undefined;
1168
+ const workflow = listingData ? (user.product === "providers" && orgContext) ? orgContext.applicantWorkflows[listingData.applicantWorkflowId || ""] : await firebaseQuery.getDocData(["applicantWorkflows", listingData.applicantWorkflowId]) : undefined;
1169
+ if (workflow && listingData) {
1170
+ workflow.workflow = await Promise.all(workflow.workflow.map(async (s) => {
1171
+ var _a, _b;
1172
+ const applicantFiles = s.files ? Object.fromEntries(await Promise.all((_a = s.files) === null || _a === void 0 ? void 0 : _a.map(async (fileId) => {
1173
+ const file = await firebaseQuery.getDocData(["files", fileId]);
1174
+ file.url = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `providers/${listingData === null || listingData === void 0 ? void 0 : listingData.providerId}/${file.fileName}`));
1175
+ return [fileId, file];
1176
+ }))) : [];
1177
+ const applicantForms = s.forms ? Object.fromEntries(await Promise.all((_b = s.forms) === null || _b === void 0 ? void 0 : _b.map(async (formId) => {
1178
+ return [formId, await firebaseQuery.getDocData(["forms", formId])];
1179
+ }))) : [];
1180
+ return { ...s, viewableFiles: applicantFiles, formDetails: applicantForms };
1181
+ }));
1182
+ delete workflow.id;
1183
+ }
1184
+ if (address) {
1185
+ delete address.id;
1186
+ }
1187
+ console.log("Setting listing");
1188
+ setFListing((listingData && workflow) ? { ...listingData, ...address, applicantWorkflow: workflow.workflow } : false);
1189
+ if (((fProvider === null || fProvider === void 0 ? void 0 : fProvider.id) === (application === null || application === void 0 ? void 0 : application.providerId)) && user.product === "providers") {
1190
+ setFProvider({ details: orgContext === null || orgContext === void 0 ? void 0 : orgContext.details, id: user.oId });
1191
+ }
1192
+ else if (listingData && (listingData === null || listingData === void 0 ? void 0 : listingData.providerId)) {
1193
+ console.log("Getting provider from DB");
1194
+ const provider = await firebaseQuery.getDocData(["providers", listingData.providerId]);
1195
+ console.log("Provider", provider);
1196
+ setFProvider({ details: provider, id: listingData.providerId });
1197
+ }
1198
+ };
1199
+ getListing();
1200
+ }, [listingId, listing]);
1201
+ (0, react_1.useEffect)(() => {
1202
+ if ((student === null || student === void 0 ? void 0 : student.id) !== (application === null || application === void 0 ? void 0 : application.uid)) {
1203
+ if (user.userType === "Students") {
1204
+ setStudent(user);
1205
+ }
1206
+ else if (user.product === "providers" && (application === null || application === void 0 ? void 0 : application.uid)) {
1207
+ firebaseQuery.getDocData(["users", application.uid]).then((s) => setStudent(s));
1208
+ }
1209
+ }
1210
+ }, []);
1211
+ (0, react_1.useEffect)(() => {
1212
+ setFListing(Object.keys(listing || {}).length > 5 ? listing : undefined);
1213
+ }, [listing]);
1214
+ (0, react_1.useEffect)(() => {
1215
+ if (provider === null || provider === void 0 ? void 0 : provider.profile)
1216
+ return;
1217
+ if (!(provider === null || provider === void 0 ? void 0 : provider.id))
1218
+ return;
1219
+ (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `providers/${provider === null || provider === void 0 ? void 0 : provider.id}/profilePic.png`)).then(setProfileUrl).catch(() => null);
1220
+ }, [provider]);
1221
+ (0, react_1.useEffect)(() => {
1222
+ console.log("APPLICATIONID", applicationId);
1223
+ if (!applicationId) {
1224
+ if (!listingId)
1225
+ return;
1226
+ firebaseQuery.getDocsWhere("applications", [(0, firestore_1.where)("status", "not-in", ["approved", "declined"]), (0, firestore_1.where)("uid", "==", user.id), (0, firestore_1.where)("listingId", "==", listingId)]).then((existingApplication) => {
1227
+ console.log("EXISTING", existingApplication);
1228
+ // get uploaded files
1229
+ if (existingApplication && Object.keys(existingApplication).length) {
1230
+ setFApplication(Object.values(existingApplication)[0]);
1231
+ setFApplicationId(Object.keys(existingApplication)[0]);
1232
+ }
1233
+ });
1234
+ return;
1235
+ }
1236
+ if (applicationId) {
1237
+ setFApplicationId(applicationId);
1238
+ if (application) {
1239
+ let applicationWithoutAdditionalData = { ...(application || {}) };
1240
+ delete applicationWithoutAdditionalData.listing;
1241
+ delete applicationWithoutAdditionalData.address;
1242
+ delete applicationWithoutAdditionalData.provider;
1243
+ setFApplication(applicationWithoutAdditionalData);
1244
+ }
1245
+ else {
1246
+ firebaseQuery.getDocData(["applications", applicationId]).then(setFApplication);
1247
+ }
1248
+ }
1249
+ }, [application, applicationId]);
1250
+ const getCurrentStage = async (stage) => {
1251
+ var _a, _b, _c, _d;
1252
+ console.log("fLSITING CURRENT STAGE", fListing);
1253
+ if (!fListing)
1254
+ throw new Error("Listing deleted");
1255
+ if (!(fListing === null || fListing === void 0 ? void 0 : fListing.applicantWorkflowId))
1256
+ throw new Error("No workflow stage");
1257
+ const mApplicantWorkflow = await firebaseQuery.getDocData(["applicantWorkflows", fListing.applicantWorkflowId]);
1258
+ const stageObj = (_a = mApplicantWorkflow === null || mApplicantWorkflow === void 0 ? void 0 : mApplicantWorkflow.workflow) === null || _a === void 0 ? void 0 : _a.find((s) => s.id === stage);
1259
+ if (!stageObj)
1260
+ throw new Error("Can't find stage.");
1261
+ const applicantFiles = stageObj.files ? Object.fromEntries(await Promise.all((_b = stageObj.files) === null || _b === void 0 ? void 0 : _b.map(async (fileId) => {
1262
+ const file = await firebaseQuery.getDocData(["files", fileId]);
1263
+ file.url = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `providers/${mApplicantWorkflow === null || mApplicantWorkflow === void 0 ? void 0 : mApplicantWorkflow.oId}/${file.fileName}`));
1264
+ return [fileId, file];
1265
+ }))) : [];
1266
+ const applicantForms = stageObj.forms ? Object.fromEntries(await Promise.all((_c = stageObj.forms) === null || _c === void 0 ? void 0 : _c.map(async (formId) => {
1267
+ return [formId, await firebaseQuery.getDocData(["forms", formId])];
1268
+ }))) : [];
1269
+ stageObj.viewableFiles = applicantFiles;
1270
+ stageObj.formDetails = applicantForms;
1271
+ return { stage: stageObj, completedSections: ((_d = fApplication === null || fApplication === void 0 ? void 0 : fApplication.completedSections) === null || _d === void 0 ? void 0 : _d[stage]) || {} };
1272
+ };
1273
+ (0, react_1.useEffect)(() => {
1274
+ const getUploadedFiles = async () => {
1275
+ if (!fApplication.completedSections) {
1276
+ setUploadedFiles({});
1277
+ return;
1278
+ }
1279
+ ;
1280
+ const fileIds = Object.values(fApplication.completedSections)
1281
+ .flatMap(stageData => Object.values(stageData.filesUploaded || {}).flatMap(fileIds => fileIds));
1282
+ const fileDataPromises = fileIds.map(async (fileId) => {
1283
+ const fileData = await firebaseQuery.getDocData(["files", fileId]);
1284
+ fileData.url = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `userFiles/${fileData.fileName}`));
1285
+ return [fileId, fileData];
1286
+ });
1287
+ const fileDataArray = await Promise.all(fileDataPromises);
1288
+ const fileDataObj = Object.fromEntries(fileDataArray);
1289
+ setUploadedFiles(fileDataObj);
1290
+ };
1291
+ const addApplication = async () => {
1292
+ console.log("ADDING APPLICATION");
1293
+ if (!fListing || !(fListing === null || fListing === void 0 ? void 0 : fListing.id) || !(fProvider === null || fProvider === void 0 ? void 0 : fProvider.id) || !(student === null || student === void 0 ? void 0 : student.id))
1294
+ return;
1295
+ const applicationData = {
1296
+ uid: student.id,
1297
+ listingId: fListing === null || fListing === void 0 ? void 0 : fListing.id,
1298
+ addressId: fListing.addressId,
1299
+ applicantWorkflowId: fListing === null || fListing === void 0 ? void 0 : fListing.applicantWorkflowId,
1300
+ providerId: fProvider.id,
1301
+ stage: 1,
1302
+ status: "draft",
1303
+ created: (new Date()).toISOString(),
1304
+ ...fApplication,
1305
+ };
1306
+ const mApplicationId = (await firebaseQuery.add(["applications"], applicationData)).id;
1307
+ console.log("APPLICATION ADDED");
1308
+ setFApplicationId(mApplicationId);
1309
+ setFApplication(applicationData);
1310
+ setDraftSaved(true);
1311
+ return;
1312
+ };
1313
+ getUploadedFiles();
1314
+ console.log("Checking IDs");
1315
+ if (!fListing || !(fListing === null || fListing === void 0 ? void 0 : fListing.id) || !(fProvider === null || fProvider === void 0 ? void 0 : fProvider.id) || !(student === null || student === void 0 ? void 0 : student.id))
1316
+ return;
1317
+ if (user.product === "providers")
1318
+ return;
1319
+ console.log("Checking dates and sections");
1320
+ if (!fApplication.completedSections && !fApplication.startDate && !fApplication.endDate)
1321
+ return;
1322
+ console.log("Application updated");
1323
+ if (!fApplicationId && !applicationId) {
1324
+ // save new
1325
+ console.log("Add application");
1326
+ addApplication();
1327
+ return;
1328
+ }
1329
+ // update
1330
+ firebaseQuery.update(["applications", (fApplicationId || applicationId)], fApplication);
1331
+ setDraftSaved(true);
1332
+ }, [fApplication]);
1333
+ const openSuccessPopup = (type) => {
1334
+ setSuccessPopup(type);
1335
+ setTimeout(() => {
1336
+ // onClose();
1337
+ }, 1500);
1338
+ };
1339
+ (0, react_1.useEffect)(() => {
1340
+ const areStagesCompleted = async () => {
1341
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1342
+ if (!fListing)
1343
+ return;
1344
+ if (fApplication.stage === undefined)
1345
+ return undefined;
1346
+ const currentStage = await getCurrentStage(fApplication.stage);
1347
+ console.log("Checking current stage is complete");
1348
+ for (const fileViewed of ((_a = currentStage.stage) === null || _a === void 0 ? void 0 : _a.files) || []) {
1349
+ console.log("Checking file viewed", fileViewed, "in", (_b = currentStage === null || currentStage === void 0 ? void 0 : currentStage.completedSections) === null || _b === void 0 ? void 0 : _b.filesViewed);
1350
+ if (!((_d = (_c = currentStage === null || currentStage === void 0 ? void 0 : currentStage.completedSections) === null || _c === void 0 ? void 0 : _c.filesViewed) === null || _d === void 0 ? void 0 : _d.includes(fileViewed))) {
1351
+ console.log("File not viewed");
1352
+ return false;
1353
+ }
1354
+ else {
1355
+ console.log("File viewed");
1356
+ }
1357
+ }
1358
+ for (const formCompleted of ((_e = currentStage === null || currentStage === void 0 ? void 0 : currentStage.stage) === null || _e === void 0 ? void 0 : _e.forms) || []) {
1359
+ console.log("Checking form completed", formCompleted, "in", (_f = currentStage === null || currentStage === void 0 ? void 0 : currentStage.completedSections) === null || _f === void 0 ? void 0 : _f.formsCompleted);
1360
+ if (!Object.keys(((_g = currentStage === null || currentStage === void 0 ? void 0 : currentStage.completedSections) === null || _g === void 0 ? void 0 : _g.formsCompleted) || {}).includes(formCompleted)) {
1361
+ console.log("Form not completed");
1362
+ return false;
1363
+ }
1364
+ else {
1365
+ console.log("Form completed");
1366
+ }
1367
+ }
1368
+ for (let i = 0; i++; i < (((_h = currentStage === null || currentStage === void 0 ? void 0 : currentStage.stage) === null || _h === void 0 ? void 0 : _h.requiredFiles) || []).length) {
1369
+ if (!Object.keys(((_j = currentStage === null || currentStage === void 0 ? void 0 : currentStage.completedSections) === null || _j === void 0 ? void 0 : _j.filesUploaded) || {}).includes(i.toString())) {
1370
+ console.log("Form not uploaded");
1371
+ return false;
1372
+ }
1373
+ else {
1374
+ console.log("Form uploaded");
1375
+ }
1376
+ }
1377
+ return true;
1378
+ };
1379
+ areStagesCompleted().then(setCurrentStageComplete);
1380
+ }, [fApplication, fListing]);
1381
+ const viewFile = (file, onOpen) => {
1382
+ var _a, _b;
1383
+ if (fApplication.reqUserType !== user.userType)
1384
+ return;
1385
+ if (fApplication.stage === undefined)
1386
+ throw new Error("Missing applciation stage.");
1387
+ if (!fListing)
1388
+ throw new Error("No associated listing.");
1389
+ const viewableFiles = (_b = (_a = fListing === null || fListing === void 0 ? void 0 : fListing.applicantWorkflow) === null || _a === void 0 ? void 0 : _a.find((stage) => stage.id === fApplication.stage)) === null || _b === void 0 ? void 0 : _b.viewableFiles;
1390
+ setFApplication((a) => {
1391
+ var _a, _b;
1392
+ const oldA = { ...a };
1393
+ const viewedFiles = ((_b = (_a = a.completedSections) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.filesViewed) || [];
1394
+ if (viewedFiles === null || viewedFiles === void 0 ? void 0 : viewedFiles.includes(file))
1395
+ return a;
1396
+ (viewableFiles === null || viewableFiles === void 0 ? void 0 : viewableFiles[file].url) && onOpen(viewableFiles === null || viewableFiles === void 0 ? void 0 : viewableFiles[file].url);
1397
+ viewedFiles === null || viewedFiles === void 0 ? void 0 : viewedFiles.push(file);
1398
+ const newA = (0, util_1.editNestedObject)(["completedSections", 1, "filesViewed"], oldA, viewedFiles);
1399
+ return newA;
1400
+ });
1401
+ };
1402
+ const addFile = (files, fileId) => {
1403
+ if (fApplication.reqUserType !== user.userType)
1404
+ throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
1405
+ if (fApplication.stage === undefined)
1406
+ throw new Error("Missing applciation stage.");
1407
+ if (!files.length)
1408
+ throw new Error("No files to upload");
1409
+ setFApplication((a) => (0, util_1.editNestedObject)(["completedSections", fApplication.stage, "filesUploaded", fileId], a, files));
1410
+ };
1411
+ const setFormComplete = (formId, e) => {
1412
+ if (fApplication.reqUserType !== user.userType)
1413
+ throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
1414
+ if (fApplication.stage === undefined)
1415
+ throw new Error("Missing applciation stage.");
1416
+ setFApplication((a) => (0, util_1.editNestedObject)(["completedSections", fApplication.stage, "formsCompleted", formId], a, e));
1417
+ };
1418
+ const successText = {
1419
+ submitted: {
1420
+ title: "Application submitted",
1421
+ desc: "We've sent your application to the placement provider. You will hear what the next steps are soon.",
1422
+ },
1423
+ draftSaved: {
1424
+ title: "Draft saved",
1425
+ desc: "Your draft has been saved. Go to the 'Applications' section from your home screen to edit.",
1426
+ },
1427
+ stageComplete: {
1428
+ title: "Stage complete",
1429
+ desc: "We have sent this to the next stage in the application process. We will update you with any progress.",
1430
+ },
1431
+ outcome: {
1432
+ title: "Outcome submitted",
1433
+ desc: "We have sent the student an email with the outcome of their application.",
1434
+ },
1435
+ };
1436
+ const onFApply = async (draft) => {
1437
+ if (draft) {
1438
+ openSuccessPopup("draftSaved");
1439
+ return;
1440
+ }
1441
+ if (!fApplicationId)
1442
+ return;
1443
+ // Check all items have been filled in.
1444
+ if (!fApplication.startDate || !fApplication.endDate)
1445
+ throw new Error("Please select dates for your placement.");
1446
+ await (0, firebase_1.executeCallable)("applications-submit", { applicationId: fApplicationId });
1447
+ const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]);
1448
+ setFApplication(newApplication);
1449
+ openSuccessPopup("submitted");
1450
+ };
1451
+ const progressStage = async (type, e) => {
1452
+ // Check all stages completed.
1453
+ if (!fApplicationId)
1454
+ return;
1455
+ if (!currentStageComplete)
1456
+ throw new Error("Complete all forms before submitting.");
1457
+ if (fApplication.reqUserType !== user.userType)
1458
+ throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
1459
+ if (fApplication.stage === undefined)
1460
+ throw new Error("Missing applciation stage.");
1461
+ await (0, firebase_1.executeCallable)("applications-changeStage", { applicationId: fApplicationId, type: type, feedback: e === null || e === void 0 ? void 0 : e.feedback });
1462
+ const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]);
1463
+ setFApplication(newApplication);
1464
+ };
1465
+ return { successText, onFApply, progressStage, currentStageComplete, uploadedFiles, getCurrentStage, setFormComplete, viewFile, addFile, setSuccessPopup, openSuccessPopup, setFApplication, fApplication, draftSaved, profileUrl, successPopup, fApplicationId, fListing, student, fProvider };
1466
+ }
949
1467
  function useProposePlacementRenderer({ user, orgContext, placement }) {
950
1468
  const [businessSectionComplete, setBusinessSectionComplete] = (0, react_1.useState)(false);
951
1469
  const [addressSectionComplete, setAddressSectionComplete] = (0, react_1.useState)(false);
@@ -1038,6 +1556,18 @@ function useProposePlacementRenderer({ user, orgContext, placement }) {
1038
1556
  console.log("Error:", error);
1039
1557
  throw error;
1040
1558
  }
1559
+ /*
1560
+ console.log("STUDENTID", student.id);
1561
+
1562
+ return await addPlacement(formData, student.id, draft).catch((e) => {
1563
+ console.log("error");
1564
+ console.log(e);
1565
+ throw e;
1566
+ }).then((e) => {
1567
+ setComplete(true);
1568
+ return e;
1569
+ });
1570
+ */
1041
1571
  };
1042
1572
  const deletePlacement = async (id) => {
1043
1573
  return await firebaseQuery.delete(["placements", id]);
@@ -1047,7 +1577,6 @@ function useProposePlacementRenderer({ user, orgContext, placement }) {
1047
1577
  setBusinessSectionComplete, setAddressSectionComplete, setPlacementSectionComplete,
1048
1578
  proposePlacement, deletePlacement, setStudent, submitSection, setFormData, resetFormData, setStage, setComplete } });
1049
1579
  }
1050
- exports.useProposePlacementRenderer = useProposePlacementRenderer;
1051
1580
  const useRefDimensions = () => {
1052
1581
  const [dimensions, setDimensions] = (0, react_1.useState)({ width: 1, height: 2 });
1053
1582
  const refCallback = (0, react_1.useCallback)((node) => {
@@ -1063,10 +1592,10 @@ const useRefDimensions = () => {
1063
1592
  return { dimensions, refCallback };
1064
1593
  };
1065
1594
  exports.useRefDimensions = useRefDimensions;
1066
- const cohortStages = ["name", "placementType", "students", "workflow", "review", "created"];
1595
+ const cohortStages = ["info", "name", "placementType", "review", "created"];
1067
1596
  const defaultCohortData = {
1068
1597
  name: "",
1069
- stage: "name",
1598
+ stage: "info",
1070
1599
  startSubmission: "",
1071
1600
  endSubmission: "",
1072
1601
  startPlacements: "",
@@ -1091,7 +1620,7 @@ function useCreateCohortRenderer({ oId, product, initialData = defaultCohortData
1091
1620
  }
1092
1621
  };
1093
1622
  (0, react_1.useEffect)(() => {
1094
- if (!Object.keys(cohortData).length || (0, util_1.objectsEqual)(initialData, cohortData))
1623
+ if (!Object.keys(cohortData).length || cohortData.stage === "name" || (0, util_1.objectsEqual)(initialData, cohortData))
1095
1624
  return;
1096
1625
  if (cohortId) {
1097
1626
  console.log("update", cohortId, cohortData);
@@ -1118,7 +1647,6 @@ function useCreateCohortRenderer({ oId, product, initialData = defaultCohortData
1118
1647
  };
1119
1648
  return ({ ...{ submitCohort, submitSection, back, setCohortData, deleteCohort, cohortData, cohortId } });
1120
1649
  }
1121
- exports.useCreateCohortRenderer = useCreateCohortRenderer;
1122
1650
  function useUserUploadHandler({ product, oId, userType, user, onComplete, userGroupId, cohortId }) {
1123
1651
  const [emptyCellsWarning, setEmptyCellsWarning] = (0, react_1.useState)(false);
1124
1652
  const [alert, setAlert] = (0, react_1.useState)();
@@ -1133,13 +1661,20 @@ function useUserUploadHandler({ product, oId, userType, user, onComplete, userGr
1133
1661
  if (!Object.entries(userData)) {
1134
1662
  return [];
1135
1663
  }
1136
- const uniqueValues = new Set(userData.map((v) => v.email));
1664
+ const emailDuplicateLookup = userData.reduce((acc, e, index) => {
1665
+ if (!e.email)
1666
+ return acc;
1667
+ acc[e.email] = [...(acc[e.email] || []), index];
1668
+ return acc;
1669
+ }, {});
1670
+ const emailDuplicates = Object.entries(emailDuplicateLookup).filter(([_, ids]) => ids.length > 1).map(([email]) => email);
1137
1671
  if (userData.filter((u) => u.email).length < userData.length) {
1138
1672
  setAlert({ msg: "Your data contains missing email addresses.", severity: "error" });
1139
1673
  return false;
1140
1674
  }
1141
- if (uniqueValues.size < userData.length) {
1142
- setAlert({ msg: "Your data contains multiple rows with the same email address. Please remove these and reupload.", severity: "error" });
1675
+ console.log("emailDuplicates", emailDuplicates);
1676
+ if (emailDuplicates.length) {
1677
+ setAlert({ msg: `Your data contains multiple rows with the same email address. Please remove these and reupload.\n\n Duplicate emails: ${emailDuplicates.join(", ")}`, severity: "error" });
1143
1678
  return false;
1144
1679
  }
1145
1680
  let emptyOptionalCell = false;
@@ -1216,193 +1751,113 @@ function useUserUploadHandler({ product, oId, userType, user, onComplete, userGr
1216
1751
  };
1217
1752
  return ({ ...{ uploadUsers, alert, onChange } });
1218
1753
  }
1219
- exports.useUserUploadHandler = useUserUploadHandler;
1220
- function useWorkflowEditor({ user, initialData, onSubmit, cohortId, product, oId }) {
1221
- const [filePopupActive, setFilePopupActive] = (0, react_1.useState)(false);
1222
- const [files, setFiles] = (0, react_1.useState)([]);
1223
- const [workflowNodes, setWorkflowNodes] = (0, react_1.useState)([]); // Workflow data
1224
- const [arrows, setArrows] = (0, react_1.useState)([]);
1225
- const [includedForms, setIncludedForms] = (0, react_1.useState)([]);
1226
- const [includedFiles, setIncludedFiles] = (0, react_1.useState)([]);
1227
- const [error, setError] = (0, react_1.useState)();
1228
- const [snackbar, setSnackbar] = (0, react_1.useState)();
1229
- const [newEdgePoint, setNewEdgePoint] = (0, react_1.useState)();
1230
- const [mousePos, setMousePos] = (0, react_1.useState)();
1231
- const [tutorialActive, setTutorialActive] = (0, react_1.useState)(false);
1232
- const containerRef = (0, react_1.useRef)(null);
1233
- let ticking = false;
1234
- const userProduct = user.product === "admin" ? product : user.product;
1235
- const userOId = user.product === "admin" ? oId : user.oId;
1236
- (0, react_1.useEffect)(() => {
1237
- let newData = initialData;
1238
- if (initialData === undefined || initialData.length === 0) {
1239
- newData = constants_1.defaultInstituteWorkflow;
1240
- setTimeout(() => {
1241
- setTutorialActive(false);
1242
- }, 1000);
1243
- }
1244
- (0, readDatabase_1.getFiles)(`${userProduct}/${userOId}/files`).then(setFiles);
1245
- setWorkflowNodes(newData);
1246
- console.log("new data", newData);
1247
- }, [initialData]);
1248
- (0, react_1.useEffect)(() => {
1249
- // New edge points
1250
- if (!newEdgePoint) {
1251
- return;
1252
- }
1253
- const removeListener = (e) => {
1254
- setNewEdgePoint(undefined);
1255
- e.preventDefault();
1256
- };
1257
- window.addEventListener("contextmenu", removeListener);
1258
- return () => {
1259
- setMousePos(undefined);
1260
- window.removeEventListener("contextmenu", removeListener);
1261
- };
1262
- }, [newEdgePoint]);
1263
- const setMousePosFunc = (e) => {
1264
- if (!newEdgePoint) {
1754
+ function useWorkflowEditor({ user, initialData, onSubmit, cohortId }) {
1755
+ const firebaseQuery = new firebaseQuery_1.default();
1756
+ const { filePopupActive, files, includedFiles, includedForms, uploadFile, fAddNode, fOnDelete, arrows, onChange, error, onMoveEnd, addEdgePoint, fWorkflowNodes, newEdgePoint, setTutorialActive, setFilePopupActive, fSubmitWorkflow, openPopup, setSnackbar, snackbar, setMousePosFunc, mousePos, tutorialActive, onDeleteArrow, containerRef, setError, setArrows, setFWorkflowNodes } = useGenericWorkflowEditor({ ...{ user, initialData }, defaultData: constants_1.defaultInstituteWorkflow, onSubmit: onSubmit });
1757
+ const addNode = ({ userType, nextStage, prevStage } = {}) => {
1758
+ return fAddNode({ userType: userType }, `${userType} review`, nextStage, prevStage);
1759
+ };
1760
+ const onDelete = (0, react_1.useCallback)(async (id) => {
1761
+ if (!id) {
1265
1762
  return;
1266
1763
  }
1267
- if (!ticking) {
1268
- requestAnimationFrame(() => {
1269
- ticking = false;
1270
- setMousePos({ x: e.pageX, y: e.pageY });
1271
- });
1764
+ return fOnDelete(id, onDeleteValidator);
1765
+ }, []);
1766
+ const onDeleteValidator = async (id) => {
1767
+ const placementsOnStage = await firebaseQuery.getCount("placements", [(0, firestore_1.where)("oId", "==", user.oId), (0, firestore_1.where)("cohort", "==", cohortId), (0, firestore_1.where)("status", "==", id)]);
1768
+ if (placementsOnStage > 0) {
1769
+ return { res: false, reason: "Cannot delete a stage with current active placements." };
1272
1770
  }
1273
- ticking = true;
1274
- };
1275
- const uploadFile = async (files) => {
1276
- const res = await (0, writeDatabase_1.uploadFiles)(files, `${userProduct}/${userOId}/files/`);
1277
- if (res === 1) {
1278
- // change this
1279
- setFilePopupActive(false);
1280
- (0, readDatabase_1.getFiles)(`${userProduct}/${userOId}/files`).then(setFiles);
1771
+ if ([1, 6, 8, 11].includes(id)) {
1772
+ return { res: false, reason: "Cannot delete core workflow component." };
1281
1773
  }
1774
+ return { res: true };
1282
1775
  };
1283
- const addNode = () => {
1284
- setWorkflowNodes((p) => ([...p, { id: (0, util_1.getUniqueId)(p), name: "", pos: { x: 10, y: 10 }, buttons: [] }]));
1776
+ const submitWorkflow = async (workflow, callOnComplete = true) => {
1777
+ const validatorTypeConversion = (newWorkflow, oldWorkflow) => {
1778
+ return validateWorkflow(newWorkflow, oldWorkflow);
1779
+ };
1780
+ return fSubmitWorkflow(validatorTypeConversion, workflow, callOnComplete);
1285
1781
  };
1286
- const onDelete = (0, react_1.useCallback)((id) => {
1287
- if ([1, 6, 8, 11].includes(id)) {
1288
- alert("Cannot delete core workflow component.");
1289
- return;
1290
- }
1291
- setWorkflowNodes((prev) => {
1292
- let oldMutable = [...prev];
1293
- oldMutable = oldMutable.filter((e) => e.id.toString() !== id.toString());
1294
- oldMutable.forEach((e, i) => {
1295
- const node = e;
1296
- // Remove any arrows to this node
1297
- node.buttons = (node.buttons && node.buttons.filter((button) => id.toString() !== button.id.toString())) || [];
1298
- oldMutable[i] = node;
1299
- });
1300
- return (oldMutable);
1301
- });
1302
- }, []);
1303
- (0, react_1.useEffect)(() => {
1304
- if (!workflowNodes) {
1305
- return;
1306
- }
1307
- setArrows(workflowNodes.reduce((acc, node) => {
1308
- if (node.buttons) {
1309
- node.buttons.forEach((buttonData) => {
1310
- acc.push({
1311
- start: node.id,
1312
- end: buttonData.id,
1313
- name: buttonData.name,
1314
- required: Boolean(buttonData.required),
1315
- });
1316
- });
1782
+ const validateWorkflow = async (newWorkflow, oldWorkflow) => {
1783
+ const getStageById = (id, checkInitialData) => {
1784
+ let node;
1785
+ if (checkInitialData) {
1786
+ if (!(oldWorkflow === null || oldWorkflow === void 0 ? void 0 : oldWorkflow.length)) {
1787
+ throw new Error("Check initial data but no oldWorkflow");
1788
+ }
1789
+ node = (oldWorkflow).find((x) => x.id === id);
1317
1790
  }
1318
- return acc;
1319
- }, []));
1320
- }, [workflowNodes]);
1321
- const onChange = (0, react_1.useCallback)((path, value) => {
1322
- setWorkflowNodes((prev) => (0, util_1.editNestedObject)(path, prev, value, true));
1323
- }, []);
1324
- const submitWorkflow = async () => {
1325
- setError(undefined);
1326
- setIncludedFiles(() => workflowNodes.reduce((acc, node) => {
1327
- if (node.files) {
1328
- acc.push(...node.files.map((f) => JSON.parse(f).name));
1791
+ else {
1792
+ node = (newWorkflow).find((x) => x.id === id);
1329
1793
  }
1330
- return (0, util_1.arrayUniqueValues)(acc);
1331
- }, []));
1332
- setIncludedForms(workflowNodes.reduce((acc, node) => {
1333
- if (node.forms) {
1334
- acc.push(...node.forms);
1794
+ if (!node) {
1795
+ throw new Error("Error in configuration. Cannot find stage: " + id);
1335
1796
  }
1336
- return (0, util_1.arrayUniqueValues)(acc);
1337
- }, []));
1338
- if (!workflowConseq()) {
1339
- setError("Your workflow is not continuous, check the onAccept and onReject values.");
1340
- return;
1341
- }
1342
- if (initialData === workflowNodes) {
1343
- onSubmit({ workflow: workflowNodes, includedFiles: includedFiles, includedForms: includedForms });
1344
- return;
1345
- }
1346
- if (initialData) {
1347
- const restrictedChanges = workflowNodes.map((currentNode) => {
1348
- var _a, _b;
1349
- const editedNode = initialData[currentNode.id];
1350
- const editedButtons = ((_a = editedNode === null || editedNode === void 0 ? void 0 : editedNode.buttons) === null || _a === void 0 ? void 0 : _a.map((x) => x.id)) || [];
1351
- const currentButtons = ((_b = currentNode === null || currentNode === void 0 ? void 0 : currentNode.buttons) === null || _b === void 0 ? void 0 : _b.map((x) => x.id)) || [];
1352
- // Should check IDs rather than the entire thing.
1353
- if (!editedButtons.every((v, i) => v === currentButtons[i])) {
1354
- return true;
1355
- }
1356
- return false;
1357
- });
1358
- if (restrictedChanges.includes(true) && Boolean(cohortId)) {
1359
- const conflicts = await (0, readDatabase_1.checkPlacementConflicts)(userOId, cohortId);
1360
- if (conflicts > 0) {
1361
- setError(`${conflicts} active placements are using this workflow. Complete these placements to edit workflow.`);
1362
- return;
1363
- }
1797
+ return node;
1798
+ };
1799
+ console.log("workflows, new, old", newWorkflow, oldWorkflow);
1800
+ // Steps:
1801
+ // Check if any deleted stages that have placements assigned to them
1802
+ const missingNodes = oldWorkflow && oldWorkflow.length ? oldWorkflow.reduce((acc, oldNode) => {
1803
+ const existsInNewWorkflow = Boolean(newWorkflow.find((newNode) => newNode.id === oldNode.id));
1804
+ if (!existsInNewWorkflow) {
1805
+ acc.push(oldNode.id);
1364
1806
  }
1365
- }
1366
- onSubmit({ workflow: workflowNodes, includedFiles: includedFiles, includedForms: includedForms });
1367
- };
1368
- const workflowConseq = () => {
1369
- const getStageById = (id) => workflowNodes.find((x) => x.id === id);
1807
+ return acc;
1808
+ }, []) : [];
1809
+ await Promise.all(missingNodes.map(async (nodeId) => {
1810
+ const placementsOnStage = await firebaseQuery.getCount("placements", [(0, firestore_1.where)("oId", "==", user.oId), (0, firestore_1.where)("cohort", "==", cohortId), (0, firestore_1.where)("status", "==", nodeId)]);
1811
+ if (placementsOnStage > 0) {
1812
+ throw new Error("Cannot delete a stage with current active placements.");
1813
+ }
1814
+ }));
1815
+ // After verifying, we have a list of stages that
1816
+ // Create paths with new workflow
1817
+ // Check cyclic
1818
+ // Check early termination
1819
+ // Check missed items
1820
+ // Create paths from old workflow
1821
+ // Check cyclic
1822
+ // Check early termination
1823
+ // Check missed items
1370
1824
  const paths = [];
1371
- const createPaths = (stage, path) => {
1372
- var _a;
1825
+ const createPaths = (stage, path, checkingInitialData) => {
1826
+ var _a, _b;
1373
1827
  if (!stage)
1374
1828
  return;
1375
1829
  const currentPath = [...(path || [])];
1376
1830
  // Check if infinite cycles
1377
- if (currentPath.includes(stage.id)) {
1831
+ if (currentPath.find((s) => s.id === stage.id)) {
1378
1832
  // ID must be already in it, so a loop has been formed.
1379
- // Get subpath from id onwards, inclusive.
1380
- // Check if there are other ways out. If not, return false!
1381
- const cyclicPath = currentPath.slice(currentPath.indexOf(stage.id));
1382
- const escapableCycle = cyclicPath.some((id, idx) => {
1383
- var _a;
1384
- let cyclicStageButtons = ((_a = getStageById(id)) === null || _a === void 0 ? void 0 : _a.buttons) || [];
1833
+ // Get just the elements in that cycle, such as [1, 2, 3, 4, 1]
1834
+ const cyclicPath = currentPath.slice(currentPath.findIndex((s) => s.id === stage.id));
1835
+ // If we have a cycle [1, 2, 3, 4, 1], check if any of them have a way out.
1836
+ const escapableCycle = cyclicPath.some((cycleStage, idx) => {
1837
+ let cyclicStageButtons = (cycleStage === null || cycleStage === void 0 ? void 0 : cycleStage.buttons) || [];
1838
+ // Keep only the arrows that don't go to the next one already documented, or the ID being checked.
1385
1839
  cyclicStageButtons = cyclicStageButtons.filter((button) => ![cyclicPath[idx + 1], stage.id].includes(button.id));
1386
1840
  return cyclicStageButtons.length > 0;
1387
1841
  });
1388
1842
  if (!escapableCycle) {
1389
- paths.push(false);
1843
+ throw new Error(`Inescapable cycle detected at ${stage.name}. Amend and resubmit.`);
1390
1844
  }
1391
1845
  return;
1392
1846
  }
1393
- currentPath.push(stage.id);
1847
+ currentPath.push(stage);
1394
1848
  if (!((_a = stage === null || stage === void 0 ? void 0 : stage.buttons) === null || _a === void 0 ? void 0 : _a.length)) {
1849
+ console.log("Final path", currentPath, checkingInitialData);
1395
1850
  // Check if in order and ends in 11
1396
1851
  if (stage.id !== 11) {
1397
- paths.push(false);
1398
- return;
1852
+ throw new Error("All paths must end with the 'Workflow end' stage. Amend and reupload.");
1399
1853
  }
1854
+ //Check if a valid path.
1400
1855
  const validEssentialRoute = [1, "Provider", 6, 8, 11];
1401
- const currentEssentialRoute = currentPath.reduce((acc, x) => {
1402
- if ([1, 6, 8, 11].includes(x)) {
1403
- acc.push(x);
1856
+ const currentEssentialRoute = currentPath.reduce((acc, node) => {
1857
+ if ([1, 6, 8, 11].includes(node.id)) {
1858
+ acc.push(node.id);
1859
+ return acc;
1404
1860
  }
1405
- const node = getStageById(x);
1406
1861
  if ((node === null || node === void 0 ? void 0 : node.userType) !== "Provider")
1407
1862
  return acc;
1408
1863
  if (!acc.includes(6) && !acc.includes("Provider")) {
@@ -1411,18 +1866,379 @@ function useWorkflowEditor({ user, initialData, onSubmit, cohortId, product, oId
1411
1866
  return acc;
1412
1867
  }, []);
1413
1868
  const valid = (0, util_1.arraysEqual)(currentEssentialRoute, validEssentialRoute);
1414
- paths.push(valid ? currentPath : false);
1869
+ if (!valid) {
1870
+ console.log("Error in", currentEssentialRoute);
1871
+ throw new Error(checkingInitialData ?
1872
+ "Cannot submit. Changing workflow will cause current placements to skip core workflow components." :
1873
+ "Missing core workflow components. Ensure all placements have a provider review, start, end and workflow end in that order.");
1874
+ }
1875
+ paths.push(currentPath);
1415
1876
  return;
1416
1877
  }
1417
- stage.buttons.forEach((button) => {
1878
+ if (checkingInitialData) {
1879
+ stage.buttons.forEach((button) => {
1880
+ createPaths(getStageById(button.id, true), currentPath, checkingInitialData);
1881
+ });
1882
+ }
1883
+ const newWorkflowStage = newWorkflow.find((newStage) => newStage.id === stage.id);
1884
+ if (!newWorkflowStage) {
1885
+ if (checkingInitialData) {
1886
+ return;
1887
+ }
1888
+ throw new Error("Cannot find node: " + stage.id);
1889
+ }
1890
+ (_b = newWorkflowStage.buttons) === null || _b === void 0 ? void 0 : _b.forEach((button) => {
1891
+ console.log("stage", stage, checkingInitialData);
1418
1892
  createPaths(getStageById(button.id), currentPath);
1419
1893
  });
1420
1894
  };
1421
- createPaths(getStageById(1));
1895
+ createPaths(getStageById(1), []);
1896
+ if (oldWorkflow && oldWorkflow.length) {
1897
+ createPaths(getStageById(1, true), [], true);
1898
+ }
1422
1899
  return paths.every((x) => x);
1423
1900
  };
1901
+ const workflowNodes = fWorkflowNodes;
1902
+ const setWorkflowNodes = setFWorkflowNodes;
1903
+ return ({ ...{ filePopupActive, files, uploadFile, addNode, onDelete, arrows, onChange, error, includedFiles, onMoveEnd, addEdgePoint, newEdgePoint, setTutorialActive, setFilePopupActive,
1904
+ includedForms, submitWorkflow, openPopup, snackbar, setSnackbar, setMousePosFunc, mousePos, tutorialActive,
1905
+ onDeleteArrow, workflowNodes, containerRef, setError, setArrows, setWorkflowNodes } });
1906
+ }
1907
+ function useApplicantWorkflowEditor({ user, initialData, onSubmit, workflowId }) {
1908
+ const firebaseQuery = new firebaseQuery_1.default();
1909
+ const { filePopupActive, includedFiles, includedForms, uploadFile, fAddNode, fOnDelete, arrows, onChange, error, onMoveEnd, addEdgePoint, fWorkflowNodes, newEdgePoint, setTutorialActive, setFilePopupActive, fSubmitWorkflow, openPopup, setSnackbar, snackbar, setMousePosFunc, mousePos, tutorialActive, onDeleteArrow, containerRef, setError, setArrows, setFWorkflowNodes } = useGenericWorkflowEditor({ ...{ user, initialData }, defaultData: workflowId ? [] : constants_1.defaultApplicantWorkflow, onSubmit: onSubmit });
1910
+ (0, react_1.useEffect)(() => {
1911
+ if (!workflowId)
1912
+ return;
1913
+ firebaseQuery.getDocData(["applicantWorkflows", workflowId]).then((e) => setFWorkflowNodes(e.workflow));
1914
+ }, [workflowId]);
1915
+ const addNode = ({ userType, nextStage, prevStage } = {}) => {
1916
+ return fAddNode({ userType: userType, buttons: [{ name: "Reject", id: 12, required: true }] }, "", nextStage, prevStage);
1917
+ };
1918
+ const onDelete = (0, react_1.useCallback)(async (id) => {
1919
+ if (!id) {
1920
+ return;
1921
+ }
1922
+ return fOnDelete(id, onDeleteValidator);
1923
+ }, []);
1924
+ const onDeleteValidator = async (id) => {
1925
+ const placementsOnStage = await firebaseQuery.getCount("placement", [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.where)("applicantWorkflowId", "==", workflowId), (0, firestore_1.where)("applicantStage", "==", id)]);
1926
+ if (placementsOnStage > 0) {
1927
+ return { res: false, reason: "Cannot delete a stage with current applicants." };
1928
+ }
1929
+ if ([1, 11, 12].includes(id)) {
1930
+ return { res: false, reason: "Cannot delete core workflow component." };
1931
+ }
1932
+ return { res: true };
1933
+ };
1934
+ const submitWorkflow = async (workflow, callOnComplete = true) => {
1935
+ const validatorTypeConversion = (newWorkflow, oldWorkflow) => {
1936
+ return validateWorkflow(newWorkflow, oldWorkflow);
1937
+ };
1938
+ return fSubmitWorkflow(validatorTypeConversion, workflow, callOnComplete);
1939
+ };
1940
+ // Change validator
1941
+ const validateWorkflow = async (newWorkflow, oldWorkflow) => {
1942
+ const getStageById = (id, checkInitialData) => {
1943
+ let node;
1944
+ if (checkInitialData) {
1945
+ if (!(oldWorkflow === null || oldWorkflow === void 0 ? void 0 : oldWorkflow.length)) {
1946
+ throw new Error("Check initial data but no oldWorkflow");
1947
+ }
1948
+ node = (oldWorkflow).find((x) => x.id === id);
1949
+ }
1950
+ else {
1951
+ node = (newWorkflow).find((x) => x.id === id);
1952
+ }
1953
+ if (!node) {
1954
+ throw new Error("Error in configuration. Cannot find stage: " + id);
1955
+ }
1956
+ return node;
1957
+ };
1958
+ console.log("workflows, new, old", newWorkflow, oldWorkflow);
1959
+ // Steps:
1960
+ // Check if any deleted stages that have placements assigned to them
1961
+ const missingNodes = oldWorkflow && oldWorkflow.length ? oldWorkflow.reduce((acc, oldNode) => {
1962
+ const existsInNewWorkflow = Boolean(newWorkflow.find((newNode) => newNode.id === oldNode.id));
1963
+ if (!existsInNewWorkflow) {
1964
+ acc.push(oldNode.id);
1965
+ }
1966
+ return acc;
1967
+ }, []) : [];
1968
+ await Promise.all(missingNodes.map(async (nodeId) => {
1969
+ const placementsOnStage = await firebaseQuery.getCount("placement", [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.where)("applicantWorkflow", "==", workflowId), (0, firestore_1.where)("applicantStage", "==", nodeId)]);
1970
+ if (placementsOnStage > 0) {
1971
+ throw new Error("Cannot delete a stage with current active placements.");
1972
+ }
1973
+ }));
1974
+ // After verifying, we have a list of stages that
1975
+ // Create paths with new workflow
1976
+ // Check cyclic
1977
+ // Check early termination
1978
+ // Check missed items
1979
+ // Create paths from old workflow
1980
+ // Check cyclic
1981
+ // Check early termination
1982
+ // Check missed items
1983
+ const paths = [];
1984
+ const createPaths = (stage, path, checkingInitialData) => {
1985
+ var _a, _b;
1986
+ if (!stage)
1987
+ return;
1988
+ const currentPath = [...(path || [])];
1989
+ // Check if infinite cycles
1990
+ if (currentPath.find((s) => s.id === stage.id)) {
1991
+ // ID must be already in it, so a loop has been formed.
1992
+ // Get just the elements in that cycle, such as [1, 2, 3, 4, 1]
1993
+ const cyclicPath = currentPath.slice(currentPath.findIndex((s) => s.id === stage.id));
1994
+ // If we have a cycle [1, 2, 3, 4, 1], check if any of them have a way out.
1995
+ const escapableCycle = cyclicPath.some((cycleStage, idx) => {
1996
+ let cyclicStageButtons = (cycleStage === null || cycleStage === void 0 ? void 0 : cycleStage.buttons) || [];
1997
+ // Keep only the arrows that don't go to the next one already documented, or the ID being checked.
1998
+ cyclicStageButtons = cyclicStageButtons.filter((button) => ![cyclicPath[idx + 1], stage.id].includes(button.id));
1999
+ return cyclicStageButtons.length > 0;
2000
+ });
2001
+ if (!escapableCycle) {
2002
+ throw new Error(`Inescapable cycle detected at ${stage.name}. Amend and resubmit.`);
2003
+ }
2004
+ return;
2005
+ }
2006
+ currentPath.push(stage);
2007
+ if (!((_a = stage === null || stage === void 0 ? void 0 : stage.buttons) === null || _a === void 0 ? void 0 : _a.length)) {
2008
+ console.log("Final path", currentPath, checkingInitialData);
2009
+ // Check if in order and ends in 11
2010
+ if (![11, 12].includes(stage.id)) {
2011
+ throw new Error("All paths must end with the 'Success' or 'Reject' stage. Amend and reupload.");
2012
+ }
2013
+ paths.push(currentPath);
2014
+ return;
2015
+ }
2016
+ if (checkingInitialData) {
2017
+ stage.buttons.forEach((button) => {
2018
+ createPaths(getStageById(button.id, true), currentPath, checkingInitialData);
2019
+ });
2020
+ }
2021
+ const newWorkflowStage = newWorkflow.find((newStage) => newStage.id === stage.id);
2022
+ if (!newWorkflowStage) {
2023
+ if (checkingInitialData) {
2024
+ return;
2025
+ }
2026
+ throw new Error("Cannot find node: " + stage.id);
2027
+ }
2028
+ (_b = newWorkflowStage.buttons) === null || _b === void 0 ? void 0 : _b.forEach((button) => {
2029
+ createPaths(getStageById(button.id), currentPath);
2030
+ });
2031
+ };
2032
+ createPaths(getStageById(1), []);
2033
+ if (oldWorkflow && oldWorkflow.length) {
2034
+ createPaths(getStageById(1, true), [], true);
2035
+ }
2036
+ return paths.every((x) => x);
2037
+ };
2038
+ const workflowNodes = fWorkflowNodes;
2039
+ const setWorkflowNodes = setFWorkflowNodes;
2040
+ return ({ ...{ filePopupActive, uploadFile, addNode, onDelete, arrows, onChange, error, includedFiles, onMoveEnd, addEdgePoint, newEdgePoint, setTutorialActive, setFilePopupActive,
2041
+ includedForms, submitWorkflow, openPopup, snackbar, setSnackbar, setMousePosFunc, mousePos, tutorialActive,
2042
+ onDeleteArrow, workflowNodes, containerRef, setError, setArrows, setWorkflowNodes } });
2043
+ }
2044
+ function useGenericWorkflowEditor({ user, initialData, defaultData, onSubmit }) {
2045
+ const [filePopupActive, setFilePopupActive] = (0, react_1.useState)(false);
2046
+ const [files, setFiles] = (0, react_1.useState)([]);
2047
+ const [fWorkflowNodes, setFWorkflowNodes] = (0, react_1.useState)([]); // Workflow data
2048
+ const [arrows, setArrows] = (0, react_1.useState)([]);
2049
+ const [includedForms, setIncludedForms] = (0, react_1.useState)([]);
2050
+ const [includedFiles, setIncludedFiles] = (0, react_1.useState)([]);
2051
+ const [error, setError] = (0, react_1.useState)();
2052
+ const [snackbar, setSnackbar] = (0, react_1.useState)();
2053
+ const [newEdgePoint, setNewEdgePoint] = (0, react_1.useState)();
2054
+ const [mousePos, setMousePos] = (0, react_1.useState)();
2055
+ const [tutorialActive, setTutorialActive] = (0, react_1.useState)(false);
2056
+ const containerRef = (0, react_1.useRef)(null);
2057
+ let ticking = false;
2058
+ (0, react_1.useEffect)(() => {
2059
+ let newData = initialData;
2060
+ if (initialData === undefined || initialData.length === 0) {
2061
+ newData = defaultData;
2062
+ setTimeout(() => {
2063
+ setTutorialActive(false);
2064
+ }, 1000);
2065
+ }
2066
+ (0, readDatabase_1.getFiles)(`${user.product}/${user.oId}/files`).then(setFiles);
2067
+ setFWorkflowNodes(newData || []);
2068
+ }, [initialData]);
2069
+ (0, react_1.useEffect)(() => {
2070
+ // New edge points
2071
+ if (!newEdgePoint) {
2072
+ return;
2073
+ }
2074
+ const removeListener = (e) => {
2075
+ setNewEdgePoint(undefined);
2076
+ e.preventDefault();
2077
+ };
2078
+ window.addEventListener("contextmenu", removeListener);
2079
+ return () => {
2080
+ setMousePos(undefined);
2081
+ window.removeEventListener("contextmenu", removeListener);
2082
+ };
2083
+ }, [newEdgePoint]);
2084
+ const setMousePosFunc = (e) => {
2085
+ if (!newEdgePoint) {
2086
+ return;
2087
+ }
2088
+ if (!ticking) {
2089
+ requestAnimationFrame(() => {
2090
+ ticking = false;
2091
+ setMousePos({ x: e.pageX, y: e.pageY });
2092
+ });
2093
+ }
2094
+ ticking = true;
2095
+ };
2096
+ const uploadFile = async (files) => {
2097
+ const res = await (0, writeDatabase_1.uploadFiles)(files, `${user.product}/${user.oId}/files/`);
2098
+ if (res === 1) {
2099
+ // change this
2100
+ setFilePopupActive(false);
2101
+ (0, readDatabase_1.getFiles)(`${user.product}/${user.oId}/files`).then(setFiles);
2102
+ }
2103
+ };
2104
+ const fAddNode = (additionalData, name, nextStage, prevStage) => {
2105
+ let nodeId = (0, util_1.getUniqueId)(fWorkflowNodes);
2106
+ // Add user type and name for institute placement workflow!
2107
+ setFWorkflowNodes((p) => {
2108
+ let newWorkflowNodes = ([...p, { id: nodeId, pos: { x: 10, y: 10 }, ...additionalData, name: name || `Stage ${nodeId}`, buttons: nextStage ? [...((additionalData === null || additionalData === void 0 ? void 0 : additionalData.buttons) || []), { required: true, name: "Accept", id: nextStage }] : [] }]);
2109
+ if (prevStage) {
2110
+ const prevStageIndex = p.findIndex((node) => node.id === prevStage);
2111
+ newWorkflowNodes[prevStageIndex] = { ...newWorkflowNodes[prevStageIndex], buttons: [{ required: true, name: "Accept", id: nodeId }] };
2112
+ }
2113
+ return newWorkflowNodes;
2114
+ });
2115
+ return nodeId;
2116
+ };
2117
+ const fOnDelete = (0, react_1.useCallback)(async (id, validation) => {
2118
+ if (!id) {
2119
+ return;
2120
+ }
2121
+ if (validation) {
2122
+ // Add placementsOnStage to validation
2123
+ const validationResult = await validation(id);
2124
+ if (!validationResult.res) {
2125
+ setError(validationResult.reason);
2126
+ return;
2127
+ }
2128
+ }
2129
+ setFWorkflowNodes((prev) => {
2130
+ var _a;
2131
+ let oldMutable = [...prev];
2132
+ // Buttons going from deleted nodes to new node
2133
+ const deletedNodeButtons = (_a = oldMutable.find((node) => node.id === id)) === null || _a === void 0 ? void 0 : _a.buttons;
2134
+ oldMutable = oldMutable.filter((e) => e.id.toString() !== id.toString());
2135
+ const arrowsToDeletedNode = oldMutable.map((e, i) => {
2136
+ var _a;
2137
+ const node = { ...e };
2138
+ // Remove any arrows to this node
2139
+ const nodeFeedsToDeleted = Boolean((_a = node.buttons) === null || _a === void 0 ? void 0 : _a.find((destination) => destination.id === id));
2140
+ if (nodeFeedsToDeleted) {
2141
+ console.log("Delete line from node", node.id);
2142
+ }
2143
+ if (nodeFeedsToDeleted) {
2144
+ const newNodeButtons = node.buttons ? node.buttons.filter((button) => id.toString() !== button.id.toString()) : [];
2145
+ node.buttons = newNodeButtons;
2146
+ console.log("New node buttons", newNodeButtons, node.buttons);
2147
+ }
2148
+ oldMutable[i] = node;
2149
+ return nodeFeedsToDeleted ? node : undefined;
2150
+ }).filter((i) => i);
2151
+ console.log("arrows to and destinations", arrowsToDeletedNode, deletedNodeButtons);
2152
+ if ((deletedNodeButtons === null || deletedNodeButtons === void 0 ? void 0 : deletedNodeButtons.length) && arrowsToDeletedNode.length) {
2153
+ const newNodeDestination = deletedNodeButtons[0].id;
2154
+ const newNodeOrigin = arrowsToDeletedNode[0].id;
2155
+ const indexOfOrigin = oldMutable.findIndex((node) => node.id === newNodeOrigin);
2156
+ oldMutable[indexOfOrigin] = { ...oldMutable[indexOfOrigin], buttons: [...(oldMutable[indexOfOrigin].buttons || []), { id: newNodeDestination, required: true, name: "Submit" }] };
2157
+ }
2158
+ return oldMutable;
2159
+ });
2160
+ }, []);
2161
+ (0, react_1.useEffect)(() => {
2162
+ error && setError(undefined);
2163
+ if (!fWorkflowNodes) {
2164
+ return;
2165
+ }
2166
+ setArrows(fWorkflowNodes.reduce((acc, node) => {
2167
+ if (node.buttons) {
2168
+ node.buttons.forEach((buttonData) => {
2169
+ acc.push({
2170
+ start: node.id,
2171
+ end: buttonData.id,
2172
+ name: buttonData.name,
2173
+ required: Boolean(buttonData.required),
2174
+ });
2175
+ });
2176
+ }
2177
+ return acc;
2178
+ }, []));
2179
+ const mIncludedFiles = fWorkflowNodes.reduce((acc, node) => {
2180
+ if (node.files) {
2181
+ acc.push(...node.files.map((f) => {
2182
+ try {
2183
+ return JSON.parse(f).name;
2184
+ }
2185
+ catch {
2186
+ return f;
2187
+ }
2188
+ }));
2189
+ }
2190
+ return (0, util_1.arrayUniqueValues)(acc);
2191
+ }, []);
2192
+ const mIncludedForms = fWorkflowNodes.reduce((acc, node) => {
2193
+ if (node.forms) {
2194
+ acc.push(...node.forms);
2195
+ }
2196
+ return (0, util_1.arrayUniqueValues)(acc);
2197
+ }, []);
2198
+ setIncludedFiles(mIncludedFiles);
2199
+ setIncludedForms(mIncludedForms);
2200
+ }, [fWorkflowNodes]);
2201
+ const onChange = (0, react_1.useCallback)((path, value) => {
2202
+ setFWorkflowNodes((prev) => (0, util_1.editNestedObject)(path, prev, value, true));
2203
+ }, []);
2204
+ const fSubmitWorkflow = async (validation, workflow, callOnComplete = true) => {
2205
+ setError(undefined);
2206
+ const fWorkflow = workflow || fWorkflowNodes;
2207
+ const mIncludedFiles = fWorkflow.reduce((acc, node) => {
2208
+ if (node.files) {
2209
+ acc.push(...node.files.map((f) => {
2210
+ try {
2211
+ return JSON.parse(f).name;
2212
+ }
2213
+ catch {
2214
+ return f;
2215
+ }
2216
+ }));
2217
+ }
2218
+ return (0, util_1.arrayUniqueValues)(acc);
2219
+ }, []);
2220
+ const mIncludedForms = fWorkflow.reduce((acc, node) => {
2221
+ if (node.forms) {
2222
+ acc.push(...node.forms);
2223
+ }
2224
+ return (0, util_1.arrayUniqueValues)(acc);
2225
+ }, []);
2226
+ setIncludedFiles(mIncludedFiles);
2227
+ setIncludedForms(mIncludedForms);
2228
+ if (initialData === fWorkflow) {
2229
+ return callOnComplete && onSubmit({ workflow: fWorkflow, includedFiles: mIncludedFiles, includedForms: mIncludedForms });
2230
+ }
2231
+ const validatedWorkflow = await validation(fWorkflow, initialData).catch((e) => {
2232
+ setError(e.message);
2233
+ throw e;
2234
+ });
2235
+ if (!validatedWorkflow) {
2236
+ return;
2237
+ }
2238
+ return callOnComplete && onSubmit({ workflow: fWorkflow, includedFiles: includedFiles, includedForms: includedForms });
2239
+ };
1424
2240
  const deleteEdge = (0, react_1.useCallback)((originId, nodeId) => {
1425
- setWorkflowNodes((prev) => (0, util_1.editNestedObject)([originId, "buttons", nodeId], prev, undefined, true));
2241
+ setFWorkflowNodes((prev) => (0, util_1.editNestedObject)([originId, "buttons", nodeId], prev, undefined, true));
1426
2242
  }, []);
1427
2243
  const onDeleteArrow = (0, react_1.useCallback)((data) => {
1428
2244
  deleteEdge(data.start, data.end);
@@ -1436,7 +2252,7 @@ function useWorkflowEditor({ user, initialData, onSubmit, cohortId, product, oId
1436
2252
  const y = containerRef.current.scrollTop + clientRect.y - top - 26;
1437
2253
  // Make sure x and y are not negative. Contrains canvas by top and left
1438
2254
  const pos = { x: x >= 0 ? x : 0, y: y >= 0 ? y : 0 };
1439
- setWorkflowNodes((p) => (0, util_1.editNestedObject)([id, "pos"], p, pos));
2255
+ setFWorkflowNodes((p) => (0, util_1.editNestedObject)([id, "pos"], p, pos));
1440
2256
  }, []);
1441
2257
  const addEdgePoint = (0, react_1.useCallback)((id, existingEdgePoint) => {
1442
2258
  var _a;
@@ -1445,7 +2261,7 @@ function useWorkflowEditor({ user, initialData, onSubmit, cohortId, product, oId
1445
2261
  setSnackbar("Cannot connect stages after workflow end.");
1446
2262
  return;
1447
2263
  }
1448
- const buttons = (_a = workflowNodes.find((node) => node.id === id)) === null || _a === void 0 ? void 0 : _a.buttons;
2264
+ const buttons = (_a = fWorkflowNodes.find((node) => node.id === id)) === null || _a === void 0 ? void 0 : _a.buttons;
1449
2265
  if ([1, 11].includes(id) && buttons && buttons.length > 0) {
1450
2266
  setSnackbar("Stage can only have one destination.");
1451
2267
  return;
@@ -1458,8 +2274,8 @@ function useWorkflowEditor({ user, initialData, onSubmit, cohortId, product, oId
1458
2274
  setNewEdgePoint(undefined);
1459
2275
  return;
1460
2276
  }
1461
- const originPos = workflowNodes.findIndex((x) => x.id.toString() === existingEdgePoint.toString());
1462
- const buttons = [...(workflowNodes[originPos].buttons || [])];
2277
+ const originPos = fWorkflowNodes.findIndex((x) => x.id.toString() === existingEdgePoint.toString());
2278
+ const buttons = [...(fWorkflowNodes[originPos].buttons || [])];
1463
2279
  if (!buttons.some((x) => x.id.toString() === id.toString())) {
1464
2280
  buttons.push({
1465
2281
  id: id,
@@ -1467,17 +2283,16 @@ function useWorkflowEditor({ user, initialData, onSubmit, cohortId, product, oId
1467
2283
  name: [1, 6, 8].includes(existingEdgePoint) ? false : "",
1468
2284
  });
1469
2285
  }
1470
- setWorkflowNodes((p) => (0, util_1.editNestedObject)([existingEdgePoint, "buttons"], p, buttons, true));
2286
+ setFWorkflowNodes((p) => (0, util_1.editNestedObject)([existingEdgePoint, "buttons"], p, buttons, true));
1471
2287
  setNewEdgePoint(undefined);
1472
- }, [workflowNodes]);
2288
+ }, [fWorkflowNodes]);
1473
2289
  const openPopup = (0, react_1.useCallback)(() => {
1474
2290
  setFilePopupActive(true);
1475
2291
  }, []);
1476
- return ({ ...{ filePopupActive, files, uploadFile, addNode, onDelete, arrows, onChange, error, includedFiles, onMoveEnd, addEdgePoint, newEdgePoint, setTutorialActive, setFilePopupActive,
1477
- includedForms, submitWorkflow, openPopup, snackbar, setSnackbar, setMousePosFunc, mousePos, tutorialActive,
1478
- onDeleteArrow, workflowNodes, containerRef, setError, setArrows, setWorkflowNodes } });
2292
+ return ({ ...{ filePopupActive, files, uploadFile, fAddNode, fOnDelete, arrows, onChange, error, includedFiles, onMoveEnd, addEdgePoint, newEdgePoint, setTutorialActive, setFilePopupActive,
2293
+ includedForms, fSubmitWorkflow, openPopup, snackbar, setSnackbar, setMousePosFunc, mousePos, tutorialActive,
2294
+ onDeleteArrow, fWorkflowNodes, containerRef, setError, setArrows, setFWorkflowNodes } });
1479
2295
  }
1480
- exports.useWorkflowEditor = useWorkflowEditor;
1481
2296
  function useInstitutePlacementListingHandler({ user }) {
1482
2297
  const [emptyCellsWarning, setEmptyCellsWarning] = (0, react_1.useState)(false);
1483
2298
  const [alert, setAlert] = (0, react_1.useState)();
@@ -1567,5 +2382,808 @@ function useInstitutePlacementListingHandler({ user }) {
1567
2382
  };
1568
2383
  return ({ ...{ uploadPlacements, alert, onChange } });
1569
2384
  }
1570
- exports.useInstitutePlacementListingHandler = useInstitutePlacementListingHandler;
2385
+ function useGetIndividualPlacementForPlacementPage({ user, placementId, organisation }) {
2386
+ var _a;
2387
+ const [placement, setPlacement] = (0, react_1.useState)();
2388
+ const [institute, setInstitute] = (0, react_1.useState)(user.product === "institutes" ? organisation === null || organisation === void 0 ? void 0 : organisation.details : undefined);
2389
+ const [workflow, setWorkflow] = (0, react_1.useState)();
2390
+ const [cohort, setCohort] = (0, react_1.useState)();
2391
+ const [student, setStudent] = (0, react_1.useState)();
2392
+ const [wStage, setWStage] = (0, react_1.useState)();
2393
+ const [snackbar, setSnackbar] = (0, react_1.useState)({ open: false });
2394
+ const [disableEmail, setDisableEmail] = (0, react_1.useState)({ parent: false, provider: false });
2395
+ const [rejectELIPopup, setRejectELIPopup] = (0, react_1.useState)(false);
2396
+ const [eliPopupOpen, setEliPopupOpen] = (0, react_1.useState)(false);
2397
+ const [eliURL, setELIURL] = (0, react_1.useState)("");
2398
+ const [rejectExternalDocPopup, setRejectExternalDocPopup] = (0, react_1.useState)(false);
2399
+ const [externalDocPopupOpen, setExternalDocPopupOpen] = (0, react_1.useState)(false);
2400
+ const [riskAssessmentURL, setRiskAssessmentURL] = (0, react_1.useState)("");
2401
+ const [dbsCheckURL, setDbsCheckURL] = (0, react_1.useState)("");
2402
+ const [uploadProviderDocPopup, setUploadProviderDocPopup] = (0, react_1.useState)();
2403
+ const [skipStagePopup, setSkipStagePopup] = (0, react_1.useState)(false);
2404
+ const [viewExternalLinkPopup, setViewExternalLinkPopup] = (0, react_1.useState)(false);
2405
+ const [externalLinkCopied, setExternalLinkCopied] = (0, react_1.useState)(false);
2406
+ const [uploadInsurance, setUploadInsurance] = (0, react_1.useState)(false);
2407
+ const [uploadRA, setUploadRA] = (0, react_1.useState)(false);
2408
+ const [uploadDBS, setUploadDBS] = (0, react_1.useState)(false);
2409
+ const [onboardingPopup, setOnboardingPopup] = (0, react_1.useState)(false);
2410
+ const [dismissOnboardingPopup, setDismissOnboardingPopup] = (0, react_1.useState)(false);
2411
+ const [addOnboardingDocsPopup, setAddOnboardingDocsPopup] = (0, react_1.useState)(false);
2412
+ const [editable, setEditable] = (0, react_1.useState)(false);
2413
+ const [withdrawFromPlacementPopup, setWithdrawFromPlacementPopup] = (0, react_1.useState)(false);
2414
+ const firebaseQuery = new firebaseQuery_1.default();
2415
+ (0, react_1.useEffect)(() => {
2416
+ if (!placementId)
2417
+ return;
2418
+ (0, readDatabase_1.getPlacementbyId)(placementId, setPlacement);
2419
+ }, [placementId]);
2420
+ (0, react_1.useEffect)(() => {
2421
+ var _a;
2422
+ console.log("p", placement);
2423
+ if (!placement) {
2424
+ return;
2425
+ }
2426
+ setEditable(!((placement.providerCompleted && placement.providerCompleted.includes("details")) || placement.completed));
2427
+ if (placement.oId) {
2428
+ if (user.product === "institutes") {
2429
+ setInstitute(organisation === null || organisation === void 0 ? void 0 : organisation.details);
2430
+ }
2431
+ else if (user.product === "providers") {
2432
+ firebaseQuery.getDocData(["institutes", placement.oId]).then((i) => setInstitute(i));
2433
+ }
2434
+ }
2435
+ if (placement.cohort) {
2436
+ if (user.product === "institutes" && user.oId === placement.oId) {
2437
+ setCohort(organisation === null || organisation === void 0 ? void 0 : organisation.cohorts[placement.cohort]);
2438
+ setWorkflow((_a = organisation === null || organisation === void 0 ? void 0 : organisation.cohorts[placement.cohort]) === null || _a === void 0 ? void 0 : _a.workflow);
2439
+ }
2440
+ else {
2441
+ firebaseQuery.getDocData(["cohorts", placement.cohort]).then((w) => {
2442
+ setWorkflow(w.workflow);
2443
+ setCohort(w);
2444
+ });
2445
+ }
2446
+ }
2447
+ else {
2448
+ setWorkflow(constants_1.defaultStudentWorkflow);
2449
+ }
2450
+ if (user.userType === "Students") {
2451
+ setStudent(user);
2452
+ }
2453
+ else {
2454
+ (0, readDatabase_1.getUserById)(placement.uid, undefined, false).then(setStudent);
2455
+ }
2456
+ }, [placement]);
2457
+ (0, react_1.useEffect)(() => {
2458
+ if (!workflow || !placement)
2459
+ return;
2460
+ const currentWorkflowStage = { ...workflow.find((obj) => obj.id === placement.status) };
2461
+ // console.log("currentWorkflowStage", currentWorkflowStage)
2462
+ currentWorkflowStage.id = placement.status;
2463
+ // Get form data for current stage
2464
+ if (currentWorkflowStage.forms) {
2465
+ (0, readDatabase_1.getFormsFromId)(["forms"], currentWorkflowStage.forms).then((details) => {
2466
+ currentWorkflowStage.formDetails = details;
2467
+ setWStage(currentWorkflowStage);
2468
+ });
2469
+ }
2470
+ else {
2471
+ setWStage(currentWorkflowStage);
2472
+ }
2473
+ }, [placement, workflow]);
2474
+ const editStage = async (nextStageId) => {
2475
+ if (!placementId || !wStage)
2476
+ return;
2477
+ await (0, writeDatabase_1.editPlacementStage)(placementId, wStage.id, nextStageId);
2478
+ setSnackbar({ open: true, message: "Stage updated." });
2479
+ };
2480
+ const sendEmail = (type) => {
2481
+ if (!placement || !student)
2482
+ return undefined;
2483
+ const sendRequest = async () => {
2484
+ await (0, firebase_1.executeCallable)("placement-sendExternalEmail", { pId: placementId, userType: type });
2485
+ setSnackbar({ open: true, message: "Email sent." });
2486
+ return;
2487
+ };
2488
+ if ((type === "provider" && !placement.providerEmail) || (type === "parent" && !student.details.parentEmail)) {
2489
+ setSnackbar({ open: true, message: `Please add a ${type} email.` });
2490
+ return;
2491
+ }
2492
+ if (!placement[`${type}Emailed`]) {
2493
+ setDisableEmail((x) => ({ ...x, [type]: true }));
2494
+ sendRequest().then(() => setDisableEmail((x) => ({ ...x, [type.toLowerCase()]: false })));
2495
+ return;
2496
+ }
2497
+ const previousEmailTime = new Date(placement[`${type}Emailed`].seconds * 1000);
2498
+ const today = new Date();
2499
+ const timeSinceEmail = (0, util_1.getDateDiff)(previousEmailTime, today);
2500
+ if (timeSinceEmail === 0) {
2501
+ setSnackbar({ open: true, message: "Emails can only be sent after 1 day." });
2502
+ return;
2503
+ }
2504
+ setDisableEmail((x) => ({ ...x, [type.toLowerCase()]: true }));
2505
+ sendRequest().then(() => setDisableEmail((x) => ({ ...x, [type.toLowerCase()]: false })));
2506
+ };
2507
+ const onboardingStatus = (placement === null || placement === void 0 ? void 0 : placement.onboarding) ? ((_a = placement.onboarding.completed) === null || _a === void 0 ? void 0 : _a.submitted) ? placement.onboarding.completed.accepted ? "Onboarding docs approved" : "Onboarding docs completed" : (user.userType === "Staff" ? "Onboarding sent" : "Complete onboarding") : "Add onboarding documents";
2508
+ const signOffPlacements = (0, util_1.getAccess)(user, "signOffPlacements");
2509
+ let canEdit = false;
2510
+ if (((wStage === null || wStage === void 0 ? void 0 : wStage.userType) === "Staff" && user.userType === "Staff" && user.product === "institutes") || (user.product === "providers" && (wStage === null || wStage === void 0 ? void 0 : wStage.userType) === "Provider") || user.userType === "Students" && (wStage === null || wStage === void 0 ? void 0 : wStage.userType) === "Students") {
2511
+ console.log("ALMOST CAN EDIT");
2512
+ if (user.userType === "Staff" && !signOffPlacements) {
2513
+ canEdit = false;
2514
+ }
2515
+ else {
2516
+ canEdit = true;
2517
+ }
2518
+ }
2519
+ const onFlagClick = async (e, onClose) => {
2520
+ if (!placement)
2521
+ return;
2522
+ if (e === "completeOnboarding" || e === "reviewOnboarding") {
2523
+ setOnboardingPopup(true);
2524
+ }
2525
+ if (e === "noInsurance") {
2526
+ if (!eliURL) {
2527
+ const storageRef = (0, storage_1.ref)(firebaseConfig_1.storage, `insurance/${placement.providerId}.pdf`);
2528
+ const file = await (0, storage_1.getDownloadURL)(storageRef);
2529
+ setELIURL(file);
2530
+ }
2531
+ setEliPopupOpen(true);
2532
+ }
2533
+ if (e === "noRiskAssessment") {
2534
+ if (!riskAssessmentURL && placement.riskAssessmentType === "file") {
2535
+ console.log("Risk assessment");
2536
+ const storageRef = (0, storage_1.ref)(firebaseConfig_1.storage, `riskAssessments/${placement.placementId || placement.id}.pdf`);
2537
+ const file = await (0, storage_1.getDownloadURL)(storageRef);
2538
+ setRiskAssessmentURL(file);
2539
+ }
2540
+ setExternalDocPopupOpen("riskAssessment");
2541
+ }
2542
+ if (e === "noDbsCheck") {
2543
+ if (!dbsCheckURL && placement.dbsCheckType === "file") {
2544
+ const storageRef = (0, storage_1.ref)(firebaseConfig_1.storage, `dbsChecks/${placement.placementId || placement.id}.pdf`);
2545
+ const file = await (0, storage_1.getDownloadURL)(storageRef);
2546
+ setDbsCheckURL(file);
2547
+ }
2548
+ setExternalDocPopupOpen("dbsCheck");
2549
+ }
2550
+ if (e === "addOnboarding") {
2551
+ if (onClose) {
2552
+ setDismissOnboardingPopup(true);
2553
+ }
2554
+ else {
2555
+ setAddOnboardingDocsPopup(true);
2556
+ }
2557
+ }
2558
+ };
2559
+ const approveELI = async () => {
2560
+ if (!placement)
2561
+ return;
2562
+ await (0, firebase_1.executeCallable)("insurance-approve", { oId: user.oId, providerId: placement.providerId });
2563
+ setEliPopupOpen(false);
2564
+ };
2565
+ const rejectELI = async ({ reason }) => {
2566
+ if (!placement || !institute)
2567
+ return;
2568
+ console.log("Reject", { reason: reason, placement: placement, instituteName: institute.name, staffEmail: user.email });
2569
+ await (0, firebase_1.executeCallable)("insurance-reject", { reason: reason, placementId: placementId, instituteName: institute.name, staffEmail: user.email });
2570
+ setRejectELIPopup(false);
2571
+ setEliPopupOpen(false);
2572
+ };
2573
+ const approveProviderDoc = async (type) => {
2574
+ await (0, firebase_1.executeCallable)(`${type}-approve`, { oId: user.oId, placementId: placementId });
2575
+ setExternalDocPopupOpen(false);
2576
+ };
2577
+ const rejectProviderDoc = async ({ reason }, type) => {
2578
+ if (!placement || !institute)
2579
+ return;
2580
+ await (0, firebase_1.executeCallable)(`${type}-reject`, { reason: reason, placementId: placementId, instituteName: institute.name, staffEmail: user.email });
2581
+ setRejectExternalDocPopup(false);
2582
+ setExternalDocPopupOpen(false);
2583
+ };
2584
+ const manuallyConfigureProvider = async () => {
2585
+ if (!placement)
2586
+ return;
2587
+ if (!placement.providerId) {
2588
+ const res = await (0, firebase_1.executeCallable)("placement-uploadProviderDetails", {
2589
+ pId: placementId,
2590
+ placement: Object.fromEntries(Object.entries(placement).filter(([k]) => k !== "contactId")),
2591
+ stage: wStage,
2592
+ skipSearch: true
2593
+ }).catch((e) => {
2594
+ throw e;
2595
+ });
2596
+ console.log("RETURN", res.data);
2597
+ setPlacement((p) => ({ ...p, ...res.data }));
2598
+ if (uploadProviderDocPopup === "insurance") {
2599
+ setUploadProviderDocPopup(undefined);
2600
+ setUploadInsurance(true);
2601
+ }
2602
+ else if (uploadProviderDocPopup === "riskAssessment") {
2603
+ setUploadProviderDocPopup(undefined);
2604
+ setUploadRA(true);
2605
+ }
2606
+ else if (uploadProviderDocPopup === "DbsCheck") {
2607
+ setUploadProviderDocPopup(undefined);
2608
+ setUploadDBS(true);
2609
+ }
2610
+ }
2611
+ };
2612
+ const withdrawFromPlacement = async () => {
2613
+ if (user.userType !== "Students")
2614
+ throw new Error("Must be a student to withdraw.");
2615
+ await (0, firebase_1.executeCallable)("placement-withdraw", { placementId: placementId });
2616
+ };
2617
+ return { placement, wStage, student, workflow, editable, withdrawFromPlacementPopup, addOnboardingDocsPopup, setAddOnboardingDocsPopup, dismissOnboardingPopup, setDismissOnboardingPopup, setWithdrawFromPlacementPopup, withdrawFromPlacement, onFlagClick, setUploadInsurance, setUploadProviderDocPopup, setUploadRA, setUploadDBS, onboardingStatus, setSkipStagePopup, onboardingPopup, setViewExternalLinkPopup, setOnboardingPopup, setRejectELIPopup, eliURL, riskAssessmentURL, dbsCheckURL, setExternalLinkCopied, skipStagePopup, snackbar, setSnackbar, cohort, disableEmail, rejectELIPopup, eliPopupOpen, rejectExternalDocPopup, externalDocPopupOpen, viewExternalLinkPopup, externalLinkCopied, uploadInsurance, uploadRA, uploadDBS, editStage, sendEmail, canEdit, approveELI, setEliPopupOpen, uploadProviderDocPopup, rejectELI, setRejectExternalDocPopup, setExternalDocPopupOpen, approveProviderDoc, rejectProviderDoc, manuallyConfigureProvider, institute };
2618
+ }
2619
+ function useOnboardingPopup({ onboarding, providerId, placementId, user, onClose }) {
2620
+ const [fileUploadPopup, setFileUploadPopup] = (0, react_1.useState)(false);
2621
+ const [form, setForm] = (0, react_1.useState)();
2622
+ const [rejectPopup, setRejectPopup] = (0, react_1.useState)(false);
2623
+ const [mOnboarding, setMOnboarding] = (0, react_1.useState)(onboarding);
2624
+ const [completedSections, setCompletedSections] = (0, react_1.useState)();
2625
+ const [uploadedFiles, setUploadedFiles] = (0, react_1.useState)({});
2626
+ const [viewableFiles, setViewableFiles] = (0, react_1.useState)();
2627
+ const [formDetails, setFormDetails] = (0, react_1.useState)({});
2628
+ const firebaseQuery = new firebaseQuery_1.default();
2629
+ const addFile = (files) => {
2630
+ if (!files.length || fileUploadPopup === false)
2631
+ return;
2632
+ setMOnboarding((a) => (0, util_1.editNestedObject)(["completed", "filesUploaded", fileUploadPopup], a, files));
2633
+ setFileUploadPopup(false);
2634
+ };
2635
+ const viewFile = (file, onOpen) => {
2636
+ setMOnboarding((a) => {
2637
+ var _a;
2638
+ const oldA = { ...a };
2639
+ const viewedFiles = a.completed ? ((_a = a.completed) === null || _a === void 0 ? void 0 : _a.filesViewed) || [] : [];
2640
+ if (viewedFiles === null || viewedFiles === void 0 ? void 0 : viewedFiles.includes(file))
2641
+ return a;
2642
+ (viewableFiles === null || viewableFiles === void 0 ? void 0 : viewableFiles[file].url) && onOpen(viewableFiles === null || viewableFiles === void 0 ? void 0 : viewableFiles[file].url);
2643
+ viewedFiles === null || viewedFiles === void 0 ? void 0 : viewedFiles.push(file);
2644
+ const newA = (0, util_1.editNestedObject)(["completed", "filesViewed"], oldA, viewedFiles);
2645
+ return newA;
2646
+ });
2647
+ };
2648
+ const setFormComplete = (e, formId) => {
2649
+ const mFormId = formId || (form === null || form === void 0 ? void 0 : form.id);
2650
+ if (!mFormId)
2651
+ return;
2652
+ setMOnboarding((a) => (0, util_1.editNestedObject)(["completed", "formsCompleted", mFormId], a, e));
2653
+ setFileUploadPopup(false);
2654
+ };
2655
+ (0, react_1.useEffect)(() => {
2656
+ if (!onboarding)
2657
+ return;
2658
+ const getOnboardingData = async () => {
2659
+ var _a, _b;
2660
+ const onboardingNew = { ...onboarding };
2661
+ const onboardingFiles = onboarding.files ? Object.fromEntries(await Promise.all((_a = onboarding.files) === null || _a === void 0 ? void 0 : _a.map(async (fileId) => {
2662
+ const file = await firebaseQuery.getDocData(["files", fileId]);
2663
+ file.url = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `providers/${providerId}/${file.fileName}`));
2664
+ return [fileId, file];
2665
+ }))) : [];
2666
+ const onboardingForms = onboarding.forms ? Object.fromEntries(await Promise.all((_b = onboarding.forms) === null || _b === void 0 ? void 0 : _b.map(async (formId) => {
2667
+ return [formId, await firebaseQuery.getDocData(["forms", formId])];
2668
+ }))) : [];
2669
+ setViewableFiles(onboardingFiles);
2670
+ setFormDetails(onboardingForms);
2671
+ return onboardingNew;
2672
+ };
2673
+ getOnboardingData().then(setMOnboarding);
2674
+ }, [onboarding]);
2675
+ (0, react_1.useEffect)(() => {
2676
+ console.log("onboarding change", mOnboarding, !(0, util_1.objectsEqual)(mOnboarding, onboarding));
2677
+ if (!(0, util_1.objectsEqual)(mOnboarding, onboarding) && placementId) {
2678
+ firebaseQuery.update(["placements", placementId], { onboarding: mOnboarding });
2679
+ }
2680
+ }, [mOnboarding]);
2681
+ const stagesCompleted = () => {
2682
+ var _a, _b;
2683
+ console.log("CompletedSections", completedSections);
2684
+ for (const fileViewed of (mOnboarding === null || mOnboarding === void 0 ? void 0 : mOnboarding.files) || []) {
2685
+ if (!((_a = completedSections === null || completedSections === void 0 ? void 0 : completedSections.filesViewed) === null || _a === void 0 ? void 0 : _a.includes(fileViewed))) {
2686
+ console.log("Checking file", fileViewed);
2687
+ console.log("File not completed");
2688
+ return false;
2689
+ }
2690
+ }
2691
+ for (const formCompleted of (mOnboarding === null || mOnboarding === void 0 ? void 0 : mOnboarding.forms) || []) {
2692
+ if (!Object.keys((completedSections === null || completedSections === void 0 ? void 0 : completedSections.formsCompleted) || {}).includes(formCompleted)) {
2693
+ console.log("Form not completed");
2694
+ return false;
2695
+ }
2696
+ }
2697
+ for (var i; i++; i < (((_b = mOnboarding === null || mOnboarding === void 0 ? void 0 : mOnboarding.requiredFiles) === null || _b === void 0 ? void 0 : _b.length) || 1)) {
2698
+ if (!Object.keys((completedSections === null || completedSections === void 0 ? void 0 : completedSections.filesUploaded) || {}).includes((i))) {
2699
+ return false;
2700
+ }
2701
+ }
2702
+ return true;
2703
+ };
2704
+ const rejectOnboarding = async ({ reason }) => {
2705
+ if (user.product !== "providers")
2706
+ return;
2707
+ return await (0, firebase_1.executeCallable)("placement-rejectOnboarding", { reason: reason, placementId: placementId }).then(() => {
2708
+ setRejectPopup(false);
2709
+ onClose();
2710
+ }).catch(() => {
2711
+ throw new Error("Error");
2712
+ });
2713
+ };
2714
+ const acceptOnboarding = async () => {
2715
+ if (user.product !== "providers" || !placementId)
2716
+ return;
2717
+ await firebaseQuery.update(["placements", placementId], { "onboarding.completed.accepted": true });
2718
+ };
2719
+ const submit = async () => {
2720
+ // Check all stages completed.
2721
+ if (!placementId)
2722
+ return;
2723
+ if (!stagesCompleted())
2724
+ throw new Error("Complete all forms before submitting.");
2725
+ await firebaseQuery.update(["placements", placementId], { ["onboarding.completed.accepted"]: false, ["onboarding.completed.submitted"]: true, ["onboarding.completed.submittedDate"]: (0, util_1.convertDate)(new Date(), "dbstring") });
2726
+ };
2727
+ (0, react_1.useEffect)(() => {
2728
+ const getUploadedFiles = async () => {
2729
+ if (!mOnboarding.completed) {
2730
+ setUploadedFiles({});
2731
+ return;
2732
+ }
2733
+ ;
2734
+ const fileIds = Object.values(mOnboarding.completed.filesUploaded || {})
2735
+ .flatMap(fileIds => fileIds);
2736
+ const fileDataPromises = fileIds.map(async (fileId) => {
2737
+ const fileData = await firebaseQuery.getDocData(["files", fileId]);
2738
+ fileData.url = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `userFiles/${fileData.fileName}`));
2739
+ return [fileId, fileData];
2740
+ });
2741
+ const fileDataArray = await Promise.all(fileDataPromises);
2742
+ const fileDataObj = Object.fromEntries(fileDataArray);
2743
+ setUploadedFiles(fileDataObj);
2744
+ };
2745
+ const addCompletedSectionURLs = async () => {
2746
+ const completedSectionsWithURLs = { ...mOnboarding === null || mOnboarding === void 0 ? void 0 : mOnboarding.completed };
2747
+ completedSectionsWithURLs.filesUploaded = Object.fromEntries(await Promise.all(Object.entries(completedSectionsWithURLs.filesUploaded || {}).map(async ([fileId, items]) => {
2748
+ const filesWithObjects = await Promise.all(items.map(async (itemId) => {
2749
+ const file = await firebaseQuery.getDocData(["files", itemId]);
2750
+ const fileUrl = await (0, storage_1.getDownloadURL)((0, storage_1.ref)(firebaseConfig_1.storage, `userFiles/${file.fileName}`));
2751
+ console.log("|FILEURL", fileUrl);
2752
+ return fileUrl;
2753
+ }));
2754
+ return [fileId, filesWithObjects];
2755
+ })));
2756
+ setCompletedSections(completedSectionsWithURLs);
2757
+ };
2758
+ getUploadedFiles();
2759
+ addCompletedSectionURLs();
2760
+ }, [mOnboarding]);
2761
+ return { addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup, viewableFiles, formDetails };
2762
+ }
2763
+ function useLoadAddresses(user, limitItems, queryConstraint) {
2764
+ const [addresses, setAddresses] = (0, react_1.useState)({});
2765
+ const [lastDoc, setLastDoc] = (0, react_1.useState)(null);
2766
+ const [loading, setLoading] = (0, react_1.useState)(false);
2767
+ const firebaseQuery = new firebaseQuery_1.default();
2768
+ const [queryConstraints, setQueryConstraints] = (0, react_1.useState)(queryConstraint || []);
2769
+ const changeQueryConstraints = (e) => {
2770
+ setQueryConstraints([...(queryConstraint || []), ...e]);
2771
+ };
2772
+ const loadAddresses = () => {
2773
+ var _a, _b, _c;
2774
+ const constraints = [(0, firestore_1.where)("oId", "==", user.oId), (0, firestore_1.where)("product", "==", user.product), (0, firestore_1.orderBy)((0, firestore_1.documentId)())];
2775
+ if (limitItems) {
2776
+ constraints.push((0, firestore_1.limit)(limitItems));
2777
+ }
2778
+ if (((_a = user.groupData) === null || _a === void 0 ? void 0 : _a.viewAddresses) === "all" || user.userGroup === "admin") {
2779
+ if (lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id) {
2780
+ constraints.push((0, firestore_1.startAfter)(lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id));
2781
+ }
2782
+ }
2783
+ else if (((_b = user.groupData) === null || _b === void 0 ? void 0 : _b.viewAddresses) === "request") {
2784
+ if (!((_c = user.visibleAddresses) === null || _c === void 0 ? void 0 : _c.length))
2785
+ return;
2786
+ constraints.push((0, firestore_1.where)((0, firestore_1.documentId)(), "in", user.visibleAddresses));
2787
+ if (lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id) {
2788
+ constraints.push((0, firestore_1.startAfter)(lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id));
2789
+ }
2790
+ }
2791
+ else {
2792
+ setLoading(false);
2793
+ return; // viewAddresses === "none", no need to load anything
2794
+ }
2795
+ queryConstraints && constraints.unshift(...queryConstraints);
2796
+ return firebaseQuery.collectionSnapshot((async (snapshot) => {
2797
+ const deletedAddresses = snapshot.docChanges().map((change) => {
2798
+ if (change.type === "removed") {
2799
+ return change.doc.id;
2800
+ }
2801
+ return;
2802
+ });
2803
+ setAddresses((prev) => Object.fromEntries(Object.entries(prev).filter(([k]) => !deletedAddresses.includes(k))));
2804
+ if (!snapshot.empty) {
2805
+ const newAddresses = snapshot.docs.map(doc => ([doc.id, { id: doc.id, ...doc.data() }]));
2806
+ const withListings = Object.fromEntries(await Promise.all(newAddresses.map(async ([k, address]) => {
2807
+ const listings = await firebaseQuery.getCount("placementListings", [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.where)("addressId", "==", k)]);
2808
+ return [k, { ...address, listings: listings }];
2809
+ })));
2810
+ setAddresses(prev => ({ ...prev, ...withListings }));
2811
+ setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
2812
+ }
2813
+ setLoading(false);
2814
+ }), "addresses", constraints, undefined, true);
2815
+ };
2816
+ (0, react_1.useEffect)(() => {
2817
+ const unsubscribe = loadAddresses();
2818
+ return () => {
2819
+ if (unsubscribe) {
2820
+ unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
2821
+ }
2822
+ };
2823
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2824
+ }, [queryConstraints]);
2825
+ const onScrollBottom = () => {
2826
+ if (!limitItems)
2827
+ return;
2828
+ if (!loading) {
2829
+ setLoading(true);
2830
+ loadAddresses();
2831
+ }
2832
+ };
2833
+ return { addresses, onScrollBottom, loading, changeQueryConstraints };
2834
+ }
2835
+ function useLoadListings(user, queryConstraint) {
2836
+ const [listings, setListings] = (0, react_1.useState)([]);
2837
+ const [lastDoc, setLastDoc] = (0, react_1.useState)(null);
2838
+ const [loading, setLoading] = (0, react_1.useState)(false);
2839
+ const firebaseQuery = new firebaseQuery_1.default();
2840
+ const [queryConstraints, setQueryConstraints] = (0, react_1.useState)(queryConstraint || []);
2841
+ const changeQueryConstraints = (e) => {
2842
+ setQueryConstraints([...(queryConstraint || []), ...e]);
2843
+ };
2844
+ const loadListings = () => {
2845
+ var _a, _b, _c, _d, _e;
2846
+ const constraints = [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.limit)(10), (0, firestore_1.orderBy)((0, firestore_1.documentId)())];
2847
+ if (((_a = user.groupData) === null || _a === void 0 ? void 0 : _a.viewPlacementListings) === "all" || user.userGroup === "admin") {
2848
+ if (lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id) {
2849
+ constraints.push((0, firestore_1.startAfter)(lastDoc));
2850
+ }
2851
+ }
2852
+ else if (((_b = user.groupData) === null || _b === void 0 ? void 0 : _b.viewPlacementListings) === "request") {
2853
+ if (!((_c = user.visibleListings) === null || _c === void 0 ? void 0 : _c.length))
2854
+ return;
2855
+ constraints.push((0, firestore_1.where)((0, firestore_1.documentId)(), 'in', user.visibleListings));
2856
+ if (lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id) {
2857
+ constraints.push((0, firestore_1.startAfter)(lastDoc));
2858
+ }
2859
+ }
2860
+ if (((_d = user.groupData) === null || _d === void 0 ? void 0 : _d.viewAddresses) !== "all" && user.userGroup !== "admin") {
2861
+ if (!((_e = user.visibleAddresses) === null || _e === void 0 ? void 0 : _e.length))
2862
+ return;
2863
+ constraints.push((0, firestore_1.where)('addressId', 'in', user.visibleAddresses));
2864
+ }
2865
+ queryConstraints && constraints.unshift(...queryConstraints);
2866
+ return firebaseQuery.collectionSnapshot((async (snapshot) => {
2867
+ const deletedListings = snapshot.docChanges().map((change) => {
2868
+ if (change.type === "removed") {
2869
+ return change.doc.id;
2870
+ }
2871
+ return;
2872
+ });
2873
+ setListings((prev) => prev.filter(([k]) => !deletedListings.includes(k)));
2874
+ if (!snapshot.empty) {
2875
+ const newListings = snapshot.docs.map(doc => ([doc.id, { ...doc.data(), id: doc.id }]));
2876
+ const listingsWithAdditionalData = await Promise.all(newListings.map(async ([id, listing]) => {
2877
+ const listingWithAdditionalData = { ...listing };
2878
+ if (listingWithAdditionalData.applicants !== undefined)
2879
+ return [id, listingWithAdditionalData];
2880
+ listingWithAdditionalData.applicants = await firebaseQuery.getCount("applications", [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.where)("placementId", "==", id), (0, firestore_1.where)("status", "==", "submitted")]);
2881
+ listingWithAdditionalData.scheduled = await firebaseQuery.getCount("placements", [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.where)("placementId", "==", id), (0, firestore_1.where)("inProgress", "==", true), (0, firestore_1.where)("startDate", ">", (0, util_1.convertDate)(new Date(), "dbstring"))]);
2882
+ listingWithAdditionalData.active = await firebaseQuery.getCount("placements", [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.where)("placementId", "==", id), (0, firestore_1.where)("active", "==", true)]);
2883
+ return [id, listingWithAdditionalData];
2884
+ }));
2885
+ setListings(prev => (Object.entries({ ...Object.fromEntries(prev), ...Object.fromEntries(listingsWithAdditionalData) })));
2886
+ setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
2887
+ }
2888
+ setLoading(false);
2889
+ }), "placementListings", constraints, undefined, true);
2890
+ };
2891
+ (0, react_1.useEffect)(() => {
2892
+ let unsubscribe;
2893
+ loadListings();
2894
+ return () => {
2895
+ if (unsubscribe) {
2896
+ unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
2897
+ }
2898
+ };
2899
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2900
+ }, [queryConstraints]);
2901
+ const onScrollBottom = () => {
2902
+ if (!loading) {
2903
+ setLoading(true);
2904
+ loadListings();
2905
+ }
2906
+ };
2907
+ return { listings, onScrollBottom, loading, changeQueryConstraints };
2908
+ }
2909
+ function useLoadApplications({ user, applicationType, listingId, queryConstraint }) {
2910
+ const [applications, setApplications] = (0, react_1.useState)([]);
2911
+ const [lastDoc, setLastDoc] = (0, react_1.useState)(null);
2912
+ const [loading, setLoading] = (0, react_1.useState)(false);
2913
+ const [type, setType] = (0, react_1.useState)(applicationType || "all");
2914
+ const firebaseQuery = new firebaseQuery_1.default();
2915
+ const [queryConstraints, setQueryConstraints] = (0, react_1.useState)(queryConstraint || []);
2916
+ const changeQueryConstraints = (e) => {
2917
+ setQueryConstraints([...(queryConstraint || []), ...e]);
2918
+ };
2919
+ const loadApplications = () => {
2920
+ var _a, _b, _c, _d;
2921
+ const constraints = [(0, firestore_1.where)("providerId", "==", user.oId), (0, firestore_1.limit)(10), (0, firestore_1.orderBy)((0, firestore_1.documentId)())];
2922
+ if (lastDoc === null || lastDoc === void 0 ? void 0 : lastDoc.id) {
2923
+ constraints.push((0, firestore_1.startAfter)(lastDoc));
2924
+ }
2925
+ switch (type) {
2926
+ case "actionRequired":
2927
+ constraints.push((0, firestore_1.where)("status", "==", "submitted"), (0, firestore_1.where)("reqUserType", "==", "Staff"));
2928
+ break;
2929
+ case "awaitingStudent":
2930
+ constraints.push((0, firestore_1.where)("status", "==", "submitted"), (0, firestore_1.where)("reqUserType", "==", "Students"));
2931
+ break;
2932
+ case "closed":
2933
+ constraints.push((0, firestore_1.where)("status", "in", ["approved", "declined"]));
2934
+ break;
2935
+ default:
2936
+ constraints.push((0, firestore_1.where)("status", "==", "submitted"));
2937
+ }
2938
+ if (listingId) {
2939
+ constraints.push((0, firestore_1.where)("listingId", "==", listingId));
2940
+ }
2941
+ console.log("Constraints before user group check", constraints);
2942
+ if (((_a = user.groupData) === null || _a === void 0 ? void 0 : _a.viewAddresses) !== "all" && user.userGroup !== "admin") {
2943
+ if (((_b = user.groupData) === null || _b === void 0 ? void 0 : _b.viewPlacementListings) === "all") {
2944
+ if (!((_c = user.visibleAddresses) === null || _c === void 0 ? void 0 : _c.length))
2945
+ return;
2946
+ constraints.push((0, firestore_1.where)('addressId', 'in', user.visibleAddresses));
2947
+ }
2948
+ else {
2949
+ if (!((_d = user.visibleListings) === null || _d === void 0 ? void 0 : _d.length))
2950
+ return;
2951
+ constraints.push((0, firestore_1.where)('placementId', 'in', user.visibleListings));
2952
+ }
2953
+ }
2954
+ queryConstraints && constraints.unshift(...queryConstraints);
2955
+ console.log("Constraints after user group check", constraints);
2956
+ return firebaseQuery.collectionSnapshot((async (snapshot) => {
2957
+ const deletedApplications = snapshot.docChanges().map((change) => {
2958
+ if (change.type === "removed") {
2959
+ return change.doc.id;
2960
+ }
2961
+ return;
2962
+ });
2963
+ console.log("applicantCount", snapshot.size);
2964
+ setApplications((prev) => prev.filter(([k]) => !deletedApplications.includes(k)));
2965
+ if (!snapshot.empty) {
2966
+ const newApplications = snapshot.docs.map(doc => ([doc.id, { id: doc.id, ...doc.data() }])).filter(([, v]) => v.status !== "draft");
2967
+ setApplications(prev => ([...prev, ...newApplications]));
2968
+ setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
2969
+ }
2970
+ setLoading(false);
2971
+ }), "applications", constraints, undefined, true);
2972
+ };
2973
+ (0, react_1.useEffect)(() => {
2974
+ let unsubscribe;
2975
+ loadApplications();
2976
+ return () => {
2977
+ if (unsubscribe) {
2978
+ unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
2979
+ }
2980
+ };
2981
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2982
+ }, [type, queryConstraints]);
2983
+ const onScrollBottom = () => {
2984
+ if (!loading) {
2985
+ setLoading(true);
2986
+ loadApplications();
2987
+ }
2988
+ };
2989
+ return { applications, type, setType, onScrollBottom, loading, changeQueryConstraints };
2990
+ }
2991
+ const useFirestoreQuery = (firebaseQuery, props) => {
2992
+ const { path, constraints = [], view, limit: itemsPerPage, formatItems, snapshot = false, filters = {}, search, onSearch, data: predefinedData } = props;
2993
+ const [data, setData] = (0, react_1.useState)(predefinedData || {});
2994
+ const [loading, setLoading] = (0, react_1.useState)(true);
2995
+ const [lastVisible, setLastVisible] = (0, react_1.useState)(null);
2996
+ const [firstVisible, setFirstVisible] = (0, react_1.useState)(null);
2997
+ const [currentPage, setCurrentPage] = (0, react_1.useState)(0);
2998
+ const listenersRef = (0, react_1.useRef)({});
2999
+ const buildConstraints = (0, react_1.useCallback)(() => {
3000
+ const allConstraints = [...constraints];
3001
+ Object.entries(filters).forEach(([key, value]) => {
3002
+ allConstraints.push((0, firestore_1.where)(key, '==', value));
3003
+ });
3004
+ return allConstraints;
3005
+ }, [constraints, filters]);
3006
+ const paginateData = (0, react_1.useCallback)((items, page) => {
3007
+ const startIndex = page * itemsPerPage;
3008
+ const endIndex = startIndex + itemsPerPage;
3009
+ const paginatedData = Object.entries(items).slice(startIndex, endIndex).reduce((acc, [key, value]) => {
3010
+ acc[key] = value;
3011
+ return acc;
3012
+ }, {});
3013
+ return paginatedData;
3014
+ }, [itemsPerPage]);
3015
+ const filterData = (0, react_1.useCallback)((items, searchString) => {
3016
+ const filteredData = Object.entries(items).filter(([key, value]) => {
3017
+ const itemString = JSON.stringify(value).toLowerCase();
3018
+ return itemString.includes(searchString.toLowerCase());
3019
+ }).reduce((acc, [key, value]) => {
3020
+ acc[key] = value;
3021
+ return acc;
3022
+ }, {});
3023
+ return filteredData;
3024
+ }, []);
3025
+ const applyFormatItems = (0, react_1.useCallback)(async (key, item) => {
3026
+ if (formatItems) {
3027
+ const result = formatItems(key, item);
3028
+ return result instanceof Promise ? await result : result;
3029
+ }
3030
+ return { key, item };
3031
+ }, [formatItems]);
3032
+ const setSearch = (0, react_1.useCallback)(async (newSearch, page = 0) => {
3033
+ if (predefinedData) {
3034
+ let results = predefinedData;
3035
+ if (newSearch) {
3036
+ results = filterData(results, newSearch);
3037
+ }
3038
+ const paginatedResults = paginateData(results, page);
3039
+ setData(paginatedResults);
3040
+ setLoading(Object.keys(paginatedResults).length < itemsPerPage ? 'loaded' : false);
3041
+ return;
3042
+ }
3043
+ if (onSearch) {
3044
+ setLoading(true);
3045
+ const results = await onSearch(newSearch, page, itemsPerPage);
3046
+ setData(results);
3047
+ setLoading(results && Object.keys(results).length < itemsPerPage ? 'loaded' : false);
3048
+ }
3049
+ }, [onSearch, itemsPerPage, filterData, paginateData, predefinedData]);
3050
+ const fetchData = (0, react_1.useCallback)(async (direction) => {
3051
+ if (snapshot) {
3052
+ return;
3053
+ }
3054
+ if (predefinedData) {
3055
+ setLoading(false);
3056
+ let results = predefinedData;
3057
+ if (search) {
3058
+ results = filterData(results, search);
3059
+ }
3060
+ const paginatedResults = paginateData(results, currentPage);
3061
+ setData(paginatedResults);
3062
+ setLoading(Object.keys(paginatedResults).length < itemsPerPage ? 'loaded' : false);
3063
+ return;
3064
+ }
3065
+ setLoading(true);
3066
+ const constraintsWithLimit = [...buildConstraints(), (0, firestore_1.limit)(itemsPerPage)];
3067
+ if (view === 'list' && lastVisible && direction === 'up') {
3068
+ constraintsWithLimit.push((0, firestore_1.startAfter)(lastVisible));
3069
+ }
3070
+ if (view === 'table' && direction) {
3071
+ if (direction === 'up' && lastVisible) {
3072
+ constraintsWithLimit.push((0, firestore_1.startAfter)(lastVisible));
3073
+ }
3074
+ if (direction === 'down' && firstVisible) {
3075
+ constraintsWithLimit.push((0, firestore_1.endBefore)(firstVisible));
3076
+ }
3077
+ }
3078
+ const results = await firebaseQuery.getDocsWhere(path, constraintsWithLimit, true);
3079
+ if (Object.keys(results).length === 0) {
3080
+ setLoading('loaded');
3081
+ return;
3082
+ }
3083
+ const formattedResults = {};
3084
+ for (const [key, item] of Object.entries(results)) {
3085
+ const formatted = await applyFormatItems(key, item);
3086
+ formattedResults[formatted.key] = formatted.item;
3087
+ }
3088
+ setData(prevData => (direction === 'down' ? formattedResults : { ...prevData, ...formattedResults }));
3089
+ const docEntries = Object.entries(results);
3090
+ if (direction === 'up' || !direction) {
3091
+ setLastVisible(docEntries[docEntries.length - 1][1]);
3092
+ }
3093
+ if (direction === 'down' || !direction) {
3094
+ setFirstVisible(docEntries[0][1]);
3095
+ }
3096
+ setLoading(false);
3097
+ }, [buildConstraints, firebaseQuery, applyFormatItems, lastVisible, firstVisible, view, snapshot, predefinedData, filterData, paginateData, currentPage, itemsPerPage]);
3098
+ const pageUp = (0, react_1.useCallback)(() => {
3099
+ setCurrentPage(prevPage => {
3100
+ const newPage = prevPage + 1;
3101
+ if (search && onSearch) {
3102
+ setSearch(search, newPage);
3103
+ }
3104
+ else {
3105
+ fetchData('up');
3106
+ }
3107
+ return newPage;
3108
+ });
3109
+ }, [fetchData, onSearch, search, setSearch]);
3110
+ const pageDown = (0, react_1.useCallback)(() => {
3111
+ setCurrentPage(prevPage => {
3112
+ const newPage = Math.max(prevPage - 1, 0);
3113
+ if (search && onSearch) {
3114
+ setSearch(search, newPage);
3115
+ }
3116
+ else {
3117
+ fetchData('down');
3118
+ }
3119
+ return newPage;
3120
+ });
3121
+ }, [fetchData, onSearch, search, setSearch]);
3122
+ const reset = (0, react_1.useCallback)(() => {
3123
+ setData(predefinedData || {});
3124
+ setLastVisible(null);
3125
+ setFirstVisible(null);
3126
+ setCurrentPage(0);
3127
+ setLoading(true);
3128
+ fetchData();
3129
+ }, [fetchData, predefinedData]);
3130
+ const setQueries = (0, react_1.useCallback)((newConstraints) => {
3131
+ reset();
3132
+ }, [reset]);
3133
+ const setFilters = (0, react_1.useCallback)((newFilters) => {
3134
+ reset();
3135
+ }, [reset]);
3136
+ (0, react_1.useEffect)(() => {
3137
+ if (!search) {
3138
+ fetchData();
3139
+ }
3140
+ else {
3141
+ setSearch(search, 0);
3142
+ }
3143
+ }, [fetchData, search, setSearch]);
3144
+ (0, react_1.useEffect)(() => {
3145
+ if (snapshot && path) {
3146
+ const constraintsWithLimit = [...buildConstraints(), (0, firestore_1.limit)(itemsPerPage)];
3147
+ const handleSnapshot = (querySnapshot) => {
3148
+ const items = {};
3149
+ querySnapshot.docs.forEach((doc) => {
3150
+ items[doc.id] = doc.data();
3151
+ });
3152
+ if (search) {
3153
+ const filteredData = filterData(items, search);
3154
+ const paginatedData = paginateData(filteredData, currentPage);
3155
+ setData(paginatedData);
3156
+ }
3157
+ else {
3158
+ const paginatedData = paginateData(items, currentPage);
3159
+ setData(paginatedData);
3160
+ }
3161
+ setLastVisible(querySnapshot.docs[querySnapshot.docs.length - 1] || null);
3162
+ setFirstVisible(querySnapshot.docs[0] || null);
3163
+ setLoading(Object.keys(items).length < itemsPerPage ? 'loaded' : false);
3164
+ };
3165
+ const unsubscribe = firebaseQuery.collectionSnapshot(handleSnapshot, path, constraintsWithLimit, undefined, true);
3166
+ listenersRef.current[path.toString()] = unsubscribe;
3167
+ return () => {
3168
+ if (listenersRef.current[path.toString()]) {
3169
+ listenersRef.current[path.toString()]();
3170
+ }
3171
+ };
3172
+ }
3173
+ return () => {
3174
+ Object.values(listenersRef.current).forEach(unsub => unsub());
3175
+ };
3176
+ }, [path, snapshot, buildConstraints, itemsPerPage, filterData, paginateData, currentPage, search, firebaseQuery]);
3177
+ return {
3178
+ data,
3179
+ loading,
3180
+ pageUp,
3181
+ pageDown: view === 'table' ? pageDown : undefined,
3182
+ setQueries,
3183
+ reset,
3184
+ setFilters,
3185
+ setSearch,
3186
+ };
3187
+ };
3188
+ exports.useFirestoreQuery = useFirestoreQuery;
1571
3189
  //# sourceMappingURL=hooks.js.map