@7365admin1/layer-common 4.97.0 → 4.97.1-staging.501

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 (62) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/assets/css/primitives.css +57 -0
  3. package/assets/css/screens.css +8 -4
  4. package/components/AccessCardDetailsDialog.vue +59 -6
  5. package/components/AccessCardPreviewDialog.vue +371 -53
  6. package/components/AppSelect.vue +143 -4
  7. package/components/BuildingUnitFormEdit.vue +6 -0
  8. package/components/BuildingUnitManagement.vue +298 -0
  9. package/components/Dialog/UpdateMoreAction.vue +11 -1
  10. package/components/DocumentForm.vue +407 -112
  11. package/components/DocumentManagement.vue +213 -12
  12. package/components/Facility/BookingSetup.vue +63 -0
  13. package/components/HidAccessLogDashboard.vue +74 -17
  14. package/components/HidAccessPermissions.vue +532 -0
  15. package/components/HidIntercomManagement.vue +183 -14
  16. package/components/HidProfileQrCode.vue +332 -0
  17. package/components/HidQrCodeConfiguration.vue +17 -195
  18. package/components/HidReaderManagement.vue +398 -0
  19. package/components/HidReaderUserRoster.vue +44 -3
  20. package/components/HidUserEnrollment.vue +2242 -279
  21. package/components/InvitationClientForm.vue +19 -1
  22. package/components/Nfc/NFCPatrolRouteForm.vue +109 -58
  23. package/components/Nfc/NFCPatrolRouteMain.vue +3 -18
  24. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +0 -8
  25. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +1 -1
  26. package/components/ServiceProviderMain.vue +48 -0
  27. package/components/SiteProviderChange.vue +371 -0
  28. package/components/TableMain.vue +22 -7
  29. package/components/VisitorForm.vue +12 -0
  30. package/components/VisitorManagement.vue +267 -5
  31. package/composables/useAccessManagement.ts +17 -1
  32. package/composables/useCustomerSite.ts +29 -0
  33. package/composables/useDocument.ts +7 -0
  34. package/composables/useFacility.ts +11 -0
  35. package/composables/useHidAmico.ts +165 -0
  36. package/composables/useHidNavigation.ts +25 -1
  37. package/composables/useHidReaderSelection.ts +50 -0
  38. package/composables/useMember.ts +12 -0
  39. package/composables/useNFCPatrolReport.ts +36 -32
  40. package/composables/useNFCPatrolReportFilters.ts +21 -3
  41. package/composables/useNFCPatrolRoute.ts +10 -3
  42. package/composables/usePeople.ts +19 -0
  43. package/composables/useServiceProvider.ts +45 -2
  44. package/composables/useSiteCategory.ts +45 -0
  45. package/package.json +1 -1
  46. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
  47. package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
  48. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +23 -0
  49. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
  50. package/types/document.d.ts +14 -0
  51. package/types/facility.d.ts +4 -0
  52. package/types/member.d.ts +4 -0
  53. package/types/nfc-patrol-report.ts +0 -2
  54. package/types/people.d.ts +28 -1
  55. package/types/service-provider.d.ts +5 -0
  56. package/types/site.d.ts +7 -1
  57. package/utils/hid-enrolment-subject.ts +366 -0
  58. package/utils/hid-permission-assignments.ts +259 -0
  59. package/utils/hid-reader-selection.ts +52 -0
  60. package/utils/nfc-patrol-time.ts +99 -0
  61. package/utils/occupancy-role.ts +109 -0
  62. package/utils/service-type.ts +30 -0
package/CHANGELOG.md CHANGED
@@ -2082,11 +2082,11 @@
2082
2082
  `utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
2083
2083
  own rule from two endpoints the console already calls, unprojected:
2084
2084
 
2085
- GET /api/members/user/:user/app/admin the Seven365 staff membership
2086
- GET /api/roles/id/:role that membership's role document
2085
+ GET /api/members/user/:user/app/admin the Seven365 staff membership
2086
+ GET /api/roles/id/:role that membership's role document
2087
2087
 
2088
- owner = member.type === "admin" && role.type === "admin" && role.default === true
2089
- staff = member.type === "admin" && role.type === "admin"
2088
+ owner = member.type === "admin" && role.type === "admin" && role.default === true
2089
+ staff = member.type === "admin" && role.type === "admin"
2090
2090
 
2091
2091
  `role.default` is the marker because it is the only property of a platform
2092
2092
  staff role no API caller can set - `role.controller.ts` validates create and
@@ -775,6 +775,63 @@ select option {
775
775
  box-shadow: var(--shadow-menu);
776
776
  }
777
777
 
778
+ /* The filter box of a `searchable` AppSelect. Sticky rather than scrolled
779
+ away with the options: the list it filters is long enough to need scrolling,
780
+ which is the only reason the box is there. */
781
+ .app-select__search {
782
+ position: sticky;
783
+ top: -6px;
784
+ z-index: 1;
785
+ display: flex;
786
+ align-items: center;
787
+ gap: 6px;
788
+ margin: -6px -6px 4px;
789
+ padding: 8px 10px;
790
+ border-bottom: 1px solid var(--border);
791
+ background: var(--card);
792
+ color: var(--muted);
793
+ }
794
+
795
+ .app-select__search-input {
796
+ flex: 1;
797
+ min-width: 0;
798
+ border: none;
799
+ outline: none;
800
+ background: none;
801
+ font-family: var(--font-sans);
802
+ font-size: var(--fs-cell);
803
+ font-weight: 500;
804
+ color: var(--text);
805
+ }
806
+
807
+ .app-select__search-input::placeholder {
808
+ color: var(--muted);
809
+ font-weight: 500;
810
+ }
811
+
812
+ .app-select__empty {
813
+ margin: 0;
814
+ padding: 10px 8px;
815
+ font-family: var(--font-sans);
816
+ font-size: var(--fs-cell);
817
+ color: var(--muted);
818
+ text-align: center;
819
+ }
820
+
821
+ /* The footer of a paging AppSelect. Same voice as the empty state - it is the
822
+ same kind of message about the list rather than an option in it - with a rule
823
+ above so it does not read as one more row to click. */
824
+ .app-select__loading {
825
+ margin: 6px 0 0;
826
+ padding: 9px 8px;
827
+ border-top: 1px solid var(--border);
828
+ font-family: var(--font-sans);
829
+ font-size: var(--fs-cell);
830
+ font-weight: 600;
831
+ color: var(--muted);
832
+ text-align: center;
833
+ }
834
+
778
835
  .app-select__option {
779
836
  display: flex;
780
837
  align-items: center;
@@ -809,10 +809,14 @@
809
809
  color: var(--muted);
810
810
  }
811
811
 
812
- /* The in-card variant: 34px, 10px radius, as drawn. */
813
- .table-card__search .v-field {
814
- border-radius: var(--r-inner);
815
- font-size: var(--fs-cell);
812
+ /* The title-row search beside the primary button. `--search` alone is
813
+ `flex: 1`, written for a filter row; in the actions group that collapses it,
814
+ so it gets a fixed width here and never more than the row on a phone. */
815
+ .table-card__search.app-field--search {
816
+ flex: 0 0 auto;
817
+ width: 260px;
818
+ max-width: 100%;
819
+ min-width: 0;
816
820
  }
817
821
 
818
822
  .table-card__primary {
@@ -47,22 +47,30 @@
47
47
  <div>
48
48
  <div class="card-details__label">User</div>
49
49
  <div class="card-details__value">
50
- {{ details.userCred?.name ?? details.user?.name ?? "N/A" }}
51
- <template v-if="details.user?.email">({{ details.user.email }})</template>
50
+ <template v-if="holderState === 'named'">
51
+ {{ holderName }}
52
+ <template v-if="details.user?.email">({{ details.user.email }})</template>
53
+ </template>
54
+ <!-- Assigned, but the server did not send the name back. Saying
55
+ so beats "N/A", which reads as "nobody holds this card". -->
56
+ <template v-else-if="holderState === 'assigned'">
57
+ <span class="text-medium-emphasis">Assigned — name unavailable</span>
58
+ </template>
59
+ <template v-else>Unassigned</template>
52
60
  </div>
53
61
  </div>
54
62
 
55
- <div v-if="details.userType">
63
+ <div v-if="holderType">
56
64
  <div class="card-details__label">User Type</div>
57
- <div class="card-details__value">{{ details.userType }}</div>
65
+ <div class="card-details__value">{{ holderType }}</div>
58
66
  </div>
59
67
 
60
68
  <!-- Visitor / Resident Credentials -->
61
69
  <template v-if="details.userCred">
62
70
  <v-divider />
63
- <div class="card-details__section">
71
+ <!-- <div class="card-details__section">
64
72
  {{ details.userType ?? "Visitor Information" }}
65
- </div>
73
+ </div> -->
66
74
 
67
75
  <div v-if="details.userCred.email">
68
76
  <div class="card-details__label">Email</div>
@@ -216,6 +224,51 @@ const sortedHistory = computed(() =>
216
224
  )
217
225
  );
218
226
 
227
+ /**
228
+ * WHO HOLDS THIS CARD.
229
+ *
230
+ * `userCred` is written by the visitor-pass flow and carries the person's
231
+ * details on the card itself. `user` is the resolved account - the card only
232
+ * stores `userId`, so the server has to join it.
233
+ *
234
+ * `card-details` does not do that join today (the lookup is commented out in
235
+ * core's `getCardDetailsRepo`), so this is empty for every card assigned
236
+ * through Assign to Person and the field falls through to `holderState`.
237
+ * Nothing here needs to change when the join is restored.
238
+ */
239
+ const holderName = computed(
240
+ () => details.value?.userCred?.name ?? details.value?.user?.name ?? "",
241
+ );
242
+
243
+ /**
244
+ * A card with a `userId` IS assigned, whether or not the name came back.
245
+ *
246
+ * Printing "N/A" for both cases is the actual defect here: it reads as "nobody
247
+ * holds this card", which is the opposite of the truth for an assigned one and
248
+ * could have someone hand out a card that is already in use.
249
+ */
250
+ const holderState = computed<"named" | "assigned" | "unassigned">(() => {
251
+ if (holderName.value) return "named";
252
+ return details.value?.userId ? "assigned" : "unassigned";
253
+ });
254
+
255
+ /**
256
+ * WHICH KIND OF PERSON HOLDS THIS CARD — Visitor/Resident, Management or
257
+ * Service Provider, matching the groups Assign to Person offers.
258
+ *
259
+ * `holderType` is what the server should send once it records the category the
260
+ * card was assigned under. `userType` is NOT that: it is the card's own pool,
261
+ * and `getCardDetailsRepo` filters on `EAccessCardUserTypes.DEFAULT`, so every
262
+ * card this dialog can load has the literal value "Visitor/Resident" whoever
263
+ * actually holds it. Preferring `holderType` means a card given to Management
264
+ * stops describing itself as a resident's the day the field arrives.
265
+ *
266
+ * Falls back to `userType` so nothing disappears in the meantime.
267
+ */
268
+ const holderType = computed(
269
+ () => details.value?.holderType ?? details.value?.userType ?? "",
270
+ );
271
+
219
272
  watch(
220
273
  () => props.modelValue,
221
274
  async (val) => {