@7365admin1/layer-common 3.2.1 → 3.2.2-staging.101

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 (126) hide show
  1. package/.changeset/camera-wall-gated-endpoint.md +47 -0
  2. package/.changeset/camera-wall-plain-english.md +60 -0
  3. package/.changeset/camera-wall-selection-and-guidance.md +65 -0
  4. package/.changeset/dark-primary-contrast.md +39 -0
  5. package/.changeset/dashboard-dark-theme-contrast.md +15 -0
  6. package/.changeset/notification-preferences.md +17 -0
  7. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
  8. package/.changeset/service-provider-invitation-actions.md +28 -0
  9. package/assets/css/primitives.css +680 -0
  10. package/assets/css/screens.css +541 -0
  11. package/assets/css/shell.css +258 -0
  12. package/assets/css/tokens.css +220 -0
  13. package/components/AccessCardQrTagging.vue +1 -1
  14. package/components/AccessManagement.vue +53 -72
  15. package/components/AppButton.vue +52 -0
  16. package/components/AppCard.vue +20 -0
  17. package/components/AppDateField.vue +35 -0
  18. package/components/AppField.vue +51 -0
  19. package/components/AppKpiTile.vue +65 -0
  20. package/components/AppSegmented.vue +38 -0
  21. package/components/AppSelect.vue +203 -0
  22. package/components/AreaChecklistHistoryLogs.vue +51 -44
  23. package/components/AreaChecklistHistoryMain.vue +6 -2
  24. package/components/AreaMain.vue +9 -17
  25. package/components/AttendanceMain.vue +4 -3
  26. package/components/Avatar/Main.vue +54 -2
  27. package/components/BillingMain.vue +26 -30
  28. package/components/BuildingManagement/buildings.vue +27 -38
  29. package/components/BuildingManagement/units.vue +26 -42
  30. package/components/BulletinBoardManagement.vue +23 -10
  31. package/components/CameraWall.vue +1034 -0
  32. package/components/CameraWallTile.vue +818 -0
  33. package/components/CardToolbar.vue +37 -0
  34. package/components/CleaningScheduleMain.vue +29 -46
  35. package/components/ClientMain.vue +90 -78
  36. package/components/DashboardEmptyState.vue +11 -1
  37. package/components/DashboardMain.vue +349 -275
  38. package/components/DashboardPanel.vue +1 -1
  39. package/components/DocumentManagement.vue +129 -93
  40. package/components/EntryPassInformation.vue +4 -4
  41. package/components/EntryPassMain.vue +49 -27
  42. package/components/EquipmentItemMain.vue +4 -6
  43. package/components/EquipmentManagementMain.vue +9 -7
  44. package/components/FeedbackMain.vue +42 -56
  45. package/components/FormDialog.vue +60 -21
  46. package/components/HidAccessLogDashboard.vue +119 -44
  47. package/components/HidCameraCaptureDialog.vue +199 -0
  48. package/components/HidIntercomManagement.vue +825 -202
  49. package/components/HidQrCodeConfiguration.vue +927 -93
  50. package/components/HidServiceSettingsPanel.vue +20 -1
  51. package/components/HidUserEnrollment.vue +454 -106
  52. package/components/InvitationMain.vue +46 -61
  53. package/components/Layout/Header.vue +289 -37
  54. package/components/Layout/NavigationDrawer.vue +340 -30
  55. package/components/ManageChecklistMain.vue +17 -17
  56. package/components/MemberMain.vue +79 -60
  57. package/components/MyAttendanceMain.vue +3 -5
  58. package/components/NavigationItem.vue +9 -0
  59. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  60. package/components/NotificationSettings.vue +361 -0
  61. package/components/OnlineFormConfigurationForm.vue +620 -224
  62. package/components/OnlineFormFill.vue +36 -8
  63. package/components/OnlineFormsConfiguration.vue +7 -3
  64. package/components/PageHeader.vue +43 -0
  65. package/components/PassInformation.vue +104 -45
  66. package/components/QrTemplate/CreditCardLandscape.vue +19 -12
  67. package/components/QrTemplate/PrintDialog.vue +209 -196
  68. package/components/RolePermissionMain.vue +184 -91
  69. package/components/ScanVisitorQRCode.vue +35 -7
  70. package/components/ScheduleAreaMain.vue +15 -49
  71. package/components/ScheduleTaskMain.vue +23 -26
  72. package/components/ScheduleTastTicketMain.vue +18 -43
  73. package/components/ServiceProviderInvitationPrompt.vue +150 -0
  74. package/components/ServiceProviderMain.vue +346 -157
  75. package/components/SiteSettings.vue +25 -10
  76. package/components/SiteSettingsWorkOrder.vue +61 -30
  77. package/components/SpecificAttr.vue +10 -2
  78. package/components/StatusChip.vue +93 -0
  79. package/components/StockCard.vue +17 -12
  80. package/components/SwitchContext.vue +33 -1
  81. package/components/TableHygiene.vue +29 -46
  82. package/components/TableList.vue +1 -1
  83. package/components/TableListSecondary.vue +1 -1
  84. package/components/TableMain.vue +161 -77
  85. package/components/TableV3.vue +61 -66
  86. package/components/UnitMain.vue +9 -17
  87. package/components/VehicleManagement.vue +20 -9
  88. package/components/VisitorForm.vue +294 -78
  89. package/components/VisitorManagement.vue +221 -71
  90. package/components/VisitorSocketPopUp.vue +56 -2
  91. package/components/WorkOrder/Main.vue +42 -51
  92. package/composables/useComment.ts +3 -3
  93. package/composables/useHidAmico.ts +151 -0
  94. package/composables/useHidNavigation.ts +0 -7
  95. package/composables/useLocalAuth.ts +9 -36
  96. package/composables/useNotificationPreference.ts +87 -0
  97. package/composables/useOnlineFormPages.ts +2 -0
  98. package/composables/useOrg.ts +1 -1
  99. package/composables/useRole.ts +22 -0
  100. package/composables/useServiceProviderInvitation.ts +145 -0
  101. package/composables/useSipWebPhone.ts +336 -0
  102. package/composables/useSiteSettings.ts +50 -0
  103. package/composables/useThemePreference.ts +63 -0
  104. package/composables/useVisitor.ts +6 -1
  105. package/composables/useVisitorSocket.ts +26 -0
  106. package/composables/useWebUsb.ts +127 -37
  107. package/nuxt.config.ts +31 -0
  108. package/package.json +4 -1
  109. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  110. package/pages/notification-settings.vue +19 -0
  111. package/plugins/secure-member.client.ts +21 -56
  112. package/plugins/vuetify.ts +68 -9
  113. package/test/visitor-socket.test.mjs +36 -0
  114. package/types/org.d.ts +2 -0
  115. package/types/site.d.ts +71 -0
  116. package/utils/camera-wall.test.ts +571 -0
  117. package/utils/camera-wall.ts +926 -0
  118. package/utils/nav-sections.test.ts +190 -0
  119. package/utils/nav-sections.ts +151 -0
  120. package/utils/status.test.ts +77 -0
  121. package/utils/status.ts +90 -0
  122. package/utils/theme.test.ts +534 -0
  123. package/utils/theme.ts +294 -0
  124. package/components/HidIdentityMapping.vue +0 -975
  125. package/middleware/member.ts +0 -4
  126. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
@@ -0,0 +1,20 @@
1
+ <!--
2
+ THE CARD.
3
+
4
+ 14px radius, a 1px `--border` rule and the design's one-pixel shadow, on
5
+ `--card`. A plain element rather than a `v-card`, because `v-card` brings its
6
+ own elevation, radius and surface colour at a specificity that a class has to
7
+ fight - which is why the radius kept coming back as 4px in Phase 3.
8
+ -->
9
+ <template>
10
+ <div class="app-card" :class="{ 'app-card--flush': flush }">
11
+ <slot />
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ defineProps({
17
+ /** Let content (a menu, a dropdown) escape the card's rounded corners. */
18
+ flush: { type: Boolean, default: false },
19
+ });
20
+ </script>
@@ -0,0 +1,35 @@
1
+ <!--
2
+ THE DATE FIELD.
3
+
4
+ The same 40px box with a calendar glyph, as the handoff draws the two date
5
+ ends of a filter row. `<input type="date">` - the browser's own picker, which
6
+ is the one a phone user already knows and the one that is localised, keyboard
7
+ accessible and free.
8
+ -->
9
+ <template>
10
+ <label class="app-field" :class="{ 'app-field--compact': compact }">
11
+ <v-icon class="app-field__icon" icon="mdi-calendar-month-outline" size="17" />
12
+ <span v-if="label" class="app-field__label">{{ label }}</span>
13
+ <input
14
+ class="app-field__input"
15
+ type="date"
16
+ :value="model"
17
+ :min="min || undefined"
18
+ :max="max || undefined"
19
+ :disabled="disabled"
20
+ @input="model = ($event.target as HTMLInputElement).value"
21
+ />
22
+ </label>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ const model = defineModel({ type: String, default: "" });
27
+
28
+ defineProps({
29
+ label: { type: String, default: "" },
30
+ min: { type: String, default: "" },
31
+ max: { type: String, default: "" },
32
+ compact: { type: Boolean, default: false },
33
+ disabled: { type: Boolean, default: false },
34
+ });
35
+ </script>
@@ -0,0 +1,51 @@
1
+ <!--
2
+ THE TEXT FIELD.
3
+
4
+ 40px (34px compact), 10px radius, a 1px border, 13.5px/500 - and the label
5
+ OUTSIDE the box as a small muted word, not Vuetify's outlined field with a
6
+ label that floats up through the border. A native `<input>`, so the browser
7
+ keeps the keyboard, autofill and accessibility for free.
8
+ -->
9
+ <template>
10
+ <label
11
+ class="app-field"
12
+ :class="[{ 'app-field--compact': compact, 'app-field--search': search }, klass]"
13
+ >
14
+ <v-icon
15
+ v-if="icon || search"
16
+ class="app-field__icon"
17
+ :icon="icon || 'mdi-magnify'"
18
+ size="18"
19
+ />
20
+ <span v-if="label" class="app-field__label">{{ label }}</span>
21
+ <input
22
+ class="app-field__input"
23
+ :type="type"
24
+ :value="model"
25
+ :placeholder="placeholder"
26
+ :disabled="disabled"
27
+ :readonly="readonly"
28
+ @input="model = ($event.target as HTMLInputElement).value"
29
+ />
30
+ </label>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ const model = defineModel({ type: String, default: "" });
35
+
36
+ defineProps({
37
+ label: { type: String, default: "" },
38
+ placeholder: { type: String, default: "" },
39
+ icon: { type: String, default: "" },
40
+ type: { type: String, default: "text" },
41
+ /** The design's search field: a magnifier and `flex:1` in a filter row. */
42
+ search: { type: Boolean, default: false },
43
+ compact: { type: Boolean, default: false },
44
+ disabled: { type: Boolean, default: false },
45
+ /** A field that prints a derived value - a preview, a computed reference -
46
+ and is not typed into. Kept distinct from `disabled`, which also greys
47
+ the box and takes it out of the tab order. */
48
+ readonly: { type: Boolean, default: false },
49
+ klass: { type: String, default: "" },
50
+ });
51
+ </script>
@@ -0,0 +1,65 @@
1
+ <!--
2
+ THE KPI TILE.
3
+
4
+ The handoff's tile is three rows: the soft icon square AND the uppercase
5
+ label together on the first, the big tabular number with its percentage pill
6
+ BESIDE it on the second, the sub-line on the third. The product had the icon
7
+ alone on a row of its own, the label under it, and the pill stranded at the
8
+ bottom next to the sub-line - the same figures in a different shape.
9
+
10
+ It draws what it is given and nothing else: no fetch, no formatting, no
11
+ threshold logic. `#action` is the far end of the first row (a filter menu, a
12
+ "View All"); `#default`, when given, replaces the figure rows entirely for
13
+ the tiles that list items instead of counting them.
14
+ -->
15
+ <template>
16
+ <AppCard class="app-kpi d-flex flex-column flex-grow-1">
17
+ <div class="app-kpi__head">
18
+ <span
19
+ v-if="icon"
20
+ class="app-kpi__icon"
21
+ :style="tint ? { backgroundColor: tint } : undefined"
22
+ >
23
+ <v-icon :icon="icon" :color="color || undefined" size="19" />
24
+ </span>
25
+
26
+ <span class="app-kpi__label">{{ label }}</span>
27
+
28
+ <span v-if="$slots.action" class="app-kpi__action">
29
+ <slot name="action" />
30
+ </span>
31
+ </div>
32
+
33
+ <slot>
34
+ <div class="app-kpi__figure">
35
+ <span class="app-kpi__value">{{ value }}</span>
36
+
37
+ <span
38
+ v-if="chip"
39
+ class="app-kpi__chip"
40
+ :class="{ 'app-kpi__chip--down': down }"
41
+ >
42
+ {{ chip }}
43
+ </span>
44
+ </div>
45
+
46
+ <div v-if="sub" class="app-kpi__sub">{{ sub }}</div>
47
+ </slot>
48
+ </AppCard>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ defineProps({
53
+ icon: { type: String, default: "" },
54
+ /** The icon square's soft fill - a token-derived colour from the caller. */
55
+ tint: { type: String, default: "" },
56
+ color: { type: String, default: "" },
57
+ label: { type: String, default: "" },
58
+ value: { type: [String, Number], default: "" },
59
+ /** The percentage pill's text, already formatted by the caller. */
60
+ chip: { type: String, default: "" },
61
+ /** Draws the pill in the error tone rather than the success one. */
62
+ down: { type: Boolean, default: false },
63
+ sub: { type: String, default: "" },
64
+ });
65
+ </script>
@@ -0,0 +1,38 @@
1
+ <!--
2
+ THE SEGMENTED CONTROL - `Today | Week | Month`.
3
+
4
+ A tray of plain `<button>`s, not a `v-btn-toggle`. The toggle brings its own
5
+ height scale, dividers, ripple and overlay layer, and its selected state is
6
+ drawn from `on-surface` - which is why the product's selected segment reads
7
+ grey where the design fills it with the accent.
8
+
9
+ Selection only: it writes the same `v-model` the toggle wrote, and the value
10
+ set is whatever the caller passes. No behaviour is added here.
11
+ -->
12
+ <template>
13
+ <div class="app-seg" role="group">
14
+ <button
15
+ v-for="option in options"
16
+ :key="String(option.value)"
17
+ type="button"
18
+ class="app-seg__item"
19
+ :class="{ 'app-seg__item--on': model === option.value }"
20
+ :aria-pressed="model === option.value"
21
+ @click="model = option.value"
22
+ >
23
+ {{ option.label }}
24
+ </button>
25
+ </div>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ defineProps({
30
+ /** `{ label, value }` - the same option list the toggle was given. */
31
+ options: {
32
+ type: Array as PropType<Array<{ label: string; value: any }>>,
33
+ default: () => [],
34
+ },
35
+ });
36
+
37
+ const model = defineModel<any>({ default: "" });
38
+ </script>
@@ -0,0 +1,203 @@
1
+ <!--
2
+ THE SELECT.
3
+
4
+ The handoff draws it as the text field with the filter's name in muted 12px
5
+ on the left, the chosen value next to it and a chevron on the right. A native
6
+ `<select>` under our chrome: the option list, the keyboard and the mobile
7
+ wheel are the browser's, so there is no menu to build and nothing to get
8
+ wrong on a phone.
9
+
10
+ `multiple` is the ONE case where native is the wrong answer. A native
11
+ `<select multiple>` is not a dropdown at all - it is an always-open scrolling
12
+ LIST BOX - so adopting it would turn the daily filter bars into list boxes
13
+ and change how the staff work. So multi opens a POPOVER in the same field
14
+ chrome, with the chosen values as the design's soft removable pills.
15
+
16
+ The single-value path below is byte-for-byte the one it has always been: a
17
+ consumer that passes neither `multiple` nor `return-object` still emits
18
+ exactly the `<select>` element's own string.
19
+ -->
20
+ <template>
21
+ <label
22
+ v-if="!multiple"
23
+ v-bind="$attrs"
24
+ class="app-field"
25
+ :class="{ 'app-field--compact': compact }"
26
+ >
27
+ <span v-if="label" class="app-field__label">{{ label }}</span>
28
+ <select
29
+ class="app-field__input"
30
+ :value="singleValue"
31
+ :disabled="disabled"
32
+ @change="onNativeChange"
33
+ >
34
+ <!-- `clearable` on a native select is the empty option, not an x. -->
35
+ <option v-if="clearable" value="">{{ placeholder }}</option>
36
+ <option v-for="opt in normalized" :key="String(opt.value)" :value="opt.value">
37
+ {{ opt.title }}
38
+ </option>
39
+ </select>
40
+ <v-icon class="app-field__icon" icon="mdi-chevron-down" size="17" />
41
+ </label>
42
+
43
+ <v-menu
44
+ v-else
45
+ :close-on-content-click="false"
46
+ :disabled="disabled"
47
+ location="bottom start"
48
+ offset="4"
49
+ >
50
+ <template #activator="{ props: activator }">
51
+ <div
52
+ v-bind="{ ...activator, ...$attrs }"
53
+ class="app-field app-select"
54
+ :class="{ 'app-field--compact': compact, 'app-select--disabled': disabled }"
55
+ role="button"
56
+ :aria-disabled="disabled"
57
+ :tabindex="disabled ? -1 : 0"
58
+ >
59
+ <span v-if="label" class="app-field__label">{{ label }}</span>
60
+ <span class="app-select__values">
61
+ <StatusChip
62
+ v-for="opt in selectedOptions"
63
+ :key="String(opt.value)"
64
+ tone="neutral"
65
+ pill
66
+ :dot="false"
67
+ >
68
+ {{ opt.title }}
69
+ <button
70
+ type="button"
71
+ class="app-select__x"
72
+ :aria-label="`Remove ${opt.title}`"
73
+ @click.stop="toggle(opt)"
74
+ >
75
+ <v-icon icon="mdi-close" size="12" />
76
+ </button>
77
+ </StatusChip>
78
+ <span v-if="!selectedOptions.length" class="app-select__placeholder">
79
+ {{ placeholder }}
80
+ </span>
81
+ </span>
82
+ <button
83
+ v-if="clearable && selectedOptions.length"
84
+ type="button"
85
+ class="app-select__clear"
86
+ aria-label="Clear"
87
+ @click.stop="clear"
88
+ >
89
+ <v-icon icon="mdi-close-circle" size="15" />
90
+ </button>
91
+ <v-icon class="app-field__icon" icon="mdi-chevron-down" size="17" />
92
+ </div>
93
+ </template>
94
+
95
+ <div class="app-select__menu">
96
+ <button
97
+ v-for="opt in normalized"
98
+ :key="String(opt.value)"
99
+ type="button"
100
+ class="app-select__option"
101
+ :aria-pressed="isOn(opt)"
102
+ @click="toggle(opt)"
103
+ >
104
+ <v-icon
105
+ class="app-select__box"
106
+ :class="{ 'app-select__box--on': isOn(opt) }"
107
+ :icon="isOn(opt) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
108
+ size="17"
109
+ />
110
+ <span class="app-select__option-text">{{ opt.title }}</span>
111
+ </button>
112
+ </div>
113
+ </v-menu>
114
+ </template>
115
+
116
+ <script setup lang="ts">
117
+ /* `v-menu` has no single root node, so a class on `<AppSelect multiple>` has
118
+ to be put on the activator by hand. The single branch lands where it always
119
+ did - on the `<label>`. */
120
+ defineOptions({ inheritAttrs: false });
121
+
122
+ /*
123
+ * One model for both shapes: a string when single, an array when multiple.
124
+ * The default stays `""` so an existing single consumer that never sets its
125
+ * v-model starts on the same empty string it starts on today.
126
+ */
127
+ const model = defineModel<any>({ default: "" });
128
+
129
+ const props = defineProps({
130
+ label: { type: String, default: "" },
131
+ /** Strings, or the `{ title, value }` shape `v-select` already takes. */
132
+ items: { type: Array as PropType<any[]>, default: () => [] },
133
+ itemTitle: { type: String, default: "title" },
134
+ itemValue: { type: String, default: "value" },
135
+ compact: { type: Boolean, default: false },
136
+ disabled: { type: Boolean, default: false },
137
+ /** Popover multi-select with removable pills. Never a native list box. */
138
+ multiple: { type: Boolean, default: false },
139
+ /** Emit the source item rather than its `itemValue`. */
140
+ returnObject: { type: Boolean, default: false },
141
+ clearable: { type: Boolean, default: false },
142
+ placeholder: { type: String, default: "" },
143
+ });
144
+
145
+ const normalized = computed(() =>
146
+ props.items.map((item: any) =>
147
+ typeof item === "object" && item !== null
148
+ ? { title: item[props.itemTitle], value: item[props.itemValue], raw: item }
149
+ : { title: String(item), value: item, raw: item }
150
+ )
151
+ );
152
+
153
+ /* A model entry is either the plain value or - under `return-object` - the
154
+ source item, so everything compares on this one key. */
155
+ const keyOf = (v: any) =>
156
+ String(v !== null && typeof v === "object" ? v[props.itemValue] : v);
157
+
158
+ /* ---- single ---------------------------------------------------------- */
159
+
160
+ const singleValue = computed(() =>
161
+ props.returnObject ? keyOf(model.value) : model.value
162
+ );
163
+
164
+ function onNativeChange(event: Event) {
165
+ const value = (event.target as HTMLSelectElement).value;
166
+ model.value = props.returnObject
167
+ ? normalized.value.find((o) => String(o.value) === value)?.raw ?? value
168
+ : value;
169
+ }
170
+
171
+ /* ---- multiple -------------------------------------------------------- */
172
+
173
+ const selected = computed<any[]>(() =>
174
+ Array.isArray(model.value) ? model.value : []
175
+ );
176
+
177
+ const selectedKeys = computed(() => new Set(selected.value.map(keyOf)));
178
+
179
+ const isOn = (opt: any) => selectedKeys.value.has(String(opt.value));
180
+
181
+ /* Fall back to the raw key when a chosen value is not in `items` - an async
182
+ item list can arrive after the model does, and the chip must still print. */
183
+ const selectedOptions = computed(() =>
184
+ selected.value.map(
185
+ (v) =>
186
+ normalized.value.find((o) => String(o.value) === keyOf(v)) ?? {
187
+ title: keyOf(v),
188
+ value: keyOf(v),
189
+ }
190
+ )
191
+ );
192
+
193
+ function toggle(opt: any) {
194
+ if (props.disabled) return;
195
+ model.value = isOn(opt)
196
+ ? selected.value.filter((v) => keyOf(v) !== String(opt.value))
197
+ : [...selected.value, props.returnObject ? opt.raw : opt.value];
198
+ }
199
+
200
+ function clear() {
201
+ model.value = [];
202
+ }
203
+ </script>
@@ -1,28 +1,27 @@
1
1
  <template>
2
2
  <v-row no-gutters align="center" justify="center">
3
3
  <v-col cols="12">
4
- <v-card class="mx-auto">
5
- <v-toolbar
6
- density="compact"
7
- color="grey-lighten-4"
8
- class="d-flex align-center px-4 py-3"
9
- >
10
- <v-btn icon variant="plain" @click="back()" class="mr-2">
11
- <v-icon>mdi-chevron-left</v-icon>
12
- </v-btn>
13
- <span class="text-h6 font-weight-bold">Area Checklist</span>
14
- </v-toolbar>
15
-
16
- <v-divider />
17
-
18
- <v-card-text class="pa-0">
4
+ <!-- The screen's name and its Back move out of the `grey-lighten-4` bar -
5
+ a slab that stayed white in dark mode - onto the design's title row.
6
+ All five service apps mount this component bare, so the layer is the
7
+ only place the page name can come from. -->
8
+ <PageHeader title="Area Checklist">
9
+ <template #actions>
10
+ <AppButton variant="ghost" icon="mdi-chevron-left" @click="back()">
11
+ Back
12
+ </AppButton>
13
+ </template>
14
+ </PageHeader>
15
+
16
+ <AppCard>
17
+ <div class="pa-0">
19
18
  <v-row no-gutters>
20
19
  <v-col
21
20
  cols="12"
22
21
  md="6"
23
22
  lg="5"
24
23
  xl="4"
25
- style="border-right: 1px solid rgba(0, 0, 0, 0.12)"
24
+ class="checklist-detail__split"
26
25
  >
27
26
  <v-row no-gutters class="pa-6">
28
27
  <v-col cols="12" class="mb-4">
@@ -47,11 +46,11 @@
47
46
 
48
47
  <v-col cols="12" class="mb-3">
49
48
  <div class="text-body-2 mb-1">Status:</div>
50
- <v-chip
51
- class="text-capitalize text-body-2 font-weight-medium"
52
- >
53
- {{ status || "N/A" }}
54
- </v-chip>
49
+ <StatusChip
50
+ class="text-capitalize"
51
+ :status="status"
52
+ :label="status || 'N/A'"
53
+ />
55
54
  </v-col>
56
55
  </v-row>
57
56
  </v-col>
@@ -119,15 +118,15 @@
119
118
  class="text-subtitle-2 font-weight-bold px-0"
120
119
  >
121
120
  Set {{ checklistSet.set }}
122
- <v-chip
121
+ <StatusChip
123
122
  v-if="checklistSet.isScheduleTask"
124
- size="x-small"
125
- color="info"
126
- variant="tonal"
127
- class="text-none ml-2"
123
+ tone="info"
124
+ :dot="false"
125
+ pill
126
+ class="ml-2"
128
127
  >
129
128
  Schedule Task
130
- </v-chip>
129
+ </StatusChip>
131
130
  </v-list-subheader>
132
131
 
133
132
  <v-list-item
@@ -161,22 +160,14 @@
161
160
  </div>
162
161
  </v-col>
163
162
  <v-col cols="12" sm="2" class="text-right">
164
- <v-chip
165
- :color="
166
- unit.status?.toLowerCase() === 'completed'
167
- ? 'success'
168
- : 'grey'
169
- "
170
- :variant="
171
- unit.status?.toLowerCase() === 'completed'
172
- ? 'flat'
173
- : 'outlined'
174
- "
175
- size="small"
163
+ <!-- Same word decides the colour, only now through the
164
+ shared status map, so "Completed" here is the same
165
+ green as on every other screen. -->
166
+ <StatusChip
176
167
  class="text-capitalize"
177
- >
178
- {{ unit.status || "Open" }}
179
- </v-chip>
168
+ :status="unit.status || 'Open'"
169
+ :label="unit.status || 'Open'"
170
+ />
180
171
  </v-col>
181
172
  </v-row>
182
173
  </v-list-item>
@@ -191,8 +182,8 @@
191
182
  </v-row>
192
183
  </v-col>
193
184
  </v-row>
194
- </v-card-text>
195
- </v-card>
185
+ </div>
186
+ </AppCard>
196
187
  </v-col>
197
188
  </v-row>
198
189
  </template>
@@ -242,3 +233,19 @@ const timeOut = ref<string>("N/A");
242
233
  const status = ref<string>("N/A");
243
234
  const signatureUrl = ref<string>("");
244
235
  </script>
236
+
237
+ <style scoped>
238
+ /* Was an inline `rgba(0,0,0,.12)`, which is the same near-black rule in dark
239
+ mode. The token follows the theme. At `md` and below the two halves stack, so
240
+ the rule moves under the first one. */
241
+ .checklist-detail__split {
242
+ border-bottom: 1px solid var(--border);
243
+ }
244
+
245
+ @media (min-width: 960px) {
246
+ .checklist-detail__split {
247
+ border-bottom: none;
248
+ border-right: 1px solid var(--border);
249
+ }
250
+ }
251
+ </style>
@@ -33,7 +33,7 @@
33
33
  <template #extension>
34
34
  <v-row no-gutters class="w-100 d-flex flex-column">
35
35
  <v-card
36
- class="w-100 px-3 d-flex align-center ga-5 py-2"
36
+ class="w-100 filter-bar d-flex align-center ga-5"
37
37
  flat
38
38
  :height="60"
39
39
  >
@@ -80,7 +80,11 @@
80
80
  </div>
81
81
  </template>
82
82
  <template #item.status="{ value }">
83
- <v-chip class="text-capitalize">{{ value || "No Status" }}</v-chip>
83
+ <StatusChip
84
+ class="text-capitalize"
85
+ :status="value"
86
+ :label="value || 'No Status'"
87
+ />
84
88
  </template>
85
89
  </TableMain></v-col
86
90
  >
@@ -49,10 +49,8 @@
49
49
  <!-- Primary action -->
50
50
  <v-btn
51
51
  v-if="canCreateArea"
52
- class="text-none mr-3"
53
- rounded="pill"
54
- variant="tonal"
55
- size="large"
52
+ class="text-none mr-3 screen-btn-primary"
53
+ variant="flat"
56
54
  @click="onCreateItem"
57
55
  >
58
56
  {{ createLabel }}
@@ -63,10 +61,8 @@
63
61
  <template #activator="{ props: t }">
64
62
  <v-btn
65
63
  v-bind="t"
66
- class="ma-1"
67
- variant="tonal"
68
- size="large"
69
- rounded="pill"
64
+ class="ma-1 screen-icon-btn"
65
+ variant="flat"
70
66
  href="/files/area-sample-data.xlsx"
71
67
  target="_blank"
72
68
  download
@@ -81,10 +77,8 @@
81
77
  <template #activator="{ props: t }">
82
78
  <v-btn
83
79
  v-bind="t"
84
- class="ma-1"
85
- variant="tonal"
86
- size="large"
87
- rounded="pill"
80
+ class="ma-1 screen-icon-btn"
81
+ variant="flat"
88
82
  @click="onImportData"
89
83
  >
90
84
  <v-icon>mdi-upload</v-icon>
@@ -97,10 +91,8 @@
97
91
  <template #activator="{ props: t }">
98
92
  <v-btn
99
93
  v-bind="t"
100
- class="ma-1"
101
- variant="tonal"
102
- size="large"
103
- rounded="pill"
94
+ class="ma-1 screen-icon-btn"
95
+ variant="flat"
104
96
  @click="handleDownloadExcel"
105
97
  :loading="isDownloading"
106
98
  >
@@ -114,7 +106,7 @@
114
106
 
115
107
  <v-spacer />
116
108
 
117
- <v-col cols="auto">
109
+ <v-col cols="auto" class="filter-field">
118
110
  <v-text-field
119
111
  v-model="searchInput"
120
112
  density="compact"
@@ -15,11 +15,12 @@
15
15
  @row-click="handleRowClick"
16
16
  >
17
17
  <template #actions>
18
+ <!-- Was `color="black"`: a black slab in both themes, and the only
19
+ black surface on the page. Same button, the design's ghost. -->
18
20
  <v-btn
19
21
  v-if="canManageAttendanceSettings"
20
- variant="flat"
21
- color="black"
22
- class="text-none"
22
+ variant="outlined"
23
+ class="text-none screen-btn-ghost"
23
24
  @click="dialogShowSettings = true"
24
25
  >
25
26
  <v-icon class="mr-2">mdi-cog</v-icon>