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/reduxHooks.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { where } from "firebase/firestore"
|
|
1
|
+
import { orderBy, where } from "firebase/firestore"
|
|
2
2
|
import { useEffect, useState } from "react"
|
|
3
3
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
|
4
4
|
import { AppDispatch, RootState } from "./config"
|
|
5
5
|
import { addContact, deleteContact, setContacts, updateContact } from "./features/contacts/contactsSlice"
|
|
6
6
|
import { Job, addJob, setJobStatus, setJobs, setMarkRead } from "./features/jobs/jobsSlice"
|
|
7
|
-
import { fetchActivePlacement } from "./features/placements/studentPlacements/activePlacement"
|
|
7
|
+
import { editActivePlacement, fetchActivePlacement, setActivePlacement } from "./features/placements/studentPlacements/activePlacement"
|
|
8
8
|
import { addCompletedStudentPlacements, deleteCompletedStudentPlacement, setCompletedStudentPlacements, updateCompletedStudentPlacement } from "./features/placements/studentPlacements/completedStudentPlacementsSlice"
|
|
9
9
|
import { addUpcomingStudentPlacements, deleteUpcomingStudentPlacement, setUpcoming, setUpcomingStudentPlacements, updateUpcomingStudentPlacement } from "./features/placements/studentPlacements/upcomingStudentPlacementsSlice"
|
|
10
10
|
import FirebaseQuery from "./firebase/firebaseQuery"
|
|
11
11
|
import { Address, Application, Contact, PlacementListing, ProviderData, StudentPlacementData, UserData } from "./typeDefinitions"
|
|
12
|
+
import {getPlacementsWhere} from "./firebase/readDatabase"
|
|
13
|
+
import {convertDate} from "./firebase/util"
|
|
12
14
|
|
|
13
15
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
|
14
16
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
|
@@ -22,7 +24,7 @@ export function useStudent({user} : {user: UserData}) {
|
|
|
22
24
|
const activePlacement = useAppSelector((state) => state.activePlacement.values)
|
|
23
25
|
const upcoming = useAppSelector((state) => state.upcomingStudentPlacements.upcoming)
|
|
24
26
|
const contacts = useAppSelector((state) => state.contacts.values)
|
|
25
|
-
const [applications, setApplications] = useState<{[key: string]: Application&{listing: PlacementListing, provider: ProviderData, address: Address}}>();
|
|
27
|
+
const [applications, setApplications] = useState<{[key: string]: Application&{listing: PlacementListing|false, provider: ProviderData, address: Address}}>();
|
|
26
28
|
const firebaseQuery = new FirebaseQuery();
|
|
27
29
|
|
|
28
30
|
const dispatch = useAppDispatch();
|
|
@@ -42,8 +44,8 @@ export function useStudent({user} : {user: UserData}) {
|
|
|
42
44
|
|
|
43
45
|
const applicationsWithProviderAndListing:{[key: string]: Application&{listing: PlacementListing, provider: ProviderData, address: Address}} = Object.fromEntries(await Promise.all(Object.entries(fApplications).map(async ([id, application]) => {
|
|
44
46
|
const provider = await firebaseQuery.getDocData(["providers", application.providerId]) as ProviderData;
|
|
45
|
-
const listing
|
|
46
|
-
const address
|
|
47
|
+
const listing = await firebaseQuery.getDocData(["placementListings", application.listingId]).catch(() => false) as PlacementListing|false;
|
|
48
|
+
const address = listing && listing?.addressId && await firebaseQuery.getDocData(["addresses", listing.addressId]) as Address;
|
|
47
49
|
|
|
48
50
|
return [id, {...application, listing: listing, provider: provider, address: address}];
|
|
49
51
|
})));
|
|
@@ -51,25 +53,44 @@ export function useStudent({user} : {user: UserData}) {
|
|
|
51
53
|
setApplications(applicationsWithProviderAndListing);
|
|
52
54
|
|
|
53
55
|
}, "applications", applicationsConstraints);
|
|
54
|
-
}, [
|
|
55
|
-
|
|
56
|
+
}, [user.id]);
|
|
57
|
+
/*
|
|
56
58
|
useEffect(() => {
|
|
57
59
|
if (!user.id) return
|
|
58
60
|
dispatch(fetchActivePlacement({userId: user.id}))
|
|
59
|
-
}, [dispatch, user.id, upcomingPlacements])
|
|
61
|
+
}, [dispatch, user.id, upcomingPlacements]);
|
|
62
|
+
*/
|
|
63
|
+
const fetchUpcomingPlacements = () => {
|
|
64
|
+
const constraints = [where("uid", "==", user.id), where("inProgress", "==", true), orderBy("startDate")]
|
|
65
|
+
firebaseQuery.collectionSnapshot(setUpcomingStudentPlacements, "placements", constraints, dispatch);
|
|
66
|
+
}
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
const fetchCompletedPlacements = () => {
|
|
69
|
+
const constraints = [where("uid", "==", user.id), where("completed", "==", true), orderBy("startDate")]
|
|
70
|
+
firebaseQuery.collectionSnapshot(setCompletedStudentPlacements, "placements", constraints, dispatch);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fetchContacts = () => {
|
|
74
|
+
const constraints = [where("uid", "==", user.id)]
|
|
75
|
+
firebaseQuery.collectionSnapshot(setContacts, "contacts", constraints, dispatch);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const updateUpcomingPlacement = () => {
|
|
64
79
|
if (!upcomingPlacements || !Object.entries(upcomingPlacements).length) {
|
|
65
80
|
dispatch(setUpcoming(undefined));
|
|
66
81
|
return;
|
|
67
82
|
}
|
|
68
83
|
const upcomingPlacement = Object.entries(upcomingPlacements).find(([, v]) => new Date(v.startDate).getTime() > today.getTime())
|
|
69
84
|
upcomingPlacement ? dispatch(setUpcoming(upcomingPlacement[1])) : dispatch(setUpcoming(undefined))
|
|
85
|
+
}
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
const updateActivePlacement = () => {
|
|
88
|
+
const key = Object.keys(upcomingPlacements).find((key) => {
|
|
89
|
+
const placement = upcomingPlacements[key]
|
|
90
|
+
return placement.active
|
|
91
|
+
})
|
|
92
|
+
key ? dispatch(setActivePlacement(upcomingPlacements[key])) : dispatch(setActivePlacement(null))
|
|
93
|
+
}
|
|
73
94
|
|
|
74
95
|
const getItemById = async (path: "contacts"|"placements", id: string) => {
|
|
75
96
|
return await firebaseQuery.getDocData([path, id])
|
|
@@ -80,9 +101,9 @@ export function useStudent({user} : {user: UserData}) {
|
|
|
80
101
|
else dispatch(deleteCompletedStudentPlacement({placementId: id}))
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
const handleUpdatePlacement = async (id: string,
|
|
84
|
-
if (upcomingPlacements[id]) dispatch(updateUpcomingStudentPlacement({placementId: id, attributes
|
|
85
|
-
else dispatch(updateCompletedStudentPlacement({placementId: id, attributes
|
|
104
|
+
const handleUpdatePlacement = async (id: string, attributes: Partial<StudentPlacementData>) => {
|
|
105
|
+
if (upcomingPlacements[id]) dispatch(updateUpcomingStudentPlacement({placementId: id, attributes}))
|
|
106
|
+
else dispatch(updateCompletedStudentPlacement({placementId: id, attributes}))
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
const handleAddPlacement = async (formData: StudentPlacementData) => {
|
|
@@ -90,19 +111,46 @@ export function useStudent({user} : {user: UserData}) {
|
|
|
90
111
|
else dispatch(addUpcomingStudentPlacements({formData}))
|
|
91
112
|
}
|
|
92
113
|
|
|
114
|
+
const getUserPlacements = async () => {
|
|
115
|
+
return await getPlacementsWhere({w: where("uid", "==", user?.id)})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const getPlacementsStart = async (start: Date, end: Date) => {
|
|
119
|
+
return await firebaseQuery.getDocsWhere("placements", [where("uid", "==", user?.id), where("startDate", ">=", convertDate(start, "dbstring")), where("startDate", "<=", convertDate(end, "dbstring"))]) as {[key:string]: StudentPlacementData};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const getPlacementsEnd = async (end: Date) => {
|
|
123
|
+
return await firebaseQuery.getDocsWhere("placements", [where("uid", "==", user?.id), where("endDate", ">=", convertDate(end, "dbstring")), where("endDate", "<=", convertDate(end, "dbstring"))]) as {[key:string]: StudentPlacementData};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const dispatchActivePlacement = () => {
|
|
127
|
+
dispatch(fetchActivePlacement({userId: user.id}))
|
|
128
|
+
}
|
|
129
|
+
|
|
93
130
|
return {
|
|
94
131
|
contacts: {
|
|
95
|
-
add: async (contactForm: Contact) => dispatch(addContact({contactForm: contactForm, userId: user.id})),
|
|
96
|
-
update: async (contactId: string, data: Partial<Contact>) => dispatch(updateContact({contactId: contactId, attributes: data})),
|
|
97
|
-
delete: async (contactId: string) => dispatch(deleteContact({contactId: contactId})),
|
|
132
|
+
add: async (contactForm: Contact) => await dispatch(addContact({contactForm: contactForm, userId: user.id})),
|
|
133
|
+
update: async (contactId: string, data: Partial<Contact>) => await dispatch(updateContact({contactId: contactId, attributes: data})),
|
|
134
|
+
delete: async (contactId: string) => await dispatch(deleteContact({contactId: contactId})),
|
|
98
135
|
contacts,
|
|
136
|
+
fetchContacts,
|
|
99
137
|
getById: async (item: string) => await getItemById("contacts", item)
|
|
100
138
|
},
|
|
139
|
+
|
|
101
140
|
placements: {
|
|
102
141
|
add: async (formData: StudentPlacementData) => await handleAddPlacement(formData),
|
|
103
|
-
update: async (placementId: string,
|
|
142
|
+
update: async (placementId: string, attributes: Partial<StudentPlacementData>) => await handleUpdatePlacement(placementId, attributes),
|
|
104
143
|
delete: async (id: string) => await handleDeletePlacement(id),
|
|
144
|
+
getUserPlacements,
|
|
145
|
+
getPlacementsStart,
|
|
146
|
+
getPlacementsEnd,
|
|
105
147
|
activePlacement,
|
|
148
|
+
editActivePlacement: (attributes: Partial<StudentPlacementData>) => dispatch(editActivePlacement(attributes)),
|
|
149
|
+
updateActivePlacement,
|
|
150
|
+
updateUpcomingPlacement,
|
|
151
|
+
fetchUpcomingPlacements,
|
|
152
|
+
fetchCompletedPlacements,
|
|
153
|
+
dispatchActivePlacement,
|
|
106
154
|
upcoming,
|
|
107
155
|
upcomingPlacements: upcomingPlacements || {},
|
|
108
156
|
completedPlacements: completedPlacements || {},
|
package/src/tasksAndTips.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {arrayUnion, orderBy, where} from "firebase/firestore";
|
|
2
2
|
import FirebaseQuery from "./firebase/firebaseQuery";
|
|
3
|
-
import {CohortData, InstituteData, ProviderData, StudentPlacementData, UserData} from "./typeDefinitions";
|
|
4
|
-
import {getAccess} from "./firebase/util";
|
|
3
|
+
import {CohortData, InstituteData, OrganisationAddress, PlacementListing, ProviderData, RegistrationRequest, StudentPlacementData, UserData} from "./typeDefinitions";
|
|
4
|
+
import {camelCaseToNormal, capitaliseWords, dateToString, getAccess} from "./firebase/util";
|
|
5
5
|
import { convertDate } from "./firebase/util";
|
|
6
6
|
|
|
7
7
|
const firebaseQuery = new FirebaseQuery;
|
|
@@ -12,7 +12,7 @@ type StudentTipNames = string
|
|
|
12
12
|
|
|
13
13
|
export type InstituteTaskNames = "missingParentEmail"|"verifyInsurance"|"verifyRiskAssessment"|"verifyDbsCheck"|"inactiveStudents"|"uploadStudents"|"inactiveStaff"|"requiredStage"|"approveExternalPlacement"|"overdueStage"
|
|
14
14
|
export type StudentTaskNames = "completeOnboarding"
|
|
15
|
-
export type ProviderTaskNames = "applicationRequireReview"|"requestedVisiblePlacementListings"|"requestedVisibleAddresses"|"completeStudentDocs"|"reviewOnboarding"|"completeListing"|"completeAddress"|"registrationRequests"|"placementStarting"|"completeFeedback"|"setUpFeedback"
|
|
15
|
+
export type ProviderTaskNames = "applicationRequireReview"|"activateStaff"|"requestedVisiblePlacementListings"|"requestedVisibleAddresses"|"completeStudentDocs"|"uploadOnboarding"|"reviewOnboarding"|"completeListing"|"completeAddress"|"registrationRequests"|"placementStarting"|"completeFeedback"|"setUpFeedback"
|
|
16
16
|
|
|
17
17
|
// IF UPDATING LOGIC WITHIN THIS FILE, PLACEMENTT-BACKEND LOGIC MUST ALSO BE CHANGED ACCORDINGLY
|
|
18
18
|
|
|
@@ -511,10 +511,11 @@ const studentTasks:StudentTaskObject = {
|
|
|
511
511
|
const providerTasks:ProviderTaskObject = {
|
|
512
512
|
requestedVisibleAddresses: {
|
|
513
513
|
callback: async (user) => {
|
|
514
|
-
|
|
514
|
+
if (!getAccess(user, "addStaff")) return;
|
|
515
|
+
const accessRequests = await firebaseQuery.getCount("users", [where("oId", "==", user.oId), where("product", "==", "providers"), orderBy("requestedVisibleAddresses")]) - ((Array.isArray(user?.requestedVisibleAddresses) && user?.requestedVisibleAddresses.length > 0) ? 1 : 0);
|
|
515
516
|
if (accessRequests === 0) return;
|
|
516
517
|
if (accessRequests === 1) {
|
|
517
|
-
const userRequestingAccess = Object.entries(await firebaseQuery.getDocsWhere("users", [where("
|
|
518
|
+
const userRequestingAccess = Object.entries(await firebaseQuery.getDocsWhere("users", [where("product", "==", "providers"), where("oId", "==", user.oId), orderBy("requestedVisibleAddresses")]) || {})[0] as [string, UserData];
|
|
518
519
|
return {
|
|
519
520
|
dismissible: false,
|
|
520
521
|
severity: "primary",
|
|
@@ -537,7 +538,8 @@ const providerTasks:ProviderTaskObject = {
|
|
|
537
538
|
},
|
|
538
539
|
requestedVisiblePlacementListings: {
|
|
539
540
|
callback: async (user) => {
|
|
540
|
-
|
|
541
|
+
if (!getAccess(user, "addStaff")) return;
|
|
542
|
+
const accessRequests = await firebaseQuery.getCount("users", [where("oId", "==", user.oId), where("product", "==", "providers"), orderBy("requestedVisibleListings")])- ((Array.isArray(user?.requestedVisibleListings) && user?.requestedVisibleListings.length > 0) ? 1 : 0);;
|
|
541
543
|
if (accessRequests === 0) return;
|
|
542
544
|
if (accessRequests === 1) {
|
|
543
545
|
const userRequestingAccess = Object.entries(await firebaseQuery.getDocsWhere("users", [where("oId", "==", user.oId), where("product", "==", "providers"), orderBy("requestedVisibleListings")]) || {})[0] as [string, UserData];
|
|
@@ -578,41 +580,212 @@ const providerTasks:ProviderTaskObject = {
|
|
|
578
580
|
},
|
|
579
581
|
completeStudentDocs: {
|
|
580
582
|
callback: async (user) => {
|
|
583
|
+
return;
|
|
581
584
|
return {} as TaskQueryReturnObject;
|
|
582
585
|
},
|
|
583
586
|
},
|
|
584
587
|
reviewOnboarding: {
|
|
585
588
|
callback: async (user) => {
|
|
586
|
-
|
|
589
|
+
const toReview = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("onboarding.completed.accepted", "==", false), where("onboarding.completed.submitted", "==", true), where("endDate", ">=", dateToString(new Date()))]);
|
|
590
|
+
|
|
591
|
+
if (toReview === 0) return;
|
|
592
|
+
if (toReview === 1) {
|
|
593
|
+
const placement = Object.entries(await firebaseQuery.getDocsWhere("placements", [where("providerId", "==", user.oId), where("onboarding.completed.accepted", "==", false), where("onboarding.completed.submitted", "==", true), where("endDate", ">=", dateToString(new Date()))]) || {})[0] as [string, StudentPlacementData];
|
|
594
|
+
const student = await firebaseQuery.getDocData(["users", placement[1].uid]) as UserData;
|
|
595
|
+
return {
|
|
596
|
+
dismissible: false,
|
|
597
|
+
severity: "primary",
|
|
598
|
+
title: `${student.details.forename} ${student.details.surname} has completed their onboarding for their placement from ${convertDate(placement[1].startDate, "visual")} to ${convertDate(placement[1].endDate, "visual")}`,
|
|
599
|
+
message: `Click to view the placement and review the onboarding.`,
|
|
600
|
+
link: `/${user.product}/placements/${placement[0]}`,
|
|
601
|
+
buttonTitle: "View",
|
|
602
|
+
} as TaskQueryReturnObject;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
dismissible: false,
|
|
607
|
+
severity: "primary",
|
|
608
|
+
title: `${toReview} student have completed their onboarding.`,
|
|
609
|
+
message: `Click to view your placements and approve completed onboarding`,
|
|
610
|
+
link: `/${user.product}/placementListings/placements`,
|
|
611
|
+
buttonTitle: "View placements",
|
|
612
|
+
} as TaskQueryReturnObject;
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
uploadOnboarding: {
|
|
616
|
+
callback: async (user) => {
|
|
617
|
+
const withoutOnboarding = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("onboarding", "==", null), where("endDate", ">=", dateToString(new Date()))]);
|
|
618
|
+
|
|
619
|
+
if (withoutOnboarding === 0) return;
|
|
620
|
+
if (withoutOnboarding === 1) {
|
|
621
|
+
const placement = Object.entries(await firebaseQuery.getDocsWhere("placements", [where("providerId", "==", user.oId), where("onboarding", "==", null), where("endDate", ">=", dateToString(new Date()))]) || {})[0] as [string, StudentPlacementData];
|
|
622
|
+
const student = await firebaseQuery.getDocData(["users", placement[1].uid]) as UserData;
|
|
623
|
+
return {
|
|
624
|
+
dismissible: false,
|
|
625
|
+
severity: "primary",
|
|
626
|
+
title: `Send onboarding documents to ${student.details.forename} ${student.details.surname}'s placement from ${convertDate(placement[1].startDate, "visual")} to ${convertDate(placement[1].endDate, "visual")}`,
|
|
627
|
+
message: `Click to view the placement and add or dismiss onboarding reminders.`,
|
|
628
|
+
link: `/${user.product}/placements/${placement[0]}`,
|
|
629
|
+
buttonTitle: "View",
|
|
630
|
+
} as TaskQueryReturnObject;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return {
|
|
634
|
+
dismissible: false,
|
|
635
|
+
severity: "primary",
|
|
636
|
+
title: `Set up onboarding for ${withoutOnboarding} placements to prepare yourself and your students.`,
|
|
637
|
+
message: `Click to view your placements and add or dismiss onboarding reminders.`,
|
|
638
|
+
link: `/${user.product}/placementListings/placements`,
|
|
639
|
+
buttonTitle: "View placements",
|
|
640
|
+
} as TaskQueryReturnObject;
|
|
587
641
|
},
|
|
588
642
|
},
|
|
589
643
|
completeListing: {
|
|
590
644
|
callback: async (user) => {
|
|
591
|
-
|
|
645
|
+
const incompleteListings = await firebaseQuery.getCount("placementListings", [where("providerId", "==", user.oId), where("status", "==", "draft")]);
|
|
646
|
+
if (incompleteListings === 0) return;
|
|
647
|
+
if (incompleteListings === 1) {
|
|
648
|
+
const incompleteListing = Object.entries(await firebaseQuery.getDocsWhere("placementListings", [where("providerId", "==", user.oId), where("status", "==", "draft")]) || {})[0] as [string, PlacementListing];
|
|
649
|
+
const address = incompleteListing[1].addressId ? await firebaseQuery.getDocData(["addresses", incompleteListing[1].addressId]) as OrganisationAddress : undefined;
|
|
650
|
+
return {
|
|
651
|
+
dismissible: false,
|
|
652
|
+
severity: "info",
|
|
653
|
+
title: `Your listing '${incompleteListing[1].title || "unnamed"}' at ${address ? `${address["address-line1"]}, ${address.postal_code.toUpperCase()}, ${capitaliseWords(camelCaseToNormal(address.country))}` : "unknown address"} requires more information before publishing.`,
|
|
654
|
+
message: `Click to complete and publish the placement listing.`,
|
|
655
|
+
link: `/${user.product}/addListing/${incompleteListing[0]}`,
|
|
656
|
+
buttonTitle: "View listing",
|
|
657
|
+
} as TaskQueryReturnObject;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
dismissible: false,
|
|
662
|
+
severity: "info",
|
|
663
|
+
title: `You have ${incompleteListings} draft listings waiting to be published.`,
|
|
664
|
+
message: `Click to review and publish the placement listings.`,
|
|
665
|
+
link: `/${user.product}/placementListings/listings`,
|
|
666
|
+
buttonTitle: "View listings",
|
|
667
|
+
} as TaskQueryReturnObject;
|
|
592
668
|
},
|
|
593
669
|
},
|
|
594
670
|
completeAddress: {
|
|
595
671
|
callback: async (user) => {
|
|
596
|
-
|
|
672
|
+
const incompleteAddresses = await firebaseQuery.getCount("addresses", [where("product", "==", "providers"), where("oId", "==", user.oId), where("stage", "!=", "complete")]);
|
|
673
|
+
if (incompleteAddresses === 0) return;
|
|
674
|
+
if (incompleteAddresses === 1) {
|
|
675
|
+
const address = Object.entries(await firebaseQuery.getDocsWhere("addresses", [where("product", "==", "providers"), where("oId", "==", user.oId), where("stage", "!=", "complete")]) || {})[0] as [string, OrganisationAddress];
|
|
676
|
+
return {
|
|
677
|
+
dismissible: false,
|
|
678
|
+
severity: "info",
|
|
679
|
+
title: `Your address: ${address[1]["address-line1"]}, ${address[1].postal_code.toUpperCase()}, ${capitaliseWords(camelCaseToNormal(address[1].country))} is currently incomplete.`,
|
|
680
|
+
message: `Click to complete the addresses.`,
|
|
681
|
+
link: `/${user.product}/addAddress/${address[0]}`,
|
|
682
|
+
buttonTitle: "View address",
|
|
683
|
+
} as TaskQueryReturnObject;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
dismissible: false,
|
|
688
|
+
severity: "info",
|
|
689
|
+
title: `You have ${incompleteAddresses} draft addresses waiting to be published.`,
|
|
690
|
+
message: `Click to review the addresses.`,
|
|
691
|
+
link: `/${user.product}/organisation/addresses`,
|
|
692
|
+
buttonTitle: "View addresses",
|
|
693
|
+
} as TaskQueryReturnObject;
|
|
597
694
|
},
|
|
598
695
|
},
|
|
599
696
|
registrationRequests: {
|
|
600
697
|
callback: async (user) => {
|
|
601
|
-
|
|
698
|
+
const regRequests = await firebaseQuery.getCount("requests", [where("product", "==", user.product), where("oId", "==", user.oId)]);
|
|
699
|
+
|
|
700
|
+
if (regRequests === 0) return;
|
|
701
|
+
if (regRequests === 1) {
|
|
702
|
+
const request = Object.entries(await firebaseQuery.getDocsWhere("requests", [where("product", "==", user.product), where("oId", "==", user.oId)]) || {})[0] as [string, RegistrationRequest];
|
|
703
|
+
return {
|
|
704
|
+
dismissible: false,
|
|
705
|
+
severity: "primary",
|
|
706
|
+
title: `${request[1].forename} ${request[1].surname} has requested to access your organisation.`,
|
|
707
|
+
message: `Click to review these request.`,
|
|
708
|
+
link: `/${user.product}/organisation/staff/requests`,
|
|
709
|
+
buttonTitle: "View requests",
|
|
710
|
+
} as TaskQueryReturnObject;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
dismissible: false,
|
|
715
|
+
severity: "primary",
|
|
716
|
+
title: `${regRequests} people have requested to register with your organisation.`,
|
|
717
|
+
message: `Click to review these requests.`,
|
|
718
|
+
link: `/${user.product}/organisation/staff/requests`,
|
|
719
|
+
buttonTitle: "View requests",
|
|
720
|
+
} as TaskQueryReturnObject;
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
activateStaff: {
|
|
724
|
+
callback: async (user) => {
|
|
725
|
+
const inactiveAccounts = await firebaseQuery.getCount("users", [where("product", "==", user.product), where("oId", "==", user.oId), where("status", "==", "inactive")]);
|
|
726
|
+
|
|
727
|
+
if (inactiveAccounts === 0) return;
|
|
728
|
+
if (inactiveAccounts === 1) {
|
|
729
|
+
const account = Object.entries(await firebaseQuery.getDocsWhere("users", [where("product", "==", user.product), where("oId", "==", user.oId), where("status", "==", "inactive")]) || {})[0] as [string, UserData];
|
|
730
|
+
return {
|
|
731
|
+
dismissible: false,
|
|
732
|
+
severity: "info",
|
|
733
|
+
title: `Activate ${account[1].details.forename} ${account[1].details.surname}'s staff account.`,
|
|
734
|
+
message: "Activate this account to give the user access to Placementt.",
|
|
735
|
+
link: `/${user.product}/organisation/staff/all`,
|
|
736
|
+
buttonTitle: "View accounts",
|
|
737
|
+
} as TaskQueryReturnObject;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return {
|
|
741
|
+
dismissible: false,
|
|
742
|
+
severity: "info",
|
|
743
|
+
title: `${inactiveAccounts} staff have inactive active accounts.`,
|
|
744
|
+
message: "Activate these accounts to give the user access to Placementt.",
|
|
745
|
+
link: `/${user.product}/organisation/staff/all`,
|
|
746
|
+
buttonTitle: "View accounts",
|
|
747
|
+
} as TaskQueryReturnObject;
|
|
602
748
|
},
|
|
603
749
|
},
|
|
604
750
|
placementStarting: {
|
|
605
751
|
callback: async (user) => {
|
|
606
|
-
|
|
752
|
+
const sevenDaysInFuture = new Date();
|
|
753
|
+
sevenDaysInFuture.setDate(sevenDaysInFuture.getDate() + 7)
|
|
754
|
+
const placementsStartingSoon = await firebaseQuery.getCount("placements", [where("providerId", "==", user.oId), where("startDate", "<=", convertDate(sevenDaysInFuture, "dbstring")), where("startDate", ">", dateToString(new Date()))]);
|
|
755
|
+
|
|
756
|
+
if (placementsStartingSoon === 0) return;
|
|
757
|
+
if (placementsStartingSoon === 1) {
|
|
758
|
+
const placement = Object.entries(await firebaseQuery.getDocsWhere("placements", [where("providerId", "==", user.oId), where("startDate", "<=", convertDate(sevenDaysInFuture, "dbstring")), where("startDate", ">", dateToString(new Date()))]) || {})[0] as [string, StudentPlacementData];
|
|
759
|
+
const student = await firebaseQuery.getDocData(["users", placement[1].uid]) as UserData;
|
|
760
|
+
return {
|
|
761
|
+
dismissible: false,
|
|
762
|
+
severity: "success",
|
|
763
|
+
title: `${student.details.forename} ${student.details.surname}'s placement from ${convertDate(placement[1].startDate, "visual")} to ${convertDate(placement[1].endDate, "visual")} is starting in less than a week.`,
|
|
764
|
+
message: `Click to view the placement and acquaint yourself with the student.`,
|
|
765
|
+
link: `/${user.product}/placements/${placement[0]}`,
|
|
766
|
+
buttonTitle: "View",
|
|
767
|
+
} as TaskQueryReturnObject;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return {
|
|
771
|
+
dismissible: false,
|
|
772
|
+
severity: "success",
|
|
773
|
+
title: `${placementsStartingSoon} placements starting soon.`,
|
|
774
|
+
message: `Click to view scheduled placements.`,
|
|
775
|
+
link: `/${user.product}/placementListings/placements`,
|
|
776
|
+
buttonTitle: "View placements",
|
|
777
|
+
} as TaskQueryReturnObject;
|
|
607
778
|
},
|
|
608
779
|
},
|
|
609
780
|
completeFeedback: {
|
|
610
781
|
callback: async (user) => {
|
|
782
|
+
return;
|
|
611
783
|
return {} as TaskQueryReturnObject;
|
|
612
784
|
},
|
|
613
785
|
},
|
|
614
786
|
setUpFeedback: {
|
|
615
787
|
callback: async (user) => {
|
|
788
|
+
return;
|
|
616
789
|
return {} as TaskQueryReturnObject;
|
|
617
790
|
},
|
|
618
791
|
},
|
package/src/typeDefinitions.ts
CHANGED
|
@@ -85,13 +85,15 @@ export type StudentPlacementData = {
|
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
onboarding: (OnboardingDocs&{
|
|
88
|
+
deadline?: string,
|
|
88
89
|
completed: {
|
|
89
|
-
submitted:
|
|
90
|
+
submitted: boolean,
|
|
91
|
+
submittedDate?: string,
|
|
90
92
|
accepted?: boolean,
|
|
91
93
|
filesViewed?: string[],
|
|
92
94
|
formsCompleted?: {[key: string]: unknown},
|
|
93
95
|
filesUploaded?: {[key: number]: string[]},
|
|
94
|
-
}})|false,
|
|
96
|
+
}})|false|null,
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
export type PlacementQuestions = {
|
|
@@ -178,29 +180,17 @@ export type PlacementListing = {
|
|
|
178
180
|
applicationType?: "simple"|"complex",
|
|
179
181
|
applicantWorkflowId?: string,
|
|
180
182
|
applicantWorkflow?: ApplicantStage[],
|
|
181
|
-
onboarding: OnboardingDocs|false,
|
|
183
|
+
onboarding: (OnboardingDocs&{deadline: number})|false,
|
|
182
184
|
maxApplicants?: number,
|
|
183
185
|
maxStudents?: number,
|
|
184
186
|
primaryContactId: string,
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
export type OnboardingDocs = {
|
|
188
|
-
deadline?: string,
|
|
189
190
|
message?: string,
|
|
190
|
-
deadlineType?: "date"|"days"
|
|
191
191
|
forms?: string[],
|
|
192
192
|
files?: string[],
|
|
193
|
-
formDetails?: {[key: string]:
|
|
194
|
-
{
|
|
195
|
-
name: string,
|
|
196
|
-
id: string,
|
|
197
|
-
description?: string,
|
|
198
|
-
product: Products,
|
|
199
|
-
oId: string,
|
|
200
|
-
form: CustomFormSchema
|
|
201
|
-
}},
|
|
202
193
|
requiredFiles?: {fileName: string, fileType: string}[]
|
|
203
|
-
viewableFiles?: {[key: string]: FileItem},
|
|
204
194
|
}
|
|
205
195
|
|
|
206
196
|
export type PlacementTemplate = PlacementListing&{templateName: string};
|
|
@@ -273,13 +263,15 @@ export type FlagCodes =
|
|
|
273
263
|
"completeOnboarding"|
|
|
274
264
|
"reviewOnboarding"|
|
|
275
265
|
"requestedVisibleAddresses"|
|
|
276
|
-
"requestedVisiblePlacementListings"
|
|
266
|
+
"requestedVisiblePlacementListings"|
|
|
267
|
+
"addOnboarding"
|
|
277
268
|
|
|
278
269
|
|
|
279
270
|
export type UserData = {
|
|
280
271
|
details: {
|
|
281
272
|
forename: string,
|
|
282
273
|
surname: string,
|
|
274
|
+
pronouns?: string,
|
|
283
275
|
parentEmail?: string,
|
|
284
276
|
year?: string
|
|
285
277
|
},
|
|
@@ -318,7 +310,7 @@ export type UserData = {
|
|
|
318
310
|
visibleCohorts?: string,
|
|
319
311
|
readUpdates?: string[],
|
|
320
312
|
contactProviderConsent?: boolean,
|
|
321
|
-
|
|
313
|
+
contactProviderConsentDate?: boolean
|
|
322
314
|
referrals?: number,
|
|
323
315
|
shareNameWithReferralLeaderboardConsent?: boolean,
|
|
324
316
|
shareNameWithReferralLeaderboardConsentDate?: boolean,
|
|
@@ -329,7 +321,8 @@ export type UserData = {
|
|
|
329
321
|
visibleAddresses?: string[],
|
|
330
322
|
visibleListings?: string[],
|
|
331
323
|
requestedVisibleAddresses?: string[],
|
|
332
|
-
requestedVisibleListings?: string[]
|
|
324
|
+
requestedVisibleListings?: string[],
|
|
325
|
+
packageData?: BillingPackage
|
|
333
326
|
};
|
|
334
327
|
|
|
335
328
|
export type AnalyticsItem = {
|
|
@@ -497,7 +490,7 @@ export type ProviderData = {
|
|
|
497
490
|
staffActive: number,
|
|
498
491
|
admin: string,
|
|
499
492
|
name: string,
|
|
500
|
-
insurance: boolean,
|
|
493
|
+
insurance: boolean|"awaitingReview",
|
|
501
494
|
regNumber?: string,
|
|
502
495
|
insuranceExpiry: string,
|
|
503
496
|
mapConsent?: boolean|"unlisted",
|
|
@@ -525,7 +518,7 @@ export type ProviderData = {
|
|
|
525
518
|
locations?: number,
|
|
526
519
|
},
|
|
527
520
|
discount?: string,
|
|
528
|
-
package
|
|
521
|
+
package: string,
|
|
529
522
|
splitBilling?: boolean,
|
|
530
523
|
adminBillingContactId: string,
|
|
531
524
|
addOnPackages?: string[],
|
|
@@ -829,6 +822,7 @@ export type Application = {
|
|
|
829
822
|
uid: string,
|
|
830
823
|
applicantWorkflowId: string,
|
|
831
824
|
listingId: string,
|
|
825
|
+
addressId: string,
|
|
832
826
|
providerId: string,
|
|
833
827
|
reqUserType: "Students"|"Staff",
|
|
834
828
|
completedSections: {[stage: number]: {
|
package/src/util.ts
CHANGED
|
@@ -32,51 +32,57 @@ export const getPlacementFlagCodes = ({placement, studentData, workflow, institu
|
|
|
32
32
|
const dbsCheckNotVerified = !institute?.verifiedDbsChecks?.includes(placement.placementId || placement.id);
|
|
33
33
|
const awaitingDbsCheck = !placement.dbsCheck || institute?.awaitingPlacementDbsChecks?.includes(placement.placementId || placement.id);
|
|
34
34
|
|
|
35
|
-
if (user.userType === "Staff" && placement.insurance && placementIsPostProviderReview && placementNotEnded && providerUnverified && !awaitingProviderInsurance) {
|
|
35
|
+
if (user.userType === "Staff" && user.product === "institutes" && placement.insurance && placementIsPostProviderReview && placementNotEnded && providerUnverified && !awaitingProviderInsurance) {
|
|
36
36
|
console.log("Add insurance flag!");
|
|
37
37
|
flags.includes("noInsurance") || flags.push("noInsurance");
|
|
38
38
|
} else {
|
|
39
39
|
flags = flags.filter((x) => x !== "noInsurance");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
if (user.userType === "Staff" && placementIsPostProviderReview && placementNotEnded && awaitingProviderInsurance) {
|
|
42
|
+
if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && placementNotEnded && awaitingProviderInsurance) {
|
|
43
43
|
flags.includes("awaitingInsurance") || flags.push("awaitingInsurance");
|
|
44
44
|
} else {
|
|
45
45
|
flags = flags.filter((x) => x !== "awaitingInsurance");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if (user.userType === "Staff" && placement.dbsCheck && workflow.find((stage) => stage.dbsCheck) && placement.dbsCheck !== true && placementIsPostProviderReview && placementNotEnded && dbsCheckNotVerified && !awaitingDbsCheck) {
|
|
48
|
+
if (user.userType === "Staff" && user.product === "institutes" && placement.dbsCheck && workflow.find((stage) => stage.dbsCheck) && placement.dbsCheck !== true && placementIsPostProviderReview && placementNotEnded && dbsCheckNotVerified && !awaitingDbsCheck) {
|
|
49
49
|
flags.includes("noDbsCheck") || flags.push("noDbsCheck");
|
|
50
50
|
} else {
|
|
51
51
|
flags = flags.filter((x) => x !== "noDbsCheck");
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
if (user.userType === "Staff" && placementIsPostProviderReview && workflow.find((stage) => stage.dbsCheck) && placementNotEnded && awaitingProviderInsurance) {
|
|
54
|
+
if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && workflow.find((stage) => stage.dbsCheck) && placementNotEnded && awaitingProviderInsurance) {
|
|
55
55
|
flags.includes("awaitingDbsCheck") || flags.push("awaitingDbsCheck");
|
|
56
56
|
} else {
|
|
57
57
|
flags = flags.filter((x) => x !== "awaitingDbsCheck");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
if (user.userType === "Staff" && placement.riskAssessment && workflow.find((stage) => stage.riskAssessment) && placement.riskAssessment !== true && placementIsPostProviderReview && placementNotEnded && riskAssessmentNotVerified && !awaitingRiskAssessment) {
|
|
60
|
+
if (user.userType === "Staff" && user.product === "institutes" && placement.riskAssessment && workflow.find((stage) => stage.riskAssessment) && placement.riskAssessment !== true && placementIsPostProviderReview && placementNotEnded && riskAssessmentNotVerified && !awaitingRiskAssessment) {
|
|
61
61
|
console.log("Add RA flag!");
|
|
62
62
|
flags.includes("noRiskAssessment") || flags.push("noRiskAssessment");
|
|
63
63
|
} else {
|
|
64
64
|
flags = flags.filter((x) => x !== "noRiskAssessment");
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (user.userType === "Staff" && placementIsPostProviderReview && workflow.find((stage) => stage.riskAssessment) && placementNotEnded && awaitingProviderInsurance) {
|
|
67
|
+
if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && workflow.find((stage) => stage.riskAssessment) && placementNotEnded && awaitingProviderInsurance) {
|
|
68
68
|
flags.includes("awaitingRiskAssessment") || flags.push("awaitingRiskAssessment");
|
|
69
69
|
} else {
|
|
70
70
|
flags = flags.filter((x) => x !== "awaitingRiskAssessment");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
if (user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding
|
|
73
|
+
if (placement.inProgress && user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding === null) {
|
|
74
|
+
flags.includes("addOnboarding") || flags.push("addOnboarding");
|
|
75
|
+
} else {
|
|
76
|
+
flags = flags.filter((x) => x !== "addOnboarding");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (placement.inProgress && user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding && (placement.onboarding.completed?.submitted && !placement.onboarding.completed.accepted) && (placement.inProgress || placement.active)) {
|
|
74
80
|
flags.includes("reviewOnboarding") || flags.push("reviewOnboarding");
|
|
75
81
|
} else {
|
|
76
82
|
flags = flags.filter((x) => x !== "reviewOnboarding");
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
if (user.userType === "Students" && placement.onboarding && (!placement.onboarding.completed || (placement.onboarding.completed && !placement.onboarding.completed?.submitted)) && (placement.inProgress || placement.active)) {
|
|
85
|
+
if (placement.inProgress && user.userType === "Students" && placement.onboarding && (!placement.onboarding.completed || (placement.onboarding.completed && !placement.onboarding.completed?.submitted)) && (placement.inProgress || placement.active)) {
|
|
80
86
|
flags.includes("completeOnboarding") || flags.push("completeOnboarding");
|
|
81
87
|
} else {
|
|
82
88
|
flags = flags.filter((x) => x !== "completeOnboarding");
|
|
@@ -87,7 +93,6 @@ export const getPlacementFlagCodes = ({placement, studentData, workflow, institu
|
|
|
87
93
|
};
|
|
88
94
|
|
|
89
95
|
|
|
90
|
-
|
|
91
96
|
export function objectsEqualNew(a: any, b: any): boolean { // Works with arrays or objects.
|
|
92
97
|
if (a === b) {
|
|
93
98
|
return true;
|