@7365admin1/layer-common 4.39.0 → 4.39.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 4.39.1
4
+
5
+ ### Patch Changes
6
+
7
+ - b1c2177: Visitor Mgmt: the Unregistered tab now appears on first load and after switching site, without a hard refresh. `useANPRSettings` takes a ref or getter for the site and keeps one entry per site; the Visitor Mgmt page reads its own site's ANPR switches instead of relying on the layout's plate toast, which kept the first site's switches after a site switch. A plate-toast link to the Unregistered tab is no longer bounced to Registered while the switches are still loading.
8
+
3
9
  ## 4.39.0
4
10
 
5
11
  ### Minor Changes
@@ -1447,11 +1453,11 @@
1447
1453
  `utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
1448
1454
  own rule from two endpoints the console already calls, unprojected:
1449
1455
 
1450
- GET /api/members/user/:user/app/admin the Seven365 staff membership
1451
- GET /api/roles/id/:role that membership's role document
1456
+ GET /api/members/user/:user/app/admin the Seven365 staff membership
1457
+ GET /api/roles/id/:role that membership's role document
1452
1458
 
1453
- owner = member.type === "admin" && role.type === "admin" && role.default === true
1454
- staff = member.type === "admin" && role.type === "admin"
1459
+ owner = member.type === "admin" && role.type === "admin" && role.default === true
1460
+ staff = member.type === "admin" && role.type === "admin"
1455
1461
 
1456
1462
  `role.default` is the marker because it is the only property of a platform
1457
1463
  staff role no API caller can set - `role.controller.ts` validates create and
@@ -1502,8 +1502,17 @@ const { org: orgId, site: siteId } = route.params as {
1502
1502
  site: string;
1503
1503
  };
1504
1504
 
1505
- const { enableUnregistered } = useANPRSettings(siteId);
1505
+ // The page reads its own site's switches rather than relying on the layout's
1506
+ // toast to have fetched them - that copy could belong to the previous site.
1507
+ const {
1508
+ enableUnregistered,
1509
+ loaded: anprSettingsLoaded,
1510
+ ensureANPRSettings,
1511
+ } = useANPRSettings(siteId);
1506
1512
  const enableUnregisteredTab = computed(() => enableUnregistered.value);
1513
+ ensureANPRSettings(true).catch(() => {
1514
+ // No settings = no Unregistered tab, same as before.
1515
+ });
1507
1516
 
1508
1517
  const {
1509
1518
  getVisitors,
@@ -2933,7 +2942,11 @@ watch([searchInput, dateFrom, dateTo, filterTypes, activeTab], () => {
2933
2942
  const syncVisitorFiltersFromRoute = () => {
2934
2943
  const requestedTab = (route.query.tab as string) || "registered";
2935
2944
  const nextTab =
2936
- (!enableUnregisteredTab.value && requestedTab === "unregistered") ||
2945
+ // Until the switches are read, keep a requested Unregistered tab (the plate
2946
+ // toast links here) instead of bouncing it to Registered.
2947
+ (anprSettingsLoaded.value &&
2948
+ !enableUnregisteredTab.value &&
2949
+ requestedTab === "unregistered") ||
2937
2950
  (!showResidentTransactionsTab.value && requestedTab === "resident-transactions")
2938
2951
  ? "registered"
2939
2952
  : requestedTab;
@@ -2992,9 +3005,9 @@ const { socketUnregisteredVisitorTrigger, visitorSocketData } =
2992
3005
  useVisitorSocket();
2993
3006
 
2994
3007
  watch(
2995
- enableUnregisteredTab,
2996
- (enabled) => {
2997
- if (!enabled && activeTab.value === "unregistered") {
3008
+ [enableUnregisteredTab, anprSettingsLoaded],
3009
+ ([enabled]) => {
3010
+ if (!enabled && anprSettingsLoaded.value && activeTab.value === "unregistered") {
2998
3011
  activeTab.value = "registered";
2999
3012
  }
3000
3013
  },
@@ -137,8 +137,9 @@ const config = useRuntimeConfig();
137
137
  const publicConfig = config.public as Record<string, string | undefined>;
138
138
  const route = useRoute();
139
139
  const { tab } = useVisitor();
140
+ // A getter, not the string: this component outlives site switches.
140
141
  const { enableUnregistered, ensureANPRSettings } = useANPRSettings(
141
- props.siteId
142
+ () => props.siteId
142
143
  );
143
144
  const enableUnregisteredSnackbar = computed(() => enableUnregistered.value);
144
145
  const { socketUnregisteredVisitorTrigger, visitorSocketData } =
@@ -1,19 +1,50 @@
1
- export default function (siteId?: string) {
2
- const stateKey = computed(() =>
3
- siteId ? `anpr-settings-${siteId}` : "anpr-settings"
4
- );
1
+ import { computed, toValue, type MaybeRefOrGetter } from "vue";
2
+
3
+ /*
4
+ * A site's ANPR switches (e.g. "Enable Unregistered Vehicles"), shared by every
5
+ * caller on the page: the Visitor Mgmt tabs, the plate toast in the layout, and
6
+ * Site Settings.
7
+ *
8
+ * `siteId` may be a ref or a getter. The layout's toast lives across site
9
+ * switches, so a plain string captured once kept answering for the FIRST site
10
+ * it saw - and the Visitor Mgmt page never fetched at all, so its
11
+ * "Unregistered" tab showed only when that stale copy happened to be for the
12
+ * right site (i.e. after a hard refresh on the page itself).
13
+ *
14
+ * One map keyed by site id: a key that is present means "fetched", even when
15
+ * the site has no settings yet (stored as `{}`).
16
+ */
17
+ export default function (siteId?: MaybeRefOrGetter<string | undefined>) {
18
+ const site = computed(() => toValue(siteId) || "");
5
19
 
6
- const anprSettings = useState<Record<string, any> | null>(
7
- stateKey.value,
8
- () => null
20
+ const bySite = useState<Record<string, Record<string, any>>>(
21
+ "anpr-settings-by-site",
22
+ () => ({})
9
23
  );
10
24
 
25
+ const anprSettings = computed<Record<string, any> | null>({
26
+ get: () => bySite.value[site.value] ?? null,
27
+ set: (value) => {
28
+ bySite.value = { ...bySite.value, [site.value]: value ?? {} };
29
+ },
30
+ });
31
+
32
+ /** True once this site's settings have been read (or saved) at least once. */
33
+ const loaded = computed(() => site.value in bySite.value);
34
+
11
35
  const enableUnregistered = computed(
12
36
  () => anprSettings.value?.ANPRSwitches?.enableUnregistered === true
13
37
  );
14
38
 
39
+ function store(forSite: unknown, response: Record<string, any> | null) {
40
+ const key = String(forSite ?? "");
41
+ if (!key) return;
42
+ bySite.value = { ...bySite.value, [key]: response ?? {} };
43
+ }
44
+
15
45
  async function fetchANPRSettings(data?: any) {
16
- const payload = data ?? (siteId ? { site: siteId, userType: "site" } : undefined);
46
+ const payload =
47
+ data ?? (site.value ? { site: site.value, userType: "site" } : undefined);
17
48
 
18
49
  if (!payload) {
19
50
  return anprSettings.value;
@@ -27,23 +58,21 @@ export default function (siteId?: string) {
27
58
  }
28
59
  );
29
60
 
30
- if (response) {
31
- anprSettings.value = response;
32
- }
61
+ store(payload.site, response);
33
62
 
34
63
  return response;
35
64
  }
36
65
 
37
66
  async function ensureANPRSettings(force = false) {
38
- if (!siteId) {
67
+ if (!site.value) {
39
68
  return anprSettings.value;
40
69
  }
41
70
 
42
- if (!force && anprSettings.value) {
71
+ if (!force && loaded.value) {
43
72
  return anprSettings.value;
44
73
  }
45
74
 
46
- return await fetchANPRSettings({ site: siteId, userType: "site" });
75
+ return await fetchANPRSettings({ site: site.value, userType: "site" });
47
76
  }
48
77
 
49
78
  async function getANPRSettingBySite(data: any) {
@@ -60,7 +89,7 @@ export default function (siteId?: string) {
60
89
  );
61
90
 
62
91
  if (response) {
63
- anprSettings.value = response;
92
+ store(data?.site, response);
64
93
  }
65
94
 
66
95
  return response;
@@ -68,6 +97,7 @@ export default function (siteId?: string) {
68
97
 
69
98
  return {
70
99
  anprSettings,
100
+ loaded,
71
101
  enableUnregistered,
72
102
  ensureANPRSettings,
73
103
  getANPRSettingBySite,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.39.0",
5
+ "version": "4.39.1",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
@@ -36,7 +36,7 @@
36
36
  "build": "nuxt build .playground",
37
37
  "generate": "nuxt generate .playground",
38
38
  "preview": "nuxt preview .playground",
39
- "test": "esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && esbuild composables/usePlateToastWindow.ts --format=esm --outfile=test/.build/usePlateToastWindow.mjs --log-level=error && esbuild utils/client-invite-modules.ts --bundle --platform=node --format=esm --outfile=test/.build/client-invite-modules.mjs --log-level=error && esbuild utils/robot-display.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-display.mjs --log-level=error && esbuild utils/building-map.ts --bundle --platform=node --format=esm --outfile=test/.build/building-map.mjs --log-level=error && esbuild utils/robot-building.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-building.mjs --log-level=error && esbuild utils/robot-fleet-core.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-fleet-core.mjs --log-level=error && node --experimental-strip-types --test \"test/*.test.mjs\" && yarn test:units",
39
+ "test": "esbuild composables/useANPRSettings.ts --format=esm --outfile=test/.build/useANPRSettings.mjs --log-level=error && esbuild composables/useVisitorSocket.ts --format=esm --outfile=test/.build/useVisitorSocket.mjs --log-level=error && esbuild composables/usePlateToastWindow.ts --format=esm --outfile=test/.build/usePlateToastWindow.mjs --log-level=error && esbuild utils/client-invite-modules.ts --bundle --platform=node --format=esm --outfile=test/.build/client-invite-modules.mjs --log-level=error && esbuild utils/robot-display.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-display.mjs --log-level=error && esbuild utils/building-map.ts --bundle --platform=node --format=esm --outfile=test/.build/building-map.mjs --log-level=error && esbuild utils/robot-building.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-building.mjs --log-level=error && esbuild utils/robot-fleet-core.ts --bundle --platform=node --format=esm --outfile=test/.build/robot-fleet-core.mjs --log-level=error && node --experimental-strip-types --test \"test/*.test.mjs\" && yarn test:units",
40
40
  "test:units": "node --experimental-strip-types --test \"utils/*.test.ts\"",
41
41
  "release": "yarn run build && changeset publish"
42
42
  },