@7365admin1/layer-common 3.2.1 → 3.2.2-staging.76

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 (55) 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/service-provider-invitation-actions.md +28 -0
  7. package/components/CameraWall.vue +1034 -0
  8. package/components/CameraWallTile.vue +818 -0
  9. package/components/DashboardMain.vue +84 -88
  10. package/components/DashboardPanel.vue +1 -1
  11. package/components/EntryPassInformation.vue +4 -4
  12. package/components/FeedbackMain.vue +7 -7
  13. package/components/HidAccessLogDashboard.vue +119 -44
  14. package/components/HidCameraCaptureDialog.vue +199 -0
  15. package/components/HidIntercomManagement.vue +808 -201
  16. package/components/HidQrCodeConfiguration.vue +907 -95
  17. package/components/HidServiceSettingsPanel.vue +20 -1
  18. package/components/HidUserEnrollment.vue +392 -81
  19. package/components/Layout/NavigationDrawer.vue +43 -2
  20. package/components/NavigationItem.vue +9 -0
  21. package/components/Nfc/NFCPatrolRouteMain.vue +52 -7
  22. package/components/OnlineFormConfigurationForm.vue +620 -224
  23. package/components/OnlineFormFill.vue +36 -8
  24. package/components/OnlineFormsConfiguration.vue +6 -2
  25. package/components/QrTemplate/CreditCardLandscape.vue +19 -12
  26. package/components/QrTemplate/PrintDialog.vue +209 -196
  27. package/components/ServiceProviderInvitationPrompt.vue +150 -0
  28. package/components/ServiceProviderMain.vue +258 -14
  29. package/components/SiteSettings.vue +4 -2
  30. package/components/VisitorForm.vue +201 -78
  31. package/components/VisitorManagement.vue +2 -3
  32. package/components/VisitorSocketPopUp.vue +56 -2
  33. package/composables/useComment.ts +3 -3
  34. package/composables/useHidAmico.ts +127 -0
  35. package/composables/useHidNavigation.ts +0 -7
  36. package/composables/useLocalAuth.ts +9 -36
  37. package/composables/useOnlineFormPages.ts +2 -0
  38. package/composables/useServiceProviderInvitation.ts +145 -0
  39. package/composables/useSipWebPhone.ts +311 -0
  40. package/composables/useSiteSettings.ts +50 -0
  41. package/composables/useVisitorSocket.ts +26 -0
  42. package/composables/useWebUsb.ts +127 -37
  43. package/package.json +3 -1
  44. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  45. package/plugins/secure-member.client.ts +21 -56
  46. package/plugins/vuetify.ts +37 -9
  47. package/test/visitor-socket.test.mjs +36 -0
  48. package/types/site.d.ts +71 -0
  49. package/utils/camera-wall.test.ts +571 -0
  50. package/utils/camera-wall.ts +926 -0
  51. package/utils/theme.test.ts +216 -0
  52. package/utils/theme.ts +163 -0
  53. package/components/HidIdentityMapping.vue +0 -975
  54. package/middleware/member.ts +0 -4
  55. 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" | "1" | "2";
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; qrFormat: "0" | "1" | "2" },
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,33 @@ 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
+
115
192
  function createIdentity(readerId: string, payload: HidIdentityPayload) {
116
193
  return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/identities`, {
117
194
  method: "POST",
@@ -151,6 +228,49 @@ export default function useHidAmico() {
151
228
  });
152
229
  }
153
230
 
231
+ function getSitePermissions(siteId: string, orgId: string, readerId: string) {
232
+ return useNuxtApp().$api<{ data: THidSitePermissions }>(
233
+ `${basePath}/sites/${siteId}/permissions`,
234
+ { method: "GET", query: { orgId, readerId } },
235
+ );
236
+ }
237
+
238
+ function ensureSipAccount(siteId: string, orgId: string) {
239
+ return useNuxtApp().$api<{ data: HidSipAccountData }>(
240
+ `${basePath}/sites/${siteId}/intercom/sip-account`,
241
+ { method: "POST", body: { orgId } },
242
+ );
243
+ }
244
+
245
+ function getPermissionCandidates(
246
+ siteId: string,
247
+ query: {
248
+ orgId: string;
249
+ readerId: string;
250
+ category: THidPermissionCategory | "intercom";
251
+ search?: string;
252
+ page?: number;
253
+ limit?: number;
254
+ },
255
+ ) {
256
+ return useNuxtApp().$api<HidPermissionListResponse>(
257
+ `${basePath}/sites/${siteId}/permission-candidates`,
258
+ { method: "GET", query },
259
+ );
260
+ }
261
+
262
+ function updateSitePermissions(
263
+ siteId: string,
264
+ orgId: string,
265
+ readerId: string,
266
+ assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>,
267
+ ) {
268
+ return useNuxtApp().$api<{ data: THidSitePermissions; message: string }>(
269
+ `${basePath}/sites/${siteId}/permissions`,
270
+ { method: "PUT", body: { orgId, readerId, assignments } },
271
+ );
272
+ }
273
+
154
274
  return {
155
275
  getReaders,
156
276
  createReader,
@@ -159,10 +279,13 @@ export default function useHidAmico() {
159
279
  testReader,
160
280
  syncReader,
161
281
  runObjectOperation,
282
+ issueVisitorQr,
162
283
  getReaderConfiguration,
163
284
  setReaderConfiguration,
164
285
  getLogs,
165
286
  getUserImage,
287
+ setUserImage,
288
+ deleteUserImage,
166
289
  getIdentities,
167
290
  createIdentity,
168
291
  updateIdentity,
@@ -170,5 +293,9 @@ export default function useHidAmico() {
170
293
  getIntercomStatus,
171
294
  makeIntercomCall,
172
295
  finalizeIntercomCall,
296
+ ensureSipAccount,
297
+ getSitePermissions,
298
+ getPermissionCandidates,
299
+ updateSitePermissions,
173
300
  };
174
301
  }
@@ -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
  });
@@ -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 }
@@ -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
+ }