@7365admin1/layer-common 3.2.1 → 3.2.2-staging.101
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/.changeset/camera-wall-gated-endpoint.md +47 -0
- package/.changeset/camera-wall-plain-english.md +60 -0
- package/.changeset/camera-wall-selection-and-guidance.md +65 -0
- package/.changeset/dark-primary-contrast.md +39 -0
- package/.changeset/dashboard-dark-theme-contrast.md +15 -0
- package/.changeset/notification-preferences.md +17 -0
- package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
- package/.changeset/service-provider-invitation-actions.md +28 -0
- package/assets/css/primitives.css +680 -0
- package/assets/css/screens.css +541 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +220 -0
- package/components/AccessCardQrTagging.vue +1 -1
- package/components/AccessManagement.vue +53 -72
- package/components/AppButton.vue +52 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +35 -0
- package/components/AppField.vue +51 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +203 -0
- package/components/AreaChecklistHistoryLogs.vue +51 -44
- package/components/AreaChecklistHistoryMain.vue +6 -2
- package/components/AreaMain.vue +9 -17
- package/components/AttendanceMain.vue +4 -3
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BuildingManagement/buildings.vue +27 -38
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BulletinBoardManagement.vue +23 -10
- package/components/CameraWall.vue +1034 -0
- package/components/CameraWallTile.vue +818 -0
- package/components/CardToolbar.vue +37 -0
- package/components/CleaningScheduleMain.vue +29 -46
- package/components/ClientMain.vue +90 -78
- package/components/DashboardEmptyState.vue +11 -1
- package/components/DashboardMain.vue +349 -275
- package/components/DashboardPanel.vue +1 -1
- package/components/DocumentManagement.vue +129 -93
- package/components/EntryPassInformation.vue +4 -4
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +4 -6
- package/components/EquipmentManagementMain.vue +9 -7
- package/components/FeedbackMain.vue +42 -56
- package/components/FormDialog.vue +60 -21
- package/components/HidAccessLogDashboard.vue +119 -44
- package/components/HidCameraCaptureDialog.vue +199 -0
- package/components/HidIntercomManagement.vue +825 -202
- package/components/HidQrCodeConfiguration.vue +927 -93
- package/components/HidServiceSettingsPanel.vue +20 -1
- package/components/HidUserEnrollment.vue +454 -106
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +289 -37
- package/components/Layout/NavigationDrawer.vue +340 -30
- package/components/ManageChecklistMain.vue +17 -17
- package/components/MemberMain.vue +79 -60
- package/components/MyAttendanceMain.vue +3 -5
- package/components/NavigationItem.vue +9 -0
- package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
- package/components/NotificationSettings.vue +361 -0
- package/components/OnlineFormConfigurationForm.vue +620 -224
- package/components/OnlineFormFill.vue +36 -8
- package/components/OnlineFormsConfiguration.vue +7 -3
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +104 -45
- package/components/QrTemplate/CreditCardLandscape.vue +19 -12
- package/components/QrTemplate/PrintDialog.vue +209 -196
- package/components/RolePermissionMain.vue +184 -91
- package/components/ScanVisitorQRCode.vue +35 -7
- package/components/ScheduleAreaMain.vue +15 -49
- package/components/ScheduleTaskMain.vue +23 -26
- package/components/ScheduleTastTicketMain.vue +18 -43
- package/components/ServiceProviderInvitationPrompt.vue +150 -0
- package/components/ServiceProviderMain.vue +346 -157
- package/components/SiteSettings.vue +25 -10
- package/components/SiteSettingsWorkOrder.vue +61 -30
- package/components/SpecificAttr.vue +10 -2
- package/components/StatusChip.vue +93 -0
- package/components/StockCard.vue +17 -12
- package/components/SwitchContext.vue +33 -1
- package/components/TableHygiene.vue +29 -46
- package/components/TableList.vue +1 -1
- package/components/TableListSecondary.vue +1 -1
- package/components/TableMain.vue +161 -77
- package/components/TableV3.vue +61 -66
- package/components/UnitMain.vue +9 -17
- package/components/VehicleManagement.vue +20 -9
- package/components/VisitorForm.vue +294 -78
- package/components/VisitorManagement.vue +221 -71
- package/components/VisitorSocketPopUp.vue +56 -2
- package/components/WorkOrder/Main.vue +42 -51
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +151 -0
- package/composables/useHidNavigation.ts +0 -7
- package/composables/useLocalAuth.ts +9 -36
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useOrg.ts +1 -1
- package/composables/useRole.ts +22 -0
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +336 -0
- package/composables/useSiteSettings.ts +50 -0
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/composables/useVisitorSocket.ts +26 -0
- package/composables/useWebUsb.ts +127 -37
- package/nuxt.config.ts +31 -0
- package/package.json +4 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/pages/notification-settings.vue +19 -0
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +68 -9
- package/test/visitor-socket.test.mjs +36 -0
- package/types/org.d.ts +2 -0
- package/types/site.d.ts +71 -0
- package/utils/camera-wall.test.ts +571 -0
- package/utils/camera-wall.ts +926 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/status.test.ts +77 -0
- package/utils/status.ts +90 -0
- package/utils/theme.test.ts +534 -0
- package/utils/theme.ts +294 -0
- package/components/HidIdentityMapping.vue +0 -975
- package/middleware/member.ts +0 -4
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
|
@@ -28,6 +28,46 @@ type HidIdentityPayload = {
|
|
|
28
28
|
metadata?: Record<string, any>;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
type HidVisitorQrData = {
|
|
32
|
+
visitorId: string;
|
|
33
|
+
readerId: string;
|
|
34
|
+
hidUserId: number;
|
|
35
|
+
qrValue: string;
|
|
36
|
+
qrFormat: "0";
|
|
37
|
+
issuedAt: string;
|
|
38
|
+
expiresAt: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type HidVisitorQrResponse = HidVisitorQrData | { data: HidVisitorQrData };
|
|
42
|
+
|
|
43
|
+
type HidPermissionListResponse = {
|
|
44
|
+
items: THidPermissionCandidate[];
|
|
45
|
+
page: number;
|
|
46
|
+
pages: number;
|
|
47
|
+
pageRange: string;
|
|
48
|
+
limit: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type HidFacialEnrollmentResult = {
|
|
52
|
+
user_id?: number;
|
|
53
|
+
success?: boolean;
|
|
54
|
+
scores?: Record<string, unknown>;
|
|
55
|
+
errors?: Array<{ code?: number; message?: string }>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type HidSipAccountData = {
|
|
59
|
+
orgId: string;
|
|
60
|
+
site: string;
|
|
61
|
+
userId: string;
|
|
62
|
+
extension: string;
|
|
63
|
+
username: string;
|
|
64
|
+
password: string;
|
|
65
|
+
sipAddress: string;
|
|
66
|
+
websocketUrl: string;
|
|
67
|
+
status: "active";
|
|
68
|
+
provisionedAt: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
31
71
|
export default function useHidAmico() {
|
|
32
72
|
const basePath = "/api/access-management/hid";
|
|
33
73
|
|
|
@@ -78,6 +118,16 @@ export default function useHidAmico() {
|
|
|
78
118
|
});
|
|
79
119
|
}
|
|
80
120
|
|
|
121
|
+
function issueVisitorQr(
|
|
122
|
+
readerId: string,
|
|
123
|
+
payload: { visitorId: string; validityMinutes: number },
|
|
124
|
+
) {
|
|
125
|
+
return useNuxtApp().$api<HidVisitorQrResponse>(`${basePath}/readers/${readerId}/visitor-qr`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
body: payload,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
81
131
|
function getReaderConfiguration(readerId: string, payload: Record<string, string[]> = {}) {
|
|
82
132
|
return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/configuration`, {
|
|
83
133
|
method: "POST",
|
|
@@ -112,6 +162,54 @@ export default function useHidAmico() {
|
|
|
112
162
|
});
|
|
113
163
|
}
|
|
114
164
|
|
|
165
|
+
function setUserImage(
|
|
166
|
+
readerId: string,
|
|
167
|
+
hidUserId: string | number,
|
|
168
|
+
image: Blob,
|
|
169
|
+
options: { timestamp?: number; match?: boolean } = {},
|
|
170
|
+
) {
|
|
171
|
+
return useNuxtApp().$api<{ data: HidFacialEnrollmentResult }>(
|
|
172
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/image`,
|
|
173
|
+
{
|
|
174
|
+
method: "PUT",
|
|
175
|
+
query: {
|
|
176
|
+
...(options.timestamp ? { timestamp: options.timestamp } : {}),
|
|
177
|
+
match: options.match ?? true,
|
|
178
|
+
},
|
|
179
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
180
|
+
body: image,
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function deleteUserImage(readerId: string, hidUserId: string | number) {
|
|
186
|
+
return useNuxtApp().$api<Record<string, unknown>>(
|
|
187
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/image`,
|
|
188
|
+
{ method: "DELETE" },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getUserPinStatus(readerId: string, hidUserId: string | number) {
|
|
193
|
+
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
194
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
195
|
+
{ method: "GET" },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function setUserPin(readerId: string, hidUserId: string | number, pin: string) {
|
|
200
|
+
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
201
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
202
|
+
{ method: "PUT", body: { pin } },
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function deleteUserPin(readerId: string, hidUserId: string | number) {
|
|
207
|
+
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
208
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
209
|
+
{ method: "DELETE" },
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
115
213
|
function createIdentity(readerId: string, payload: HidIdentityPayload) {
|
|
116
214
|
return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/identities`, {
|
|
117
215
|
method: "POST",
|
|
@@ -151,6 +249,49 @@ export default function useHidAmico() {
|
|
|
151
249
|
});
|
|
152
250
|
}
|
|
153
251
|
|
|
252
|
+
function getSitePermissions(siteId: string, orgId: string, readerId: string) {
|
|
253
|
+
return useNuxtApp().$api<{ data: THidSitePermissions }>(
|
|
254
|
+
`${basePath}/sites/${siteId}/permissions`,
|
|
255
|
+
{ method: "GET", query: { orgId, readerId } },
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function ensureSipAccount(siteId: string, orgId: string) {
|
|
260
|
+
return useNuxtApp().$api<{ data: HidSipAccountData }>(
|
|
261
|
+
`${basePath}/sites/${siteId}/intercom/sip-account`,
|
|
262
|
+
{ method: "POST", body: { orgId } },
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getPermissionCandidates(
|
|
267
|
+
siteId: string,
|
|
268
|
+
query: {
|
|
269
|
+
orgId: string;
|
|
270
|
+
readerId: string;
|
|
271
|
+
category: THidPermissionCategory | "intercom";
|
|
272
|
+
search?: string;
|
|
273
|
+
page?: number;
|
|
274
|
+
limit?: number;
|
|
275
|
+
},
|
|
276
|
+
) {
|
|
277
|
+
return useNuxtApp().$api<HidPermissionListResponse>(
|
|
278
|
+
`${basePath}/sites/${siteId}/permission-candidates`,
|
|
279
|
+
{ method: "GET", query },
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function updateSitePermissions(
|
|
284
|
+
siteId: string,
|
|
285
|
+
orgId: string,
|
|
286
|
+
readerId: string,
|
|
287
|
+
assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>,
|
|
288
|
+
) {
|
|
289
|
+
return useNuxtApp().$api<{ data: THidSitePermissions; message: string }>(
|
|
290
|
+
`${basePath}/sites/${siteId}/permissions`,
|
|
291
|
+
{ method: "PUT", body: { orgId, readerId, assignments } },
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
154
295
|
return {
|
|
155
296
|
getReaders,
|
|
156
297
|
createReader,
|
|
@@ -159,10 +300,16 @@ export default function useHidAmico() {
|
|
|
159
300
|
testReader,
|
|
160
301
|
syncReader,
|
|
161
302
|
runObjectOperation,
|
|
303
|
+
issueVisitorQr,
|
|
162
304
|
getReaderConfiguration,
|
|
163
305
|
setReaderConfiguration,
|
|
164
306
|
getLogs,
|
|
165
307
|
getUserImage,
|
|
308
|
+
setUserImage,
|
|
309
|
+
deleteUserImage,
|
|
310
|
+
getUserPinStatus,
|
|
311
|
+
setUserPin,
|
|
312
|
+
deleteUserPin,
|
|
166
313
|
getIdentities,
|
|
167
314
|
createIdentity,
|
|
168
315
|
updateIdentity,
|
|
@@ -170,5 +317,9 @@ export default function useHidAmico() {
|
|
|
170
317
|
getIntercomStatus,
|
|
171
318
|
makeIntercomCall,
|
|
172
319
|
finalizeIntercomCall,
|
|
320
|
+
ensureSipAccount,
|
|
321
|
+
getSitePermissions,
|
|
322
|
+
getPermissionCandidates,
|
|
323
|
+
updateSitePermissions,
|
|
173
324
|
};
|
|
174
325
|
}
|
|
@@ -41,13 +41,6 @@ export default function useHidNavigation() {
|
|
|
41
41
|
params: { org, site },
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
-
{
|
|
45
|
-
title: "Identity Mapping",
|
|
46
|
-
route: {
|
|
47
|
-
name: "org-site-access-mgmt-identity-mapping",
|
|
48
|
-
params: { org, site },
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
44
|
{
|
|
52
45
|
title: "Access Logs",
|
|
53
46
|
route: {
|
|
@@ -1,36 +1,7 @@
|
|
|
1
1
|
export default function useLocalAuth() {
|
|
2
2
|
const { cookieConfig } = useRuntimeConfig().public;
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
country: "",
|
|
6
|
-
address1: "",
|
|
7
|
-
address2: "",
|
|
8
|
-
city: "",
|
|
9
|
-
province: "",
|
|
10
|
-
postalCode: "",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const user = {
|
|
14
|
-
_id: "",
|
|
15
|
-
givenName: "",
|
|
16
|
-
middleName: "",
|
|
17
|
-
surname: "",
|
|
18
|
-
address: _address,
|
|
19
|
-
email: "",
|
|
20
|
-
password: "",
|
|
21
|
-
type: "",
|
|
22
|
-
createdAt: "",
|
|
23
|
-
organization: "",
|
|
24
|
-
role: "",
|
|
25
|
-
primaryPhone: "",
|
|
26
|
-
mobilePhone: "",
|
|
27
|
-
serviceProviders: [],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const currentUser = useState(
|
|
31
|
-
"currentUser",
|
|
32
|
-
(): TUser | null => JSON.parse(JSON.stringify(user))
|
|
33
|
-
);
|
|
4
|
+
const currentUser = useState<TUser | null>("currentUser", () => null);
|
|
34
5
|
|
|
35
6
|
async function authenticate() {
|
|
36
7
|
const user = useCookie("user", cookieConfig).value;
|
|
@@ -41,9 +12,7 @@ export default function useLocalAuth() {
|
|
|
41
12
|
);
|
|
42
13
|
|
|
43
14
|
watchEffect(() => {
|
|
44
|
-
|
|
45
|
-
currentUser.value = getCurrentUserReq.value as TUser;
|
|
46
|
-
}
|
|
15
|
+
currentUser.value = (getCurrentUserReq.value as TUser | null) ?? null;
|
|
47
16
|
});
|
|
48
17
|
|
|
49
18
|
watchEffect(() => {
|
|
@@ -70,6 +39,7 @@ export default function useLocalAuth() {
|
|
|
70
39
|
useCookie("sid", cookieConfig).value = null;
|
|
71
40
|
useCookie("user", cookieConfig).value = null;
|
|
72
41
|
useCookie("organization", cookieConfig).value = null;
|
|
42
|
+
currentUser.value = null;
|
|
73
43
|
}
|
|
74
44
|
|
|
75
45
|
async function logout() {
|
|
@@ -79,17 +49,20 @@ export default function useLocalAuth() {
|
|
|
79
49
|
await useNuxtApp().$api(`/api/auth/${sid}`, {
|
|
80
50
|
method: "DELETE",
|
|
81
51
|
});
|
|
82
|
-
|
|
83
|
-
clearCookies();
|
|
84
52
|
} catch (error) {
|
|
85
53
|
console.error("Logout failed:", error);
|
|
86
54
|
}
|
|
55
|
+
|
|
56
|
+
clearCookies();
|
|
87
57
|
}
|
|
88
58
|
}
|
|
89
59
|
|
|
90
60
|
async function getCurrentUser() {
|
|
91
61
|
const user = useCookie("user", cookieConfig).value;
|
|
92
|
-
if (!user)
|
|
62
|
+
if (!user) {
|
|
63
|
+
currentUser.value = null;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
93
66
|
const _user = await useNuxtApp().$api<TUser>(`/api/users/id/${user}`, {
|
|
94
67
|
method: "GET",
|
|
95
68
|
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A person's own notification settings.
|
|
3
|
+
*
|
|
4
|
+
* The server decides everything that matters here: which categories this person
|
|
5
|
+
* is offered, which channels each one can actually reach them on, and what the
|
|
6
|
+
* words are. Nothing on this side maintains a second list — a screen that made
|
|
7
|
+
* up its own module names is how the previous version came to show three
|
|
8
|
+
* switches that governed nothing.
|
|
9
|
+
*
|
|
10
|
+
* There is no user id in either call. Whose settings these are comes from the
|
|
11
|
+
* session, so a person can only ever see and change their own.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type TNotificationChannelSetting = {
|
|
15
|
+
key: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** false = this category cannot reach them this way; drawn, dimmed, explained */
|
|
18
|
+
supported: boolean;
|
|
19
|
+
/** the plain-English reason, present only when unsupported */
|
|
20
|
+
note?: string;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type TNotificationCategorySetting = {
|
|
25
|
+
key: string;
|
|
26
|
+
label: string;
|
|
27
|
+
description: string;
|
|
28
|
+
safety: boolean;
|
|
29
|
+
channels: TNotificationChannelSetting[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TNotificationPreferences = {
|
|
33
|
+
channels: Array<{ key: string; label: string }>;
|
|
34
|
+
categories: TNotificationCategorySetting[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const EMPTY: TNotificationPreferences = { channels: [], categories: [] };
|
|
38
|
+
|
|
39
|
+
export default function useNotificationPreference() {
|
|
40
|
+
function getPreferences() {
|
|
41
|
+
return useNuxtApp().$api<{ data: TNotificationPreferences }>(
|
|
42
|
+
"/api/notification-preferences",
|
|
43
|
+
{ method: "GET" },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Save the whole screen at once.
|
|
49
|
+
*
|
|
50
|
+
* What is sent is the list of switches that are OFF, which is also how the
|
|
51
|
+
* server stores it: somebody who has changed nothing has no stored row at
|
|
52
|
+
* all, and behaves exactly as they did before this screen existed.
|
|
53
|
+
*/
|
|
54
|
+
function savePreferences(preferences: TNotificationPreferences) {
|
|
55
|
+
const off = preferences.categories.flatMap((category) =>
|
|
56
|
+
category.channels
|
|
57
|
+
.filter((channel) => channel.supported && !channel.enabled)
|
|
58
|
+
.map((channel) => ({ category: category.key, channel: channel.key })),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return useNuxtApp().$api<{ data: TNotificationPreferences }>(
|
|
62
|
+
"/api/notification-preferences",
|
|
63
|
+
{ method: "PUT", body: { off } },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { getPreferences, savePreferences, EMPTY_PREFERENCES: EMPTY };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The sentence shown when somebody switches a safety or security alert off.
|
|
72
|
+
*
|
|
73
|
+
* It says what will stop arriving and how to undo it, and it does not argue —
|
|
74
|
+
* the owner's decision is that these can be switched off, so the job here is to
|
|
75
|
+
* make sure nobody does it by accident, not to talk them out of it.
|
|
76
|
+
*/
|
|
77
|
+
export function safetyOffConfirmation(categoryLabel: string, channelLabel: string) {
|
|
78
|
+
return {
|
|
79
|
+
title: `Turn off ${categoryLabel}?`,
|
|
80
|
+
body:
|
|
81
|
+
`You will stop getting ${categoryLabel.toLowerCase()} alerts under "${channelLabel}". ` +
|
|
82
|
+
`These are safety and security alerts, so they are on for everyone by default. ` +
|
|
83
|
+
`You can turn them back on here at any time.`,
|
|
84
|
+
confirm: "Turn it off",
|
|
85
|
+
cancel: "Keep it on",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -20,6 +20,8 @@ export type FormBlock =
|
|
|
20
20
|
| { type: "checkbox-group"; title?: string; single?: boolean; items: CheckboxItemDef[] }
|
|
21
21
|
| { type: "paragraph"; text: string }
|
|
22
22
|
| { type: "section-header"; text: string }
|
|
23
|
+
| { type: "ordered-list"; title?: string; items: string[] }
|
|
24
|
+
| { type: "unordered-list"; title?: string; items: string[] }
|
|
23
25
|
| { type: "divider" }
|
|
24
26
|
| { type: "signature-row"; label: string; key: string }
|
|
25
27
|
| { type: "acknowledgement"; text: string }
|
package/composables/useOrg.ts
CHANGED
|
@@ -170,7 +170,7 @@ export default function useOrg() {
|
|
|
170
170
|
|
|
171
171
|
function updateOrg(
|
|
172
172
|
id: string,
|
|
173
|
-
value: Partial<Pick<TOrg, "name" | "type" | "email" | "nature" | "contact">>
|
|
173
|
+
value: Partial<Pick<TOrg, "name" | "type" | "email" | "nature" | "contact" | "terms" | "policies">>
|
|
174
174
|
) {
|
|
175
175
|
return useNuxtApp()
|
|
176
176
|
.$api<Record<string, any>>(`/api/organizations/${id}`, {
|
package/composables/useRole.ts
CHANGED
|
@@ -68,6 +68,26 @@ export default function useRole() {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function getDeletionPreview(_id: string) {
|
|
72
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
73
|
+
`/api/roles/${_id}/deletion-preview`,
|
|
74
|
+
{ method: "GET" }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function deleteWithReassignments(
|
|
79
|
+
_id: string,
|
|
80
|
+
reassignments: Array<{ memberId: string; roleId: string }>
|
|
81
|
+
) {
|
|
82
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
83
|
+
`/api/roles/${_id}/delete-with-reassignments`,
|
|
84
|
+
{
|
|
85
|
+
method: "PUT",
|
|
86
|
+
body: { reassignments },
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
const role = useState<TRole>("userRole", () => {
|
|
72
92
|
return {
|
|
73
93
|
_id: "",
|
|
@@ -103,6 +123,8 @@ export default function useRole() {
|
|
|
103
123
|
updateRoleById,
|
|
104
124
|
updateRoleFieldById,
|
|
105
125
|
deleteRole,
|
|
126
|
+
getDeletionPreview,
|
|
127
|
+
deleteWithReassignments,
|
|
106
128
|
updatePermissionById,
|
|
107
129
|
getRoleByUserId,
|
|
108
130
|
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service-provider invitations — the list a property manager sees and the
|
|
3
|
+
* things they can do to a row.
|
|
4
|
+
*
|
|
5
|
+
* Separate from `useVerification` because these rows are not generic
|
|
6
|
+
* verifications any more: they have their own states (waiting for Seven365,
|
|
7
|
+
* waiting for the provider, not approved, declined) and their own actions,
|
|
8
|
+
* and the server scopes them to the caller's own organisation.
|
|
9
|
+
*/
|
|
10
|
+
export type TServiceProviderInvitation = {
|
|
11
|
+
_id: string;
|
|
12
|
+
email: string;
|
|
13
|
+
type: string;
|
|
14
|
+
status: string;
|
|
15
|
+
statusLabel: string;
|
|
16
|
+
rejectionReason?: string | null;
|
|
17
|
+
createdAt?: string;
|
|
18
|
+
expireAt?: string;
|
|
19
|
+
deletedAt?: string | null;
|
|
20
|
+
metadata?: Record<string, any>;
|
|
21
|
+
history?: Array<{
|
|
22
|
+
action: string;
|
|
23
|
+
at: string;
|
|
24
|
+
byName?: string;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The words on screen. Kept beside the server's own list on purpose — a
|
|
30
|
+
* status the server invents must never render as a raw stored value. */
|
|
31
|
+
export const SERVICE_PROVIDER_INVITE_LABELS: Record<string, string> = {
|
|
32
|
+
"awaiting-approval": "Waiting for Seven365",
|
|
33
|
+
pending: "Waiting for the provider",
|
|
34
|
+
rejected: "Not approved",
|
|
35
|
+
complete: "Accepted",
|
|
36
|
+
declined: "Declined",
|
|
37
|
+
cancelled: "Cancelled",
|
|
38
|
+
expired: "Expired",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function serviceProviderInviteLabel(status?: string) {
|
|
42
|
+
return SERVICE_PROVIDER_INVITE_LABELS[String(status ?? "")] ?? "Unknown";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function useServiceProviderInvitation() {
|
|
46
|
+
const base = "/api/service-provider-invitations";
|
|
47
|
+
|
|
48
|
+
function getInvitations({
|
|
49
|
+
org = "",
|
|
50
|
+
status = "",
|
|
51
|
+
page = 1,
|
|
52
|
+
limit = 20,
|
|
53
|
+
} = {}): Promise<{
|
|
54
|
+
items: TServiceProviderInvitation[];
|
|
55
|
+
pages: number;
|
|
56
|
+
pageRange: string;
|
|
57
|
+
}> {
|
|
58
|
+
return useNuxtApp().$api(base, {
|
|
59
|
+
method: "GET",
|
|
60
|
+
query: { org, status, page, limit },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cancelInvitation(id: string) {
|
|
65
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/cancel`, {
|
|
66
|
+
method: "PUT",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Sends the same invitation again. Never creates a second row. */
|
|
71
|
+
function resendInvitation(id: string) {
|
|
72
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/resend`, {
|
|
73
|
+
method: "PUT",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Soft delete — the row leaves this list, Seven365 keeps the record. */
|
|
78
|
+
function deleteInvitation(id: string) {
|
|
79
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}`, {
|
|
80
|
+
method: "DELETE",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---- Seven365 super admin ------------------------------------------------
|
|
85
|
+
|
|
86
|
+
function getApprovals({
|
|
87
|
+
status = "awaiting-approval",
|
|
88
|
+
page = 1,
|
|
89
|
+
limit = 20,
|
|
90
|
+
includeDeleted = false,
|
|
91
|
+
} = {}): Promise<{
|
|
92
|
+
items: TServiceProviderInvitation[];
|
|
93
|
+
pages: number;
|
|
94
|
+
pageRange: string;
|
|
95
|
+
awaitingApprovalCount: number;
|
|
96
|
+
}> {
|
|
97
|
+
return useNuxtApp().$api(`${base}/approvals`, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
query: { status, page, limit, includeDeleted },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getApprovalCount(): Promise<{ count: number }> {
|
|
104
|
+
return useNuxtApp().$api(`${base}/approvals/count`, { method: "GET" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function approveInvitation(id: string) {
|
|
108
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/approve`, {
|
|
109
|
+
method: "PUT",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The reason is required — it is what the property manager is shown. */
|
|
114
|
+
function rejectInvitation(id: string, reason: string) {
|
|
115
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/reject`, {
|
|
116
|
+
method: "PUT",
|
|
117
|
+
body: { reason },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---- the invited service provider ----------------------------------------
|
|
122
|
+
|
|
123
|
+
function getInvitation(id: string): Promise<TServiceProviderInvitation> {
|
|
124
|
+
return useNuxtApp().$api(`${base}/${id}`, { method: "GET" });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function declineInvitation(id: string) {
|
|
128
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/decline`, {
|
|
129
|
+
method: "PUT",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
getInvitations,
|
|
135
|
+
cancelInvitation,
|
|
136
|
+
resendInvitation,
|
|
137
|
+
deleteInvitation,
|
|
138
|
+
getApprovals,
|
|
139
|
+
getApprovalCount,
|
|
140
|
+
approveInvitation,
|
|
141
|
+
rejectInvitation,
|
|
142
|
+
getInvitation,
|
|
143
|
+
declineInvitation,
|
|
144
|
+
};
|
|
145
|
+
}
|