@7365admin1/layer-common 3.2.2-staging.120 → 3.2.2-staging.122

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.
@@ -1,140 +1,140 @@
1
+ <!--
2
+ HID ACCESS LOG DASHBOARD - who went through a reader, and when.
3
+
4
+ Was a bespoke screen: an above-card toolbar, a `v-tabs` strip, a raw
5
+ `v-table`, a footer pager, an empty state and its own two-colour chip
6
+ palette, in about forty literal hexes. Those literals are why it had no dark
7
+ theme - `#0a2638` on the dark card measures **1.07:1**.
8
+
9
+ It is the design's standard table screen WITH TABS, so it is drawn with the
10
+ shared one: `TableMain` supplies the heading, the card, the in-card toolbar,
11
+ the `app-tab` strip, the uppercase header row, the row spec and the pager;
12
+ search, the reader picker and the status filter move onto the card's
13
+ `extension` row. Every row, tab, action, dialog and handler is unchanged -
14
+ the same `paginatedRows` still feed the table and the history icon still
15
+ opens the same dialog.
16
+
17
+ The handoff draws no HID table, so it states no column proportions - the
18
+ table takes `TableMain`'s automatic layout, as panels 2 and 4 do.
19
+ -->
1
20
  <template>
2
21
  <section class="hid-access-log-dashboard">
3
- <div class="toolbar">
4
- <v-btn
5
- rounded="xl"
6
- class="text-none export-button"
7
- variant="flat"
8
- prepend-icon="mdi-download"
9
- @click="openExportDialog"
10
- >
11
- Export
12
- </v-btn>
13
-
14
- <v-select
15
- v-if="readers.length"
16
- v-model="selectedReaderId"
17
- :items="readerOptions"
18
- item-title="title"
19
- item-value="value"
20
- variant="outlined"
21
- density="compact"
22
- hide-details
23
- class="reader-input"
24
- @update:model-value="loadDashboard"
25
- />
26
-
27
- <div class="toolbar-spacer" />
28
-
29
- <v-text-field
30
- v-model="search"
31
- placeholder="Search"
32
- variant="outlined"
33
- density="compact"
34
- hide-details
35
- class="search-input"
36
- />
37
-
38
- <v-select
39
- v-model="status"
40
- :items="statusOptions"
41
- variant="outlined"
42
- density="compact"
43
- hide-details
44
- class="status-input"
45
- />
46
- </div>
47
-
48
- <v-card flat border class="table-card">
49
- <v-tabs
50
- v-model="activeTab"
51
- class="access-tabs"
52
- density="compact"
53
- @update:model-value="onTabChange"
54
- >
55
- <v-tab value="resident" class="text-none">Resident</v-tab>
56
- <v-tab value="visitor" class="text-none">Visitor</v-tab>
57
- <v-tab value="administrator" class="text-none">Administrator</v-tab>
58
- </v-tabs>
59
-
60
- <div class="table-body">
61
- <v-table>
62
- <thead>
63
- <tr>
64
- <th>Name</th>
65
- <th>Facial Data</th>
66
- <th>Last Access Method</th>
67
- <th>Last access time</th>
68
- <th>Status</th>
69
- <th class="text-right"></th>
70
- </tr>
71
- </thead>
72
- <tbody>
73
- <tr v-for="row in paginatedRows" :key="row.key">
74
- <td>
75
- <div class="name-cell">
76
- {{ row.name }}
77
- <v-tooltip v-if="row.name && row.name !== 'N/A'" activator="parent" location="top">
78
- {{ row.name }}
79
- </v-tooltip>
80
- </div>
81
- </td>
82
- <td>
83
- <v-avatar v-if="row.faceImage" size="34" rounded="lg" class="hid-face-avatar">
84
- <v-img :src="row.faceImage" cover />
85
- </v-avatar>
86
- <span v-else>{{ row.facialData }}</span>
87
- </td>
88
- <td>{{ row.method }}</td>
89
- <td>{{ row.lastAccessTime }}</td>
90
- <td>
91
- <v-chip
92
- size="small"
93
- variant="flat"
94
- class="status-chip"
95
- :class="getAuthorizationChipClass(row.status)"
96
- >
97
- {{ row.status }}
98
- <v-tooltip v-if="row.authorizationReason" activator="parent" location="top">
99
- {{ row.authorizationReason }}
100
- </v-tooltip>
101
- </v-chip>
102
- </td>
103
- <td class="text-right">
104
- <v-btn
105
- icon="mdi-history"
106
- variant="text"
107
- density="comfortable"
108
- @click="openHistoryDialog(row)"
109
- />
110
- </td>
111
- </tr>
112
- </tbody>
113
- </v-table>
114
-
115
- <div v-if="!filteredRows.length" class="empty-state">
116
- No access logs found.
22
+ <TableMain
23
+ title="Access Logs"
24
+ :headers="logHeaders"
25
+ :items="paginatedRows"
26
+ :loading="loading"
27
+ :items-per-page="-1"
28
+ :page="page"
29
+ :pages="pages"
30
+ :page-range="pageRange"
31
+ item-value="key"
32
+ show-header
33
+ @refresh="loadDashboard"
34
+ @update:page="page = $event"
35
+ >
36
+ <template #actions>
37
+ <AppButton icon="mdi-download" @click="openExportDialog">Export</AppButton>
38
+ </template>
39
+
40
+ <!-- Was `v-tabs`/`v-tab`, whose selected state is drawn from Vuetify's
41
+ `on-surface` rather than the design's accent underline. `.app-tab` is
42
+ the shared strip every other tabbed table already uses. -->
43
+ <template #tabs>
44
+ <button
45
+ v-for="tab in tabOptions"
46
+ :key="tab.value"
47
+ type="button"
48
+ role="tab"
49
+ class="app-tab"
50
+ :class="{ 'app-tab--active': activeTab === tab.value }"
51
+ :aria-selected="activeTab === tab.value"
52
+ @click="activeTab = tab.value; onTabChange(tab.value)"
53
+ >
54
+ {{ tab.label }}
55
+ </button>
56
+ </template>
57
+
58
+ <template #extension>
59
+ <div class="app-filter-row hid-log-filters">
60
+ <AppSelect
61
+ v-if="readers.length"
62
+ v-model="selectedReaderId"
63
+ class="reader-select"
64
+ :items="readerOptions"
65
+ @update:model-value="loadDashboard"
66
+ />
67
+ <AppField v-model="search" search placeholder="Search" />
68
+ <AppSelect v-model="status" :items="statusOptions" />
117
69
  </div>
118
- </div>
119
-
120
- <div class="table-footer">
121
- <span>{{ pageRange }}</span>
122
- <v-btn
123
- icon="mdi-chevron-left"
124
- variant="text"
125
- density="comfortable"
126
- :disabled="page <= 1"
127
- @click="page--"
128
- />
129
- <v-btn
130
- icon="mdi-chevron-right"
131
- variant="text"
132
- density="comfortable"
133
- :disabled="page >= pages"
134
- @click="page++"
70
+ </template>
71
+
72
+ <!-- Staging's hover-the-full-name tooltip, kept. -->
73
+ <template #[`item.name`]="{ item }">
74
+ <span class="app-cell--strong">
75
+ {{ item.name }}
76
+ <v-tooltip
77
+ v-if="item.name && item.name !== 'N/A'"
78
+ activator="parent"
79
+ location="top"
80
+ >
81
+ {{ item.name }}
82
+ </v-tooltip>
83
+ </span>
84
+ </template>
85
+
86
+ <!-- The face thumbnail, unchanged. It prints whatever the row already
87
+ carried; this screen does not read or export a template. -->
88
+ <template #[`item.facialData`]="{ item }">
89
+ <v-avatar v-if="item.faceImage" size="34" rounded="lg" class="hid-face-avatar">
90
+ <v-img :src="item.faceImage" cover />
91
+ </v-avatar>
92
+ <span v-else class="app-cell--muted">{{ item.facialData }}</span>
93
+ </template>
94
+
95
+ <template #[`item.method`]="{ item }">
96
+ <span class="app-cell--muted">{{ item.method }}</span>
97
+ </template>
98
+
99
+ <template #[`item.lastAccessTime`]="{ item }">
100
+ <span class="app-cell--num">{{ item.lastAccessTime }}</span>
101
+ </template>
102
+
103
+ <!-- The shared chip. This screen's `mapped-chip` / `unmapped-chip` /
104
+ `unknown-chip` were a private copy of the same three tones
105
+ `utils/status.ts` now carries for Authorized / Not Authorized /
106
+ Unknown. Staging's reason tooltip rides on the chip. -->
107
+ <template #[`item.status`]="{ item }">
108
+ <StatusChip :status="item.status">
109
+ {{ item.status }}
110
+ <v-tooltip
111
+ v-if="item.authorizationReason"
112
+ activator="parent"
113
+ location="top"
114
+ >
115
+ {{ item.authorizationReason }}
116
+ </v-tooltip>
117
+ </StatusChip>
118
+ </template>
119
+
120
+ <template #[`item.action-table`]="{ item }">
121
+ <AppButton
122
+ variant="row"
123
+ icon="mdi-history"
124
+ aria-label="View access history"
125
+ @click="openHistoryDialog(item)"
135
126
  />
136
- </div>
137
- </v-card>
127
+ </template>
128
+
129
+ <!-- The screen's own sentence, kept: the shared empty state says "No data
130
+ available", which says less than naming what was searched for. -->
131
+ <template #no-data>
132
+ <div class="table-card__empty">
133
+ <v-icon icon="mdi-history" size="32" />
134
+ <span>No access logs found.</span>
135
+ </div>
136
+ </template>
137
+ </TableMain>
138
138
 
139
139
  <v-dialog v-model="historyDialog" max-width="980">
140
140
  <v-card class="history-dialog" rounded="lg">
@@ -142,17 +142,16 @@
142
142
 
143
143
  <v-card-text class="history-body">
144
144
  <div class="history-toolbar">
145
- <v-btn
146
- rounded="xl"
147
- class="text-none export-button"
148
- variant="flat"
149
- prepend-icon="mdi-download"
150
- @click="openExportDialog('history')"
151
- >
145
+ <AppButton icon="mdi-download" @click="openExportDialog('history')">
152
146
  Export Log
153
- </v-btn>
147
+ </AppButton>
154
148
  <div class="toolbar-spacer" />
155
- <v-btn icon="mdi-close" variant="text" density="comfortable" @click="historyDialog = false" />
149
+ <AppButton
150
+ variant="icon"
151
+ icon="mdi-close"
152
+ aria-label="Close access log"
153
+ @click="historyDialog = false"
154
+ />
156
155
  </div>
157
156
 
158
157
  <v-card flat border class="history-table-card">
@@ -177,18 +176,17 @@
177
176
  <td>{{ row.readerLocation }}</td>
178
177
  <td>{{ row.method }}</td>
179
178
  <td>{{ row.lastAccessTime }}</td>
179
+ <!-- Staging's authorization column, on the shared chip: the
180
+ private `mapped-chip`/`unmapped-chip`/`unknown-chip`
181
+ palette is gone, and `utils/status.ts` carries the same
182
+ three tones for these three words. -->
180
183
  <td>
181
- <v-chip
182
- size="small"
183
- variant="flat"
184
- class="status-chip"
185
- :class="getAuthorizationChipClass(row.status)"
186
- >
184
+ <StatusChip :status="row.status">
187
185
  {{ row.status }}
188
186
  <v-tooltip v-if="row.authorizationReason" activator="parent" location="top">
189
187
  {{ row.authorizationReason }}
190
188
  </v-tooltip>
191
- </v-chip>
189
+ </StatusChip>
192
190
  </td>
193
191
  </tr>
194
192
  </tbody>
@@ -207,116 +205,54 @@
207
205
  <v-card-title class="dialog-title">Export</v-card-title>
208
206
 
209
207
  <v-card-text class="dialog-body">
208
+ <!--
209
+ Was a `v-menu` + `v-text-field` + `v-date-picker` per date. The
210
+ design's date end is `AppDateField` - a native `<input type="date">`,
211
+ which brings the platform's own picker, localisation and keyboard
212
+ support instead of a 320px popover. `exportForm` still holds
213
+ DD/MM/YYYY, so everything downstream is untouched; the two computeds
214
+ below translate at the edge.
215
+ -->
210
216
  <div class="field-label">Start Date <span>*</span></div>
211
- <v-menu
212
- v-model="startDateMenu"
213
- :close-on-content-click="false"
214
- location="bottom"
215
- content-class="compact-date-menu"
216
- >
217
- <template #activator="{ props: menuProps }">
218
- <v-text-field
219
- v-bind="menuProps"
220
- v-model="exportForm.startDate"
221
- aria-label="Start Date"
222
- prepend-inner-icon="mdi-calendar"
223
- placeholder="DD/MM/YYYY"
224
- density="compact"
225
- variant="outlined"
226
- hide-details="auto"
227
- readonly
228
- class="mb-3"
229
- />
230
- </template>
231
- <v-date-picker
232
- :model-value="parseExportDate(exportForm.startDate, false)"
233
- hide-header
234
- width="320"
235
- class="compact-date-picker"
236
- @update:model-value="setExportDate('startDate', $event)"
237
- />
238
- </v-menu>
217
+ <AppDateField v-model="exportStartInput" aria-label="Start Date" class="mb-3" />
239
218
 
240
219
  <div class="field-label">End Date <span>*</span></div>
241
- <v-menu
242
- v-model="endDateMenu"
243
- :close-on-content-click="false"
244
- location="bottom"
245
- content-class="compact-date-menu"
246
- >
247
- <template #activator="{ props: menuProps }">
248
- <v-text-field
249
- v-bind="menuProps"
250
- v-model="exportForm.endDate"
251
- aria-label="End Date"
252
- prepend-inner-icon="mdi-calendar"
253
- placeholder="DD/MM/YYYY"
254
- density="compact"
255
- variant="outlined"
256
- hide-details="auto"
257
- readonly
258
- class="mb-3"
259
- />
260
- </template>
261
- <v-date-picker
262
- :model-value="parseExportDate(exportForm.endDate, false)"
263
- hide-header
264
- width="320"
265
- class="compact-date-picker"
266
- @update:model-value="setExportDate('endDate', $event)"
267
- />
268
- </v-menu>
220
+ <AppDateField v-model="exportEndInput" aria-label="End Date" class="mb-3" />
269
221
 
270
222
  <div class="field-label">Users</div>
271
- <v-select
223
+ <AppSelect
272
224
  v-model="exportForm.users"
273
225
  :items="userOptions"
274
- item-title="title"
275
- item-value="value"
276
226
  aria-label="Users"
277
227
  placeholder="Select"
278
- density="compact"
279
- variant="outlined"
280
- hide-details="auto"
281
228
  multiple
282
- chips
283
- closable-chips
284
229
  class="mb-3"
285
230
  />
286
231
 
287
232
  <div class="field-label">Access Method</div>
288
- <v-select
233
+ <AppSelect
289
234
  v-model="exportForm.accessMethod"
290
235
  :items="accessMethodOptions"
291
236
  aria-label="Access Method"
292
237
  placeholder="Select"
293
- density="compact"
294
- variant="outlined"
295
- hide-details="auto"
296
238
  class="mb-3"
297
239
  />
298
240
 
299
241
  <div class="field-label">Reader Location</div>
300
- <v-select
242
+ <AppSelect
301
243
  v-model="exportForm.readerLocation"
302
244
  :items="readerLocationOptions"
303
245
  aria-label="Reader Location"
304
246
  placeholder="Select"
305
- density="compact"
306
- variant="outlined"
307
- hide-details="auto"
308
247
  class="mb-3"
309
248
  />
310
249
 
311
250
  <div class="field-label">Status (Optional)</div>
312
- <v-select
251
+ <AppSelect
313
252
  v-model="exportForm.status"
314
253
  :items="statusOptions"
315
254
  aria-label="Status"
316
255
  placeholder="Select"
317
- density="compact"
318
- variant="outlined"
319
- hide-details="auto"
320
256
  />
321
257
  </v-card-text>
322
258
 
@@ -396,8 +332,6 @@ const exportDialog = ref(false);
396
332
  const historyDialog = ref(false);
397
333
  const selectedHistoryRow = ref<AccessRow>();
398
334
  const exportScope = ref<"dashboard" | "history">("dashboard");
399
- const startDateMenu = ref(false);
400
- const endDateMenu = ref(false);
401
335
  const exportForm = reactive({
402
336
  startDate: "",
403
337
  endDate: "",
@@ -514,6 +448,33 @@ const filteredRows = computed(() => {
514
448
  });
515
449
  });
516
450
 
451
+ /**
452
+ * The three tabs, as data rather than three `v-tab` elements, so the shared
453
+ * `app-tab` strip can render them. Same values, same order, same handler.
454
+ */
455
+ const tabOptions: Array<{ label: string; value: AccessTab }> = [
456
+ { label: "Resident", value: "resident" },
457
+ { label: "Visitor", value: "visitor" },
458
+ { label: "Administrator", value: "administrator" },
459
+ ];
460
+
461
+ /**
462
+ * `sortable: false` keeps the previous behaviour - the raw `v-table` never
463
+ * sorted - and no `weight` is set, so the table uses `TableMain`'s automatic
464
+ * layout. The handoff draws no HID table, so the screen's own fixed column
465
+ * percentages had no design number behind them.
466
+ */
467
+ const logHeaders = [
468
+ { title: "Name", value: "name", sortable: false },
469
+ { title: "Facial Data", value: "facialData", sortable: false },
470
+ // Staging (b0667a7) dropped Unit from this table - the unit still shows in
471
+ // the per-identity history dialog, so the column is not carried back.
472
+ { title: "Last Access Method", value: "method", sortable: false },
473
+ { title: "Last access time", value: "lastAccessTime", sortable: false },
474
+ { title: "Status", value: "status", sortable: false },
475
+ { title: "", value: "action-table", sortable: false },
476
+ ];
477
+
517
478
  const pages = computed(() => Math.max(1, Math.ceil(filteredRows.value.length / limit)));
518
479
 
519
480
  const paginatedRows = computed(() => {
@@ -1062,18 +1023,32 @@ function parseExportDate(value: string, endOfDay: boolean) {
1062
1023
  return date;
1063
1024
  }
1064
1025
 
1065
- function setExportDate(field: "startDate" | "endDate", value: unknown) {
1066
- const date = normalizePickerDate(value);
1067
- if (!date) return;
1068
-
1069
- exportForm[field] = toDisplayDate(date);
1070
- if (field === "startDate") {
1071
- startDateMenu.value = false;
1072
- } else {
1073
- endDateMenu.value = false;
1074
- }
1026
+ /**
1027
+ * `exportForm` stores DD/MM/YYYY, because that is what `parseExportDate`,
1028
+ * `isWithinExportRange` and the PDF header all read. A native `<input
1029
+ * type="date">` speaks YYYY-MM-DD. Rather than change the stored format - which
1030
+ * would reach into the export itself - the translation happens here, at the
1031
+ * edge, exactly where the old `v-date-picker` did it.
1032
+ */
1033
+ function exportDateModel(field: "startDate" | "endDate") {
1034
+ return computed({
1035
+ get: () => {
1036
+ const date = parseExportDate(exportForm[field], false);
1037
+ if (!date) return "";
1038
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1039
+ const day = String(date.getDate()).padStart(2, "0");
1040
+ return `${date.getFullYear()}-${month}-${day}`;
1041
+ },
1042
+ set: (value: string) => {
1043
+ const date = normalizePickerDate(value);
1044
+ exportForm[field] = date ? toDisplayDate(date) : "";
1045
+ },
1046
+ });
1075
1047
  }
1076
1048
 
1049
+ const exportStartInput = exportDateModel("startDate");
1050
+ const exportEndInput = exportDateModel("endDate");
1051
+
1077
1052
  function normalizePickerDate(value: unknown) {
1078
1053
  if (value instanceof Date && !Number.isNaN(value.getTime())) {
1079
1054
  return value;
@@ -1274,12 +1249,6 @@ function getAuthorizationStatus(
1274
1249
  };
1275
1250
  }
1276
1251
 
1277
- function getAuthorizationChipClass(status: AuthorizationStatus) {
1278
- if (status === "Authorized") return "mapped-chip";
1279
- if (status === "Not Authorized") return "unmapped-chip";
1280
- return "unknown-chip";
1281
- }
1282
-
1283
1252
  function findIdentityForAccessLog(rawLog: Record<string, any>, event: Record<string, any>) {
1284
1253
  const eventIdentityId = event.payload?.identity?._id;
1285
1254
  if (eventIdentityId) {
@@ -1501,6 +1470,12 @@ function drawAccessLogPdf(pdf: any, rows: PdfAccessRow[]) {
1501
1470
  });
1502
1471
  }
1503
1472
 
1473
+ /**
1474
+ * The EXPORTED PDF, not a screen. Its colours stay literal on purpose: a PDF is
1475
+ * white paper with dark ink whichever theme the operator happens to be in, and
1476
+ * theming it would produce a document that does not match what prints. Same
1477
+ * reasoning as the printed pass preview in `HidQrCodeConfiguration`.
1478
+ */
1504
1479
  function buildPdfHtml() {
1505
1480
  const rows = exportRowsData.value.map((row) => ({
1506
1481
  name: row.name,
@@ -1678,106 +1653,53 @@ function escapeHtml(value: unknown) {
1678
1653
  </script>
1679
1654
 
1680
1655
  <style scoped>
1656
+ /*
1657
+ WHAT IS LEFT AFTER THE CARD, TABS AND TABLE MOVED TO `TableMain`.
1658
+
1659
+ The toolbar, the card, the `v-tabs` strip and its seven column widths, the
1660
+ footer pager, the empty state and the two private chip palettes are gone -
1661
+ they were a private copy of the shared table drawn in literal hexes. The
1662
+ `compact-date-menu` / `compact-date-picker` global overrides go with the
1663
+ `v-date-picker` they were correcting: a native date input needs none of them.
1664
+
1665
+ What remains is the two dialogs, and every colour in them is now a token, so
1666
+ the dark theme reaches them for the first time.
1667
+ */
1681
1668
  .hid-access-log-dashboard {
1682
1669
  padding: 24px 0;
1683
1670
  width: 100%;
1684
1671
  min-width: 0;
1685
1672
  }
1686
1673
 
1687
- .toolbar {
1688
- display: flex;
1689
- flex-wrap: wrap;
1690
- gap: 12px;
1691
- align-items: center;
1692
- margin-bottom: 16px;
1693
- }
1694
-
1695
1674
  .toolbar-spacer {
1696
1675
  flex: 1;
1697
1676
  }
1698
1677
 
1699
- .export-button {
1700
- min-width: 170px;
1701
- background: #e4e4e4;
1702
- color: #0a2638;
1678
+ .hid-log-filters {
1679
+ /* The filter row sits INSIDE the card's extension, so it needs the card's
1680
+ own side padding rather than the row's default bottom margin. */
1681
+ margin: 0;
1682
+ padding: 10px 14px;
1703
1683
  }
1704
1684
 
1705
- .reader-input,
1706
- .search-input {
1685
+ .hid-log-filters :deep(.app-field) {
1707
1686
  max-width: 240px;
1708
1687
  }
1709
1688
 
1710
- .status-input {
1711
- max-width: 160px;
1712
- }
1713
-
1714
- .table-card {
1715
- display: flex;
1716
- flex-direction: column;
1717
- min-height: 430px;
1718
- overflow: hidden;
1719
- }
1720
-
1721
- .table-body {
1722
- flex: 1;
1723
- overflow-x: auto;
1724
- }
1725
-
1726
- .table-card :deep(table) {
1727
- table-layout: fixed;
1728
- width: 100%;
1729
- min-width: 880px;
1730
- }
1731
-
1732
- .table-card :deep(th),
1733
- .table-card :deep(td) {
1734
- white-space: nowrap;
1735
- }
1736
-
1737
- .table-card :deep(th:nth-child(1)),
1738
- .table-card :deep(td:nth-child(1)) {
1739
- width: 25%;
1740
- }
1741
-
1742
- .table-card :deep(th:nth-child(2)),
1743
- .table-card :deep(td:nth-child(2)) {
1744
- width: 13%;
1745
- }
1746
-
1747
- .table-card :deep(th:nth-child(3)),
1748
- .table-card :deep(td:nth-child(3)) {
1749
- width: 21%;
1750
- }
1751
-
1752
- .table-card :deep(th:nth-child(4)),
1753
- .table-card :deep(td:nth-child(4)) {
1754
- width: 21%;
1755
- }
1756
-
1757
- .table-card :deep(th:nth-child(5)),
1758
- .table-card :deep(td:nth-child(5)) {
1759
- width: 14%;
1760
- }
1761
-
1762
- .table-card :deep(th:nth-child(6)),
1763
- .table-card :deep(td:nth-child(6)) {
1764
- width: 6%;
1765
- }
1766
-
1767
- .name-cell {
1768
- display: block;
1769
- min-width: 0;
1770
- overflow: hidden;
1771
- text-overflow: ellipsis;
1772
- white-space: nowrap;
1689
+ /* The reader option now reads "Reader - Portal", so it needs more room than
1690
+ the other filters. `AppSelect`'s root class IS `app-field`, so this has to
1691
+ be more specific than the rule above rather than a `.app-select` one. */
1692
+ .hid-log-filters :deep(.app-field.reader-select) {
1693
+ max-width: 320px;
1773
1694
  }
1774
1695
 
1775
1696
  .history-dialog {
1776
1697
  overflow: hidden;
1777
1698
  }
1778
1699
 
1700
+ /* Was `#4d9bc2`, a fixed blue that sat on the dark card unchanged. */
1779
1701
  .history-title {
1780
- color: #4d9bc2;
1702
+ color: var(--accent-text);
1781
1703
  font-size: 18px;
1782
1704
  font-weight: 600;
1783
1705
  padding: 16px 20px 8px;
@@ -1797,10 +1719,34 @@ function escapeHtml(value: unknown) {
1797
1719
  .history-table-card {
1798
1720
  overflow-x: auto;
1799
1721
  overflow-y: hidden;
1722
+ border: 1px solid var(--border);
1723
+ border-radius: var(--r-inner);
1724
+ background: var(--card);
1800
1725
  }
1801
1726
 
1802
1727
  .history-table-card :deep(table) {
1803
1728
  min-width: 760px;
1729
+ background: transparent;
1730
+ }
1731
+
1732
+ /*
1733
+ The history table stays a `v-table` rather than a nested `TableMain` - it is
1734
+ a read-only strip inside a dialog with no toolbar, pager or filters, and
1735
+ nesting the full card inside a dialog would draw a second heading. Its
1736
+ colours become tokens so it follows the theme; its SHAPE is unchanged.
1737
+ */
1738
+ .history-table-card :deep(th) {
1739
+ background: var(--thead);
1740
+ color: var(--muted);
1741
+ font-size: 11px;
1742
+ font-weight: 800;
1743
+ text-transform: uppercase;
1744
+ letter-spacing: 0.04em;
1745
+ }
1746
+
1747
+ .history-table-card :deep(td) {
1748
+ color: var(--text);
1749
+ font-size: 13px;
1804
1750
  }
1805
1751
 
1806
1752
  .table-refresh {
@@ -1808,79 +1754,16 @@ function escapeHtml(value: unknown) {
1808
1754
  display: flex;
1809
1755
  align-items: center;
1810
1756
  padding: 0 14px;
1811
- background: #f3f3f3;
1812
- border-bottom: 1px solid #e7e7e7;
1813
- color: #0a2638;
1814
- }
1815
-
1816
- .table-footer {
1817
- display: flex;
1818
- justify-content: flex-end;
1819
- align-items: center;
1820
- gap: 8px;
1821
- min-height: 44px;
1822
- padding: 4px 10px;
1823
- border-top: 1px solid #eeeeee;
1824
- background: #f6f6f6;
1825
- color: #0a2638;
1826
- font-size: 13px;
1827
- }
1828
-
1829
- .access-tabs {
1830
- border-bottom: 1px solid #eeeeee;
1831
- }
1832
-
1833
- .access-tabs :deep(.v-slide-group__content) {
1834
- display: grid;
1835
- grid-template-columns: repeat(3, minmax(0, 1fr));
1836
- width: 100%;
1837
- }
1838
-
1839
- .access-tabs :deep(.v-tab) {
1840
- flex: none;
1841
- min-width: 0;
1842
- width: 100%;
1843
- max-width: none;
1844
- color: #0a2638;
1845
- font-size: 12px;
1846
- font-weight: 600;
1847
- }
1848
-
1849
- .access-tabs :deep(.v-tab--selected) {
1850
- font-weight: 600;
1851
- }
1852
-
1853
- .status-chip {
1854
- min-width: 110px;
1855
- justify-content: center;
1856
- font-weight: 600;
1857
- }
1858
-
1859
- .mapped-chip {
1860
- background: #dff7e7 !important;
1861
- color: #1b7f3a !important;
1862
- }
1863
-
1864
- .unmapped-chip {
1865
- background: #fde8e8 !important;
1866
- color: #b42318 !important;
1867
- }
1868
-
1869
- .unknown-chip {
1870
- background: #fff0cc !important;
1871
- color: #9a6700 !important;
1872
- }
1873
-
1874
- .hid-face-avatar {
1875
- border: 1px solid #d8dde4;
1876
- background: #f2f4f7;
1757
+ background: var(--thead);
1758
+ border-bottom: 1px solid var(--border);
1759
+ color: var(--muted);
1877
1760
  }
1878
1761
 
1879
1762
  .empty-state {
1880
1763
  min-height: 280px;
1881
1764
  display: grid;
1882
1765
  place-items: center;
1883
- color: #687382;
1766
+ color: var(--muted);
1884
1767
  font-size: 13px;
1885
1768
  }
1886
1769
 
@@ -1892,73 +1775,52 @@ function escapeHtml(value: unknown) {
1892
1775
  font-size: 14px;
1893
1776
  font-weight: 700;
1894
1777
  padding: 12px 16px;
1895
- border-bottom: 1px solid #ececec;
1778
+ border-bottom: 1px solid var(--border);
1779
+ color: var(--text);
1896
1780
  }
1897
1781
 
1898
1782
  .dialog-body {
1899
1783
  padding: 18px 16px 20px;
1900
1784
  }
1901
1785
 
1786
+ /* Was `#4f5a66` / `#e53935` - the same two colours on both themes. */
1902
1787
  .field-label {
1903
- color: #4f5a66;
1788
+ color: var(--text2);
1904
1789
  font-size: 12px;
1905
1790
  font-weight: 600;
1906
1791
  margin-bottom: 6px;
1907
1792
  }
1908
1793
 
1909
1794
  .field-label span {
1910
- color: #e53935;
1795
+ color: var(--err);
1911
1796
  }
1912
1797
 
1798
+ /* The split footer bar. The handoff does not draw one, so the SHAPE is the
1799
+ screen's own; only the colours change - `#062d42` was a fixed navy that sat
1800
+ on the dark card unchanged. Same treatment as panels 2 and 4. */
1913
1801
  .dialog-actions {
1914
1802
  display: grid;
1915
1803
  grid-template-columns: 1fr 1fr;
1916
1804
  }
1917
1805
 
1918
1806
  .action-cancel {
1919
- background: #ffffff;
1920
- color: #111827;
1807
+ background: var(--card);
1808
+ color: var(--text2);
1921
1809
  border-radius: 0;
1922
1810
  }
1923
1811
 
1924
1812
  .action-submit {
1925
- background: #062d42;
1926
- color: #ffffff;
1813
+ background: var(--accent-strong);
1814
+ color: var(--on-accent-strong);
1927
1815
  border-radius: 0;
1928
1816
  }
1929
1817
 
1930
- :global(.compact-date-menu) {
1931
- max-width: 320px;
1932
- }
1933
-
1934
- :global(.compact-date-menu .v-overlay__content) {
1935
- max-width: 320px;
1936
- }
1937
-
1938
- :global(.compact-date-picker) {
1939
- border-radius: 8px;
1940
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.16);
1941
- }
1942
-
1943
- :global(.compact-date-picker .v-picker-title),
1944
- :global(.compact-date-picker .v-date-picker-header) {
1945
- display: none;
1946
- }
1947
-
1948
- :global(.compact-date-picker .v-picker__body) {
1949
- padding-top: 4px;
1950
- }
1951
-
1952
- :global(.compact-date-picker .v-date-picker-month) {
1953
- padding: 0 12px 12px;
1954
- }
1955
-
1956
- :global(.compact-date-picker .v-date-picker-month__day) {
1957
- height: 34px;
1818
+ .hid-face-avatar {
1819
+ border: 1px solid var(--border);
1820
+ background: var(--hover);
1958
1821
  }
1959
1822
 
1960
1823
  @media (max-width: 960px) {
1961
- .toolbar,
1962
1824
  .history-toolbar {
1963
1825
  align-items: stretch;
1964
1826
  }
@@ -1967,17 +1829,11 @@ function escapeHtml(value: unknown) {
1967
1829
  display: none;
1968
1830
  }
1969
1831
 
1970
- .export-button,
1971
- .reader-input,
1972
- .search-input,
1973
- .status-input {
1832
+ .hid-log-filters :deep(.app-field),
1833
+ .hid-log-filters :deep(.app-select) {
1974
1834
  max-width: none;
1975
1835
  width: 100%;
1976
1836
  }
1977
-
1978
- .table-card {
1979
- min-height: 360px;
1980
- }
1981
1837
  }
1982
1838
 
1983
1839
  @media (max-width: 600px) {
@@ -1985,14 +1841,6 @@ function escapeHtml(value: unknown) {
1985
1841
  padding: 12px 0;
1986
1842
  }
1987
1843
 
1988
- .access-tabs :deep(.v-slide-group__content) {
1989
- display: flex;
1990
- }
1991
-
1992
- .access-tabs :deep(.v-tab) {
1993
- min-width: 132px;
1994
- }
1995
-
1996
1844
  .history-body {
1997
1845
  padding: 0 12px 12px;
1998
1846
  }
@@ -2009,12 +1857,5 @@ function escapeHtml(value: unknown) {
2009
1857
  max-width: calc(100vw - 24px) !important;
2010
1858
  margin: 12px !important;
2011
1859
  }
2012
-
2013
- :global(.compact-date-menu),
2014
- :global(.compact-date-menu .v-overlay__content),
2015
- :global(.compact-date-picker) {
2016
- max-width: calc(100vw - 32px) !important;
2017
- width: calc(100vw - 32px) !important;
2018
- }
2019
1860
  }
2020
1861
  </style>