@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
@@ -1,46 +1,23 @@
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
- {{ mode }} Visitor
7
- </span>
8
- </v-row>
9
- </v-toolbar>
2
+ <v-card width="100%" class="screen-modal visitor-select">
3
+ <v-card-title class="text-capitalize">{{ mode }} Visitor</v-card-title>
10
4
 
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 Visitor Type</span>
5
+ <div class="screen-modal__body visitor-select__body">
6
+ <p class="visitor-select__hint">Please Select Visitor Type</p>
13
7
  <template v-for="item in selection" :key="item.value">
14
- <v-btn
8
+ <AppButton
15
9
  v-if="showSelection(item)"
16
- color="primary-button"
17
- block
18
- variant="flat"
19
- rounded="md"
20
- size="48"
21
- :text="item.label"
10
+ class="visitor-select__option"
22
11
  @click="select(item.value)"
23
- class="my-2 text-capitalize text-subtitle-2"
24
- />
12
+ >
13
+ {{ item.label }}
14
+ </AppButton>
25
15
  </template>
26
- </v-card-text>
16
+ </div>
27
17
 
28
- <v-toolbar density="compact">
29
- <v-row no-gutters>
30
- <v-col cols="12">
31
- <v-btn
32
- tile
33
- block
34
- variant="text"
35
- class="text-none"
36
- size="48"
37
- @click="cancel"
38
- >
39
- Cancel
40
- </v-btn>
41
- </v-col>
42
- </v-row>
43
- </v-toolbar>
18
+ <div class="visitor-select__footer">
19
+ <AppButton variant="ghost" @click="cancel">Cancel</AppButton>
20
+ </div>
44
21
  </v-card>
45
22
  </template>
46
23
 
@@ -69,6 +46,32 @@ function showSelection(item: any) {
69
46
  if(item.value === 'walk-in' && prop.mode === 'register') {
70
47
  return false ;
71
48
  } return true;
72
-
49
+
73
50
  }
74
51
  </script>
52
+
53
+ <style scoped>
54
+ /* Same shape as the Add Vehicle prompt: full-width choices, one per row. */
55
+ .visitor-select__body {
56
+ padding: 20px;
57
+ }
58
+
59
+ .visitor-select__hint {
60
+ margin: 0 0 14px;
61
+ text-align: center;
62
+ font-size: var(--fs-cell);
63
+ color: var(--muted);
64
+ }
65
+
66
+ .visitor-select__option {
67
+ display: flex;
68
+ width: 100%;
69
+ margin-bottom: 10px;
70
+ }
71
+
72
+ .visitor-select__footer {
73
+ display: flex;
74
+ justify-content: flex-end;
75
+ padding: 0 20px 20px;
76
+ }
77
+ </style>
@@ -3,26 +3,27 @@
3
3
  @after-enter="handleScanVisitorQRCode">
4
4
  <v-container class="d-flex justify-center" max-height="90vh">
5
5
  <!-- QR code reader container -->
6
- <v-card elevation="2" class="d-flex flex-column align-center pa-2 w-100 h-100">
7
- <v-toolbar>
8
- <v-card-title class="text-h5"> Scan QR Code </v-card-title>
9
- <v-spacer />
10
- <v-btn color="grey-darken-1" icon="mdi-close" @click="closeDialog()" />
11
- </v-toolbar>
12
-
13
- <div id="reader" class="d-flex justify-center align-center w-100 h-100"
14
- style="height: calc(100vh - 64px)">
15
- <!-- The QR code scanner box -->
16
- <!-- This is where the QR code scanner will be displayed -->
6
+ <v-card class="screen-modal qr-scanner">
7
+ <div class="qr-scanner__head">
8
+ <v-card-title>Scan QR Code</v-card-title>
9
+ <AppButton variant="icon" icon="mdi-close" @click="closeDialog()" />
17
10
  </div>
18
- <!-- Show Error Messages -->
19
- <div v-if="showNotFoundMessage" class="text-error mt-8">
20
- <p>{{ errorMessage }}</p>
11
+
12
+ <div class="screen-modal__body qr-scanner__body">
13
+ <div id="reader" class="qr-scanner__reader">
14
+ <!-- The QR code scanner box -->
15
+ <!-- This is where the QR code scanner will be displayed -->
16
+ </div>
17
+ <!-- Show Error Messages -->
18
+ <p v-if="showNotFoundMessage" class="qr-scanner__error">{{ errorMessage }}</p>
19
+ <!-- Switch camera button inside the reader box -->
20
+ <AppButton
21
+ icon="mdi-camera-switch"
22
+ class="qr-scanner__switch"
23
+ aria-label="Switch camera"
24
+ @click="switchCamera()"
25
+ />
21
26
  </div>
22
- <!-- Switch camera button inside the reader box -->
23
- <v-btn color="primary" icon class="mt-4" @click="switchCamera()">
24
- <v-icon icon="mdi-camera-switch" large />
25
- </v-btn>
26
27
  </v-card>
27
28
  </v-container>
28
29
  </v-dialog>
@@ -136,3 +137,49 @@ const stopScanner = () => {
136
137
  });
137
138
  };
138
139
  </script>
140
+
141
+ <style scoped>
142
+ /* The card used to be `w-100` inside the centring container; keep that width
143
+ or the dialog collapses to the width of the reader box. */
144
+ .qr-scanner {
145
+ width: 100%;
146
+ }
147
+
148
+ .qr-scanner__head {
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: space-between;
152
+ padding-right: 12px;
153
+ }
154
+
155
+ .qr-scanner__body {
156
+ display: flex;
157
+ flex-direction: column;
158
+ align-items: center;
159
+ gap: 16px;
160
+ padding: 4px 20px 20px;
161
+ }
162
+
163
+ /* The scanner paints its own video into this box; only the frame is ours. */
164
+ .qr-scanner__reader {
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ width: 100%;
169
+ min-height: 320px;
170
+ border: 1px solid var(--border);
171
+ border-radius: var(--r-inner);
172
+ background: var(--thead);
173
+ }
174
+
175
+ .qr-scanner__error {
176
+ margin: 0;
177
+ font-size: var(--fs-cell);
178
+ color: var(--err);
179
+ }
180
+
181
+ .qr-scanner__switch {
182
+ width: var(--btn-h);
183
+ padding: 0;
184
+ }
185
+ </style>
@@ -90,19 +90,14 @@
90
90
  </v-snackbar>
91
91
 
92
92
  <v-dialog v-model="permanentMessageDialog.modal" max-width="480" persistent>
93
- <v-card>
94
- <v-card-title class="text-h6">Notice</v-card-title>
95
- <v-card-text>{{ permanentMessageDialog.text }}</v-card-text>
96
- <v-card-actions>
97
- <v-spacer />
98
- <v-btn
99
- color="primary"
100
- variant="elevated"
101
- @click="permanentMessageDialog.modal = false"
102
- >
103
- Close
104
- </v-btn>
105
- </v-card-actions>
93
+ <v-card class="screen-modal socket-notice">
94
+ <v-card-title>Notice</v-card-title>
95
+ <div class="screen-modal__body socket-notice__body">
96
+ {{ permanentMessageDialog.text }}
97
+ </div>
98
+ <div class="socket-notice__footer">
99
+ <AppButton @click="permanentMessageDialog.modal = false">Close</AppButton>
100
+ </div>
106
101
  </v-card>
107
102
  </v-dialog>
108
103
  </div>
@@ -372,3 +367,16 @@ onUnmounted(() => {
372
367
 
373
368
 
374
369
  </script>
370
+
371
+ <style scoped>
372
+ .socket-notice__body {
373
+ padding: 4px 20px 0;
374
+ color: var(--text2);
375
+ }
376
+
377
+ .socket-notice__footer {
378
+ display: flex;
379
+ justify-content: flex-end;
380
+ padding: 16px 20px 20px;
381
+ }
382
+ </style>
@@ -1,22 +1,25 @@
1
1
  <template>
2
- <v-card height="100%" width="100%" flat class="pa-2 pa-sm-5">
2
+ <AppCard class="pa-2 pa-sm-5">
3
3
  <v-row no-gutters>
4
4
  <v-col cols="12">
5
5
  <v-row no-gutters class="d-flex ga-2 justify-space-between align-start mb-3 mb-sm-5 flex-wrap">
6
6
 
7
7
  <div class="d-flex flex-column" style="min-width: 0;">
8
- <span class="text-subtitle-1 font-weight-bold" style="word-break: break-word;">Visitor
8
+ <span class="screen-card-title" style="word-break: break-word;">Visitor
9
9
  Management - Visitor ({{ items.length }} visitors)</span>
10
- <span style="font-size: clamp(11px, 1.1vw, 13px); word-break: break-word;">Date Range: {{
10
+ <span class="report-meta" style="word-break: break-word;">Date Range: {{
11
11
  formatDateDDMMYYYY(dateFrom) }} - {{ formatDateDDMMYYYY(dateTo) ?? "" }}</span>
12
- <span class="text-subtitle-1" style="word-break: break-word;">Site Name: {{ activeSiteName
12
+ <span class="report-meta" style="word-break: break-word;">Site Name: {{ activeSiteName
13
13
  }}</span>
14
14
  </div>
15
15
 
16
16
  </v-row>
17
- <div style="overflow-x: auto; max-width: 100%;">
17
+ <!-- `text-black` forced every cell AND the empty-state line to
18
+ literal black, which on the dark theme's card measured
19
+ 1.26:1. The table takes the theme's own ink now. -->
20
+ <div class="table-card" style="overflow-x: auto; max-width: 100%;">
18
21
  <v-data-table :headers="headers" :items="items" show-header :items-per-page="100"
19
- hide-default-footer class="text-black" no-data-text="No entries to display">
22
+ hide-default-footer no-data-text="No entries to display">
20
23
 
21
24
  <template v-slot:item.typeCompany="{ item }">
22
25
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
@@ -77,13 +80,13 @@
77
80
  <template v-slot:item.checkInOutImage="{ item }">
78
81
  <div class="d-flex flex-column ga-2">
79
82
  <div v-if="item?.snapshotEntryImage" class="d-flex flex-column">
80
- <span class="text-caption font-weight-bold text-blue-darken-1">Check-In</span>
83
+ <span class="snapshot-label snapshot-label--in">Check-In</span>
81
84
  <v-img :src="getFileUrlAnpr(`${siteId}/${item.snapshotEntryImage}`)"
82
85
  style="max-width: clamp(60px, 10vw, 100px); max-height: clamp(60px, 10vw, 100px);"
83
86
  class="border" />
84
87
  </div>
85
88
  <div v-if="item?.snapshotExitImage" class="d-flex flex-column">
86
- <span class="text-caption font-weight-bold text-red-darken-1">Check-Out</span>
89
+ <span class="snapshot-label snapshot-label--out">Check-Out</span>
87
90
  <v-img :src="getFileUrlAnpr(`${siteId}/${item.snapshotExitImage}`)"
88
91
  style="max-width: clamp(60px, 10vw, 100px); max-height: clamp(60px, 10vw, 100px);"
89
92
  class="border" />
@@ -101,7 +104,7 @@
101
104
  </v-col>
102
105
 
103
106
  </v-row>
104
- </v-card>
107
+ </AppCard>
105
108
  </template>
106
109
 
107
110
  <script setup lang="ts">
@@ -311,44 +314,56 @@ onMounted(() => {
311
314
  box-sizing: border-box;
312
315
  }
313
316
 
314
- :deep(.v-card) {
315
- overflow-x: auto;
317
+ /* The handoff names no type for a report's meta lines or a snapshot caption,
318
+ so both take the design's own secondary sizes. The captions were
319
+ `text-blue-darken-1` / `text-red-darken-1` - fixed Vuetify hues that do not
320
+ follow the theme. */
321
+ .report-meta {
322
+ font-family: var(--font-sans);
323
+ font-size: var(--fs-breadcrumb);
324
+ color: var(--text2);
316
325
  }
317
326
 
318
- :deep(.v-data-table-header__content) {
319
- background-color: #1976d2 !important;
320
- color: white !important;
321
- font-weight: bold;
327
+ .snapshot-label {
328
+ font-family: var(--font-sans);
329
+ font-size: var(--fs-chip);
330
+ font-weight: var(--fw-chip);
322
331
  }
323
332
 
324
- :deep(.v-data-table thead tr th) {
325
- background-color: #1976d2 !important;
326
- color: white !important;
327
- border-right: 1px solid rgba(255, 255, 255, 0.2) !important;
328
- font-size: clamp(11px, 1.2vw, 14px) !important;
329
- padding: clamp(8px, 1vw, 12px) !important;
330
- word-break: break-word;
333
+ .snapshot-label--in {
334
+ color: var(--info);
331
335
  }
332
336
 
333
- :deep(.v-data-table thead tr th:last-child) {
334
- border-right: none !important;
337
+ .snapshot-label--out {
338
+ /* Was `var(--error)`, which is not a token here - the name is `--err`. An
339
+ undefined custom property invalidates the declaration silently, so the
340
+ label inherited instead of turning red. */
341
+ color: var(--err);
342
+ }
343
+
344
+ :deep(.v-card) {
345
+ overflow-x: auto;
335
346
  }
336
347
 
348
+ /* The header row was a fixed `#1976d2` fill with white labels and the cell
349
+ rules were a fixed `#e0e0e0`, none of which follows the theme and none of
350
+ which is in the handoff - the design's table head is 11px/800 uppercase in
351
+ `--muted` on `--thead`. Deleted so the shared `.table-card` rule applies;
352
+ only the column rule this report actually needs is kept, on `--border`. */
353
+ :deep(.v-data-table thead tr th),
337
354
  :deep(.v-data-table tbody tr td) {
338
- border-right: 1px solid #e0e0e0 !important;
339
- font-size: clamp(10px, 1.1vw, 13px) !important;
340
- padding: clamp(6px, 0.8vw, 10px) !important;
355
+ border-right: 1px solid var(--border) !important;
341
356
  word-break: break-word;
342
- word-wrap: break-word;
343
357
  overflow-wrap: break-word;
344
358
  }
345
359
 
360
+ :deep(.v-data-table thead tr th:last-child),
346
361
  :deep(.v-data-table tbody tr td:last-child) {
347
362
  border-right: none !important;
348
363
  }
349
364
 
350
365
  :deep(.v-data-table tbody tr:last-child td) {
351
- border-bottom: 1px solid #e0e0e0 !important;
366
+ border-bottom: 1px solid var(--border) !important;
352
367
  }
353
368
 
354
369
  :deep(.v-img) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.118",
5
+ "version": "3.2.2-staging.121",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -18,5 +18,100 @@
18
18
  "els": 3,
19
19
  "textChars": 2,
20
20
  "recordedFrom": "0642279"
21
+ },
22
+ "AccessCardAssignToUnitForm": {
23
+ "els": 177,
24
+ "textChars": 131,
25
+ "recordedFrom": "178ebf0"
26
+ },
27
+ "AccessCardReplaceForm": {
28
+ "els": 68,
29
+ "textChars": 83,
30
+ "recordedFrom": "178ebf0"
31
+ },
32
+ "AddEqupmentForm": {
33
+ "els": 90,
34
+ "textChars": 99,
35
+ "recordedFrom": "178ebf0"
36
+ },
37
+ "BuildingForm": {
38
+ "els": 126,
39
+ "textChars": 69,
40
+ "recordedFrom": "178ebf0"
41
+ },
42
+ "BulletinBoardForm": {
43
+ "els": 172,
44
+ "textChars": 165,
45
+ "recordedFrom": "178ebf0"
46
+ },
47
+ "CameraForm": {
48
+ "els": 127,
49
+ "textChars": 80,
50
+ "recordedFrom": "178ebf0"
51
+ },
52
+ "DocumentForm": {
53
+ "els": 52,
54
+ "textChars": 57,
55
+ "recordedFrom": "178ebf0"
56
+ },
57
+ "InvitationClientForm": {
58
+ "els": 101,
59
+ "textChars": 47,
60
+ "recordedFrom": "178ebf0"
61
+ },
62
+ "InvitationForm": {
63
+ "els": 86,
64
+ "textChars": 43,
65
+ "recordedFrom": "178ebf0"
66
+ },
67
+ "ServiceProviderFormCreate": {
68
+ "els": 51,
69
+ "textChars": 38,
70
+ "recordedFrom": "178ebf0"
71
+ },
72
+ "AcceptDialog": {
73
+ "els": 24,
74
+ "textChars": 106,
75
+ "recordedFrom": "178ebf0"
76
+ },
77
+ "Card/DeleteConfirmation": {
78
+ "els": 21,
79
+ "textChars": 129,
80
+ "recordedFrom": "178ebf0"
81
+ },
82
+ "Dialog/DeleteConfirmation": {
83
+ "els": 21,
84
+ "textChars": 129,
85
+ "recordedFrom": "178ebf0"
86
+ },
87
+ "HidAccessLogDashboard": {
88
+ "els": 172,
89
+ "textChars": 437,
90
+ "recordedFrom": "178ebf0"
91
+ },
92
+ "HidIntercomManagement": {
93
+ "els": 148,
94
+ "textChars": 167,
95
+ "recordedFrom": "178ebf0"
96
+ },
97
+ "HidUserEnrollment": {
98
+ "els": 142,
99
+ "textChars": 287,
100
+ "recordedFrom": "178ebf0"
101
+ },
102
+ "HidReaderManagement": {
103
+ "els": 112,
104
+ "textChars": 377,
105
+ "recordedFrom": "178ebf0"
106
+ },
107
+ "HidQrCodeConfiguration": {
108
+ "els": 106,
109
+ "textChars": 453,
110
+ "recordedFrom": "178ebf0"
111
+ },
112
+ "HidServiceSettingsPanel": {
113
+ "els": 18,
114
+ "textChars": 164,
115
+ "recordedFrom": "178ebf0"
21
116
  }
22
117
  }
@@ -78,6 +78,10 @@ if (-not (Test-Path "$fork\vite.config.mjs")) {
78
78
  # The hardened runner lives in the repo, next to this script, so a fork always
79
79
  # gets the CURRENT one rather than whatever the canonical harness was carrying.
80
80
  Copy-Item (Join-Path $PSScriptRoot "render-check.mjs") -Destination $fork -Force
81
+ # `probe.mjs` is imported by the runner, so a fork without it dies on
82
+ # ERR_MODULE_NOT_FOUND before it measures anything. It was missing from this
83
+ # list, which every fork made before now had to work around by hand.
84
+ Copy-Item (Join-Path $PSScriptRoot "probe.mjs") -Destination $fork -Force
81
85
  Copy-Item (Join-Path $PSScriptRoot "baselines.json") -Destination $fork -Force -ErrorAction SilentlyContinue
82
86
 
83
87
  # A junctioned `node_modules` SHARES `node_modules/.vite` with the harness it
@@ -62,6 +62,39 @@ test("the product's own status words keep the tone the screens already gave them
62
62
  }
63
63
  });
64
64
 
65
+ /**
66
+ * HID enrolment. These two are the whole point of the Status column on
67
+ * `HidUserEnrollment` - if they were left unlisted they would BOTH be neutral
68
+ * and the column would stop distinguishing anything, so they are pinned.
69
+ */
70
+ test("Mapped and Unmapped keep the tones the enrolment screen already gave them", () => {
71
+ assert.equal(statusTone("Mapped"), "ok");
72
+ assert.equal(statusTone("Unmapped"), "warn");
73
+ });
74
+
75
+ /**
76
+ * HID intercom SIP state. "Disabled" and "Not connected" must stay NEUTRAL -
77
+ * painting an intentionally-off intercom red would read as a fault.
78
+ */
79
+ /**
80
+ * HID access authorization, landing on `staging` in b0667a7. Pinned before that
81
+ * merge so the words cannot arrive and quietly render neutral - the difference
82
+ * between a granted and a denied door is the whole point of the column.
83
+ */
84
+ test("HID authorization states are known to the shared map before b0667a7 merges", () => {
85
+ assert.equal(statusTone("Authorized"), "ok");
86
+ assert.equal(statusTone("Not Authorized"), "err");
87
+ assert.equal(statusTone("Unknown"), "warn");
88
+ });
89
+
90
+ test("intercom SIP states keep the tones the intercom screen already gave them", () => {
91
+ assert.equal(statusTone("Connected"), "ok");
92
+ assert.equal(statusTone("Connecting"), "warn");
93
+ assert.equal(statusTone("Not connected"), "neutral");
94
+ assert.equal(statusTone("Disabled"), "neutral");
95
+ assert.equal(statusTone("Failed"), "err");
96
+ });
97
+
65
98
  /**
66
99
  * The one place the handoff and the old per-screen colour disagree: `Open` was
67
100
  * grey on the cleaning schedules and the design names it `ok`. Pinned so the
package/utils/status.ts CHANGED
@@ -70,6 +70,42 @@ const TONES: Record<string, TStatusTone> = {
70
70
  expired: "neutral", // was `grey`
71
71
 
72
72
  "awaiting approval": "warn", // a waiting state, like Pending
73
+
74
+ /**
75
+ * HID enrolment: is this reader identity tied to a real person record yet?
76
+ * Same rule as the Phase 4 block above - not a new opinion, but the tone
77
+ * `HidUserEnrollment` already painted these two words, moved into the one
78
+ * list. Left unlisted they would BOTH come out neutral, which would erase
79
+ * the only distinction the column exists to draw.
80
+ */
81
+ mapped: "ok", // was `status-success`, #1b7f3a on #dff7e7
82
+ unmapped: "warn", // was `status-warning`, #9a6700 on #fff0cc
83
+
84
+ /**
85
+ * HID intercom SIP state, from `HidIntercomManagement`'s own
86
+ * `getStatusClass`. Same rule again: the tone the screen already gave the
87
+ * word, moved into the one list. "Disabled" and "Not connected" are NOT
88
+ * failures - the operator turned SIP off, or never set it up - so they stay
89
+ * neutral, which is what `status-muted` painted them.
90
+ */
91
+ /**
92
+ * HID access authorization. These arrive with `b0667a7` on `staging`
93
+ * ("Call the Authorized status API from Amico via Access Logs"), which
94
+ * replaces the Access Logs' Mapped/Unmapped column with these three words.
95
+ * Listed here AHEAD of that merge so the shared chip already knows them -
96
+ * unlisted they would all come out neutral, silently erasing the difference
97
+ * between a granted and a denied door. Tones are that commit's own:
98
+ * `mapped-chip` green, `unmapped-chip` red, `unknown-chip` amber.
99
+ */
100
+ authorized: "ok",
101
+ "not authorized": "err",
102
+ unknown: "warn",
103
+
104
+ connected: "ok", // was `status-success`
105
+ connecting: "warn", // was `status-warning`, the fall-through case
106
+ "not connected": "neutral", // was `status-muted`
107
+ disabled: "neutral", // was `status-muted`
108
+ failed: "err", // was `status-error`
73
109
  };
74
110
 
75
111
  /**