@7365admin1/layer-common 3.2.2-staging.84 → 3.2.2-staging.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/css/screens.css +471 -0
- package/components/AttendanceMain.vue +4 -3
- package/components/CleaningScheduleMain.vue +29 -46
- package/components/HidIntercomManagement.vue +40 -24
- package/components/HidQrCodeConfiguration.vue +24 -2
- package/components/HidUserEnrollment.vue +68 -31
- package/components/InvitationMain.vue +10 -8
- package/components/MemberMain.vue +9 -6
- package/components/MyAttendanceMain.vue +3 -5
- package/components/RolePermissionMain.vue +15 -10
- package/components/ScheduleTaskMain.vue +23 -26
- package/components/ServiceProviderMain.vue +17 -42
- package/components/StatusChip.vue +16 -2
- package/components/TableMain.vue +0 -133
- package/components/VisitorForm.vue +100 -7
- package/components/VisitorManagement.vue +170 -2
- package/composables/useHidAmico.ts +26 -2
- package/composables/useSipWebPhone.ts +28 -3
- package/package.json +1 -1
- package/plugins/vuetify.ts +1 -0
- package/utils/status.test.ts +36 -0
- package/utils/status.ts +36 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 4 - THE MODULE-SCREEN PATTERNS.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 wrote the tokens, Phase 2 the shell, Phase 3 the table card. What is
|
|
5
|
+
* left on a module screen is everything AROUND that table: the filter bar over
|
|
6
|
+
* it, the tabs that switch it, the dialogs it opens, the plain cards and the
|
|
7
|
+
* settings accordion. Those patterns are drawn once, here, and the screens opt
|
|
8
|
+
* in by class name - so a screen's diff is class names and chips, not a block
|
|
9
|
+
* of CSS repeated twenty times.
|
|
10
|
+
*
|
|
11
|
+
* OPT-IN, NOT GLOBAL. Every rule below is under a class this layer's own
|
|
12
|
+
* components add to their own markup. Nothing here selects a bare Vuetify class
|
|
13
|
+
* on its own, so no application page is repainted by a stylesheet it never
|
|
14
|
+
* asked for - the eleven apps get these surfaces through the components they
|
|
15
|
+
* already mount, and their own markup is Phases 6 and 7.
|
|
16
|
+
*
|
|
17
|
+
* VISUAL ONLY. No rule here hides, moves, reorders, disables or reveals a
|
|
18
|
+
* control: everything is colour, type, radius, border, shadow and spacing.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* ------------------------------------------------------------------ */
|
|
22
|
+
/* 1. The filter bar. */
|
|
23
|
+
/* ------------------------------------------------------------------ */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The row of filters that sits over a table. On these screens it is a
|
|
27
|
+
* `v-card flat` with a fixed 60px height and `ga-5`, which on a phone puts
|
|
28
|
+
* three 300px fields on one 60px line and clips them. This is the same fields
|
|
29
|
+
* in the same order, on the design's surface, wrapping instead of clipping.
|
|
30
|
+
*/
|
|
31
|
+
.filter-bar {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--grid-gap);
|
|
36
|
+
width: 100%;
|
|
37
|
+
padding: 10px var(--cellpad-x);
|
|
38
|
+
background: transparent;
|
|
39
|
+
font-family: var(--font-sans);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* 40px, 10px radius, 13.5px - the design's input, whatever Vuetify's density
|
|
43
|
+
would otherwise make of it. `.filter-field` is the same input on its own,
|
|
44
|
+
for the screens whose one search box sits in a toolbar rather than a row. */
|
|
45
|
+
.filter-bar .v-field,
|
|
46
|
+
.filter-field .v-field {
|
|
47
|
+
border-radius: var(--r-inner);
|
|
48
|
+
font-size: var(--fs-cell);
|
|
49
|
+
font-family: var(--font-sans);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.filter-bar .v-field__input,
|
|
53
|
+
.filter-field .v-field__input {
|
|
54
|
+
min-height: var(--input-h);
|
|
55
|
+
font-size: var(--fs-cell);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.filter-bar .v-label,
|
|
59
|
+
.filter-field .v-label,
|
|
60
|
+
.filter-bar .v-field__input input::placeholder,
|
|
61
|
+
.filter-field .v-field__input input::placeholder {
|
|
62
|
+
font-size: var(--fs-cell);
|
|
63
|
+
font-family: var(--font-sans);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* A field is at least readable-wide and then shares what is left. Below 600
|
|
67
|
+
there is no room for two, so they stack - the same rule `.filter-row` uses. */
|
|
68
|
+
.filter-bar > * {
|
|
69
|
+
flex: 1 1 200px;
|
|
70
|
+
min-width: 0;
|
|
71
|
+
max-width: 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.filter-bar > .filter-bar__fit {
|
|
75
|
+
flex: 0 0 auto;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@media (max-width: 599.98px) {
|
|
79
|
+
.filter-bar {
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
align-items: stretch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.filter-bar > * {
|
|
85
|
+
flex: 1 1 auto;
|
|
86
|
+
width: 100%;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ------------------------------------------------------------------ */
|
|
91
|
+
/* 2. Tabs. */
|
|
92
|
+
/* ------------------------------------------------------------------ */
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 13px, the active one at 800 in `--accent-text` over a 2px accent underline.
|
|
96
|
+
* Vuetify's own tab label is uppercase 14px/500 with wide tracking and it puts
|
|
97
|
+
* `text-uppercase` on it as a utility, so these need the same weight of hand
|
|
98
|
+
* the nav items needed in Phase 2.
|
|
99
|
+
*/
|
|
100
|
+
.screen-tabs .v-tab {
|
|
101
|
+
font-family: var(--font-sans);
|
|
102
|
+
font-size: var(--fs-nav) !important;
|
|
103
|
+
font-weight: var(--fw-cell) !important;
|
|
104
|
+
letter-spacing: 0 !important;
|
|
105
|
+
text-transform: none !important;
|
|
106
|
+
color: var(--muted);
|
|
107
|
+
min-width: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.screen-tabs .v-tab.v-tab--selected {
|
|
111
|
+
font-weight: var(--fw-table-head) !important;
|
|
112
|
+
color: var(--accent-text);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.screen-tabs .v-tab .v-tab__slider {
|
|
116
|
+
height: 2px;
|
|
117
|
+
background: var(--accent);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ------------------------------------------------------------------ */
|
|
121
|
+
/* 3. Cards, section headings and empty states. */
|
|
122
|
+
/* ------------------------------------------------------------------ */
|
|
123
|
+
|
|
124
|
+
/** The card contract, for the surfaces that are not the table card. */
|
|
125
|
+
.screen-card {
|
|
126
|
+
font-family: var(--font-sans);
|
|
127
|
+
background: var(--card);
|
|
128
|
+
border: 1px solid var(--border);
|
|
129
|
+
border-radius: var(--r-card);
|
|
130
|
+
box-shadow: var(--shadow);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.screen-title {
|
|
134
|
+
font-family: var(--font-sans);
|
|
135
|
+
font-size: var(--fs-h1);
|
|
136
|
+
font-weight: var(--fw-h1);
|
|
137
|
+
letter-spacing: var(--ls-h1);
|
|
138
|
+
color: var(--text);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.screen-card-title {
|
|
142
|
+
font-family: var(--font-sans);
|
|
143
|
+
font-size: var(--fs-card-title) !important;
|
|
144
|
+
font-weight: var(--fw-card-title) !important;
|
|
145
|
+
letter-spacing: 0 !important;
|
|
146
|
+
color: var(--text);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.screen-empty {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
gap: 8px;
|
|
155
|
+
padding: 52px 16px;
|
|
156
|
+
font-family: var(--font-sans);
|
|
157
|
+
font-size: var(--fs-cell);
|
|
158
|
+
color: var(--muted);
|
|
159
|
+
text-align: center;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.screen-empty .v-icon {
|
|
163
|
+
color: var(--muted);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The one-line explanation under a setting's name. It was
|
|
168
|
+
* `text-grey-darken-1` - a fixed Vuetify grey, i.e. the same dark grey on the
|
|
169
|
+
* dark page - so the sentence that explains a switch was the hardest thing on
|
|
170
|
+
* the screen to read. `--muted` is the design's own, and it is a token, so it
|
|
171
|
+
* follows the theme.
|
|
172
|
+
*/
|
|
173
|
+
.screen-hint {
|
|
174
|
+
font-family: var(--font-sans);
|
|
175
|
+
color: var(--muted);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ------------------------------------------------------------------ */
|
|
179
|
+
/* 4. Dialogs. */
|
|
180
|
+
/* ------------------------------------------------------------------ */
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* THE ONE RULE HERE THAT IS NOT OPT-IN, AND WHY.
|
|
184
|
+
*
|
|
185
|
+
* The product opens its dialogs from more than sixty different form and dialog
|
|
186
|
+
* components, not from the twenty-three module screens. Giving the design's
|
|
187
|
+
* 16px corner to a modal by editing sixty files would be sixty chances to
|
|
188
|
+
* break a dialog for a corner radius. So the SHAPE is stated once, for any
|
|
189
|
+
* card that Vuetify has put inside an overlay - shape and shadow only. It
|
|
190
|
+
* repaints no colour, moves no control and changes no type, and it is the same
|
|
191
|
+
* `.v-overlay__content > .v-card` selector Phase 3 already had to out-specify.
|
|
192
|
+
*/
|
|
193
|
+
.v-overlay__content > .v-card,
|
|
194
|
+
.v-overlay__content > form > .v-card {
|
|
195
|
+
border-radius: var(--r-modal) !important;
|
|
196
|
+
box-shadow: var(--shadow-modal) !important;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* `.v-overlay__content > .v-card` is MORE SPECIFIC than a class on the card
|
|
201
|
+
* (Phase 3 measured a dialog stuck at its factory 4px radius because of it),
|
|
202
|
+
* so the radius has to be `!important` to be reached at all.
|
|
203
|
+
*
|
|
204
|
+
* The body is what scrolls, not the whole card: a modal whose card scrolls
|
|
205
|
+
* takes its Cancel/Submit footer off screen with it. `.screen-modal__body` is
|
|
206
|
+
* only applied where a screen's dialog already has a distinct body element.
|
|
207
|
+
*/
|
|
208
|
+
.screen-modal {
|
|
209
|
+
font-family: var(--font-sans);
|
|
210
|
+
border-radius: var(--r-modal) !important;
|
|
211
|
+
box-shadow: var(--shadow-modal) !important;
|
|
212
|
+
background: var(--card) !important;
|
|
213
|
+
max-height: calc(100vh - 32px);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.screen-modal .v-card-title {
|
|
217
|
+
font-family: var(--font-sans);
|
|
218
|
+
font-size: var(--fs-card-title) !important;
|
|
219
|
+
font-weight: var(--fw-card-title) !important;
|
|
220
|
+
letter-spacing: 0 !important;
|
|
221
|
+
color: var(--text);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.screen-modal__body {
|
|
225
|
+
overflow-y: auto;
|
|
226
|
+
font-size: var(--fs-cell);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* The design's two buttons, for the dialogs and toolbars that draw their own. */
|
|
230
|
+
.screen-btn-primary {
|
|
231
|
+
height: var(--btn-h);
|
|
232
|
+
border-radius: var(--r-inner);
|
|
233
|
+
padding: 0 16px;
|
|
234
|
+
font-family: var(--font-sans);
|
|
235
|
+
font-size: var(--fs-btn);
|
|
236
|
+
font-weight: var(--fw-btn-strong);
|
|
237
|
+
letter-spacing: 0;
|
|
238
|
+
text-transform: none;
|
|
239
|
+
background: var(--accent-strong);
|
|
240
|
+
color: var(--on-accent-strong);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Vuetify paints `variant="tonal"` with an overlay layer ABOVE the button's own
|
|
244
|
+
background, so without this the accent fill shows through a wash. */
|
|
245
|
+
.screen-btn-primary .v-btn__overlay,
|
|
246
|
+
.screen-btn-ghost .v-btn__overlay {
|
|
247
|
+
opacity: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The icon-only actions next to a primary button (download template, import,
|
|
252
|
+
* export). The design draws an icon button as a bordered square the height of
|
|
253
|
+
* the input next to it - the same button Phase 2 gave the top bar - not a
|
|
254
|
+
* tinted pill.
|
|
255
|
+
*/
|
|
256
|
+
.screen-icon-btn {
|
|
257
|
+
height: var(--btn-h);
|
|
258
|
+
width: var(--btn-h);
|
|
259
|
+
min-width: var(--btn-h);
|
|
260
|
+
border-radius: var(--r-inner);
|
|
261
|
+
border: 1px solid var(--border);
|
|
262
|
+
background: var(--card);
|
|
263
|
+
color: var(--text2);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.screen-icon-btn .v-btn__overlay {
|
|
267
|
+
opacity: 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.screen-btn-ghost {
|
|
271
|
+
height: var(--btn-h);
|
|
272
|
+
border-radius: var(--r-inner);
|
|
273
|
+
padding: 0 16px;
|
|
274
|
+
font-family: var(--font-sans);
|
|
275
|
+
font-size: var(--fs-btn);
|
|
276
|
+
font-weight: var(--fw-btn);
|
|
277
|
+
letter-spacing: 0;
|
|
278
|
+
text-transform: none;
|
|
279
|
+
color: var(--text2);
|
|
280
|
+
border-color: var(--border);
|
|
281
|
+
background: var(--card);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* ------------------------------------------------------------------ */
|
|
285
|
+
/* 5. The settings accordion. */
|
|
286
|
+
/* ------------------------------------------------------------------ */
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The design draws settings as a stack of bordered sections with one open at a
|
|
290
|
+
* time. The "one at a time" half is BEHAVIOUR and is left exactly as the panel
|
|
291
|
+
* has it (`multiple`, on the site settings screen); this is the drawn half:
|
|
292
|
+
* the border, the radius, the title type and the open section's surface.
|
|
293
|
+
*/
|
|
294
|
+
.screen-accordion .v-expansion-panel {
|
|
295
|
+
font-family: var(--font-sans);
|
|
296
|
+
background: var(--card);
|
|
297
|
+
border: 1px solid var(--border);
|
|
298
|
+
border-radius: var(--r-card) !important;
|
|
299
|
+
box-shadow: none !important;
|
|
300
|
+
margin-bottom: 10px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.screen-accordion .v-expansion-panel::after {
|
|
304
|
+
display: none;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.screen-accordion .v-expansion-panel-title {
|
|
308
|
+
font-family: var(--font-sans);
|
|
309
|
+
font-size: var(--fs-card-title);
|
|
310
|
+
font-weight: var(--fw-card-title);
|
|
311
|
+
color: var(--text);
|
|
312
|
+
min-height: 56px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.screen-accordion .v-expansion-panel-title--active {
|
|
316
|
+
color: var(--text);
|
|
317
|
+
border-bottom: 1px solid var(--border);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.screen-accordion .v-expansion-panel-text {
|
|
321
|
+
font-size: var(--fs-cell);
|
|
322
|
+
color: var(--text2);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.screen-accordion .v-expansion-panel-title__overlay {
|
|
326
|
+
background: transparent;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* ------------------------------------------------------------------ */
|
|
330
|
+
/* 6. The table card (moved here from TableMain, unchanged). */
|
|
331
|
+
/* ------------------------------------------------------------------ */
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Phase 3 drew this inside `TableMain`'s own scoped block. It is moved here,
|
|
335
|
+
* rule for rule, because it is not TableMain's alone any more: `BillingMain`
|
|
336
|
+
* draws the same card from its own copy of that markup, and a second copy of
|
|
337
|
+
* the CSS is how the two drift apart. `TableMain` still puts the same classes
|
|
338
|
+
* on the same elements - only the stylesheet moved, so nothing it renders
|
|
339
|
+
* changes.
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
/* ------------------------------------------------------------------ */
|
|
343
|
+
/* The card. */
|
|
344
|
+
/* ------------------------------------------------------------------ */
|
|
345
|
+
|
|
346
|
+
.table-card {
|
|
347
|
+
font-family: var(--font-sans);
|
|
348
|
+
background: var(--card);
|
|
349
|
+
border: 1px solid var(--border);
|
|
350
|
+
border-radius: var(--r-card);
|
|
351
|
+
box-shadow: var(--shadow);
|
|
352
|
+
overflow: hidden;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* The toolbar was `color="grey-lighten-4"` - a literal Vuetify grey, which is
|
|
357
|
+
* a near-white bar sitting on a #1c1e24 card in dark mode. It is the card's
|
|
358
|
+
* own surface now, with the design's rule under it.
|
|
359
|
+
*/
|
|
360
|
+
.table-card__toolbar {
|
|
361
|
+
border-bottom: 1px solid var(--border);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.table-card__range {
|
|
365
|
+
font-size: 12px;
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
color: var(--muted);
|
|
368
|
+
font-variant-numeric: tabular-nums;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ------------------------------------------------------------------ */
|
|
372
|
+
/* The header row and the rows under it. */
|
|
373
|
+
/* ------------------------------------------------------------------ */
|
|
374
|
+
|
|
375
|
+
.table-card thead th {
|
|
376
|
+
background: var(--thead) !important;
|
|
377
|
+
color: var(--muted) !important;
|
|
378
|
+
font-size: var(--fs-table-head) !important;
|
|
379
|
+
font-weight: var(--fw-table-head) !important;
|
|
380
|
+
letter-spacing: var(--ls-table-head);
|
|
381
|
+
text-transform: uppercase;
|
|
382
|
+
padding: 10px var(--cellpad-x) !important;
|
|
383
|
+
height: auto !important;
|
|
384
|
+
border-bottom: 1px solid var(--border) !important;
|
|
385
|
+
white-space: nowrap;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.table-card tbody td {
|
|
389
|
+
font-size: var(--fs-cell);
|
|
390
|
+
font-weight: var(--fw-cell);
|
|
391
|
+
color: var(--text);
|
|
392
|
+
padding: var(--cellpad) var(--cellpad-x) !important;
|
|
393
|
+
height: auto !important;
|
|
394
|
+
font-variant-numeric: tabular-nums;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.table-card tbody tr:hover > td {
|
|
398
|
+
background: var(--hover);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* ------------------------------------------------------------------ */
|
|
402
|
+
/* Empty state, in-card search, buttons and in-card tabs. */
|
|
403
|
+
/* ------------------------------------------------------------------ */
|
|
404
|
+
|
|
405
|
+
.table-card__empty {
|
|
406
|
+
display: flex;
|
|
407
|
+
flex-direction: column;
|
|
408
|
+
align-items: center;
|
|
409
|
+
justify-content: center;
|
|
410
|
+
gap: 8px;
|
|
411
|
+
padding: 52px 16px;
|
|
412
|
+
font-size: var(--fs-cell);
|
|
413
|
+
color: var(--muted);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.table-card__empty .v-icon {
|
|
417
|
+
color: var(--muted);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* The in-card variant: 34px, 10px radius, as drawn. */
|
|
421
|
+
.table-card__search .v-field {
|
|
422
|
+
border-radius: var(--r-inner);
|
|
423
|
+
font-size: var(--fs-cell);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.table-card__primary {
|
|
427
|
+
height: var(--btn-h);
|
|
428
|
+
border-radius: var(--r-inner);
|
|
429
|
+
padding: 0 16px;
|
|
430
|
+
font-size: var(--fs-btn);
|
|
431
|
+
font-weight: var(--fw-btn-strong);
|
|
432
|
+
letter-spacing: 0;
|
|
433
|
+
background: var(--accent-strong);
|
|
434
|
+
color: var(--on-accent-strong);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.table-card__ghost {
|
|
438
|
+
height: var(--btn-h);
|
|
439
|
+
border-radius: var(--r-inner);
|
|
440
|
+
padding: 0 16px;
|
|
441
|
+
font-size: var(--fs-btn);
|
|
442
|
+
font-weight: var(--fw-btn);
|
|
443
|
+
letter-spacing: 0;
|
|
444
|
+
color: var(--text2);
|
|
445
|
+
border-color: var(--border);
|
|
446
|
+
background: var(--card);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Tabs that a screen puts in this card's extension slot: 13px, the active one
|
|
451
|
+
* at 800 in `--accent-text` over a 2px accent underline. Tabs elsewhere in the
|
|
452
|
+
* product are their own screens' and are restyled with them.
|
|
453
|
+
*/
|
|
454
|
+
.table-card .v-tab {
|
|
455
|
+
font-size: var(--fs-nav);
|
|
456
|
+
font-weight: var(--fw-cell);
|
|
457
|
+
letter-spacing: 0;
|
|
458
|
+
text-transform: none;
|
|
459
|
+
color: var(--muted);
|
|
460
|
+
min-width: 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.table-card .v-tab.v-tab--selected {
|
|
464
|
+
font-weight: var(--fw-table-head);
|
|
465
|
+
color: var(--accent-text);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.table-card .v-tab .v-tab__slider {
|
|
469
|
+
height: 2px;
|
|
470
|
+
background: var(--accent);
|
|
471
|
+
}
|
|
@@ -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="
|
|
21
|
-
|
|
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>
|
|
@@ -17,11 +17,7 @@
|
|
|
17
17
|
>
|
|
18
18
|
<template #extension>
|
|
19
19
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
20
|
-
<
|
|
21
|
-
class="w-100 px-3 d-flex align-center ga-5 py-2"
|
|
22
|
-
flat
|
|
23
|
-
:height="60"
|
|
24
|
-
>
|
|
20
|
+
<div class="filter-bar">
|
|
25
21
|
<InputDateTimePicker
|
|
26
22
|
v-model:utc="startDate"
|
|
27
23
|
density="compact"
|
|
@@ -47,7 +43,7 @@
|
|
|
47
43
|
item-value="value"
|
|
48
44
|
label="Status"
|
|
49
45
|
/>
|
|
50
|
-
</
|
|
46
|
+
</div>
|
|
51
47
|
</v-row>
|
|
52
48
|
</template>
|
|
53
49
|
|
|
@@ -60,25 +56,23 @@
|
|
|
60
56
|
: ""
|
|
61
57
|
}}
|
|
62
58
|
</template>
|
|
59
|
+
<!--
|
|
60
|
+
A countdown, not a status word, so it passes the tone the screen has
|
|
61
|
+
already worked out from the time left rather than a word to map.
|
|
62
|
+
-->
|
|
63
63
|
<template #item.closeIn="{ item }">
|
|
64
|
-
<
|
|
64
|
+
<StatusChip
|
|
65
65
|
class="text-capitalize"
|
|
66
|
-
|
|
67
|
-
:
|
|
68
|
-
|
|
69
|
-
>
|
|
70
|
-
{{ remainingTime[item._id] || "00h 00m" }}
|
|
71
|
-
</v-chip>
|
|
66
|
+
:tone="getCloseInTone(item._id)"
|
|
67
|
+
:label="remainingTime[item._id] || '00h 00m'"
|
|
68
|
+
/>
|
|
72
69
|
</template>
|
|
73
70
|
<template #item.status="{ value }">
|
|
74
|
-
<
|
|
71
|
+
<StatusChip
|
|
75
72
|
class="text-capitalize"
|
|
76
|
-
:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
>
|
|
80
|
-
{{ value || "No Status" }}
|
|
81
|
-
</v-chip>
|
|
73
|
+
:status="value"
|
|
74
|
+
:label="value || 'No Status'"
|
|
75
|
+
/>
|
|
82
76
|
</template>
|
|
83
77
|
<template #item.completedAt="{ value }">
|
|
84
78
|
{{
|
|
@@ -159,33 +153,22 @@ const downloadingId = ref<string | null>(null);
|
|
|
159
153
|
const { getCleaningSchedules, downloadChecklistPdf } = useCleaningSchedules();
|
|
160
154
|
const { formatDate } = useUtils();
|
|
161
155
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
case "accepted":
|
|
174
|
-
return "success";
|
|
175
|
-
case "closed":
|
|
176
|
-
case "rejected":
|
|
177
|
-
return "error";
|
|
178
|
-
default:
|
|
179
|
-
return "secondary";
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const getCloseInColor = (id: string): string => {
|
|
156
|
+
/**
|
|
157
|
+
* The status word now picks its own colour, from the one shared mapping in
|
|
158
|
+
* `utils/status.ts` - which carries every word this screen used to switch on,
|
|
159
|
+
* with the tone it used to give it. The screen no longer keeps a private
|
|
160
|
+
* palette that only it agrees with.
|
|
161
|
+
*
|
|
162
|
+
* The countdown is not a status word, so the thresholds stay here; only the
|
|
163
|
+
* NAMES change, from Vuetify colours to the design's tones. Same three bands,
|
|
164
|
+
* same boundaries.
|
|
165
|
+
*/
|
|
166
|
+
const getCloseInTone = (id: string): "info" | "warn" | "err" => {
|
|
184
167
|
const secs = remainingSeconds.value[id];
|
|
185
|
-
if (secs === undefined) return "
|
|
186
|
-
if (secs <= 0) return "
|
|
187
|
-
if (secs < 3 * 3600) return "
|
|
188
|
-
return "
|
|
168
|
+
if (secs === undefined) return "info";
|
|
169
|
+
if (secs <= 0) return "err";
|
|
170
|
+
if (secs < 3 * 3600) return "warn";
|
|
171
|
+
return "info";
|
|
189
172
|
};
|
|
190
173
|
|
|
191
174
|
const canDownloadSchedule = computed(() => props.canDownloadSchedule);
|