@7365admin1/layer-common 4.112.0 → 4.113.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 4.113.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 92f08bca: Vehicle Mgmt: new "QR sticker" column (Linked / None / n of m) and an All / With sticker / Without sticker filter, shown only while the site's QR switch is on
8
+
3
9
  ## 4.112.0
4
10
 
5
11
  ### Minor Changes
@@ -48,9 +48,18 @@
48
48
 
49
49
  <!-- Search joins the toolbar row rather than stacking a second one. -->
50
50
  <template #prepend-additional>
51
+ <v-select v-if="qrEnabled" v-model="qrStickerFilter" :items="qrStickerFilterItems" label="QR sticker"
52
+ density="compact" variant="outlined" hide-details class="vehicle-qr-filter" style="max-width: 190px"
53
+ data-test="vehicle-qr-filter" />
51
54
  <AppField v-model="searchInput" search compact placeholder="Search" klass="app-toolbar__search" />
52
55
  </template>
53
56
 
57
+ <template #item.qrSticker="{ item }">
58
+ <v-chip size="small" variant="tonal" :color="qrStickerSummary(rowPlates(item)).color" data-test="vehicle-qr-cell">
59
+ {{ qrStickerSummary(rowPlates(item)).label }}
60
+ </v-chip>
61
+ </template>
62
+
54
63
  <template #item.category="{ value }">
55
64
  {{ formatValues("category", value) }}
56
65
  </template>
@@ -439,7 +448,7 @@ import {
439
448
  // `error.message` put ofetch's own developer string in the delete dialog:
440
449
  // `[PUT] "/api/vehicles/6a9691f25648d7633171c9e3": 502`.
441
450
  import { errorConverter } from '../utils/data';
442
- import { stickerCodeHint } from '../utils/vehicle-qr-blank';
451
+ import { qrStickerSummary, stickerCodeHint } from '../utils/vehicle-qr-blank';
443
452
  import { isPermissionRefusalError } from '../utils/permission-refusal';
444
453
 
445
454
 
@@ -456,9 +465,11 @@ const props = defineProps({
456
465
  });
457
466
 
458
467
 
459
- const headers = [
468
+ const headers = computed(() => [
460
469
  { title: "Name", value: "name" },
461
470
  { title: "Vehicle Numbers", value: "plates" },
471
+ // Only while the site's QR switch is on (like the print button).
472
+ ...(qrEnabled.value ? [{ title: "QR sticker", value: "qrSticker", sortable: false }] : []),
462
473
  { title: "NRIC", value: "nric" },
463
474
  { title: "Block", value: "block.name" },
464
475
  { title: "Floor", value: "level.name" },
@@ -467,7 +478,7 @@ const headers = [
467
478
  // { title: "Type", value: "type" },
468
479
  // { title: "Status", value: "status" },
469
480
  { title: "", value: "action" },
470
- ]
481
+ ])
471
482
 
472
483
  const plateHeaders = [
473
484
  { title: "Plate Number", value: "plateNumber" },
@@ -546,6 +557,12 @@ const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, blockVe
546
557
  // Vehicle QR stickers: the site's switch (Settings) decides whether the print
547
558
  // and re-issue actions exist; only people who may update vehicles get them.
548
559
  const qrEnabled = ref(false);
560
+ const qrStickerFilter = ref("");
561
+ const qrStickerFilterItems = [
562
+ { title: "All", value: "" },
563
+ { title: "With sticker", value: "with" },
564
+ { title: "Without sticker", value: "without" },
565
+ ];
549
566
  const qrStickersOn = computed(() => qrEnabled.value && props.canUpdateVehicle);
550
567
  const qrVehicleIds = ref<string[]>([]);
551
568
  const qrReissuePlate = ref<TPlateNumber | null>(null);
@@ -838,6 +855,7 @@ const { data: getVehiclesReq, refresh: getVehiclesRefresh, pending: getVehiclesP
838
855
  search: searchInput.value,
839
856
  view: activeTab.value,
840
857
  site: props.site,
858
+ qrSticker: qrEnabled.value ? qrStickerFilter.value : "",
841
859
  }),
842
860
  {
843
861
  watch: [page],
@@ -1321,6 +1339,11 @@ watch(searchInput, () => {
1321
1339
  debouncedSearch()
1322
1340
  })
1323
1341
 
1342
+ watch(qrStickerFilter, () => {
1343
+ page.value = 1;
1344
+ getVehiclesRefresh();
1345
+ })
1346
+
1324
1347
  // A new tab starts from its first page and loads straight away.
1325
1348
  watch(activeTab, () => {
1326
1349
  page.value = 1;
@@ -9,12 +9,13 @@ export default function useVehicle() {
9
9
  site = "",
10
10
  status = "",
11
11
  view = "",
12
+ qrSticker = "",
12
13
  } = {}) {
13
14
  // `view` is the screen's tab: residents, season-passes or block-listed.
14
15
  // Every status comes back unless `status` narrows it.
15
16
  return await useNuxtApp().$api<Record<string, any>>("/api/vehicles", {
16
17
  method: "GET",
17
- query: { search, page, limit, order, type, category, site, status, view },
18
+ query: { search, page, limit, order, type, category, site, status, view, qrSticker },
18
19
  });
19
20
  }
20
21
 
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.112.0",
5
+ "version": "4.113.0",
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.",
package/types/people.d.ts CHANGED
@@ -48,7 +48,7 @@ declare type TOccupancyRoleOption = {
48
48
  description: string;
49
49
  };
50
50
 
51
- declare type TPlateNumber = { plateNumber: string, recNo?: string, _id?: string, status: TVehicleStatus, type: TVehicleType, anprCameras?: TVehicleAnprCamera[] };
51
+ declare type TPlateNumber = { plateNumber: string, recNo?: string, _id?: string, status: TVehicleStatus, type: TVehicleType, anprCameras?: TVehicleAnprCamera[], hasQrSticker?: boolean };
52
52
 
53
53
 
54
54
  // `""` is the FORM's "nothing picked yet" - it is stripped from the payload
@@ -52,3 +52,12 @@ export const isBlankCode = (plate: unknown): boolean => /^([0-9A-Z]{4}-){5}[0-9A
52
52
  export function plateLines(plate: string): string[] {
53
53
  return isBlankCode(plate) ? [plate.slice(0, 14), plate.slice(15)] : [plate];
54
54
  }
55
+
56
+ /** The list's "QR sticker" cell for one row: how many of its plates already hold a sticker. */
57
+ export function qrStickerSummary(plates: Array<{ hasQrSticker?: boolean }>): { label: string; color: string } {
58
+ const total = plates.length;
59
+ const linked = plates.filter((p) => p?.hasQrSticker === true).length;
60
+ if (!total || !linked) return { label: "None", color: "grey" };
61
+ if (linked === total) return { label: "Linked", color: "success" };
62
+ return { label: `${linked} of ${total}`, color: "warning" };
63
+ }