@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.
Files changed (126) hide show
  1. package/.changeset/camera-wall-gated-endpoint.md +47 -0
  2. package/.changeset/camera-wall-plain-english.md +60 -0
  3. package/.changeset/camera-wall-selection-and-guidance.md +65 -0
  4. package/.changeset/dark-primary-contrast.md +39 -0
  5. package/.changeset/dashboard-dark-theme-contrast.md +15 -0
  6. package/.changeset/notification-preferences.md +17 -0
  7. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
  8. package/.changeset/service-provider-invitation-actions.md +28 -0
  9. package/assets/css/primitives.css +680 -0
  10. package/assets/css/screens.css +541 -0
  11. package/assets/css/shell.css +258 -0
  12. package/assets/css/tokens.css +220 -0
  13. package/components/AccessCardQrTagging.vue +1 -1
  14. package/components/AccessManagement.vue +53 -72
  15. package/components/AppButton.vue +52 -0
  16. package/components/AppCard.vue +20 -0
  17. package/components/AppDateField.vue +35 -0
  18. package/components/AppField.vue +51 -0
  19. package/components/AppKpiTile.vue +65 -0
  20. package/components/AppSegmented.vue +38 -0
  21. package/components/AppSelect.vue +203 -0
  22. package/components/AreaChecklistHistoryLogs.vue +51 -44
  23. package/components/AreaChecklistHistoryMain.vue +6 -2
  24. package/components/AreaMain.vue +9 -17
  25. package/components/AttendanceMain.vue +4 -3
  26. package/components/Avatar/Main.vue +54 -2
  27. package/components/BillingMain.vue +26 -30
  28. package/components/BuildingManagement/buildings.vue +27 -38
  29. package/components/BuildingManagement/units.vue +26 -42
  30. package/components/BulletinBoardManagement.vue +23 -10
  31. package/components/CameraWall.vue +1034 -0
  32. package/components/CameraWallTile.vue +818 -0
  33. package/components/CardToolbar.vue +37 -0
  34. package/components/CleaningScheduleMain.vue +29 -46
  35. package/components/ClientMain.vue +90 -78
  36. package/components/DashboardEmptyState.vue +11 -1
  37. package/components/DashboardMain.vue +349 -275
  38. package/components/DashboardPanel.vue +1 -1
  39. package/components/DocumentManagement.vue +129 -93
  40. package/components/EntryPassInformation.vue +4 -4
  41. package/components/EntryPassMain.vue +49 -27
  42. package/components/EquipmentItemMain.vue +4 -6
  43. package/components/EquipmentManagementMain.vue +9 -7
  44. package/components/FeedbackMain.vue +42 -56
  45. package/components/FormDialog.vue +60 -21
  46. package/components/HidAccessLogDashboard.vue +119 -44
  47. package/components/HidCameraCaptureDialog.vue +199 -0
  48. package/components/HidIntercomManagement.vue +825 -202
  49. package/components/HidQrCodeConfiguration.vue +927 -93
  50. package/components/HidServiceSettingsPanel.vue +20 -1
  51. package/components/HidUserEnrollment.vue +454 -106
  52. package/components/InvitationMain.vue +46 -61
  53. package/components/Layout/Header.vue +289 -37
  54. package/components/Layout/NavigationDrawer.vue +340 -30
  55. package/components/ManageChecklistMain.vue +17 -17
  56. package/components/MemberMain.vue +79 -60
  57. package/components/MyAttendanceMain.vue +3 -5
  58. package/components/NavigationItem.vue +9 -0
  59. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  60. package/components/NotificationSettings.vue +361 -0
  61. package/components/OnlineFormConfigurationForm.vue +620 -224
  62. package/components/OnlineFormFill.vue +36 -8
  63. package/components/OnlineFormsConfiguration.vue +7 -3
  64. package/components/PageHeader.vue +43 -0
  65. package/components/PassInformation.vue +104 -45
  66. package/components/QrTemplate/CreditCardLandscape.vue +19 -12
  67. package/components/QrTemplate/PrintDialog.vue +209 -196
  68. package/components/RolePermissionMain.vue +184 -91
  69. package/components/ScanVisitorQRCode.vue +35 -7
  70. package/components/ScheduleAreaMain.vue +15 -49
  71. package/components/ScheduleTaskMain.vue +23 -26
  72. package/components/ScheduleTastTicketMain.vue +18 -43
  73. package/components/ServiceProviderInvitationPrompt.vue +150 -0
  74. package/components/ServiceProviderMain.vue +346 -157
  75. package/components/SiteSettings.vue +25 -10
  76. package/components/SiteSettingsWorkOrder.vue +61 -30
  77. package/components/SpecificAttr.vue +10 -2
  78. package/components/StatusChip.vue +93 -0
  79. package/components/StockCard.vue +17 -12
  80. package/components/SwitchContext.vue +33 -1
  81. package/components/TableHygiene.vue +29 -46
  82. package/components/TableList.vue +1 -1
  83. package/components/TableListSecondary.vue +1 -1
  84. package/components/TableMain.vue +161 -77
  85. package/components/TableV3.vue +61 -66
  86. package/components/UnitMain.vue +9 -17
  87. package/components/VehicleManagement.vue +20 -9
  88. package/components/VisitorForm.vue +294 -78
  89. package/components/VisitorManagement.vue +221 -71
  90. package/components/VisitorSocketPopUp.vue +56 -2
  91. package/components/WorkOrder/Main.vue +42 -51
  92. package/composables/useComment.ts +3 -3
  93. package/composables/useHidAmico.ts +151 -0
  94. package/composables/useHidNavigation.ts +0 -7
  95. package/composables/useLocalAuth.ts +9 -36
  96. package/composables/useNotificationPreference.ts +87 -0
  97. package/composables/useOnlineFormPages.ts +2 -0
  98. package/composables/useOrg.ts +1 -1
  99. package/composables/useRole.ts +22 -0
  100. package/composables/useServiceProviderInvitation.ts +145 -0
  101. package/composables/useSipWebPhone.ts +336 -0
  102. package/composables/useSiteSettings.ts +50 -0
  103. package/composables/useThemePreference.ts +63 -0
  104. package/composables/useVisitor.ts +6 -1
  105. package/composables/useVisitorSocket.ts +26 -0
  106. package/composables/useWebUsb.ts +127 -37
  107. package/nuxt.config.ts +31 -0
  108. package/package.json +4 -1
  109. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  110. package/pages/notification-settings.vue +19 -0
  111. package/plugins/secure-member.client.ts +21 -56
  112. package/plugins/vuetify.ts +68 -9
  113. package/test/visitor-socket.test.mjs +36 -0
  114. package/types/org.d.ts +2 -0
  115. package/types/site.d.ts +71 -0
  116. package/utils/camera-wall.test.ts +571 -0
  117. package/utils/camera-wall.ts +926 -0
  118. package/utils/nav-sections.test.ts +190 -0
  119. package/utils/nav-sections.ts +151 -0
  120. package/utils/status.test.ts +77 -0
  121. package/utils/status.ts +90 -0
  122. package/utils/theme.test.ts +534 -0
  123. package/utils/theme.ts +294 -0
  124. package/components/HidIdentityMapping.vue +0 -975
  125. package/middleware/member.ts +0 -4
  126. 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 _address = {
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
- if (getCurrentUserReq.value) {
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) return null;
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 }
@@ -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}`, {
@@ -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
+ }