placementt-core 11.0.533 → 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/lib/constants.d.ts +1 -0
- package/lib/constants.js +6 -0
- package/lib/constants.js.map +1 -1
- package/lib/features/analytics/useAnalytics.d.ts +2 -0
- package/lib/features/analytics/useAnalytics.js +21 -16
- package/lib/features/analytics/useAnalytics.js.map +1 -1
- package/lib/features/global/downtime/useDowntime.d.ts +1 -0
- package/lib/features/global/downtime/useDowntime.js +9 -7
- package/lib/features/global/downtime/useDowntime.js.map +1 -1
- package/lib/features/global/users/useUserFunctions.js +1 -1
- package/lib/features/global/users/useUserFunctions.js.map +1 -1
- package/lib/features/placements/studentPlacements/activePlacement.d.ts +5 -1
- package/lib/features/placements/studentPlacements/activePlacement.js +7 -3
- package/lib/features/placements/studentPlacements/activePlacement.js.map +1 -1
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.d.ts +3 -2
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.js +4 -1
- package/lib/features/placements/studentPlacements/completedStudentPlacementsSlice.js.map +1 -1
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.d.ts +2 -2
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.js +1 -1
- package/lib/features/placements/studentPlacements/upcomingStudentPlacementsSlice.js.map +1 -1
- package/lib/features/placements/studentPlacements/useStudentPlacements.d.ts +2 -12
- package/lib/features/placements/studentPlacements/useStudentPlacements.js +1 -26
- package/lib/features/placements/studentPlacements/useStudentPlacements.js.map +1 -1
- package/lib/features/updates/useUpdates.d.ts +1 -0
- package/lib/features/updates/useUpdates.js +13 -12
- package/lib/features/updates/useUpdates.js.map +1 -1
- package/lib/firebase/firebase.d.ts +3 -1
- package/lib/firebase/firebase.js +4 -3
- package/lib/firebase/firebase.js.map +1 -1
- package/lib/firebase/firebaseQuery.d.ts +3 -1
- package/lib/firebase/firebaseQuery.js +8 -1
- package/lib/firebase/firebaseQuery.js.map +1 -1
- package/lib/firebase/readDatabase.d.ts +2 -4
- package/lib/firebase/readDatabase.js +28 -5
- package/lib/firebase/readDatabase.js.map +1 -1
- package/lib/firebase/writeDatabase.d.ts +6 -2
- package/lib/firebase/writeDatabase.js +2 -1
- package/lib/firebase/writeDatabase.js.map +1 -1
- package/lib/hooks.d.ts +108 -9
- package/lib/hooks.js +547 -58
- package/lib/hooks.js.map +1 -1
- package/lib/reduxHooks.d.ts +41 -3
- package/lib/reduxHooks.js +61 -18
- package/lib/reduxHooks.js.map +1 -1
- package/lib/tasksAndTips.d.ts +1 -1
- package/lib/tasksAndTips.js +179 -8
- package/lib/tasksAndTips.js.map +1 -1
- package/lib/typeDefinitions.d.ts +14 -22
- package/lib/util.js +14 -8
- package/lib/util.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +7 -1
- package/src/features/analytics/useAnalytics.tsx +24 -15
- package/src/features/global/downtime/useDowntime.tsx +11 -7
- package/src/features/global/users/useUserFunctions.tsx +1 -1
- package/src/features/placements/studentPlacements/activePlacement.ts +8 -3
- package/src/features/placements/studentPlacements/completedStudentPlacementsSlice.ts +5 -2
- package/src/features/placements/studentPlacements/upcomingStudentPlacementsSlice.ts +2 -2
- package/src/features/placements/studentPlacements/useStudentPlacements.tsx +4 -28
- package/src/features/updates/useUpdates.tsx +14 -12
- package/src/firebase/firebase.tsx +5 -3
- package/src/firebase/firebaseQuery.tsx +8 -1
- package/src/firebase/readDatabase.tsx +33 -6
- package/src/firebase/writeDatabase.tsx +3 -1
- package/src/hooks.tsx +695 -61
- package/src/reduxHooks.ts +68 -20
- package/src/tasksAndTips.ts +184 -11
- package/src/typeDefinitions.ts +14 -20
- package/src/util.ts +14 -9
package/src/hooks.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
ApplicantStage,
|
|
16
16
|
ApplicantWorkflow,
|
|
17
17
|
Application,
|
|
18
|
-
ArrowObject, CohortData, FileItem, FlagCodes, InstituteData, OnboardingDocs, PlacementListing, Products,
|
|
18
|
+
ArrowObject, CohortData, CustomFormSchema, FileItem, FlagCodes, InstituteData, OnboardingDocs, PlacementListing, Products,
|
|
19
19
|
ProviderData,
|
|
20
20
|
QueryObject, SavedPlacement, StudentPlacementData, UserData, UserGroupData, WorkflowStage
|
|
21
21
|
} from "./typeDefinitions";
|
|
@@ -1356,7 +1356,7 @@ export function usePublicPlacementListingLoader({providerId, number=5}:PublicPla
|
|
|
1356
1356
|
const applicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", itemObj.applicantWorkflowId]) as ApplicantWorkflow).workflow.filter((i) => i.id === 1)[0];
|
|
1357
1357
|
const applicantFiles = applicantWorkflow.files ? Object.fromEntries(await Promise.all(applicantWorkflow.files?.map(async (fileId) => {
|
|
1358
1358
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1359
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
1359
|
+
file.url = await getDownloadURL(ref(storage, `providers/${itemObj.providerId}/${file.fileName}`));
|
|
1360
1360
|
|
|
1361
1361
|
return [fileId, file];
|
|
1362
1362
|
}))) : [];
|
|
@@ -1408,7 +1408,7 @@ export type ApplicationHookParams = {
|
|
|
1408
1408
|
profileUrl: string | undefined;
|
|
1409
1409
|
successPopup: "submitted" | "draftSaved" | "stageComplete" | "outcome" | undefined;
|
|
1410
1410
|
fApplicationId: string | undefined;
|
|
1411
|
-
fListing: PlacementListing | undefined;
|
|
1411
|
+
fListing: PlacementListing | false | undefined;
|
|
1412
1412
|
student: UserData | undefined;
|
|
1413
1413
|
fProvider: {
|
|
1414
1414
|
details?: ProviderData;
|
|
@@ -1458,13 +1458,14 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1458
1458
|
const [fApplication, setFApplication] = useState<Partial<Application>>(application ? applicationWithoutAdditionalData : {
|
|
1459
1459
|
uid: user.userType === "Students" ? user.id : undefined,
|
|
1460
1460
|
listingId: listingId,
|
|
1461
|
+
addressId: listing?.addressId,
|
|
1461
1462
|
stage: 1,
|
|
1462
1463
|
reqUserType: "Students",
|
|
1463
1464
|
status: "draft"});
|
|
1464
1465
|
const [fApplicationId, setFApplicationId] = useState<string|undefined>(applicationId);
|
|
1465
1466
|
const [draftSaved, setDraftSaved] = useState(false);
|
|
1466
1467
|
const [fProvider, setFProvider] = useState<{details?: ProviderData, profile?: string, id?: string}|undefined>(provider);
|
|
1467
|
-
const [fListing, setFListing] = useState<PlacementListing|undefined>(listing);
|
|
1468
|
+
const [fListing, setFListing] = useState<PlacementListing|false|undefined>(Object.keys(listing || {}).length > 5 ? listing : undefined);
|
|
1468
1469
|
const [student, setStudent] = useState<UserData|undefined>(user.userType === "Students" ? user : undefined);
|
|
1469
1470
|
const [profileUrl, setProfileUrl] = useState<string>();
|
|
1470
1471
|
const [successPopup, setSuccessPopup] = useState<"submitted"|"draftSaved"|"stageComplete"|"outcome">();
|
|
@@ -1476,34 +1477,44 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1476
1477
|
console.log("Checking ID")
|
|
1477
1478
|
if (!listingId) return;
|
|
1478
1479
|
console.log("Getting listing")
|
|
1480
|
+
console.log("LISTING PARAM", listing, Object.keys(listing || {}).length > 5);
|
|
1479
1481
|
|
|
1480
|
-
const listingData = listing || await firebaseQuery.getDocData(["placementListings", listingId]) as PlacementListing;
|
|
1481
|
-
|
|
1482
|
-
|
|
1482
|
+
const listingData = (Object.keys(listing || {}).length > 5) ? listing : (await firebaseQuery.getDocData(["placementListings", listingId]).catch(() => false) as PlacementListing|false);
|
|
1483
|
+
|
|
1484
|
+
console.log("LISTINGDATA", listingData, Object.keys(listing || {}).length > 5 ? {a: "string"} : "AAA");
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
const address = listingData ? (user.product === "providers" && orgContext) ? orgContext.addresses[listingData.addressId || ""] : await firebaseQuery.getDocData(["addresses", listingData.addressId as string]) as Address : undefined;
|
|
1489
|
+
const workflow = listingData ? (user.product === "providers" && orgContext) ? orgContext.applicantWorkflows[listingData.applicantWorkflowId || ""] as ApplicantWorkflow : await firebaseQuery.getDocData(["applicantWorkflows", listingData.applicantWorkflowId as string]) as ApplicantWorkflow : undefined;
|
|
1483
1490
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1491
|
+
if (workflow && listingData) {
|
|
1492
|
+
workflow.workflow = await Promise.all(workflow.workflow.map(async (s) => {
|
|
1493
|
+
const applicantFiles = s.files ? Object.fromEntries(await Promise.all(s.files?.map(async (fileId: string) => {
|
|
1494
|
+
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1495
|
+
file.url = await getDownloadURL(ref(storage, `providers/${listingData?.providerId}/${file.fileName}`));
|
|
1496
|
+
|
|
1497
|
+
return [fileId, file];
|
|
1498
|
+
}))) : [];
|
|
1499
|
+
const applicantForms = s.forms ? Object.fromEntries(await Promise.all(s.forms?.map(async (formId: string) => {
|
|
1500
|
+
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
1501
|
+
}))) : [];
|
|
1502
|
+
|
|
1503
|
+
return {...s, viewableFiles: applicantFiles, formDetails: applicantForms};
|
|
1504
|
+
}));
|
|
1505
|
+
delete workflow.id;
|
|
1506
|
+
}
|
|
1507
|
+
if (address) {
|
|
1508
|
+
delete address.id;
|
|
1509
|
+
}
|
|
1497
1510
|
|
|
1498
|
-
delete address.id;
|
|
1499
|
-
delete workflow.id;
|
|
1500
1511
|
console.log("Setting listing")
|
|
1501
|
-
setFListing({...listingData, ...address, applicantWorkflow: workflow.workflow});
|
|
1512
|
+
setFListing((listingData && workflow) ? {...listingData, ...address, applicantWorkflow: workflow.workflow} : false);
|
|
1513
|
+
|
|
1502
1514
|
|
|
1503
|
-
console.log("Getting provider", listingData?.providerId);
|
|
1504
1515
|
if ((fProvider?.id === application?.providerId) && user.product === "providers") {
|
|
1505
1516
|
setFProvider({details: orgContext?.details, id: user.oId});
|
|
1506
|
-
} else if (listingData?.providerId) {
|
|
1517
|
+
} else if (listingData && listingData?.providerId) {
|
|
1507
1518
|
console.log("Getting provider from DB");
|
|
1508
1519
|
const provider = await firebaseQuery.getDocData(["providers", listingData.providerId]) as ProviderData;
|
|
1509
1520
|
console.log("Provider", provider);
|
|
@@ -1525,7 +1536,7 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1525
1536
|
}, []);
|
|
1526
1537
|
|
|
1527
1538
|
useEffect(() => {
|
|
1528
|
-
setFListing(listing);
|
|
1539
|
+
setFListing(Object.keys(listing || {}).length > 5 ? listing : undefined);
|
|
1529
1540
|
}, [listing]);
|
|
1530
1541
|
|
|
1531
1542
|
useEffect(() => {
|
|
@@ -1550,7 +1561,18 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1550
1561
|
});
|
|
1551
1562
|
return;
|
|
1552
1563
|
}
|
|
1553
|
-
if (applicationId)
|
|
1564
|
+
if (applicationId) {
|
|
1565
|
+
setFApplicationId(applicationId);
|
|
1566
|
+
if (application) {
|
|
1567
|
+
let applicationWithoutAdditionalData = {...(application || {})} as any;
|
|
1568
|
+
delete applicationWithoutAdditionalData.listing;
|
|
1569
|
+
delete applicationWithoutAdditionalData.address;
|
|
1570
|
+
delete applicationWithoutAdditionalData.provider;
|
|
1571
|
+
setFApplication(applicationWithoutAdditionalData);
|
|
1572
|
+
} else {
|
|
1573
|
+
firebaseQuery.getDocData(["applications", applicationId]).then(setFApplication);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1554
1576
|
}, [application, applicationId]);
|
|
1555
1577
|
|
|
1556
1578
|
const getCurrentStage = async (stage: number): Promise<{
|
|
@@ -1565,6 +1587,8 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1565
1587
|
} | undefined;
|
|
1566
1588
|
};
|
|
1567
1589
|
}> => {
|
|
1590
|
+
console.log("fLSITING CURRENT STAGE", fListing);
|
|
1591
|
+
if (!fListing) throw new Error("Listing deleted");
|
|
1568
1592
|
if (!fListing?.applicantWorkflowId) throw new Error("No workflow stage");
|
|
1569
1593
|
|
|
1570
1594
|
const mApplicantWorkflow = (await firebaseQuery.getDocData(["applicantWorkflows", fListing.applicantWorkflowId]) as ApplicantWorkflow);
|
|
@@ -1575,7 +1599,7 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1575
1599
|
|
|
1576
1600
|
const applicantFiles = stageObj.files ? Object.fromEntries(await Promise.all(stageObj.files?.map(async (fileId) => {
|
|
1577
1601
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
1578
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
1602
|
+
file.url = await getDownloadURL(ref(storage, `providers/${mApplicantWorkflow?.oId}/${file.fileName}`));
|
|
1579
1603
|
|
|
1580
1604
|
return [fileId, file];
|
|
1581
1605
|
}))) : [];
|
|
@@ -1615,11 +1639,12 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1615
1639
|
|
|
1616
1640
|
const addApplication = async () => {
|
|
1617
1641
|
console.log("ADDING APPLICATION");
|
|
1618
|
-
if (!fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1642
|
+
if (!fListing || !fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1619
1643
|
|
|
1620
1644
|
const applicationData = {
|
|
1621
1645
|
uid: student.id,
|
|
1622
1646
|
listingId: fListing?.id,
|
|
1647
|
+
addressId: fListing.addressId,
|
|
1623
1648
|
applicantWorkflowId: fListing?.applicantWorkflowId,
|
|
1624
1649
|
providerId: fProvider.id,
|
|
1625
1650
|
stage: 1,
|
|
@@ -1637,9 +1662,9 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1637
1662
|
getUploadedFiles();
|
|
1638
1663
|
|
|
1639
1664
|
console.log("Checking IDs");
|
|
1640
|
-
|
|
1641
|
-
if (!fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1642
|
-
if (user.product === "providers")
|
|
1665
|
+
|
|
1666
|
+
if (!fListing || !fListing?.id || !fProvider?.id || !student?.id) return;
|
|
1667
|
+
if (user.product === "providers") return;
|
|
1643
1668
|
|
|
1644
1669
|
console.log("Checking dates and sections");
|
|
1645
1670
|
if (!fApplication.completedSections && !fApplication.startDate && !fApplication.endDate) return;
|
|
@@ -1709,6 +1734,7 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1709
1734
|
const viewFile = (file: string, onOpen: (url: string) => void) => {
|
|
1710
1735
|
if (fApplication.reqUserType !== user.userType) return;
|
|
1711
1736
|
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1737
|
+
if (!fListing) throw new Error("No associated listing.");
|
|
1712
1738
|
|
|
1713
1739
|
const viewableFiles = fListing?.applicantWorkflow?.find((stage) => stage.id === fApplication.stage)?.viewableFiles;
|
|
1714
1740
|
|
|
@@ -1763,21 +1789,29 @@ export function useCreateApplicationRenderer({user, listingId, listing, provider
|
|
|
1763
1789
|
openSuccessPopup("draftSaved")
|
|
1764
1790
|
return;
|
|
1765
1791
|
}
|
|
1792
|
+
|
|
1793
|
+
if (!fApplicationId) return;
|
|
1766
1794
|
|
|
1767
1795
|
// Check all items have been filled in.
|
|
1768
1796
|
if (!fApplication.startDate || !fApplication.endDate) throw new Error("Please select dates for your placement.");
|
|
1769
1797
|
|
|
1770
1798
|
await executeCallable("applications-submit", {applicationId: fApplicationId});
|
|
1799
|
+
|
|
1800
|
+
const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]) as Application;
|
|
1801
|
+
setFApplication(newApplication);
|
|
1771
1802
|
openSuccessPopup("submitted")
|
|
1772
1803
|
};
|
|
1773
1804
|
|
|
1774
1805
|
const progressStage = async (type: number|"accept"|"reject", e?: {feedback?: string}) => {
|
|
1775
1806
|
// Check all stages completed.
|
|
1807
|
+
if (!fApplicationId) return;
|
|
1776
1808
|
if (!currentStageComplete) throw new Error("Complete all forms before submitting.");
|
|
1777
1809
|
if (fApplication.reqUserType !== user.userType) throw new Error(`Incorrect user type. Expected ${fApplication.reqUserType}, got: ${user.userType}`);
|
|
1778
1810
|
if (fApplication.stage === undefined) throw new Error("Missing applciation stage.")
|
|
1779
1811
|
|
|
1780
1812
|
await executeCallable("applications-changeStage", {applicationId: fApplicationId, type: type, feedback: e?.feedback});
|
|
1813
|
+
const newApplication = await firebaseQuery.getDocData(["applications", fApplicationId]) as Application;
|
|
1814
|
+
setFApplication(newApplication);
|
|
1781
1815
|
};
|
|
1782
1816
|
|
|
1783
1817
|
|
|
@@ -1795,8 +1829,6 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1795
1829
|
const [student, setStudent] = useState<UserData|undefined>((user && (user?.userType === "Students")) ? user : undefined);
|
|
1796
1830
|
const [cohort, setCohort] = useState<CohortData>();
|
|
1797
1831
|
const [complete, setComplete] = useState(false);
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
1832
|
const [stage, setStage] = useState<"provider"|"address"|"dates"|"review">("dates");
|
|
1801
1833
|
|
|
1802
1834
|
const sections:Array<"dates"|"provider"|"address"|"review"> = ["dates", "provider", "address", "review"];
|
|
@@ -1865,16 +1897,32 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1865
1897
|
setFormData(undefined);
|
|
1866
1898
|
};
|
|
1867
1899
|
|
|
1868
|
-
const proposePlacement = async (draft=false) => {
|
|
1900
|
+
const proposePlacement = async (draft = false) => {
|
|
1901
|
+
const getPlacementStatus = (startDate: Date, endDate: Date) => {
|
|
1902
|
+
const today = new Date()
|
|
1903
|
+
if (startDate <= today && endDate >= today) return { active: !draft ? true : false, inProgress: true, completed: false};
|
|
1904
|
+
else if (endDate <= today) return { completed: true, inProgress: false, active: false};
|
|
1905
|
+
return { completed: false, inProgress: true, active: false };
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1869
1908
|
if (!formData || !student) {
|
|
1870
1909
|
throw new Error("Cannot find placement details.");
|
|
1871
1910
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1911
|
+
|
|
1912
|
+
try {
|
|
1913
|
+
console.log("formData", formData);
|
|
1914
|
+
const status = getPlacementStatus(new Date(formData.startDate), new Date(formData.endDate));
|
|
1915
|
+
const newFormData = {...formData, ...status}
|
|
1916
|
+
|
|
1917
|
+
if (newFormData.id && newFormData.uid && draft === newFormData.draft) await firebaseQuery.update(["placements", newFormData.id], newFormData);
|
|
1918
|
+
else await addPlacement(newFormData, student.id, draft)
|
|
1919
|
+
return {status}
|
|
1920
|
+
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
console.log("Error:", error);
|
|
1923
|
+
throw error;
|
|
1876
1924
|
}
|
|
1877
|
-
|
|
1925
|
+
/*
|
|
1878
1926
|
console.log("STUDENTID", student.id);
|
|
1879
1927
|
|
|
1880
1928
|
return await addPlacement(formData, student.id, draft).catch((e) => {
|
|
@@ -1885,9 +1933,9 @@ export function useProposePlacementRenderer({user, orgContext, placement}:
|
|
|
1885
1933
|
setComplete(true);
|
|
1886
1934
|
return e;
|
|
1887
1935
|
});
|
|
1888
|
-
|
|
1889
|
-
|
|
1936
|
+
*/
|
|
1890
1937
|
};
|
|
1938
|
+
|
|
1891
1939
|
|
|
1892
1940
|
const deletePlacement = async (id:string) => {
|
|
1893
1941
|
return await firebaseQuery.delete(["placements", id]);
|
|
@@ -2982,6 +3030,9 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
2982
3030
|
const [uploadRA, setUploadRA] = useState(false);
|
|
2983
3031
|
const [uploadDBS, setUploadDBS] = useState(false);
|
|
2984
3032
|
const [onboardingPopup, setOnboardingPopup] = useState(false);
|
|
3033
|
+
const [dismissOnboardingPopup, setDismissOnboardingPopup] = useState(false);
|
|
3034
|
+
const [addOnboardingDocsPopup, setAddOnboardingDocsPopup] = useState(false);
|
|
3035
|
+
|
|
2985
3036
|
const [editable, setEditable] = useState(false);
|
|
2986
3037
|
|
|
2987
3038
|
const [withdrawFromPlacementPopup, setWithdrawFromPlacementPopup] = useState(false);
|
|
@@ -3031,7 +3082,7 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3031
3082
|
if (user.userType === "Students") {
|
|
3032
3083
|
setStudent(user);
|
|
3033
3084
|
} else {
|
|
3034
|
-
getUserById(placement.uid).then(setStudent);
|
|
3085
|
+
getUserById(placement.uid, undefined, false).then(setStudent);
|
|
3035
3086
|
}
|
|
3036
3087
|
|
|
3037
3088
|
}, [placement]);
|
|
@@ -3097,15 +3148,14 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3097
3148
|
};
|
|
3098
3149
|
|
|
3099
3150
|
|
|
3100
|
-
const onboardingStatus:"Add onboarding documents"|"Onboarding sent"|"Onboarding docs completed"|"Onboarding docs approved"|"Complete onboarding" =
|
|
3151
|
+
const onboardingStatus:"Add onboarding documents"|"Onboarding sent"|"Onboarding docs completed"|"Onboarding docs approved"|"Complete onboarding" =
|
|
3152
|
+
placement?.onboarding ? placement.onboarding.completed?.submitted ? placement.onboarding.completed.accepted ? "Onboarding docs approved" : "Onboarding docs completed" : (user.userType === "Staff" ? "Onboarding sent" : "Complete onboarding") : "Add onboarding documents";
|
|
3101
3153
|
|
|
3102
3154
|
const signOffPlacements = getAccess(user, "signOffPlacements");
|
|
3103
3155
|
|
|
3104
3156
|
let canEdit = false;
|
|
3105
3157
|
|
|
3106
|
-
if ((wStage?.userType === "Staff" && user.product === "
|
|
3107
|
-
canEdit = true;
|
|
3108
|
-
} else if ((wStage?.userType === "Staff" && user.userType === "Staff" && user.product === "institutes") || (user.product === "providers" && wStage?.userType === "Provider") || user.userType === "Students" && wStage?.userType === "Students") {
|
|
3158
|
+
if ((wStage?.userType === "Staff" && user.userType === "Staff" && user.product === "institutes") || (user.product === "providers" && wStage?.userType === "Provider") || user.userType === "Students" && wStage?.userType === "Students") {
|
|
3109
3159
|
console.log("ALMOST CAN EDIT");
|
|
3110
3160
|
if (user.userType === "Staff" && !signOffPlacements) {
|
|
3111
3161
|
canEdit = false;
|
|
@@ -3116,7 +3166,7 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3116
3166
|
|
|
3117
3167
|
|
|
3118
3168
|
|
|
3119
|
-
const onFlagClick = async (e:FlagCodes) => {
|
|
3169
|
+
const onFlagClick = async (e:FlagCodes, onClose?: boolean) => {
|
|
3120
3170
|
if (!placement) return;
|
|
3121
3171
|
if (e === "completeOnboarding" || e === "reviewOnboarding") {
|
|
3122
3172
|
setOnboardingPopup(true);
|
|
@@ -3146,6 +3196,13 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3146
3196
|
}
|
|
3147
3197
|
setExternalDocPopupOpen("dbsCheck");
|
|
3148
3198
|
}
|
|
3199
|
+
if (e === "addOnboarding") {
|
|
3200
|
+
if (onClose) {
|
|
3201
|
+
setDismissOnboardingPopup(true);
|
|
3202
|
+
} else {
|
|
3203
|
+
setAddOnboardingDocsPopup(true);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3149
3206
|
};
|
|
3150
3207
|
|
|
3151
3208
|
const approveELI = async () => {
|
|
@@ -3187,7 +3244,7 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3187
3244
|
throw e;
|
|
3188
3245
|
});
|
|
3189
3246
|
console.log("RETURN", res.data);
|
|
3190
|
-
setPlacement((p) => ({...p, ...res.data}));
|
|
3247
|
+
setPlacement((p) => ({...p, ...res.data as any}));
|
|
3191
3248
|
if (uploadProviderDocPopup === "insurance") {
|
|
3192
3249
|
setUploadProviderDocPopup(undefined);
|
|
3193
3250
|
setUploadInsurance(true);
|
|
@@ -3206,26 +3263,28 @@ export function useGetIndividualPlacementForPlacementPage({user, placementId, or
|
|
|
3206
3263
|
await executeCallable("placement-withdraw", {placementId: placementId});
|
|
3207
3264
|
}
|
|
3208
3265
|
|
|
3209
|
-
return {placement, wStage, student, workflow, editable, withdrawFromPlacementPopup, setWithdrawFromPlacementPopup, withdrawFromPlacement, onFlagClick, setUploadInsurance, setUploadProviderDocPopup, setUploadRA, setUploadDBS, onboardingStatus, setSkipStagePopup, onboardingPopup, setViewExternalLinkPopup, setOnboardingPopup, setRejectELIPopup, eliURL, riskAssessmentURL, dbsCheckURL, setExternalLinkCopied, skipStagePopup, snackbar, setSnackbar, cohort, disableEmail, rejectELIPopup, eliPopupOpen, rejectExternalDocPopup, externalDocPopupOpen, viewExternalLinkPopup, externalLinkCopied, uploadInsurance, uploadRA, uploadDBS, editStage, sendEmail, canEdit, approveELI, setEliPopupOpen, uploadProviderDocPopup, rejectELI, setRejectExternalDocPopup, setExternalDocPopupOpen, approveProviderDoc, rejectProviderDoc, manuallyConfigureProvider, institute}
|
|
3266
|
+
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}
|
|
3210
3267
|
}
|
|
3211
3268
|
|
|
3212
|
-
export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onboarding: (
|
|
3269
|
+
export function useOnboardingPopup({onboarding, providerId, placementId, user, onClose}:{onboarding: (
|
|
3213
3270
|
OnboardingDocs&{
|
|
3214
3271
|
completed: {
|
|
3215
|
-
submitted:
|
|
3272
|
+
submitted: boolean,
|
|
3273
|
+
submittedDate?: string,
|
|
3216
3274
|
accepted?: boolean,
|
|
3217
3275
|
filesViewed?: string[],
|
|
3218
3276
|
formsCompleted?: {[key: string]: unknown},
|
|
3219
3277
|
filesUploaded?: {[key: number]: string[]},
|
|
3220
3278
|
}}
|
|
3221
|
-
), placementId: string, user: UserData, onClose: () => void}) {
|
|
3279
|
+
), placementId: string, user: UserData, providerId: string, onClose: () => void}) {
|
|
3222
3280
|
|
|
3223
3281
|
const [fileUploadPopup, setFileUploadPopup] = useState<false|number>(false);
|
|
3224
3282
|
const [form, setForm] = useState<{id: string, name: string, [key:string]: unknown}>();
|
|
3225
3283
|
const [rejectPopup, setRejectPopup] = useState(false);
|
|
3226
3284
|
const [mOnboarding, setMOnboarding] = useState(onboarding);
|
|
3227
3285
|
const [completedSections, setCompletedSections] = useState<{
|
|
3228
|
-
submitted:
|
|
3286
|
+
submitted: boolean,
|
|
3287
|
+
submittedDate?: string,
|
|
3229
3288
|
filesViewed?: string[] | undefined;
|
|
3230
3289
|
formsCompleted?: {
|
|
3231
3290
|
[key: string]: unknown;
|
|
@@ -3234,7 +3293,19 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3234
3293
|
[key: number]: string[];
|
|
3235
3294
|
} | undefined;
|
|
3236
3295
|
}>();
|
|
3296
|
+
|
|
3237
3297
|
const [uploadedFiles, setUploadedFiles] = useState<{[key: string]: FileItem}>({});
|
|
3298
|
+
const [viewableFiles, setViewableFiles] = useState<{[key: string]: FileItem}>();
|
|
3299
|
+
const [formDetails, setFormDetails] = useState<{
|
|
3300
|
+
[key: string]: {
|
|
3301
|
+
name: string;
|
|
3302
|
+
id: string;
|
|
3303
|
+
description?: string;
|
|
3304
|
+
product: Products;
|
|
3305
|
+
oId: string;
|
|
3306
|
+
form: CustomFormSchema;
|
|
3307
|
+
};
|
|
3308
|
+
}>({});
|
|
3238
3309
|
|
|
3239
3310
|
const firebaseQuery = new FirebaseQuery();
|
|
3240
3311
|
|
|
@@ -3252,7 +3323,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3252
3323
|
const viewedFiles = a.completed ? a.completed?.filesViewed || [] : [];
|
|
3253
3324
|
if (viewedFiles?.includes(file)) return a;
|
|
3254
3325
|
|
|
3255
|
-
|
|
3326
|
+
viewableFiles?.[file].url && onOpen(viewableFiles?.[file].url);
|
|
3256
3327
|
viewedFiles?.push(file);
|
|
3257
3328
|
|
|
3258
3329
|
const newA = editNestedObject(["completed", "filesViewed"], oldA, viewedFiles) as any;
|
|
@@ -3275,7 +3346,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3275
3346
|
|
|
3276
3347
|
const onboardingFiles = onboarding.files ? Object.fromEntries(await Promise.all(onboarding.files?.map(async (fileId) => {
|
|
3277
3348
|
const file = await firebaseQuery.getDocData(["files", fileId]);
|
|
3278
|
-
file.url = await getDownloadURL(ref(storage, `
|
|
3349
|
+
file.url = await getDownloadURL(ref(storage, `providers/${providerId}/${file.fileName}`));
|
|
3279
3350
|
|
|
3280
3351
|
return [fileId, file];
|
|
3281
3352
|
}))) : [];
|
|
@@ -3283,8 +3354,8 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3283
3354
|
return [formId, await firebaseQuery.getDocData(["forms", formId])];
|
|
3284
3355
|
}))) : [];
|
|
3285
3356
|
|
|
3286
|
-
|
|
3287
|
-
|
|
3357
|
+
setViewableFiles(onboardingFiles);
|
|
3358
|
+
setFormDetails(onboardingForms);
|
|
3288
3359
|
return onboardingNew;
|
|
3289
3360
|
};
|
|
3290
3361
|
getOnboardingData().then(setMOnboarding);
|
|
@@ -3346,7 +3417,7 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3346
3417
|
// Check all stages completed.
|
|
3347
3418
|
if (!placementId) return;
|
|
3348
3419
|
if (!stagesCompleted()) throw new Error("Complete all forms before submitting.");
|
|
3349
|
-
await firebaseQuery.update(["placements", placementId], {["onboarding.completed.submitted"]: convertDate(new Date(), "dbstring") as string});
|
|
3420
|
+
await firebaseQuery.update(["placements", placementId], {["onboarding.completed.accepted"]: false, ["onboarding.completed.submitted"]: true, ["onboarding.completed.submittedDate"]: convertDate(new Date(), "dbstring") as string});
|
|
3350
3421
|
};
|
|
3351
3422
|
|
|
3352
3423
|
useEffect(() => {
|
|
@@ -3391,5 +3462,568 @@ export function useOnboardingPopup({onboarding, placementId, user, onClose}:{onb
|
|
|
3391
3462
|
addCompletedSectionURLs();
|
|
3392
3463
|
}, [mOnboarding]);
|
|
3393
3464
|
|
|
3394
|
-
return {addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup};
|
|
3395
|
-
}
|
|
3465
|
+
return {addFile, viewFile, uploadedFiles, setFormComplete, setRejectPopup, stagesCompleted, setForm, mOnboarding, form, submit, acceptOnboarding, rejectOnboarding, rejectPopup, completedSections, fileUploadPopup, setFileUploadPopup, viewableFiles, formDetails};
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
|
|
3469
|
+
|
|
3470
|
+
|
|
3471
|
+
|
|
3472
|
+
export function useLoadAddresses(user: UserData, limitItems?: number, queryConstraint?: QueryConstraint[]) {
|
|
3473
|
+
const [addresses, setAddresses] = useState<{[key: string]: OrganisationAddress}>({});
|
|
3474
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3475
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3476
|
+
|
|
3477
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3478
|
+
|
|
3479
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3480
|
+
|
|
3481
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3482
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3483
|
+
};
|
|
3484
|
+
|
|
3485
|
+
const loadAddresses = () => {
|
|
3486
|
+
const constraints:QueryConstraint[] = [where("oId", "==", user.oId), where("product", "==", user.product), orderBy(documentId())]
|
|
3487
|
+
|
|
3488
|
+
if (limitItems) {
|
|
3489
|
+
constraints.push(limit(limitItems));
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
if (user.groupData?.viewAddresses === "all" || user.userGroup === "admin") {
|
|
3493
|
+
if (lastDoc?.id) {
|
|
3494
|
+
constraints.push(startAfter(lastDoc?.id));
|
|
3495
|
+
}
|
|
3496
|
+
} else if (user.groupData?.viewAddresses === "request") {
|
|
3497
|
+
if (!user.visibleAddresses?.length) return;
|
|
3498
|
+
|
|
3499
|
+
constraints.push(where(documentId(), "in", user.visibleAddresses))
|
|
3500
|
+
if (lastDoc?.id) {
|
|
3501
|
+
constraints.push(startAfter(lastDoc?.id));
|
|
3502
|
+
}
|
|
3503
|
+
} else {
|
|
3504
|
+
setLoading(false);
|
|
3505
|
+
return; // viewAddresses === "none", no need to load anything
|
|
3506
|
+
}
|
|
3507
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3508
|
+
|
|
3509
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3510
|
+
const deletedAddresses = snapshot.docChanges().map((change) => {
|
|
3511
|
+
if (change.type === "removed") {
|
|
3512
|
+
return change.doc.id;
|
|
3513
|
+
}
|
|
3514
|
+
return;
|
|
3515
|
+
})
|
|
3516
|
+
|
|
3517
|
+
setAddresses((prev) => Object.fromEntries(Object.entries(prev).filter(([k]) => !deletedAddresses.includes(k))))
|
|
3518
|
+
|
|
3519
|
+
if (!snapshot.empty) {
|
|
3520
|
+
|
|
3521
|
+
|
|
3522
|
+
const newAddresses:[string, OrganisationAddress][] = snapshot.docs.map(doc => ([doc.id, {id: doc.id, ...doc.data() as OrganisationAddress}]));
|
|
3523
|
+
|
|
3524
|
+
const withListings:{[key: string]: OrganisationAddress&{listings: number}} = Object.fromEntries(await Promise.all(newAddresses.map(async ([k, address]) => {
|
|
3525
|
+
const listings = await firebaseQuery.getCount("placementListings", [where("providerId", "==", user.oId), where("addressId", "==", k)]);
|
|
3526
|
+
return [k, {...address, listings: listings}];
|
|
3527
|
+
})));
|
|
3528
|
+
|
|
3529
|
+
setAddresses(prev => ({...prev, ...withListings}));
|
|
3530
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3531
|
+
}
|
|
3532
|
+
setLoading(false);
|
|
3533
|
+
}), "addresses", constraints, undefined, true);
|
|
3534
|
+
};
|
|
3535
|
+
|
|
3536
|
+
useEffect(() => {
|
|
3537
|
+
const unsubscribe = loadAddresses();
|
|
3538
|
+
|
|
3539
|
+
return () => {
|
|
3540
|
+
if (unsubscribe) {
|
|
3541
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3545
|
+
}, [queryConstraints]);
|
|
3546
|
+
|
|
3547
|
+
const onScrollBottom = () => {
|
|
3548
|
+
if (!limitItems) return;
|
|
3549
|
+
if (!loading) {
|
|
3550
|
+
setLoading(true);
|
|
3551
|
+
loadAddresses();
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
|
|
3555
|
+
return { addresses, onScrollBottom, loading, changeQueryConstraints };
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
|
|
3560
|
+
|
|
3561
|
+
export function useLoadListings(user: UserData, queryConstraint?: QueryConstraint[]) {
|
|
3562
|
+
const [listings, setListings] = useState<[string, PlacementListing&{applicants?: number, scheduled?: number, active?: number}][]>([]);
|
|
3563
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3564
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3565
|
+
|
|
3566
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3567
|
+
|
|
3568
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3569
|
+
|
|
3570
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3571
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3572
|
+
};
|
|
3573
|
+
|
|
3574
|
+
const loadListings = () => {
|
|
3575
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3576
|
+
|
|
3577
|
+
if (user.groupData?.viewPlacementListings === "all" || user.userGroup === "admin") {
|
|
3578
|
+
if (lastDoc?.id) {
|
|
3579
|
+
constraints.push(startAfter(lastDoc));
|
|
3580
|
+
}
|
|
3581
|
+
} else if (user.groupData?.viewPlacementListings === "request") {
|
|
3582
|
+
if (!user.visibleListings?.length) return;
|
|
3583
|
+
constraints.push(where(documentId(), 'in', user.visibleListings));
|
|
3584
|
+
if (lastDoc?.id) {
|
|
3585
|
+
constraints.push(startAfter(lastDoc));
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
if (user.groupData?.viewAddresses !== "all" && user.userGroup !== "admin") {
|
|
3590
|
+
if (!user.visibleAddresses?.length) return;
|
|
3591
|
+
|
|
3592
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3593
|
+
}
|
|
3594
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3595
|
+
|
|
3596
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3597
|
+
const deletedListings = snapshot.docChanges().map((change) => {
|
|
3598
|
+
if (change.type === "removed") {
|
|
3599
|
+
return change.doc.id;
|
|
3600
|
+
}
|
|
3601
|
+
return;
|
|
3602
|
+
})
|
|
3603
|
+
|
|
3604
|
+
setListings((prev) => prev.filter(([k]) => !deletedListings.includes(k)))
|
|
3605
|
+
|
|
3606
|
+
if (!snapshot.empty) {
|
|
3607
|
+
const newListings:[string, PlacementListing][] = snapshot.docs.map(doc => ([doc.id, {...doc.data() as PlacementListing, id: doc.id}]));
|
|
3608
|
+
|
|
3609
|
+
const listingsWithAdditionalData:[string, PlacementListing&{applicants?: number, scheduled?: number, active?: number}][] = await Promise.all(newListings.map(async ([id, listing]) => {
|
|
3610
|
+
const listingWithAdditionalData = {...listing} as PlacementListing&{applicants?: number, scheduled?: number, active?: number};
|
|
3611
|
+
|
|
3612
|
+
if (listingWithAdditionalData.applicants !== undefined) return [id, listingWithAdditionalData];
|
|
3613
|
+
|
|
3614
|
+
listingWithAdditionalData.applicants = await firebaseQuery.getCount("applications", [where("providerId", "==", user.oId), where("placementId", "==", id), where("status", "==", "submitted")]);
|
|
3615
|
+
listingWithAdditionalData.scheduled = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("placementId", "==", id), where("inProgress", "==", true), where("startDate", ">", convertDate(new Date(), "dbstring"))]);
|
|
3616
|
+
listingWithAdditionalData.active = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("placementId", "==", id), where("active", "==", true)]);
|
|
3617
|
+
|
|
3618
|
+
return [id, listingWithAdditionalData];
|
|
3619
|
+
}));
|
|
3620
|
+
setListings(prev => (Object.entries({...Object.fromEntries(prev), ...Object.fromEntries(listingsWithAdditionalData)})));
|
|
3621
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3622
|
+
}
|
|
3623
|
+
setLoading(false);
|
|
3624
|
+
}), "placementListings", constraints, undefined, true);
|
|
3625
|
+
};
|
|
3626
|
+
|
|
3627
|
+
useEffect(() => {
|
|
3628
|
+
let unsubscribe: () => void;
|
|
3629
|
+
|
|
3630
|
+
|
|
3631
|
+
|
|
3632
|
+
loadListings();
|
|
3633
|
+
|
|
3634
|
+
return () => {
|
|
3635
|
+
if (unsubscribe) {
|
|
3636
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3637
|
+
}
|
|
3638
|
+
};
|
|
3639
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3640
|
+
}, [queryConstraints]);
|
|
3641
|
+
|
|
3642
|
+
const onScrollBottom = () => {
|
|
3643
|
+
if (!loading) {
|
|
3644
|
+
setLoading(true);
|
|
3645
|
+
loadListings();
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
|
|
3649
|
+
return { listings, onScrollBottom, loading, changeQueryConstraints };
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
|
|
3653
|
+
|
|
3654
|
+
|
|
3655
|
+
|
|
3656
|
+
|
|
3657
|
+
export function useLoadApplications({user, applicationType, listingId, queryConstraint}:{user: UserData, applicationType?: "all"|"actionRequired"|"awaitingStudent"|"closed", listingId?: string, queryConstraint?: QueryConstraint[]}) {
|
|
3658
|
+
const [applications, setApplications] = useState<[string, Application][]>([]);
|
|
3659
|
+
const [lastDoc, setLastDoc] = useState<QueryDocumentSnapshot|null>(null);
|
|
3660
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
3661
|
+
const [type, setType] = useState<"actionRequired"|"awaitingStudent"|"closed"|"all">(applicationType || "all");
|
|
3662
|
+
|
|
3663
|
+
const firebaseQuery = new FirebaseQuery();
|
|
3664
|
+
|
|
3665
|
+
const [queryConstraints, setQueryConstraints] = useState<QueryConstraint[]>(queryConstraint || []);
|
|
3666
|
+
|
|
3667
|
+
const changeQueryConstraints = (e: QueryConstraint[]) => {
|
|
3668
|
+
setQueryConstraints([...(queryConstraint || []), ...e]);
|
|
3669
|
+
};
|
|
3670
|
+
|
|
3671
|
+
const loadApplications = () => {
|
|
3672
|
+
const constraints:QueryConstraint[] = [where("providerId", "==", user.oId), limit(10), orderBy(documentId())]
|
|
3673
|
+
|
|
3674
|
+
|
|
3675
|
+
if (lastDoc?.id) {
|
|
3676
|
+
constraints.push(startAfter(lastDoc));
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
switch (type) {
|
|
3680
|
+
case "actionRequired":
|
|
3681
|
+
constraints.push(where("status", "==", "submitted"), where("reqUserType", "==", "Staff"));
|
|
3682
|
+
break;
|
|
3683
|
+
case "awaitingStudent":
|
|
3684
|
+
constraints.push(where("status", "==", "submitted"), where("reqUserType", "==", "Students"));
|
|
3685
|
+
break;
|
|
3686
|
+
case "closed":
|
|
3687
|
+
constraints.push(where("status", "in", ["approved", "declined"]));
|
|
3688
|
+
break;
|
|
3689
|
+
default:
|
|
3690
|
+
constraints.push(where("status", "==", "submitted"));
|
|
3691
|
+
}
|
|
3692
|
+
if (listingId) {
|
|
3693
|
+
constraints.push(where("listingId", "==", listingId));
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
console.log("Constraints before user group check", constraints);
|
|
3697
|
+
if (user.groupData?.viewAddresses !== "all" && user.userGroup !== "admin") {
|
|
3698
|
+
if (user.groupData?.viewPlacementListings === "all") {
|
|
3699
|
+
if (!user.visibleAddresses?.length) return;
|
|
3700
|
+
constraints.push(where('addressId', 'in', user.visibleAddresses));
|
|
3701
|
+
} else {
|
|
3702
|
+
if (!user.visibleListings?.length) return;
|
|
3703
|
+
constraints.push(where('placementId', 'in', user.visibleListings));
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
queryConstraints && constraints.unshift(...queryConstraints);
|
|
3708
|
+
console.log("Constraints after user group check", constraints);
|
|
3709
|
+
|
|
3710
|
+
return firebaseQuery.collectionSnapshot((async (snapshot: QuerySnapshot<DocumentData>) => {
|
|
3711
|
+
const deletedApplications = snapshot.docChanges().map((change) => {
|
|
3712
|
+
if (change.type === "removed") {
|
|
3713
|
+
return change.doc.id;
|
|
3714
|
+
}
|
|
3715
|
+
return;
|
|
3716
|
+
})
|
|
3717
|
+
|
|
3718
|
+
console.log("applicantCount", snapshot.size);
|
|
3719
|
+
|
|
3720
|
+
setApplications((prev) => prev.filter(([k]) => !deletedApplications.includes(k)))
|
|
3721
|
+
|
|
3722
|
+
if (!snapshot.empty) {
|
|
3723
|
+
const newApplications:[string, Application][] = snapshot.docs.map(doc => ([doc.id, {id: doc.id, ...doc.data() as Application}])).filter(([, v]) => (v as Application).status !== "draft") as [string, Application][];
|
|
3724
|
+
|
|
3725
|
+
setApplications(prev => ([...prev, ...newApplications]));
|
|
3726
|
+
setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
|
|
3727
|
+
}
|
|
3728
|
+
setLoading(false);
|
|
3729
|
+
}), "applications", constraints, undefined, true);
|
|
3730
|
+
};
|
|
3731
|
+
|
|
3732
|
+
useEffect(() => {
|
|
3733
|
+
let unsubscribe: () => void;
|
|
3734
|
+
|
|
3735
|
+
loadApplications();
|
|
3736
|
+
return () => {
|
|
3737
|
+
if (unsubscribe) {
|
|
3738
|
+
unsubscribe(); // Unsubscribe from the snapshot listener when the component unmounts
|
|
3739
|
+
}
|
|
3740
|
+
};
|
|
3741
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3742
|
+
}, [type, queryConstraints]);
|
|
3743
|
+
|
|
3744
|
+
const onScrollBottom = () => {
|
|
3745
|
+
if (!loading) {
|
|
3746
|
+
setLoading(true);
|
|
3747
|
+
loadApplications();
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3750
|
+
|
|
3751
|
+
return { applications, type, setType, onScrollBottom, loading, changeQueryConstraints };
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
|
|
3755
|
+
|
|
3756
|
+
|
|
3757
|
+
|
|
3758
|
+
|
|
3759
|
+
|
|
3760
|
+
type ViewType = 'list' | 'table';
|
|
3761
|
+
|
|
3762
|
+
export interface UseFirestoreQueryProps {
|
|
3763
|
+
path?: string | string[];
|
|
3764
|
+
constraints?: QueryConstraint[];
|
|
3765
|
+
view: ViewType;
|
|
3766
|
+
limit: number;
|
|
3767
|
+
formatItems?: (key: string, item: any) => Promise<{ key: string, item: any }> | { key: string, item: any };
|
|
3768
|
+
snapshot?: boolean;
|
|
3769
|
+
filters?: { [key: string]: any };
|
|
3770
|
+
search?: string;
|
|
3771
|
+
onSearch?: (search: string, page: number, limit: number) => Promise<{ [key: string]: any }>;
|
|
3772
|
+
data?: { [key: string]: any };
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
export interface UseFirestoreQueryReturn {
|
|
3776
|
+
data: { [key: string]: any };
|
|
3777
|
+
loading: boolean | 'loaded';
|
|
3778
|
+
pageUp: () => void;
|
|
3779
|
+
pageDown?: () => void;
|
|
3780
|
+
setQueries: (newConstraints: QueryConstraint[]) => void;
|
|
3781
|
+
reset: () => void;
|
|
3782
|
+
setFilters: (newFilters: { [key: string]: any }) => void;
|
|
3783
|
+
setSearch: (search: string) => void;
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
export const useFirestoreQuery = (firebaseQuery: FirebaseQuery, props: UseFirestoreQueryProps): UseFirestoreQueryReturn => {
|
|
3787
|
+
const { path, constraints = [], view, limit: itemsPerPage, formatItems, snapshot = false, filters = {}, search, onSearch, data: predefinedData } = props;
|
|
3788
|
+
|
|
3789
|
+
const [data, setData] = useState<{ [key: string]: any }>(predefinedData || {});
|
|
3790
|
+
const [loading, setLoading] = useState<boolean | 'loaded'>(true);
|
|
3791
|
+
const [lastVisible, setLastVisible] = useState<QueryDocumentSnapshot<DocumentData> | null>(null);
|
|
3792
|
+
const [firstVisible, setFirstVisible] = useState<QueryDocumentSnapshot<DocumentData> | null>(null);
|
|
3793
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
3794
|
+
const listenersRef = useRef<{ [key: string]: () => void }>({});
|
|
3795
|
+
|
|
3796
|
+
const buildConstraints = useCallback(() => {
|
|
3797
|
+
const allConstraints = [...constraints];
|
|
3798
|
+
|
|
3799
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
3800
|
+
allConstraints.push(where(key, '==', value));
|
|
3801
|
+
});
|
|
3802
|
+
|
|
3803
|
+
return allConstraints;
|
|
3804
|
+
}, [constraints, filters]);
|
|
3805
|
+
|
|
3806
|
+
const paginateData = useCallback((items: { [key: string]: any }, page: number) => {
|
|
3807
|
+
const startIndex = page * itemsPerPage;
|
|
3808
|
+
const endIndex = startIndex + itemsPerPage;
|
|
3809
|
+
const paginatedData = Object.entries(items).slice(startIndex, endIndex).reduce((acc, [key, value]) => {
|
|
3810
|
+
acc[key] = value;
|
|
3811
|
+
return acc;
|
|
3812
|
+
}, {} as { [key: string]: any });
|
|
3813
|
+
|
|
3814
|
+
return paginatedData;
|
|
3815
|
+
}, [itemsPerPage]);
|
|
3816
|
+
|
|
3817
|
+
const filterData = useCallback((items: { [key: string]: any }, searchString: string) => {
|
|
3818
|
+
const filteredData = Object.entries(items).filter(([key, value]) => {
|
|
3819
|
+
const itemString = JSON.stringify(value).toLowerCase();
|
|
3820
|
+
return itemString.includes(searchString.toLowerCase());
|
|
3821
|
+
}).reduce((acc, [key, value]) => {
|
|
3822
|
+
acc[key] = value;
|
|
3823
|
+
return acc;
|
|
3824
|
+
}, {} as { [key: string]: any });
|
|
3825
|
+
|
|
3826
|
+
return filteredData;
|
|
3827
|
+
}, []);
|
|
3828
|
+
|
|
3829
|
+
const applyFormatItems = useCallback(
|
|
3830
|
+
async (key: string, item: any): Promise<{ key: string, item: any }> => {
|
|
3831
|
+
if (formatItems) {
|
|
3832
|
+
const result = formatItems(key, item);
|
|
3833
|
+
return result instanceof Promise ? await result : result;
|
|
3834
|
+
}
|
|
3835
|
+
return { key, item };
|
|
3836
|
+
},
|
|
3837
|
+
[formatItems]
|
|
3838
|
+
);
|
|
3839
|
+
|
|
3840
|
+
const setSearch = useCallback(async (newSearch: string, page: number = 0) => {
|
|
3841
|
+
if (predefinedData) {
|
|
3842
|
+
let results = predefinedData;
|
|
3843
|
+
|
|
3844
|
+
if (newSearch) {
|
|
3845
|
+
results = filterData(results, newSearch);
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
const paginatedResults = paginateData(results, page);
|
|
3849
|
+
setData(paginatedResults);
|
|
3850
|
+
|
|
3851
|
+
setLoading(Object.keys(paginatedResults).length < itemsPerPage ? 'loaded' : false);
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
if (onSearch) {
|
|
3856
|
+
setLoading(true);
|
|
3857
|
+
const results = await onSearch(newSearch, page, itemsPerPage);
|
|
3858
|
+
setData(results);
|
|
3859
|
+
setLoading(results && Object.keys(results).length < itemsPerPage ? 'loaded' : false);
|
|
3860
|
+
}
|
|
3861
|
+
}, [onSearch, itemsPerPage, filterData, paginateData, predefinedData]);
|
|
3862
|
+
|
|
3863
|
+
const fetchData = useCallback(async (direction?: 'up' | 'down') => {
|
|
3864
|
+
if (snapshot) {
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
if (predefinedData) {
|
|
3869
|
+
setLoading(false);
|
|
3870
|
+
let results = predefinedData;
|
|
3871
|
+
|
|
3872
|
+
if (search) {
|
|
3873
|
+
results = filterData(results, search);
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
const paginatedResults = paginateData(results, currentPage);
|
|
3877
|
+
setData(paginatedResults);
|
|
3878
|
+
|
|
3879
|
+
setLoading(Object.keys(paginatedResults).length < itemsPerPage ? 'loaded' : false);
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
setLoading(true);
|
|
3884
|
+
const constraintsWithLimit = [...buildConstraints(), limit(itemsPerPage)];
|
|
3885
|
+
|
|
3886
|
+
if (view === 'list' && lastVisible && direction === 'up') {
|
|
3887
|
+
constraintsWithLimit.push(startAfter(lastVisible));
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
if (view === 'table' && direction) {
|
|
3891
|
+
if (direction === 'up' && lastVisible) {
|
|
3892
|
+
constraintsWithLimit.push(startAfter(lastVisible));
|
|
3893
|
+
}
|
|
3894
|
+
if (direction === 'down' && firstVisible) {
|
|
3895
|
+
constraintsWithLimit.push(endBefore(firstVisible));
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
const results = await firebaseQuery.getDocsWhere(path!, constraintsWithLimit, true) as QuerySnapshot<DocumentData>;
|
|
3900
|
+
|
|
3901
|
+
if (Object.keys(results).length === 0) {
|
|
3902
|
+
setLoading('loaded');
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
const formattedResults: { [key: string]: any } = {};
|
|
3907
|
+
for (const [key, item] of Object.entries(results)) {
|
|
3908
|
+
const formatted = await applyFormatItems(key, item);
|
|
3909
|
+
formattedResults[formatted.key] = formatted.item;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
setData(prevData => (direction === 'down' ? formattedResults : { ...prevData, ...formattedResults }));
|
|
3913
|
+
|
|
3914
|
+
const docEntries = Object.entries(results);
|
|
3915
|
+
if (direction === 'up' || !direction) {
|
|
3916
|
+
setLastVisible(docEntries[docEntries.length - 1][1]);
|
|
3917
|
+
}
|
|
3918
|
+
if (direction === 'down' || !direction) {
|
|
3919
|
+
setFirstVisible(docEntries[0][1]);
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
setLoading(false);
|
|
3923
|
+
}, [buildConstraints, firebaseQuery, applyFormatItems, lastVisible, firstVisible, view, snapshot, predefinedData, filterData, paginateData, currentPage, itemsPerPage]);
|
|
3924
|
+
|
|
3925
|
+
const pageUp = useCallback(() => {
|
|
3926
|
+
setCurrentPage(prevPage => {
|
|
3927
|
+
const newPage = prevPage + 1;
|
|
3928
|
+
|
|
3929
|
+
if (search && onSearch) {
|
|
3930
|
+
setSearch(search, newPage);
|
|
3931
|
+
} else {
|
|
3932
|
+
fetchData('up');
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
return newPage;
|
|
3936
|
+
});
|
|
3937
|
+
}, [fetchData, onSearch, search, setSearch]);
|
|
3938
|
+
|
|
3939
|
+
const pageDown = useCallback(() => {
|
|
3940
|
+
setCurrentPage(prevPage => {
|
|
3941
|
+
const newPage = Math.max(prevPage - 1, 0);
|
|
3942
|
+
|
|
3943
|
+
if (search && onSearch) {
|
|
3944
|
+
setSearch(search, newPage);
|
|
3945
|
+
} else {
|
|
3946
|
+
fetchData('down');
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
return newPage;
|
|
3950
|
+
});
|
|
3951
|
+
}, [fetchData, onSearch, search, setSearch]);
|
|
3952
|
+
|
|
3953
|
+
const reset = useCallback(() => {
|
|
3954
|
+
setData(predefinedData || {});
|
|
3955
|
+
setLastVisible(null);
|
|
3956
|
+
setFirstVisible(null);
|
|
3957
|
+
setCurrentPage(0);
|
|
3958
|
+
setLoading(true);
|
|
3959
|
+
fetchData();
|
|
3960
|
+
}, [fetchData, predefinedData]);
|
|
3961
|
+
|
|
3962
|
+
const setQueries = useCallback((newConstraints: QueryConstraint[]) => {
|
|
3963
|
+
reset();
|
|
3964
|
+
}, [reset]);
|
|
3965
|
+
|
|
3966
|
+
const setFilters = useCallback((newFilters: { [key: string]: any }) => {
|
|
3967
|
+
reset();
|
|
3968
|
+
}, [reset]);
|
|
3969
|
+
|
|
3970
|
+
useEffect(() => {
|
|
3971
|
+
if (!search) {
|
|
3972
|
+
fetchData();
|
|
3973
|
+
} else {
|
|
3974
|
+
setSearch(search, 0);
|
|
3975
|
+
}
|
|
3976
|
+
}, [fetchData, search, setSearch]);
|
|
3977
|
+
|
|
3978
|
+
useEffect(() => {
|
|
3979
|
+
if (snapshot && path) {
|
|
3980
|
+
const constraintsWithLimit = [...buildConstraints(), limit(itemsPerPage)];
|
|
3981
|
+
|
|
3982
|
+
const handleSnapshot = (querySnapshot: DocumentData) => {
|
|
3983
|
+
const items: { [key: string]: any } = {};
|
|
3984
|
+
querySnapshot.docs.forEach((doc: QueryDocumentSnapshot<DocumentData>) => {
|
|
3985
|
+
items[doc.id] = doc.data();
|
|
3986
|
+
});
|
|
3987
|
+
|
|
3988
|
+
if (search) {
|
|
3989
|
+
const filteredData = filterData(items, search);
|
|
3990
|
+
const paginatedData = paginateData(filteredData, currentPage);
|
|
3991
|
+
setData(paginatedData);
|
|
3992
|
+
} else {
|
|
3993
|
+
const paginatedData = paginateData(items, currentPage);
|
|
3994
|
+
setData(paginatedData);
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
setLastVisible(querySnapshot.docs[querySnapshot.docs.length - 1] || null);
|
|
3998
|
+
setFirstVisible(querySnapshot.docs[0] || null);
|
|
3999
|
+
|
|
4000
|
+
setLoading(Object.keys(items).length < itemsPerPage ? 'loaded' : false);
|
|
4001
|
+
};
|
|
4002
|
+
|
|
4003
|
+
const unsubscribe = firebaseQuery.collectionSnapshot(handleSnapshot, path, constraintsWithLimit, undefined, true);
|
|
4004
|
+
|
|
4005
|
+
listenersRef.current[path.toString()] = unsubscribe;
|
|
4006
|
+
|
|
4007
|
+
return () => {
|
|
4008
|
+
if (listenersRef.current[path.toString()]) {
|
|
4009
|
+
listenersRef.current[path.toString()]();
|
|
4010
|
+
}
|
|
4011
|
+
};
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
return () => {
|
|
4015
|
+
Object.values(listenersRef.current).forEach(unsub => unsub());
|
|
4016
|
+
};
|
|
4017
|
+
}, [path, snapshot, buildConstraints, itemsPerPage, filterData, paginateData, currentPage, search, firebaseQuery]);
|
|
4018
|
+
|
|
4019
|
+
return {
|
|
4020
|
+
data,
|
|
4021
|
+
loading,
|
|
4022
|
+
pageUp,
|
|
4023
|
+
pageDown: view === 'table' ? pageDown : undefined,
|
|
4024
|
+
setQueries,
|
|
4025
|
+
reset,
|
|
4026
|
+
setFilters,
|
|
4027
|
+
setSearch,
|
|
4028
|
+
};
|
|
4029
|
+
};
|