@7365admin1/layer-common 3.2.2-staging.118 → 3.2.2-staging.121

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 (35) hide show
  1. package/assets/css/primitives.css +168 -0
  2. package/assets/css/screens.css +76 -1
  3. package/components/AccessCard/AvailableStats.vue +24 -16
  4. package/components/AddPassKeyToVisitor.vue +95 -73
  5. package/components/AppDateField.vue +15 -1
  6. package/components/AppField.vue +25 -0
  7. package/components/BuildingUnitFormEdit.vue +5 -1
  8. package/components/Card/DeleteConfirmation.vue +5 -2
  9. package/components/HidAccessLogDashboard.vue +288 -447
  10. package/components/HidIntercomManagement.vue +387 -521
  11. package/components/HidQrCodeConfiguration.vue +293 -369
  12. package/components/HidReaderManagement.vue +197 -320
  13. package/components/HidServiceSettingsPanel.vue +38 -26
  14. package/components/HidUserEnrollment.vue +239 -344
  15. package/components/IncidentReport/Authorities.vue +30 -18
  16. package/components/Nfc/NFCTagForm.vue +5 -1
  17. package/components/ScanVisitorQRCode.vue +64 -28
  18. package/components/SearchVehicleNumberUser.vue +39 -37
  19. package/components/TableMain.vue +50 -1
  20. package/components/VehicleAddSelection.vue +48 -32
  21. package/components/VehicleForm.vue +112 -129
  22. package/components/VehicleUpdateMoreAction.vue +59 -46
  23. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  24. package/components/VisitorFormSelection.vue +40 -37
  25. package/components/VisitorPassKeyQRScanner.vue +65 -18
  26. package/components/VisitorSocketPopUp.vue +21 -13
  27. package/components/VisitorsReportPreview.vue +44 -29
  28. package/package.json +1 -1
  29. package/tools/render-harness/baselines.json +95 -0
  30. package/tools/render-harness/harness-init.ps1 +4 -0
  31. package/utils/status.test.ts +33 -0
  32. package/utils/status.ts +36 -0
  33. package/utils/theme-aa-ledger.ts +142 -0
  34. package/utils/theme.test.ts +95 -34
  35. package/utils/theme.ts +55 -28
@@ -2,37 +2,39 @@
2
2
  <v-row no-gutters class="px-5 pt-4">
3
3
  <!-- Authorities (Police/SCDF/Ambulance called in at the scene) -->
4
4
  <v-col cols="12" class="border-b pb-5 mb-5 mt-1">
5
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
5
+ <p class="screen-card-title mb-2">
6
6
  Authorities (Police/SCDF/Ambulance called in at the scene)?
7
7
  </p>
8
8
  <p
9
9
  v-if="authorities?.authoritiesValue == 'no'"
10
- class="mt-2 text-h6 text-capitalize"
10
+ class="report-answer mt-2 text-capitalize"
11
11
  >
12
12
  {{ authorities?.authoritiesValue }}
13
13
  </p>
14
14
 
15
- <v-data-table
16
- v-else
17
- :headers="authoritiesTableHeader"
18
- :items="authorities?.authoritiesCalled"
19
- fixed-header
20
- hide-default-footer
21
- :hide-default-header="false"
22
- items-per-page="100"
23
- style="max-height: calc(100vh - (200px))"
24
- class="border mt-5"
25
- >
26
- </v-data-table>
15
+ <AppCard v-else class="table-card mt-5">
16
+ <v-data-table
17
+ :headers="authoritiesTableHeader"
18
+ :items="authorities?.authoritiesCalled"
19
+ fixed-header
20
+ hide-default-footer
21
+ :hide-default-header="false"
22
+ items-per-page="100"
23
+ style="max-height: calc(100vh - (200px))"
24
+ >
25
+ </v-data-table>
26
+ </AppCard>
27
27
  </v-col>
28
- <div class="w-100 my-8 mx-2">
29
- <v-table class="responsive-table border elevation-0">
28
+ <!-- `w-100` + `mx-2` made the card 16px wider than its column, so its right
29
+ corner was clipped off. The card supplies its own edge now. -->
30
+ <AppCard class="table-card w-100 my-8">
31
+ <v-table class="responsive-table elevation-0">
30
32
  <thead>
31
33
  <tr>
32
34
  <th
33
35
  v-for="(item, index) in concernHeader"
34
36
  :key="index"
35
- class="text-left font-weight-bold text-black border-e"
37
+ class="text-left border-e"
36
38
  >
37
39
  {{ item }}
38
40
  </th>
@@ -196,10 +198,20 @@
196
198
  </tr>
197
199
  </tbody>
198
200
  </v-table>
199
- </div>
201
+ </AppCard>
200
202
  </v-row>
201
203
  </template>
202
204
 
205
+ <style scoped>
206
+ /* The prototype states no size for a plain answer line, so it takes the body
207
+ row's own type rather than Vuetify's `text-h6`. */
208
+ .report-answer {
209
+ font-size: var(--fs-cell);
210
+ font-weight: var(--fw-cell);
211
+ color: var(--text);
212
+ }
213
+ </style>
214
+
203
215
  <script lang="ts" setup>
204
216
  definePageMeta({
205
217
  middleware: ["01-auth", "02-org"],
@@ -64,6 +64,10 @@
64
64
  </v-col>
65
65
 
66
66
  <v-col cols="6" class="pa-0">
67
+ <!-- Was `color="black"`: a fixed slab that stayed black on the dark
68
+ card. `primary-button` is the theme's FILL token, and its pair
69
+ `on-primary-button` supplies the white label - unlike `primary`,
70
+ which is a FOREGROUND here (links, icons, text buttons). -->
67
71
  <v-btn
68
72
  block
69
73
  tile
@@ -71,7 +75,7 @@
71
75
  class="text-none"
72
76
  size="large"
73
77
  height="48"
74
- color="black"
78
+ color="primary-button"
75
79
  @click="submit"
76
80
  :loading="loading"
77
81
  :disabled="!valid || loading || !hasChanges"
@@ -9,36 +9,27 @@
9
9
  >
10
10
  <v-container class="d-flex justify-center" max-height="90vh">
11
11
  <!-- QR code reader container -->
12
- <v-card
13
- elevation="2"
14
- class="d-flex flex-column align-center pa-2 w-100 h-100"
15
- >
16
- <v-toolbar>
17
- <v-card-title class="text-h5"> Scan QR Code </v-card-title>
18
- <v-spacer />
19
- <v-btn
20
- color="grey-darken-1"
21
- icon="mdi-close"
22
- @click="closeDialog()"
23
- />
24
- </v-toolbar>
25
-
26
- <div
27
- id="reader"
28
- class="d-flex justify-center align-center w-100 h-100"
29
- style="height: calc(100vh - 64px)"
30
- >
31
- <!-- The QR code scanner box -->
32
- <!-- This is where the QR code scanner will be displayed -->
12
+ <v-card class="screen-modal qr-scanner">
13
+ <div class="qr-scanner__head">
14
+ <v-card-title>Scan QR Code</v-card-title>
15
+ <AppButton variant="icon" icon="mdi-close" @click="closeDialog()" />
33
16
  </div>
34
- <!-- Show Error Messages -->
35
- <div v-if="showNotFoundMessage" class="text-error mt-4 text-h5">
36
- <p>{{ errorMessage }}</p>
17
+
18
+ <div class="screen-modal__body qr-scanner__body">
19
+ <div id="reader" class="qr-scanner__reader">
20
+ <!-- The QR code scanner box -->
21
+ <!-- This is where the QR code scanner will be displayed -->
22
+ </div>
23
+ <!-- Show Error Messages -->
24
+ <p v-if="showNotFoundMessage" class="qr-scanner__error">{{ errorMessage }}</p>
25
+ <!-- Switch camera button inside the reader box -->
26
+ <AppButton
27
+ icon="mdi-camera-switch"
28
+ class="qr-scanner__switch"
29
+ aria-label="Switch camera"
30
+ @click="switchCamera()"
31
+ />
37
32
  </div>
38
- <!-- Switch camera button inside the reader box -->
39
- <v-btn color="primary" icon class="mt-4" @click="switchCamera()">
40
- <v-icon icon="mdi-camera-switch" large />
41
- </v-btn>
42
33
  </v-card>
43
34
  </v-container>
44
35
  </v-dialog>
@@ -225,3 +216,48 @@ const stopScanner = () => {
225
216
  });
226
217
  };
227
218
  </script>
219
+
220
+ <style scoped>
221
+ /* Same scanner frame as the pass-key scanner: the card used to be `w-100`
222
+ inside the centring container, so keep that width. */
223
+ .qr-scanner {
224
+ width: 100%;
225
+ }
226
+
227
+ .qr-scanner__head {
228
+ display: flex;
229
+ align-items: center;
230
+ justify-content: space-between;
231
+ padding-right: 12px;
232
+ }
233
+
234
+ .qr-scanner__body {
235
+ display: flex;
236
+ flex-direction: column;
237
+ align-items: center;
238
+ gap: 16px;
239
+ padding: 4px 20px 20px;
240
+ }
241
+
242
+ .qr-scanner__reader {
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ width: 100%;
247
+ min-height: 320px;
248
+ border: 1px solid var(--border);
249
+ border-radius: var(--r-inner);
250
+ background: var(--thead);
251
+ }
252
+
253
+ .qr-scanner__error {
254
+ margin: 0;
255
+ font-size: var(--fs-cell);
256
+ color: var(--err);
257
+ }
258
+
259
+ .qr-scanner__switch {
260
+ width: var(--btn-h);
261
+ padding: 0;
262
+ }
263
+ </style>
@@ -1,44 +1,35 @@
1
1
  <template>
2
- <v-card width="100%">
3
- <v-toolbar>
4
- <v-row no-gutters>
5
- <v-col cols="12" class="text-h6 font-weight-bold px-3">Vehicle Number ({{ vehicleNumber }})</v-col>
6
- </v-row>
7
- </v-toolbar>
2
+ <v-card width="100%" class="screen-modal search-vehicle-user">
3
+ <v-card-title>Vehicle Number ({{ vehicleNumber }})</v-card-title>
8
4
 
9
- <v-card-text style="max-height: 100vh; overflow-y: auto">
10
- <v-data-table :items="prop.vehicleNumberUsersList" hide-default-footer :headers="headers" density="comfortable"
11
- item-key="_id ">
12
- <template #item.name="{ item }">
13
- <span class="d-flex align-center ga-2">
14
- <span>
15
- <AvatarMain :name="item?.name" :size="20" :id="item?._id" />
5
+ <div class="screen-modal__body search-vehicle-user__body">
6
+ <!-- `table-card` is the shared table card's own class, so the header
7
+ type, the cell padding and the row rule are the design's - without
8
+ TableMain's page toolbar, which a two-row picker inside a dialog
9
+ has nothing to put in. -->
10
+ <AppCard class="table-card">
11
+ <v-data-table :items="prop.vehicleNumberUsersList" hide-default-footer :headers="headers" density="comfortable"
12
+ item-key="_id ">
13
+ <template #item.name="{ item }">
14
+ <span class="d-flex align-center ga-2">
15
+ <span>
16
+ <AvatarMain :name="item?.name" :size="20" :id="item?._id" />
17
+ </span>
18
+ <span class="text-capitalize">{{ item?.name }}</span>
16
19
  </span>
17
- <span class="text-capitalize">{{ item?.name }}</span>
18
- </span>
19
- </template>
20
+ </template>
20
21
 
21
- <template #item.actions="{ item }">
22
- <v-btn text="Select" rounded="md" flat color="blue" density="comfortable" :ripple="false" @click="handleSelectUser(item)" />
23
- </template>
24
- </v-data-table>
25
- </v-card-text>
22
+ <template #item.actions="{ item }">
23
+ <AppButton variant="ghost" @click="handleSelectUser(item)">Select</AppButton>
24
+ </template>
25
+ </v-data-table>
26
+ </AppCard>
27
+ </div>
26
28
 
27
- <v-toolbar class="pa-0" density="compact">
28
- <v-row no-gutters>
29
- <v-col cols="6" class="pa-0">
30
- <v-btn block variant="text" class="text-none" size="large" @click="emit('close')" height="48">
31
- Close
32
- </v-btn>
33
- </v-col>
34
-
35
- <v-col cols="6" class="pa-0">
36
- <v-btn block variant="flat" color="black" class="text-none" height="48" tile @click="emit('close')">
37
- Add Different User
38
- </v-btn>
39
- </v-col>
40
- </v-row>
41
- </v-toolbar>
29
+ <div class="search-vehicle-user__footer">
30
+ <AppButton variant="ghost" @click="emit('close')">Close</AppButton>
31
+ <AppButton @click="emit('close')">Add Different User</AppButton>
32
+ </div>
42
33
  <v-dialog v-model="confirmAction" persistent max-width="450">
43
34
  <DialogReplaceAutofillPrompt @close="confirmAction = false" @proceed="handleUpdateUserDetails"/>
44
35
  </v-dialog>
@@ -89,4 +80,15 @@ onMounted(() => {
89
80
  })
90
81
  </script>
91
82
 
92
- <style scoped></style>
83
+ <style scoped>
84
+ .search-vehicle-user__body {
85
+ padding: 4px 20px 0;
86
+ }
87
+
88
+ .search-vehicle-user__footer {
89
+ display: flex;
90
+ justify-content: flex-end;
91
+ gap: 8px;
92
+ padding: 16px 20px 20px;
93
+ }
94
+ </style>
@@ -155,8 +155,14 @@
155
155
  </div>
156
156
 
157
157
  <!-- Data Table -->
158
+ <!--
159
+ `tableHeaders`, not `headers`, and the two are the SAME ARRAY unless a
160
+ caller has opted into fixed proportions by putting `weight` on a
161
+ column. See `columnsAreWeighted` below.
162
+ -->
158
163
  <v-data-table
159
- :headers="headers"
164
+ :class="columnsAreWeighted ? 'table-card__fixed' : undefined"
165
+ :headers="tableHeaders"
160
166
  :items="items"
161
167
  :item-value="itemValue"
162
168
  :items-per-page="itemsPerPage"
@@ -257,6 +263,49 @@ const props = defineProps({
257
263
  },
258
264
  });
259
265
 
266
+ /**
267
+ * THE DESIGN'S FIXED COLUMN PROPORTIONS, AND WHY THEY ARE OPT-IN.
268
+ *
269
+ * Every table in the prototype is a CSS grid with an explicit, per-table
270
+ * `grid-template-columns` - `0.9fr 1.5fr 1.7fr 0.8fr 1fr 1.1fr` on Feedbacks,
271
+ * `2fr 1fr 1fr` on Units, `1.6fr 1fr 1.3fr 1.6fr 48px` on Pass & Key. Those
272
+ * ratios are a per-screen design decision. Nothing in a caller's `headers`
273
+ * array carries that information, so this shared component cannot derive them,
274
+ * and inventing one set of proportions for 21 components and 18 app pages would
275
+ * be worse than the automatic layout they have now.
276
+ *
277
+ * So it is stated, not guessed: a header may carry `weight: 1.5` and this turns
278
+ * the weights into percentage widths and switches the table to
279
+ * `table-layout: fixed`, which is what makes a width binding rather than a hint.
280
+ * A column with NO weight is left exactly as the caller wrote it, so an
281
+ * existing `width` (an action column pinned at 48px, say) survives untouched
282
+ * and only the weighted columns share out what is left.
283
+ *
284
+ * NO caller changes. With no weights anywhere, `tableHeaders` IS `props.headers`
285
+ * - the same array reference - and the table renders exactly what it renders
286
+ * today, automatic widths and all.
287
+ */
288
+ const columnsAreWeighted = computed(() =>
289
+ props.headers.some((h) => Number(h?.weight) > 0)
290
+ );
291
+
292
+ const tableHeaders = computed(() => {
293
+ if (!columnsAreWeighted.value) return props.headers;
294
+
295
+ // Columns with no weight keep whatever width they already had (often none,
296
+ // e.g. a 48px action column), and only the weighted ones share what is left.
297
+ const total = props.headers.reduce(
298
+ (sum, h) => sum + (Number(h?.weight) > 0 ? Number(h.weight) : 0),
299
+ 0
300
+ );
301
+
302
+ return props.headers.map((h) =>
303
+ Number(h?.weight) > 0
304
+ ? { ...h, width: `${((Number(h.weight) / total) * 100).toFixed(4)}%` }
305
+ : h
306
+ );
307
+ });
308
+
260
309
  const emits = defineEmits([
261
310
  "create",
262
311
  "scan",
@@ -1,36 +1,24 @@
1
1
  <template>
2
- <v-card width="100%">
3
- <v-toolbar>
4
- <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-h5 text-capitalize">
6
- Add Vehicle
7
- </span>
8
- </v-row>
9
- </v-toolbar>
10
-
11
- <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5">
12
- <span class="text-subtitle-2 w-100 font-weight-medium d-flex justify-center mb-3">Please Select Status</span>
13
- <template v-for="item in selection" :key="item.value">
14
- <v-btn color="primary-button" block variant="flat" rounded="md" size="48" :text="item.label" @click="select(item.value)" class="my-2 text-capitalize text-subtitle-2" />
15
- </template>
16
- </v-card-text>
17
-
18
- <v-toolbar density="compact">
19
- <v-row no-gutters>
20
- <v-col cols="12">
21
- <v-btn
22
- tile
23
- block
24
- variant="text"
25
- class="text-none"
26
- size="48"
27
- @click="cancel"
28
- >
29
- Cancel
30
- </v-btn>
31
- </v-col>
32
- </v-row>
33
- </v-toolbar>
2
+ <v-card width="100%" class="screen-modal vehicle-add">
3
+ <v-card-title>Add Vehicle</v-card-title>
4
+
5
+ <div class="screen-modal__body vehicle-add__body">
6
+ <p class="vehicle-add__hint">Please Select Status</p>
7
+ <AppButton
8
+ v-for="item in selection"
9
+ :key="item.value"
10
+ class="vehicle-add__option"
11
+ @click="select(item.value)"
12
+ >
13
+ {{ item.label }}
14
+ </AppButton>
15
+ </div>
16
+
17
+ <div class="vehicle-add__footer">
18
+ <AppButton variant="ghost" class="vehicle-add__cancel" @click="cancel">
19
+ Cancel
20
+ </AppButton>
21
+ </div>
34
22
  </v-card>
35
23
  </template>
36
24
 
@@ -56,3 +44,31 @@ function select(value: TVehicleType | 'seasonpass') {
56
44
  emit("select", value);
57
45
  }
58
46
  </script>
47
+
48
+ <style scoped>
49
+ /* The three choices are full-width by the screen's own design, so the only
50
+ thing said here is the width and the stack - the button itself is AppButton. */
51
+ .vehicle-add__body {
52
+ padding: 20px;
53
+ }
54
+
55
+ .vehicle-add__hint {
56
+ margin: 0 0 14px;
57
+ text-align: center;
58
+ font-size: var(--fs-cell);
59
+ color: var(--muted);
60
+ }
61
+
62
+ .vehicle-add__option {
63
+ display: flex;
64
+ width: 100%;
65
+ margin-bottom: 10px;
66
+ }
67
+
68
+ .vehicle-add__footer {
69
+ display: flex;
70
+ justify-content: flex-end;
71
+ gap: 8px;
72
+ padding: 0 20px 20px;
73
+ }
74
+ </style>