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.
- package/.eslintrc.js +40 -40
- package/.gitattributes +2 -2
- package/lib/config.d.ts +0 -1
- package/lib/constants.d.ts +6 -2
- package/lib/constants.js +136 -136
- package/lib/constants.js.map +1 -1
- package/lib/features/analytics/useAnalytics.d.ts +0 -1
- package/lib/features/analytics/useAnalytics.js +3 -4
- package/lib/features/analytics/useAnalytics.js.map +1 -1
- package/lib/features/config.d.ts +133 -133
- package/lib/features/config.js +35 -35
- package/lib/features/contacts/contacts.d.ts +75 -75
- package/lib/features/contacts/contacts.js +105 -105
- package/lib/features/contacts/contactsSlice.d.ts +5 -5
- package/lib/features/contacts/useContacts.js +1 -2
- package/lib/features/contacts/useContacts.js.map +1 -1
- package/lib/features/downtime/useDowntime.d.ts +11 -11
- package/lib/features/downtime/useDowntime.js +22 -22
- package/lib/features/dropdown/useDropdown.d.ts +2 -3
- package/lib/features/dropdown/useDropdown.js +1 -2
- package/lib/features/dropdown/useDropdown.js.map +1 -1
- package/lib/features/global/downtime/useDowntime.d.ts +0 -1
- package/lib/features/global/downtime/useDowntime.js +1 -2
- package/lib/features/global/downtime/useDowntime.js.map +1 -1
- package/lib/features/global/users/useUserFunctions.d.ts +0 -1
- package/lib/features/global/users/useUserFunctions.js +7 -8
- package/lib/features/global/users/useUserFunctions.js.map +1 -1
- package/lib/features/placements/studentPlacements/activePlacement.d.ts +1 -1
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.d.ts +2 -2
- package/lib/features/placements/studentPlacements/studentPlacementsSlice.d.ts +63 -63
- package/lib/features/placements/studentPlacements/studentPlacementsSlice.js +81 -81
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.d.ts +2 -2
- package/lib/features/placements/studentPlacements/useStudentPlacements.d.ts +0 -1
- package/lib/features/placements/studentPlacements/useStudentPlacements.js +1 -2
- package/lib/features/placements/studentPlacements/useStudentPlacements.js.map +1 -1
- package/lib/features/providerPlacements/providerPlacementsSlice.d.ts +19 -19
- package/lib/features/providerPlacements/providerPlacementsSlice.js +24 -24
- package/lib/features/referrals/useReferrals.d.ts +0 -1
- package/lib/features/referrals/useReferrals.js +1 -2
- package/lib/features/referrals/useReferrals.js.map +1 -1
- package/lib/features/studentPlacements/studentPlacementsSlice.d.ts +62 -62
- package/lib/features/studentPlacements/studentPlacementsSlice.js +87 -87
- package/lib/features/studentPlacements/useStudentPlacements.d.ts +6 -6
- package/lib/features/studentPlacements/useStudentPlacements.js +18 -18
- package/lib/features/updates/useUpdates.js +1 -2
- package/lib/features/updates/useUpdates.js.map +1 -1
- package/lib/features/userSlice.d.ts +26 -26
- package/lib/features/userSlice.js +23 -23
- package/lib/features/users/useUserFunctions.d.ts +25 -25
- package/lib/features/users/useUserFunctions.js +124 -124
- package/lib/features/users/userSlice.d.ts +46 -46
- package/lib/features/users/userSlice.js +48 -48
- package/lib/firebase/firebase.d.ts +3 -1
- package/lib/firebase/firebase.js +9 -3
- package/lib/firebase/firebase.js.map +1 -1
- package/lib/firebase/firebaseConfig.js +3 -0
- package/lib/firebase/firebaseConfig.js.map +1 -1
- package/lib/firebase/firebaseQuery.d.ts +3 -1
- package/lib/firebase/firebaseQuery.js +11 -1
- package/lib/firebase/firebaseQuery.js.map +1 -1
- package/lib/firebase/persistence.js +2 -2
- package/lib/firebase/persistence.js.map +1 -1
- package/lib/firebase/readDatabase.d.ts +8 -7
- package/lib/firebase/readDatabase.js +41 -8
- package/lib/firebase/readDatabase.js.map +1 -1
- package/lib/firebase/util.d.ts +3 -4
- package/lib/firebase/util.js +49 -4
- package/lib/firebase/util.js.map +1 -1
- package/lib/firebase/writeDatabase.d.ts +7 -3
- package/lib/firebase/writeDatabase.js +9 -2
- package/lib/firebase/writeDatabase.js.map +1 -1
- package/lib/hooks.d.ts +476 -20
- package/lib/hooks.js +1855 -237
- package/lib/hooks.js.map +1 -1
- package/lib/images/GatsbyBenchmarks.d.ts +0 -1
- package/lib/images/GatsbyBenchmarks.js +1 -1
- package/lib/images/GatsbyBenchmarks.js.map +1 -1
- package/lib/reduxHooks.d.ts +9 -2
- package/lib/reduxHooks.js +36 -9
- package/lib/reduxHooks.js.map +1 -1
- package/lib/tasksAndTips.d.ts +25 -5
- package/lib/tasksAndTips.js +517 -48
- package/lib/tasksAndTips.js.map +1 -1
- package/lib/typeDefinitions.d.ts +472 -55
- package/lib/util.d.ts +1 -0
- package/lib/util.js +85 -7
- package/lib/util.js.map +1 -1
- package/package.json +52 -49
- package/src/DatabaseDefinitions.ts +18 -18
- package/src/apiCalls.ts +128 -128
- package/src/config.ts +50 -50
- package/src/constants.ts +714 -707
- package/src/databaseTypes.ts +42 -42
- package/src/features/analytics/useAnalytics.tsx +64 -64
- package/src/features/contacts/contactsSlice.ts +147 -147
- package/src/features/contacts/useContacts.tsx +73 -73
- package/src/features/dropdown/useDropdown.tsx +52 -52
- package/src/features/global/downtime/useDowntime.tsx +23 -23
- package/src/features/global/users/useUserFunctions.tsx +132 -132
- package/src/features/jobs/jobsSlice.ts +65 -65
- package/src/features/placements/studentPlacements/activePlacement.ts +68 -68
- package/src/features/placements/studentPlacements/completedStudentPlacementsSlice.ts +97 -97
- package/src/features/placements/studentPlacements/upcomingStudentPlacementsSlice.ts +108 -108
- package/src/features/placements/studentPlacements/useStudentPlacements.tsx +9 -9
- package/src/features/placements/types.ts +10 -10
- package/src/features/referrals/useReferrals.tsx +56 -56
- package/src/features/updates/useUpdates.tsx +38 -38
- package/src/firebase/firebase.tsx +144 -138
- package/src/firebase/firebaseConfig.tsx +45 -42
- package/src/firebase/firebaseQuery.tsx +150 -140
- package/src/firebase/persistence.ts +84 -84
- package/src/firebase/readDatabase.tsx +235 -197
- package/src/firebase/util.tsx +352 -308
- package/src/firebase/writeDatabase.tsx +77 -68
- package/src/hooks.tsx +4029 -1928
- package/src/images/GatsbyBenchmarks.tsx +711 -711
- package/src/images/LogFuturePlacement.jsx +64 -64
- package/src/images/LogPreviousPlacement.jsx +228 -228
- package/src/images/gatsby_benchmarks.svg +466 -466
- package/src/images/log_future_placement.svg +114 -114
- package/src/images/log_previous_placement.svg +199 -199
- package/src/index.ts +34 -34
- package/src/readDatabase.tsx +3 -3
- package/src/reduxHooks.ts +231 -200
- package/src/tasksAndTips.ts +917 -410
- package/src/tutorialTips.ts +58 -58
- package/src/typeDefinitions.ts +893 -503
- package/src/util.ts +137 -47
- package/tsconfig.dev.json +5 -5
- 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.
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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 (!
|
|
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
|
-
|
|
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") ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = ["
|
|
1595
|
+
const cohortStages = ["info", "name", "placementType", "review", "created"];
|
|
1067
1596
|
const defaultCohortData = {
|
|
1068
1597
|
name: "",
|
|
1069
|
-
stage: "
|
|
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
|
|
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
|
-
|
|
1142
|
-
|
|
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
|
-
|
|
1220
|
-
|
|
1221
|
-
const
|
|
1222
|
-
const
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
const
|
|
1226
|
-
|
|
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
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
-
|
|
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
|
|
1284
|
-
|
|
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
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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.
|
|
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
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
const escapableCycle = cyclicPath.some((
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
-
|
|
1843
|
+
throw new Error(`Inescapable cycle detected at ${stage.name}. Amend and resubmit.`);
|
|
1390
1844
|
}
|
|
1391
1845
|
return;
|
|
1392
1846
|
}
|
|
1393
|
-
currentPath.push(stage
|
|
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.
|
|
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,
|
|
1402
|
-
if ([1, 6, 8, 11].includes(
|
|
1403
|
-
acc.push(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1462
|
-
const 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
|
-
|
|
2286
|
+
setFWorkflowNodes((p) => (0, util_1.editNestedObject)([existingEdgePoint, "buttons"], p, buttons, true));
|
|
1471
2287
|
setNewEdgePoint(undefined);
|
|
1472
|
-
}, [
|
|
2288
|
+
}, [fWorkflowNodes]);
|
|
1473
2289
|
const openPopup = (0, react_1.useCallback)(() => {
|
|
1474
2290
|
setFilePopupActive(true);
|
|
1475
2291
|
}, []);
|
|
1476
|
-
return ({ ...{ filePopupActive, files, uploadFile,
|
|
1477
|
-
includedForms,
|
|
1478
|
-
onDeleteArrow,
|
|
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
|
-
|
|
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
|