@7365admin1/layer-common 4.2.10 → 4.2.12

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,31 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 4.2.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 15e187e: Send the site with the role list request, so a site's roles-and-permissions screen shows its own roles instead of every other estate's.
8
+
9
+ ## 4.2.11
10
+
11
+ ### Patch Changes
12
+
13
+ - 6b6e083: Say why the dashboard is empty instead of drawing nothing.
14
+
15
+ Every widget on `DashboardMain` is permission-gated and the gate fails closed,
16
+ so a role holding none of the widget permissions renders the header, the site
17
+ name and the Customize / Export / range controls — and then nothing at all. On
18
+ 2026-09-04 a security-agency member whose role carried only Vehicle Mgmt and
19
+ Daily Occurrence Books reported that as a broken page; the gating was correct
20
+ and the blankness was the whole defect.
21
+
22
+ The dashboard now says which of the two reasons it is empty for, because they
23
+ need different people to act: the role grants no widget (an administrator has
24
+ to add the permission) or every widget was hidden in Customize (the reader can
25
+ put them back). Nothing is granted and no gate is loosened. The message is
26
+ suppressed while the role is still resolving and when the role fetch failed, so
27
+ it never flashes on load and never blames a role for a failed request.
28
+
3
29
  ## 4.2.10
4
30
 
5
31
  ### Patch Changes
@@ -554,11 +580,11 @@
554
580
  `utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
555
581
  own rule from two endpoints the console already calls, unprojected:
556
582
 
557
- GET /api/members/user/:user/app/admin the Seven365 staff membership
558
- GET /api/roles/id/:role that membership's role document
583
+ GET /api/members/user/:user/app/admin the Seven365 staff membership
584
+ GET /api/roles/id/:role that membership's role document
559
585
 
560
- owner = member.type === "admin" && role.type === "admin" && role.default === true
561
- staff = member.type === "admin" && role.type === "admin"
586
+ owner = member.type === "admin" && role.type === "admin" && role.default === true
587
+ staff = member.type === "admin" && role.type === "admin"
562
588
 
563
589
  `role.default` is the marker because it is the only property of a platform
564
590
  staff role no API caller can set - `role.controller.ts` validates create and
@@ -276,6 +276,39 @@
276
276
  }}). The figures for it are missing, not zero — refresh to try again.
277
277
  </v-alert>
278
278
 
279
+ <!--
280
+ A BLANK DASHBOARD MUST SAY WHY IT IS BLANK.
281
+
282
+ Every widget on this page is permission-gated (`canViewWidget`), and the
283
+ gate fails closed. A role holding none of the widget permissions therefore
284
+ renders the header, the range control and the Customize button — and then
285
+ nothing at all, with no way for the reader to tell an empty role from a
286
+ broken page. That cost a production escalation on 2026-09-04: a security
287
+ member whose role carried only Vehicle Mgmt and Daily Occurrence Books saw
288
+ an empty dashboard and reported it as a fault.
289
+
290
+ Two different sentences, because they need two different actions: the role
291
+ grants no widget (an administrator has to grant one), or the reader hid
292
+ them all in Customize (they can put them back themselves).
293
+ -->
294
+ <v-alert
295
+ v-if="noWidgetsGranted || noWidgetsShown"
296
+ type="info"
297
+ variant="tonal"
298
+ density="compact"
299
+ icon="mdi-view-dashboard-outline"
300
+ class="mb-4"
301
+ >
302
+ <template v-if="noWidgetsGranted">
303
+ Your role doesn't include any of the dashboard widgets, so there is
304
+ nothing to show here. Ask an administrator to add the permissions for
305
+ the areas you work in.
306
+ </template>
307
+ <template v-else>
308
+ Every widget is hidden. Use Customize to choose which ones to show.
309
+ </template>
310
+ </v-alert>
311
+
279
312
  <!-- ── KPI Cards ───────────────────────────────────────────────────────── -->
280
313
  <v-row dense class="mb-1 align-stretch">
281
314
  <v-col
@@ -2485,6 +2518,29 @@ const visibleKpiCards = computed(() =>
2485
2518
  kpiCards.value.filter((card) => isWidgetVisible(card.key))
2486
2519
  );
2487
2520
 
2521
+ /**
2522
+ * Nothing to draw, and which of the two reasons it is.
2523
+ *
2524
+ * `noWidgetsGranted` asks the permission gate alone, over this mode's whole
2525
+ * widget set — not over the reader's Customize choice — so hiding every widget
2526
+ * by hand is never reported as a missing permission.
2527
+ */
2528
+ const noWidgetsGranted = computed(
2529
+ () =>
2530
+ // Only once a role is actually loaded. `canViewWidget` fails closed while
2531
+ // it is still resolving and if the fetch fails outright, and "your role
2532
+ // grants nothing" is the wrong sentence for both of those — it would flash
2533
+ // on every load and would lie about a failed request.
2534
+ Boolean(userAppRole.value) &&
2535
+ !defaultVisibleWidgetKeys.value.some((key) => canViewWidget(key))
2536
+ );
2537
+
2538
+ const noWidgetsShown = computed(
2539
+ () =>
2540
+ Boolean(userAppRole.value) &&
2541
+ !visibleWidgetKeys.value.some((key) => isWidgetVisible(key))
2542
+ );
2543
+
2488
2544
  /**
2489
2545
  * The percentage pill's label. Same number the chip has always shown, written
2490
2546
  * the way the handoff prints it - `+0%` / `-6.4%`, the sign carrying what the
@@ -297,6 +297,7 @@ const {
297
297
  search: headerSearch.value,
298
298
  type: props.type,
299
299
  org: props.orgId,
300
+ site: props.siteId,
300
301
  }),
301
302
  {
302
303
  watch: [page, headerSearch],
@@ -23,10 +23,11 @@ export default function useRole() {
23
23
  limit = 20,
24
24
  type = "",
25
25
  org = "",
26
+ site = "",
26
27
  } = {}) {
27
28
  return useNuxtApp().$api<Record<string, any>>("/api/roles", {
28
29
  method: "GET",
29
- query: { search, page, limit, type, org },
30
+ query: { search, page, limit, type, org, site },
30
31
  });
31
32
  }
32
33
 
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.2.10",
5
+ "version": "4.2.12",
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.",