@7365admin1/layer-common 4.82.1-staging.476 → 4.83.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 (78) hide show
  1. package/CHANGELOG.md +10 -4
  2. package/assets/css/primitives.css +0 -57
  3. package/assets/css/screens.css +4 -8
  4. package/components/AccessCardDetailsDialog.vue +4 -57
  5. package/components/AccessCardPreviewDialog.vue +50 -291
  6. package/components/AppSelect.vue +4 -143
  7. package/components/AttendanceDetailsDialog.vue +1 -48
  8. package/components/AttendanceMain.vue +10 -104
  9. package/components/AttendanceSettingsDialog.vue +1 -53
  10. package/components/BuildingUnitFormEdit.vue +0 -6
  11. package/components/BulletinBoardView.vue +1 -1
  12. package/components/Dialog/UpdateMoreAction.vue +1 -11
  13. package/components/DocumentForm.vue +112 -407
  14. package/components/DocumentManagement.vue +12 -213
  15. package/components/Facility/BookingSetup.vue +0 -63
  16. package/components/HidAccessLogDashboard.vue +17 -74
  17. package/components/HidIntercomManagement.vue +14 -183
  18. package/components/HidQrCodeConfiguration.vue +195 -17
  19. package/components/HidReaderManagement.vue +0 -398
  20. package/components/HidReaderUserRoster.vue +3 -44
  21. package/components/HidUserEnrollment.vue +250 -1573
  22. package/components/InventoryMain.vue +2 -11
  23. package/components/InvitationClientForm.vue +1 -19
  24. package/components/MemberMain.vue +1 -2
  25. package/components/MyAttendanceMain.vue +2 -60
  26. package/components/NotificationBell.vue +2 -14
  27. package/components/RolePermissionMain.vue +2 -4
  28. package/components/TableMain.vue +7 -22
  29. package/components/VisitorForm.vue +0 -12
  30. package/components/VisitorManagement.vue +5 -267
  31. package/composables/useAttendance.ts +0 -53
  32. package/composables/useAttendancePermission.ts +0 -25
  33. package/composables/useCleaningPermission.ts +0 -5
  34. package/composables/useDocument.ts +0 -7
  35. package/composables/useFacility.ts +0 -11
  36. package/composables/useHidAmico.ts +0 -165
  37. package/composables/useHidNavigation.ts +1 -25
  38. package/composables/useLandscapingPermission.ts +0 -5
  39. package/composables/useMember.ts +0 -12
  40. package/composables/usePeople.ts +0 -19
  41. package/composables/usePestControlPermission.ts +0 -5
  42. package/composables/usePoolMaintenancePermission.ts +0 -5
  43. package/composables/useSecurityPermission.ts +0 -5
  44. package/composables/useServiceProvider.ts +2 -40
  45. package/constants/permissions.ts +0 -1
  46. package/nuxt.config.ts +0 -18
  47. package/package.json +2 -4
  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 -16
  52. package/types/attendance.d.ts +1 -13
  53. package/types/document.d.ts +0 -14
  54. package/types/facility.d.ts +0 -4
  55. package/types/member.d.ts +0 -4
  56. package/types/people.d.ts +1 -28
  57. package/types/service-provider.d.ts +0 -5
  58. package/utils/notification-target.ts +4 -43
  59. package/utils/role.ts +2 -15
  60. package/components/AttendanceProxyDialog.vue +0 -391
  61. package/components/BuildingUnitManagement.vue +0 -298
  62. package/components/HidAccessPermissions.vue +0 -424
  63. package/components/HidProfileQrCode.vue +0 -332
  64. package/composables/useHidReaderSelection.ts +0 -50
  65. package/composables/useSiteActions.ts +0 -29
  66. package/composables/useSiteCategory.ts +0 -45
  67. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
  68. package/server/plugins/no-powered-by.ts +0 -7
  69. package/utils/access-suspended.ts +0 -18
  70. package/utils/attendance-proxy.ts +0 -110
  71. package/utils/hid-enrolment-subject.ts +0 -144
  72. package/utils/hid-permission-assignments.ts +0 -190
  73. package/utils/hid-reader-selection.ts +0 -52
  74. package/utils/occupancy-role.ts +0 -109
  75. package/utils/sanitize-html.ts +0 -16
  76. package/utils/security-headers.ts +0 -52
  77. package/utils/service-type.ts +0 -30
  78. package/utils/site-actions.ts +0 -18
@@ -5,7 +5,7 @@
5
5
  :org="orgId"
6
6
  message="Enable HID as a service for this site before managing physical HID cards."
7
7
  >
8
- <HidUserEnrollment :site="siteId" :org="orgId" card-management />
8
+ <HidUserEnrollment :site="siteId" card-management />
9
9
  </HidEnabledGate>
10
10
  </v-container>
11
11
  </template>
@@ -5,7 +5,7 @@
5
5
  :org="orgId"
6
6
  message="Enable HID as a service for this site before managing HID readers."
7
7
  >
8
- <HidReaderManagement :site="siteId" :org="orgId" />
8
+ <HidReaderManagement :site="siteId" />
9
9
  </HidEnabledGate>
10
10
  </v-container>
11
11
  </template>
package/plugins/API.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  import { isPermissionRefusal } from "../utils/permission-refusal";
2
- import { accessSuspendedMessage, SIGNIN_NOTICE_COOKIE } from "../utils/access-suspended";
3
2
 
4
3
  export default defineNuxtPlugin((nuxtApp) => {
5
4
  const { cookieConfig } = useRuntimeConfig().public;
6
- let signingOut = false;
7
5
 
8
6
  const api = $fetch.create({
9
7
  baseURL: "/",
@@ -34,20 +32,6 @@ export default defineNuxtPlugin((nuxtApp) => {
34
32
  // 401 + "You do not have permission ..." (vehicle writes and every other
35
33
  // `requireSiteReach` gate). See utils/permission-refusal.ts.
36
34
  onResponseError({ response }) {
37
- // Every membership suspended (2026-10-01): the server already ended the
38
- // session. Sign out here and let the sign-in page say why, once.
39
- const suspended = accessSuspendedMessage((response as any)?._data);
40
- if (suspended && import.meta.client && !signingOut) {
41
- signingOut = true;
42
- void nuxtApp.runWithContext(() => {
43
- useCookie(SIGNIN_NOTICE_COOKIE, { ...cookieConfig, maxAge: 300 }).value = suspended;
44
- for (const name of ["sid", "user", "organization"]) useCookie(name, cookieConfig).value = null;
45
- const { APP_MAIN, APP_NAME } = useRuntimeConfig().public as Record<string, string>;
46
- if (String(APP_NAME ?? "").toLowerCase() === "main" || !APP_MAIN) return navigateTo("/sign-in");
47
- window.location.href = `${APP_MAIN}/sign-in`;
48
- });
49
- return;
50
- }
51
35
  if (!isPermissionRefusal(response?.status, (response as any)?._data)) return;
52
36
  if (!import.meta.client) return;
53
37
  const refresh = (nuxtApp as any).$refreshAppRole;
@@ -11,11 +11,9 @@ declare type TAttendance = {
11
11
  declare type TAttendanceCheckIn = {
12
12
  checkIn: {
13
13
  timestamp: string;
14
- location?: TAttendanceLocation;
14
+ location: TAttendanceLocation;
15
15
  img?: string;
16
- privatePhoto?: string;
17
16
  };
18
- proxy?: TAttendanceProxy;
19
17
  };
20
18
 
21
19
  declare type TAttendanceCheckOut = {
@@ -37,14 +35,4 @@ declare type TAttendanceSettings = {
37
35
  location?: TAttendanceLocation;
38
36
  isGeofencingEnabled?: boolean;
39
37
  mile?: number;
40
- /** Site device mode: one shared phone may check staff in/out (default OFF). */
41
- siteDeviceMode?: boolean;
42
- };
43
-
44
- /** Who a Site device mode check-in is for. Never the full NRIC. */
45
- declare type TAttendanceProxy = {
46
- name: string;
47
- nricLast4: string;
48
- nricPrefix?: string;
49
- post?: string;
50
38
  };
@@ -3,18 +3,4 @@ declare type TDocument = {
3
3
  name: string;
4
4
  attachment: string;
5
5
  type: string;
6
- /**
7
- * Set at upload or renewal, absent when the document never expires.
8
- *
9
- * An ISO instant on the way out of the API and a `YYYY-MM-DD` string on the
10
- * way back in, which is what `InputDatePicker` binds to - hence the loose
11
- * type rather than `string`.
12
- */
13
- expiryDate?: string | Date | null;
14
- remarks?: string;
15
- size?: number;
16
- parentId?: string | null;
17
- createdAt?: string | Date;
18
- updatedAt?: string | Date;
19
- status?: string;
20
6
  };
@@ -73,10 +73,6 @@ declare type TFacility = {
73
73
  hourInterval?: number;
74
74
  isBookingFeeEnabled: boolean;
75
75
  isGuestEnabled: boolean;
76
- /** Residents may complete this facility's checklist themselves. */
77
- isResidentChecklistEnabled?: boolean;
78
- /** Security may complete it. Mutually exclusive with the above. */
79
- isSecurityChecklistEnabled?: boolean;
80
76
  bookingAmount: number | null;
81
77
  peakBookingFeeAmount?: number | null;
82
78
  bookingFeeGuestAmount?: number | null;
package/types/member.d.ts CHANGED
@@ -10,10 +10,6 @@ declare type TMember = {
10
10
  customerOrgId?: string;
11
11
  customerSiteId?: string;
12
12
  status?: string;
13
- email?: string;
14
- /** The building unit this member manages (a unit's Management tab). */
15
- unit?: string | null;
16
- unitName?: string;
17
13
  createdAt?: string;
18
14
  updatedAt?: string;
19
15
  deletedAt?: string;
package/types/people.d.ts CHANGED
@@ -22,38 +22,11 @@ declare type TPeople = {
22
22
  email?: string;
23
23
  files?: { name: string, id: string }[];
24
24
  isOwner?: boolean;
25
- /**
26
- * Owner / occupant / person-in-charge, as the BUSINESS people form asks it.
27
- * Absent on everyone else - `isOwner` is still the flag that decides unit
28
- * ownership, and the server derives it from this when it is sent. See
29
- * `OccupancyRoles` in core's `person.model.ts`.
30
- */
31
- occupancyRole?: TOccupancyRole;
32
- // Link to this person's `users` row. Empty for the person types that carry
33
- // no login, and empty for a resident whose account was never written.
34
- user?: string;
35
- };
36
-
37
- declare type TOccupancyRole = "owner" | "occupant" | "person-in-charge";
38
-
39
- /** One standing and everything the product says about it. See `utils/occupancy-role.ts`. */
40
- declare type TOccupancyRoleOption = {
41
- value: TOccupancyRole;
42
- /** The label a stored role is printed with. */
43
- title: string;
44
- /** The row menu's wording. */
45
- action: string;
46
- /** The confirmation's heading and its sentence. */
47
- prompt: string;
48
- description: string;
49
25
  };
50
26
 
51
27
  declare type TPlateNumber = { plateNumber: string, recNo?: string, _id?: string, status: TVehicleStatus, type: TVehicleType, anprCameras?: TVehicleAnprCamera[] };
52
28
 
53
29
 
54
- // `""` is the FORM's "nothing picked yet" - it is stripped from the payload
55
- // before it is sent, because absence is what the server reads as "never
56
- // asked". See `PeopleFormMgmt.vue`.
57
- declare type TPeoplePayload = Pick<TGuest, "name" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "nric" | "contact" | "remarks" | "org" | "site" | "start" | "end" | "type" | "isOwner"> & { status?: string; user?: string; occupancyRole?: TOccupancyRole | "" }
30
+ declare type TPeoplePayload = Pick<TGuest, "name" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "nric" | "contact" | "remarks" | "org" | "site" | "start" | "end" | "type" | "isOwner"> & { status?: string }
58
31
 
59
32
  declare type TPeopleType = "guest" | "resident" | "tenant"
@@ -7,11 +7,6 @@ declare type TServiceProvider = {
7
7
  serviceProviderOrgId: string;
8
8
  type: string;
9
9
  nature: string;
10
- status?: string;
11
- email?: string;
12
- /** The building unit this provider serves (a unit's Management tab). */
13
- unit?: string | null;
14
- unitName?: string;
15
10
  };
16
11
 
17
12
  declare type TServiceProviderName = {
@@ -45,7 +45,7 @@ export type TTargetRow = {
45
45
  * (core `withNotificationKind`); `site` stands in for `siteId` on rows kept
46
46
  * unscoped on purpose. Older rows have neither and are read from `module`.
47
47
  */
48
- params?: { id?: string; org?: string; kind?: string; site?: string; storeId?: string } | null;
48
+ params?: { id?: string; org?: string; kind?: string; site?: string } | null;
49
49
  };
50
50
 
51
51
  /** What the surrounding app knows that the stored row does not. */
@@ -127,26 +127,9 @@ export function notificationKind(item: TTargetRow | null | undefined): string {
127
127
  return "";
128
128
  }
129
129
  if (module === "other") {
130
- if (/(accepted|declined) your invitation$/i.test(title)) return "serviceProviderInviteAnswer";
131
- // Inventory alerts sent before core had an inventory module were stored as "other".
132
- return /request waiting|request (was )?(approved|rejected)|low (on )?stock|stock handed to you|hand-over (confirmed|declined)/i.test(title)
133
- ? "inventory"
134
- : "";
130
+ return /(accepted|declined) your invitation$/i.test(title) ? "serviceProviderInviteAnswer" : "";
135
131
  }
136
- // Older rows spelled the work order module with a hyphen.
137
- return module === "work-order" ? "workOrder" : module;
138
- }
139
-
140
- /**
141
- * Which inventory tab an alert is about. Alerts carry the store id but no tab,
142
- * so the tab follows the alert's fixed title (API-core inventory-notify.service)
143
- * - the same rule the mobile apps use. Values are the web tabs' own names.
144
- */
145
- export function inventoryTabForAlert(title: string): "requests" | "stock" | "mine" | "" {
146
- if (/stock handed to you|hand-over (confirmed|declined)/i.test(title)) return "mine";
147
- if (/request waiting|request (was )?(approved|rejected)/i.test(title)) return "requests";
148
- if (/low (on )?stock/i.test(title)) return "stock";
149
- return "";
132
+ return module;
150
133
  }
151
134
 
152
135
  /**
@@ -199,8 +182,6 @@ const SITE_PAGES: Record<string, TSitePage[]> = {
199
182
  { apps: ["security"], page: () => "virtual-patrol/cctv" },
200
183
  { apps: ["property-management"], page: () => "cctv" },
201
184
  ],
202
- // A robot at the site raised an alarm (core safety alert, category robotAlert).
203
- robotAlert: [{ apps: ["security", "property-management", "hygiene"], page: () => "robot-management/alerts" }],
204
185
  // hygiene has its Service Providers page too since 2026-09-28
205
186
  serviceProviderInviteAnswer: [{ apps: SERVICE_APPS, page: () => "service-providers" }],
206
187
  };
@@ -233,12 +214,7 @@ function sitePageTarget(
233
214
  const list = apps === "any" ? [home, ...SERVICE_APPS.filter((a) => a !== home)] : apps;
234
215
  for (const app of list) candidates.push({ app, path: base + page(id) });
235
216
  }
236
- if (kind !== "inventory") return { candidates };
237
- // Open the alert's own store on the tab it is about (InventoryMain reads ?storeId=&tab=).
238
- const storeId = String(item.params?.storeId ?? item.params?.id ?? "").trim();
239
- const tab = inventoryTabForAlert(String(item.title ?? ""));
240
- const query: Record<string, string> = { ...(storeId ? { storeId } : {}), ...(tab ? { tab } : {}) };
241
- return Object.keys(query).length ? { candidates, query } : { candidates };
217
+ return { candidates };
242
218
  }
243
219
 
244
220
  /** Seven365 staff pages, in the organisation app. */
@@ -318,21 +294,6 @@ export function notificationTarget(
318
294
  };
319
295
  }
320
296
 
321
- /** Kinds that only the iService365 phone apps can open (residents' own screens). */
322
- const PHONE_ONLY_KINDS = new Set(["prelovedMarketplace", "marketplace-order"]);
323
-
324
- /**
325
- * What the bell says when a click has nowhere to go, so a click is never a
326
- * silent nothing. A link grants nothing either way: the page opened still
327
- * checks the person may see it.
328
- */
329
- export function noTargetMessage(item: TTargetRow | null | undefined): string {
330
- if (PHONE_ONLY_KINDS.has(notificationKind(item))) {
331
- return "This notification opens in the iService365 app on your phone.";
332
- }
333
- return "There is no page to open for this notification here. It has been marked as read.";
334
- }
335
-
336
297
  /**
337
298
  * The full URL for a target whose page lives in another web app, or "" when
338
299
  * there is none to build (no path, or that app's URL is not configured).
package/utils/role.ts CHANGED
@@ -9,19 +9,6 @@
9
9
  export const showSiteField = (siteState: boolean, type: string): boolean =>
10
10
  siteState && type !== "admin";
11
11
 
12
- /**
13
- * A role's name with its site, "Admin · St. Regis Residences" (owner
14
- * 2026-10-01: role names are unique per SITE, so two sites may both have an
15
- * "Admin" - two independent roles - and every list that can show both must
16
- * say which is which). Organisation-level roles (no site) show the name alone.
17
- * `siteName` comes from the role list (`GET /api/roles`, core 2026-10-01).
18
- */
19
- export function roleLabel(role: { name?: unknown; siteName?: unknown; site?: unknown } | null | undefined): string {
20
- const name = typeof role?.name === "string" ? role.name : "";
21
- const site = typeof role?.siteName === "string" ? role.siteName.trim() : "";
22
- return role?.site && site ? `${name} · ${site}` : name;
23
- }
24
-
25
12
  /**
26
13
  * The Assign Role picker's list, with the member's CURRENT role always in it.
27
14
  *
@@ -63,13 +50,13 @@ export function memberRoleChip(
63
50
  const limited = limitedSites(item);
64
51
  if (orgLevel && limited.length) {
65
52
  return {
66
- text: limited.map((l) => `${l.roleName || "No role"} · ${l.siteName || "a site"}`).join(", "),
53
+ text: limited.map((l) => `${l.roleName || "No role"} (${l.siteName || "a site"})`).join(", "),
67
54
  tone: "info",
68
55
  };
69
56
  }
70
57
  if (orgLevel && item?.roleState === "other-site") {
71
58
  const site = typeof item?.roleSiteName === "string" && item.roleSiteName ? item.roleSiteName : "";
72
- return { text: site ? `${name || "Role"} · ${site}` : name || "Role", tone: "info" };
59
+ return { text: site ? `${name || "Role"} (${site})` : name || "Role", tone: "info" };
73
60
  }
74
61
  switch (item?.roleState) {
75
62
  case "missing":