@7365admin1/layer-common 4.35.1-staging.385 → 4.37.0

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 (69) hide show
  1. package/CHANGELOG.md +16 -4
  2. package/assets/css/primitives.css +0 -43
  3. package/components/AccessCardAddForm.vue +4 -5
  4. package/components/AccessCardAssignToUnitForm.vue +2 -3
  5. package/components/AccessCardPreviewDialog.vue +3 -4
  6. package/components/AppSelect.vue +3 -77
  7. package/components/CameraForm.vue +6 -20
  8. package/components/DashboardMain.vue +60 -66
  9. package/components/Dialog/UpdateMoreAction.vue +1 -11
  10. package/components/DocumentForm.vue +112 -407
  11. package/components/DocumentManagement.vue +12 -213
  12. package/components/EntryPass/QrTemplatePreview.vue +25 -21
  13. package/components/EntryPassInformation.vue +24 -119
  14. package/components/Facility/BookingSetup.vue +0 -63
  15. package/components/HidAccessLogDashboard.vue +17 -74
  16. package/components/HidIntercomManagement.vue +14 -180
  17. package/components/HidQrCodeConfiguration.vue +195 -17
  18. package/components/HidReaderManagement.vue +0 -398
  19. package/components/HidReaderUserRoster.vue +3 -44
  20. package/components/HidUserEnrollment.vue +155 -845
  21. package/components/InvitationClientForm.vue +1 -19
  22. package/components/Layout/NavigationDrawer.vue +1 -4
  23. package/components/NotificationBell.vue +7 -59
  24. package/components/RolePermissionFormCreate.vue +3 -4
  25. package/components/RolePermissionFormPreviewUpdate.vue +5 -12
  26. package/components/RolePermissionMain.vue +0 -1
  27. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  28. package/components/ServiceProviderMain.vue +30 -178
  29. package/components/VisitorForm.vue +0 -12
  30. package/components/VisitorManagement.vue +10 -285
  31. package/components/VisitorSocketPopUp.vue +1 -2
  32. package/composables/useANPRSettings.ts +15 -45
  33. package/composables/useAccessManagement.ts +0 -5
  34. package/composables/useCleaningPermission.ts +0 -2
  35. package/composables/useCommonPermission.ts +0 -32
  36. package/composables/useDocument.ts +0 -7
  37. package/composables/useFacility.ts +0 -11
  38. package/composables/useHidAmico.ts +0 -159
  39. package/composables/useHidNavigation.ts +1 -25
  40. package/composables/usePeople.ts +0 -19
  41. package/composables/usePropertyPermission.ts +0 -19
  42. package/composables/useSecurityPermission.ts +0 -19
  43. package/composables/useServiceProvider.ts +0 -3
  44. package/composables/useServiceProviderInvitation.ts +3 -101
  45. package/composables/useSiteEntryPassSettings.ts +0 -26
  46. package/composables/useWebUsb.ts +11 -14
  47. package/package.json +2 -2
  48. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
  49. package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
  50. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
  51. package/plugins/API.ts +0 -11
  52. package/types/document.d.ts +0 -14
  53. package/types/facility.d.ts +0 -4
  54. package/types/people.d.ts +1 -28
  55. package/utils/module-routes.ts +1 -8
  56. package/utils/nav-sections.ts +2 -8
  57. package/utils/notification-target.ts +8 -277
  58. package/utils/permission-labels.ts +2 -20
  59. package/components/ClientInvitations.vue +0 -291
  60. package/components/HidAccessPermissions.vue +0 -424
  61. package/components/HidProfileQrCode.vue +0 -332
  62. package/composables/useHidReaderSelection.ts +0 -50
  63. package/composables/useSiteCategory.ts +0 -45
  64. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
  65. package/utils/dashboard-widget-prefs.ts +0 -54
  66. package/utils/hid-permission-assignments.ts +0 -190
  67. package/utils/hid-reader-selection.ts +0 -52
  68. package/utils/occupancy-role.ts +0 -109
  69. package/utils/sp-inviter-row.ts +0 -97
@@ -101,14 +101,9 @@
101
101
  style="max-height: calc(100vh - 280px)"
102
102
  >
103
103
  <template #item.companyName="{ item }">
104
- <span
105
- class="d-block text-truncate sp-col-company"
106
- :class="{ 'sp-muted': item.companyKnown === false }"
107
- >{{ item.companyName }}</span>
108
- <div v-if="item.contactPhone" class="sp-sub">{{ item.contactPhone }}</div>
109
- </template>
110
- <template #item.invited="{ item }">
111
- <span class="d-block text-truncate">{{ item.invitedLine || "-" }}</span>
104
+ <span class="d-block text-truncate sp-col-company">{{
105
+ item.companyName
106
+ }}</span>
112
107
  </template>
113
108
  <template #item.email="{ item }">
114
109
  <span class="d-block text-truncate sp-col-email">{{
@@ -133,10 +128,9 @@
133
128
  -->
134
129
  <StatusChip
135
130
  class="text-none"
136
- :status="item.viewStatus || item.statusRaw || item.status"
131
+ :status="item.statusRaw || item.status"
137
132
  :label="item.status"
138
133
  />
139
- <div v-if="item.statusLine" class="mt-1 sp-sub">{{ item.statusLine }}</div>
140
134
  <!-- Was `text-caption text-medium-emphasis` - Roboto 12px at
141
135
  Vuetify's 60% opacity. The design's sub-line is `--muted`. -->
142
136
  <div v-if="item.rejectionReason" class="mt-1 sp-reject-reason">
@@ -163,11 +157,6 @@
163
157
  </v-btn>
164
158
  </template>
165
159
  <v-list density="compact" min-width="220">
166
- <v-list-item
167
- v-if="item.providerOrgId"
168
- title="View provider"
169
- @click="openProvider(item)"
170
- />
171
160
  <v-list-item
172
161
  title="Cancel invitation"
173
162
  :disabled="!canCancelInvite(item)"
@@ -189,21 +178,6 @@
189
178
  />
190
179
  </v-list>
191
180
  </v-menu>
192
- <!--
193
- A provider on this site through an accepted invitation. Its row is
194
- the engagement itself, not a stored service-providers record, so
195
- the In-Active move (which acts on that record) does not apply.
196
- -->
197
- <v-btn
198
- v-else-if="item.source === 'engagement'"
199
- icon
200
- variant="text"
201
- density="comfortable"
202
- aria-label="View provider"
203
- @click="openProvider(item)"
204
- >
205
- <v-icon>mdi-information-outline</v-icon>
206
- </v-btn>
207
181
  <v-menu v-else-if="item.source === 'provider'" location="bottom end">
208
182
  <template #activator="{ props: menuProps }">
209
183
  <v-btn
@@ -265,26 +239,6 @@
265
239
 
266
240
  Same members, same two columns, same loading and empty copy, same close.
267
241
  -->
268
- <!-- The provider, as the organisation that invited it may see it. -->
269
- <v-dialog v-model="providerDialog" max-width="480">
270
- <v-card class="screen-modal">
271
- <v-card-title>{{ providerRow?.companyName }}</v-card-title>
272
- <v-card-text class="screen-modal__body">
273
- <dl class="sp-details">
274
- <template v-for="line in providerLines" :key="line[0]">
275
- <dt>{{ line[0] }}</dt>
276
- <dd>{{ line[1] }}</dd>
277
- </template>
278
- </dl>
279
- </v-card-text>
280
- <div class="screen-modal__footer">
281
- <v-btn class="screen-btn-ghost" variant="outlined" @click="providerDialog = false">
282
- Close
283
- </v-btn>
284
- </div>
285
- </v-card>
286
- </v-dialog>
287
-
288
242
  <v-dialog v-model="viewDialog" max-width="514">
289
243
  <v-card class="screen-modal">
290
244
  <v-card-title>Members ({{ memberDialogCount }})</v-card-title>
@@ -715,13 +669,6 @@ import useVerification from "../composables/useVerification";
715
669
  // import useRole from "../composables/useRole";
716
670
  import { errorConverter } from "../utils/data";
717
671
  import { pickRouteName } from "../utils/route-name";
718
- import {
719
- sentCompany,
720
- sentInvitedLine,
721
- sentStatusLine,
722
- spDay,
723
- spTypeLabel,
724
- } from "../utils/sp-inviter-row";
725
672
 
726
673
  type ListTab = "active" | "pending" | "inactive";
727
674
 
@@ -734,21 +681,9 @@ type TableRow = {
734
681
  type: string;
735
682
  members: string;
736
683
  status: string;
737
- /** provider: a stored row; engagement: on the site through an accepted
738
- * invitation; invite: an invitation this organisation sent. */
739
- source: "provider" | "engagement" | "invite";
684
+ source: "provider" | "invite";
740
685
  /** the stored status, for deciding which actions a row may offer */
741
686
  statusRaw?: string;
742
- /** what the row reads as (a lapsed "pending" is "expired") - the chip tone */
743
- viewStatus?: string;
744
- statusLine?: string;
745
- companyKnown?: boolean;
746
- invitedLine?: string;
747
- providerOrgId?: string;
748
- contactEmail?: string;
749
- contactPhone?: string;
750
- answeredAt?: string | null;
751
- onSite?: boolean;
752
687
  /** shown under a "Not approved" row, in Seven365's own words */
753
688
  rejectionReason?: string;
754
689
  siteName?: string;
@@ -839,7 +774,7 @@ const billingRouteName = pickRouteName(
839
774
  "service-provider-mgmt-billing"
840
775
  );
841
776
 
842
- const providerHeaders = [
777
+ const headers = [
843
778
  {
844
779
  title: "Company name",
845
780
  value: "companyName",
@@ -862,8 +797,7 @@ const providerHeaders = [
862
797
  maxWidth: "180px",
863
798
  },
864
799
  {
865
- // The provider's people assigned to this site - not its whole headcount.
866
- title: "Staff on site",
800
+ title: "Members",
867
801
  value: "members",
868
802
  sortable: false,
869
803
  width: "88px",
@@ -885,20 +819,6 @@ const providerHeaders = [
885
819
  },
886
820
  ];
887
821
 
888
- /** The Invitations tab: who it went to, who sent it and when, what happened. */
889
- const inviteHeaders = [
890
- { ...providerHeaders[0], title: "Company" },
891
- { ...providerHeaders[1], title: "Invited email" },
892
- { ...providerHeaders[2], title: "Service" },
893
- { title: "Invited", value: "invited", sortable: false, minWidth: "150px" },
894
- { ...providerHeaders[4], width: "200px" },
895
- providerHeaders[5],
896
- ];
897
-
898
- const headers = computed(() =>
899
- listTab.value === "pending" ? inviteHeaders : providerHeaders
900
- );
901
-
902
822
  const { natureOfBusinessV3 } = useLocal();
903
823
  const { requiredRule, emailRule, debounce } = useUtils();
904
824
  const { getUserByEmail } = useUser();
@@ -907,10 +827,7 @@ const listTab = ref<ListTab>("active");
907
827
  /** The same three tabs, in the same order, with the same labels. */
908
828
  const listTabs: Array<{ value: ListTab; label: string }> = [
909
829
  { value: "active", label: "Service Providers" },
910
- // Every invitation this site sent, whatever became of it - including the
911
- // accepted ones, so the client can see "Accepted" and not just a row that
912
- // vanished (owner request 2026-09-24).
913
- { value: "pending", label: "Invitations" },
830
+ { value: "pending", label: "Pending" },
914
831
  { value: "inactive", label: "In-Active" },
915
832
  ];
916
833
  const page = ref(1);
@@ -1238,40 +1155,19 @@ function rowActionLabel(row: TableRow) {
1238
1155
  }
1239
1156
 
1240
1157
  function typeDisplay(type: string, nature?: string) {
1241
- return spTypeLabel(type, nature, natureOfBusinessV3);
1242
- }
1243
-
1244
- const providerDialog = ref(false);
1245
- const providerRow = ref<TableRow | null>(null);
1246
-
1247
- function openProvider(row: TableRow) {
1248
- providerRow.value = row;
1249
- providerDialog.value = true;
1158
+ if (nature && String(nature).trim()) return String(nature);
1159
+ // useLocal returns a plain array; reading `.value` off it was always
1160
+ // undefined, so every row fell through to the raw stored slug
1161
+ // ("security_agency") instead of "Security".
1162
+ const source = natureOfBusinessV3 as any;
1163
+ const list = (Array.isArray(source) ? source : source?.value) as Array<{
1164
+ title?: string;
1165
+ value?: string;
1166
+ }>;
1167
+ const hit = list?.find((x) => x.value === type);
1168
+ return hit?.title ?? type ?? "-";
1250
1169
  }
1251
1170
 
1252
- /** Only what the inviting organisation needs; nothing about the provider's
1253
- * own staff or other clients. */
1254
- const providerLines = computed<Array<[string, string]>>(() => {
1255
- const row = providerRow.value;
1256
- if (!row) return [];
1257
- const lines: Array<[string, string]> = [
1258
- ["Service", row.type],
1259
- ["Email", row.contactEmail || row.email],
1260
- ];
1261
- if (row.contactPhone) lines.push(["Phone", row.contactPhone]);
1262
- if (row.source === "invite") {
1263
- lines.push(["Invited", row.invitedLine || "-"]);
1264
- if (row.answeredAt) lines.push(["Accepted", spDay(row.answeredAt)]);
1265
- lines.push([
1266
- "On this site",
1267
- row.onSite ? "Yes" : "No - removed since it accepted",
1268
- ]);
1269
- } else {
1270
- lines.push(["Staff on this site", row.members]);
1271
- }
1272
- return lines.filter((line) => line[1]);
1273
- });
1274
-
1275
1171
  function statusDisplay(raw: string, source: TableRow["source"]) {
1276
1172
  if (source === "invite") return "Pending";
1277
1173
  const s = (raw || "").toLowerCase();
@@ -1329,14 +1225,12 @@ function mapProviderRows(raw: Record<string, any>[]): TableRow[] {
1329
1225
  _id: String(row._id ?? ""),
1330
1226
  userId: String(row.user ?? ""),
1331
1227
  serviceProviderOrgId: String(row.serviceProviderOrgId ?? ""),
1332
- companyName: row.name || "-",
1228
+ companyName: row.name ?? "-",
1333
1229
  email: formatEmailCell(row.email),
1334
- contactEmail: String(row.email ?? ""),
1335
- contactPhone: String(row.contact ?? ""),
1336
1230
  type: typeDisplay(row.type ?? "", row.nature),
1337
1231
  members: formatMembersCell(row.members),
1338
1232
  status: statusDisplay(row.status ?? "active", "provider"),
1339
- source: row.source === "engagement" ? "engagement" : "provider",
1233
+ source: "provider",
1340
1234
  }));
1341
1235
  }
1342
1236
 
@@ -1350,35 +1244,24 @@ function inviteTypeLabel(t: string) {
1350
1244
  }
1351
1245
 
1352
1246
  function mapInviteRows(raw: Record<string, any>[]): TableRow[] {
1353
- return raw.map((row) => {
1354
- const company = sentCompany(row);
1355
- return {
1247
+ return raw.map((row) => ({
1356
1248
  _id: String(row._id ?? ""),
1357
1249
  userId: String(row.user ?? ""),
1358
- serviceProviderOrgId: String(row.providerOrgId || row.metadata?.serviceProviderOrgId || ""),
1359
- companyName: company.text,
1360
- companyKnown: company.known,
1250
+ serviceProviderOrgId: String(row.metadata?.serviceProviderOrgId ?? ""),
1251
+ companyName: row.metadata?.name || "-",
1361
1252
  email: formatEmailCell(row.email),
1362
1253
  type: row.metadata?.app
1363
1254
  ? typeDisplay(row.metadata.app)
1364
1255
  : inviteTypeLabel(row.type ?? ""),
1365
- members: "",
1366
- status:
1367
- row.statusLabel || serviceProviderInviteLabel(row.viewStatus || row.status),
1256
+ members: "-",
1257
+ // Every invitation used to read "Pending" whatever had happened to it. It
1258
+ // now says which of the seven states it is actually in, in words.
1259
+ status: row.statusLabel || serviceProviderInviteLabel(row.status),
1368
1260
  statusRaw: String(row.status ?? ""),
1369
- viewStatus: String(row.viewStatus || row.status || ""),
1370
- statusLine: sentStatusLine(row),
1371
- invitedLine: sentInvitedLine(row),
1372
- providerOrgId: String(row.providerOrgId ?? ""),
1373
- contactEmail: String(row.contactEmail ?? ""),
1374
- contactPhone: String(row.contactPhone ?? ""),
1375
- answeredAt: row.answeredAt ?? null,
1376
- onSite: Boolean(row.onSite),
1377
1261
  rejectionReason: row.rejectionReason ?? "",
1378
1262
  siteName: row.metadata?.siteName ?? "",
1379
1263
  source: "invite",
1380
- };
1381
- });
1264
+ }));
1382
1265
  }
1383
1266
 
1384
1267
  async function loadList() {
@@ -1408,10 +1291,8 @@ async function loadList() {
1408
1291
  // cancellation is visible here instead of the row simply vanishing.
1409
1292
  const data = await getInvitations({
1410
1293
  org: props.orgId,
1411
- // this site's invitations, not every site the organisation has
1412
- siteId: props.siteId,
1413
1294
  status:
1414
- "awaiting-approval,pending,complete,rejected,declined,cancelled,expired",
1295
+ "awaiting-approval,pending,rejected,declined,cancelled,expired",
1415
1296
  page: page.value,
1416
1297
  search: searchText.value?.trim() || "",
1417
1298
  limit: pageSize,
@@ -1429,9 +1310,6 @@ async function loadList() {
1429
1310
  search: searchText.value?.trim() || "",
1430
1311
  limit: pageSize,
1431
1312
  status: statusParam,
1432
- // the active tab also lists providers on this site through an
1433
- // accepted invitation
1434
- ...(statusParam === "active" ? { include: "engagements" } : {}),
1435
1313
  });
1436
1314
  items.value = mapProviderRows(rowsOf(data));
1437
1315
  pages.value = data.pages ?? 0;
@@ -1688,32 +1566,6 @@ async function submitServiceProviderInvite() {
1688
1566
 
1689
1567
  /* The design's sub-line: `--muted` at 12px, the same figure batch I's
1690
1568
  `AttachmentsViewer` settled on. There is no `--fs-sub` token. */
1691
- .sp-muted {
1692
- color: var(--muted);
1693
- }
1694
-
1695
- .sp-sub {
1696
- font-family: var(--font-sans);
1697
- font-size: 12px;
1698
- color: var(--muted);
1699
- }
1700
-
1701
- .sp-details {
1702
- display: grid;
1703
- grid-template-columns: max-content 1fr;
1704
- gap: 8px 16px;
1705
- margin: 0;
1706
- }
1707
-
1708
- .sp-details dt {
1709
- color: var(--muted);
1710
- }
1711
-
1712
- .sp-details dd {
1713
- margin: 0;
1714
- overflow-wrap: anywhere;
1715
- }
1716
-
1717
1569
  .sp-reject-reason,
1718
1570
  .sp-no-permission,
1719
1571
  .sp-members-note {
@@ -910,20 +910,8 @@ const hidQrReaderId = computed(() => (
910
910
  hidQrConfiguredReaderId.value || String(hidQrReader.value?._id || "")
911
911
  ));
912
912
 
913
- /*
914
- * Visitor QR passes are hidden at the manager's request (2026-09-23).
915
- *
916
- * A flag rather than a deletion: issuing, the preview dialog, printing and the
917
- * regenerate handler below are all intact and untouched, so bringing the
918
- * feature back is this one line. Every surface that offers a Visitor QR reads
919
- * `hidQrCodePassAllowed`, which is why the switch belongs here and not in each
920
- * of them.
921
- */
922
- const HID_VISITOR_QR_ENABLED = false;
923
-
924
913
  const hidQrCodePassAllowed = computed(() => {
925
914
  return (
926
- HID_VISITOR_QR_ENABLED &&
927
915
  hidQrCodePassEnabled.value &&
928
916
  ["guest", "contractor", "walk-in"].includes(prop.type)
929
917
  );