@7365admin1/layer-common 3.2.2-staging.159 → 3.2.2-staging.161

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.
@@ -398,6 +398,31 @@
398
398
  opacity: 0;
399
399
  }
400
400
 
401
+ /*
402
+ * DISABLED. The two rules above set `background` and `color` unconditionally,
403
+ * and they out-specify the treatment Vuetify would otherwise give a disabled
404
+ * button - so a Submit that a `v-form` has genuinely gated OFF was painting
405
+ * the full accent fill at full opacity, identical to a live one. Measured on
406
+ * `OnlineFormFill` with its required field emptied: enabled and disabled came
407
+ * back byte-identical (`rgb(75, 116, 196)` / `rgb(255, 255, 255)` /
408
+ * `opacity: 1`) on BOTH themes. The gate held; nothing showed it.
409
+ *
410
+ * 0.5 is the design's own disabled step - `.app-btn:disabled` in
411
+ * `primitives.css` uses it - so the two button families dim by the same amount
412
+ * rather than inventing a second number here.
413
+ *
414
+ * Both the attribute and Vuetify's class are named: `:disabled` is what a
415
+ * plain `<button>` carries, `.v-btn--disabled` is what Vuetify adds, and a
416
+ * rule that matched only one of them would go quiet the moment the other path
417
+ * was used.
418
+ */
419
+ .v-btn.screen-btn-primary:disabled,
420
+ .v-btn.screen-btn-primary.v-btn--disabled,
421
+ .v-btn.screen-btn-ghost:disabled,
422
+ .v-btn.screen-btn-ghost.v-btn--disabled {
423
+ opacity: 0.5;
424
+ }
425
+
401
426
  /**
402
427
  * The icon-only actions next to a primary button (download template, import,
403
428
  * export). The design draws an icon button as a bordered square the height of
@@ -1,20 +1,26 @@
1
1
  <template>
2
- <v-card width="100%" style="display: flex; flex-direction: column; max-height: 92vh">
3
- <v-toolbar>
4
- <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-h5">
6
- {{ prop.mode === "edit" ? "Edit" : "New" }} Online Form
7
- </span>
8
- </v-row>
9
- </v-toolbar>
10
-
11
- <v-card-text style="flex: 1; overflow-y: auto; padding: 0">
12
- <v-form v-model="validForm">
2
+ <!-- The card wears `.screen-modal` for the 16px corner, the `--card` surface
3
+ and the modal shadow. Its title row and its Cancel/Create row were both
4
+ `v-toolbar`s - Vuetify `surface` slabs that stay a visible grey bar on
5
+ the dark card - and are now a `v-card-title` and the shared
6
+ `.screen-modal__footer` on the card's own surface. -->
7
+ <v-card class="screen-modal d-flex flex-column" width="100%">
8
+ <v-card-title class="flex-grow-0">
9
+ {{ prop.mode === "edit" ? "Edit" : "New" }} Online Form
10
+ </v-card-title>
11
+
12
+ <v-card-text class="screen-modal__body flex-grow-1 pa-0">
13
+ <!-- `.filter-field` is a DESCENDANT selector (`.filter-field .v-field`),
14
+ so one class on the form gives every input below it the design's
15
+ 40px/10px/13.5px geometry. Not one `AppField` anywhere in here: the
16
+ Create gate runs off these `:rules`, and AppField carries none. -->
17
+ <v-form v-model="validForm" class="filter-field">
13
18
  <div class="pa-4 pb-0">
14
19
 
15
20
  <!-- Form Name -->
16
21
  <div class="mb-3">
17
- <InputLabel title="Form Name" />
22
+ <!-- Create is gated on this being filled, so the label says so. -->
23
+ <InputLabel title="Form Name" :required="prop.mode !== 'edit'" />
18
24
  <v-text-field
19
25
  v-if="prop.mode !== 'edit'"
20
26
  v-model="formName"
@@ -90,13 +96,14 @@
90
96
  <span class="text-subtitle-2 font-weight-bold">Form Blocks</span>
91
97
  <v-menu :close-on-content-click="true">
92
98
  <template #activator="{ props: menuProps }">
99
+ <!-- The design's primary button. `tonal` painted Vuetify's own
100
+ wash over the accent; `.screen-btn-primary` is the 40px
101
+ accent fill the rest of the layer uses. -->
93
102
  <v-btn
94
103
  v-bind="menuProps"
95
- size="small"
96
- color="primary"
97
- variant="tonal"
104
+ variant="flat"
105
+ class="screen-btn-primary"
98
106
  prepend-icon="mdi-plus"
99
- class="text-none"
100
107
  >
101
108
  Add Block
102
109
  </v-btn>
@@ -118,11 +125,14 @@
118
125
  </v-menu>
119
126
  </div>
120
127
 
121
- <!-- Empty state -->
128
+ <!-- Empty state. The dashed edge was a hardcoded `#e0e0e0` - the same
129
+ near-white line on the dark card, where it read as a bright box
130
+ around nothing. `--border` is the design's own and follows the
131
+ theme; the dashed treatment stays, because this is a drop target
132
+ and not a card. -->
122
133
  <div
123
134
  v-if="blocks.length === 0"
124
- class="text-center pa-8 text-medium-emphasis text-caption"
125
- style="border: 2px dashed #e0e0e0; border-radius: 8px"
135
+ class="online-form-config__empty"
126
136
  >
127
137
  No blocks yet. Click "Add Block" to start building your form.
128
138
  </div>
@@ -133,18 +143,25 @@
133
143
  :key="block._uid"
134
144
  class="mb-2"
135
145
  >
136
- <v-card variant="outlined" class="rounded-lg">
146
+ <v-card class="screen-card" flat>
137
147
  <!-- Header row -->
138
148
  <div class="d-flex align-center px-3 py-2 ga-2">
139
- <v-chip
140
- size="x-small"
141
- :color="blockTypeColor(block.type)"
142
- variant="flat"
143
- label
144
- style="min-width: 110px; justify-content: center; flex-shrink: 0"
145
- >
146
- {{ blockTypeLabel(block.type) }}
147
- </v-chip>
149
+ <!-- MEASURED, and the colour coding is deliberately GONE.
150
+ `blockTypeColor` painted eight fixed Vuetify palette fills
151
+ - teal, purple, orange, indigo - the SAME on both themes,
152
+ with white 10px text on them: "Heading" read 4.37:1 and
153
+ "Field" 3.67:1, identically light and dark, which is the
154
+ tell that nothing about them followed the theme. The
155
+ design has one tag, and it is neutral; the tag already
156
+ PRINTS the block type, so the eight colours told the user
157
+ nothing the word did not. Deleting the map is the fix, not
158
+ re-tinting it. -->
159
+ <StatusChip
160
+ :label="blockTypeLabel(block.type)"
161
+ tone="neutral"
162
+ :dot="false"
163
+ class="online-form-config__tag"
164
+ />
148
165
  <span
149
166
  class="text-caption text-medium-emphasis flex-grow-1"
150
167
  style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
@@ -363,54 +380,51 @@
363
380
  </v-form>
364
381
  </v-card-text>
365
382
 
366
- <!-- Action bar -->
367
- <v-toolbar density="compact">
368
- <v-row no-gutters>
369
- <v-col cols="6">
370
- <v-btn tile block variant="text" class="text-none" size="48" @click="emit('cancel')">Cancel</v-btn>
371
- </v-col>
372
- <v-col cols="6">
373
- <v-btn
374
- tile block variant="flat" color="black" class="text-none" size="48"
375
- :disabled="!canSubmit || loading"
376
- :loading="loading"
377
- @click="submit"
378
- >
379
- {{ prop.mode === "edit" ? "Update" : "Create Form" }}
380
- </v-btn>
381
- </v-col>
382
- </v-row>
383
- </v-toolbar>
383
+ <!-- The design's footer: two equal-width buttons, ghost then primary. The
384
+ confirming button was a hardcoded `color="black"` fill, the same black
385
+ on both themes. `loading` keeps it a `v-btn` - AppButton has no
386
+ in-flight state. `:disabled` is unchanged, so the submit gate is
387
+ exactly the gate it was. -->
388
+ <div class="screen-modal__footer flex-grow-0">
389
+ <v-btn variant="outlined" class="screen-btn-ghost" @click="emit('cancel')">Cancel</v-btn>
390
+ <v-btn
391
+ variant="flat"
392
+ class="screen-btn-primary"
393
+ :disabled="!canSubmit || loading"
394
+ :loading="loading"
395
+ @click="submit"
396
+ >
397
+ {{ prop.mode === "edit" ? "Update" : "Create Form" }}
398
+ </v-btn>
399
+ </div>
384
400
 
385
401
  <!-- Template picker dialog -->
386
402
  <v-dialog v-model="showTemplatePicker" width="520" scrollable>
387
- <v-card>
388
- <v-toolbar density="compact">
389
- <span class="pl-4 font-weight-medium text-subtitle-1">Load Template</span>
390
- </v-toolbar>
391
- <div class="pt-3 px-4 pb-2 text-caption text-medium-emphasis" style="white-space: normal">
403
+ <v-card class="screen-modal">
404
+ <v-card-title>Load Template</v-card-title>
405
+ <div class="screen-hint px-6 pb-2" style="white-space: normal">
392
406
  Select a template to pre-populate form blocks. You can customise them after loading.
393
407
  </div>
394
- <v-card-text style="max-height: 55vh; overflow-y: auto; padding: 0">
395
- <v-list density="compact">
408
+ <v-card-text class="screen-modal__body pa-0" style="max-height: 55vh">
409
+ <!-- `bg-color="transparent"` so the list sits on the card's own
410
+ surface rather than Vuetify's `surface` slab. -->
411
+ <v-list density="compact" bg-color="transparent">
396
412
  <v-list-item
397
413
  v-if="sortedTemplateNames.length === 0"
398
414
  disabled
399
415
  title="All templates have already been added for this site"
400
- class="text-caption text-medium-emphasis"
401
416
  />
402
417
  <v-list-item
403
418
  v-for="name in sortedTemplateNames"
404
419
  :key="name"
405
420
  :title="name"
406
- class="text-caption"
407
421
  @click="loadTemplate(name)"
408
422
  />
409
423
  </v-list>
410
424
  </v-card-text>
411
- <v-toolbar density="compact">
412
- <v-btn tile block variant="text" class="text-none" size="48" @click="showTemplatePicker = false">Close</v-btn>
413
- </v-toolbar>
425
+ <div class="screen-modal__footer">
426
+ <v-btn variant="outlined" class="screen-btn-ghost" @click="showTemplatePicker = false">Close</v-btn>
427
+ </div>
414
428
  </v-card>
415
429
  </v-dialog>
416
430
  </v-card>
@@ -528,20 +542,6 @@ function blockTypeLabel(type: string): string {
528
542
  return map[type] ?? type;
529
543
  }
530
544
 
531
- function blockTypeColor(type: string): string {
532
- const map: Record<string, string> = {
533
- "section-header": "blue-grey",
534
- paragraph: "grey",
535
- "ordered-list": "indigo",
536
- "unordered-list": "indigo",
537
- divider: "grey",
538
- field: "teal",
539
- "checkbox-group": "purple",
540
- "signature-row": "orange",
541
- };
542
- return map[type] ?? "grey";
543
- }
544
-
545
545
  function blockSummary(block: AnyBlock): string {
546
546
  switch (block.type) {
547
547
  case "section-header":
@@ -720,3 +720,26 @@ async function submit() {
720
720
  }
721
721
  }
722
722
  </script>
723
+
724
+ <style scoped>
725
+ /* The block builder's drop target. Same copy, same dashed treatment, on the
726
+ design's own border colour and secondary ink so it follows the theme. */
727
+ .online-form-config__empty {
728
+ padding: 32px 16px;
729
+ text-align: center;
730
+ font-family: var(--font-sans);
731
+ font-size: var(--fs-cell);
732
+ color: var(--muted);
733
+ border: 2px dashed var(--border);
734
+ border-radius: var(--r-inner);
735
+ }
736
+
737
+ /* The tags sit in a column, so they keep the fixed width the `v-chip` had -
738
+ otherwise "Divider" and "Numbered List" start their summaries at different
739
+ x positions down the list. */
740
+ .online-form-config__tag {
741
+ min-width: 110px;
742
+ justify-content: center;
743
+ flex-shrink: 0;
744
+ }
745
+ </style>
@@ -1,32 +1,29 @@
1
1
  <template>
2
- <v-card width="100%">
3
- <!-- Title bar -->
4
- <v-toolbar color="grey-darken-3" style="min-height: 56px">
5
- <v-row no-gutters class="fill-height px-6" align="center">
6
- <span
7
- class="font-weight-bold text-subtitle-1 text-white text-uppercase"
8
- style="letter-spacing: 0.5px; line-height: 1.3"
9
- >
10
- {{ definition.title }}
11
- </span>
12
- <v-spacer v-if="$slots['title-actions']" />
13
- <slot name="title-actions" />
14
- </v-row>
15
- </v-toolbar>
2
+ <v-card class="screen-modal" width="100%">
3
+ <!-- The title row was a `grey-darken-3` v-toolbar with white uppercase
4
+ text: the same hardcoded dark slab on both themes. A `v-card-title` on
5
+ `.screen-modal` is the same row on the card's own surface, so it
6
+ follows the theme - and the actions slot keeps its place on the right. -->
7
+ <v-card-title class="d-flex align-center ga-2">
8
+ <span class="flex-grow-1">{{ definition.title }}</span>
9
+ <slot name="title-actions" />
10
+ </v-card-title>
16
11
 
17
12
  <!-- Submission metadata bar (readonly only) -->
18
13
  <div
19
14
  v-if="readonly && (submissionStatus)"
20
- class="px-6 py-2 d-flex align-center ga-4"
21
- style="background: #f5f5f5; border-bottom: 1px solid #e0e0e0; flex-wrap: wrap"
15
+ class="online-form-fill__meta px-6 py-2 d-flex align-center ga-4"
22
16
  >
23
- <v-chip
17
+ <!-- `StatusChip` IS the design's status pill: it maps the WORD to a tone
18
+ through the shared `utils/status.ts`, so "Pending" is the same amber
19
+ here as on a work order, and it carries `--tone-bg` with the ink so
20
+ both halves of the measured pair arrive together. The `v-chip` it
21
+ replaces painted a flat fixed-palette fill under a white label. -->
22
+ <StatusChip
24
23
  v-if="submissionStatus"
25
- :color="submissionStatusColor"
26
- variant="flat"
27
- size="x-small"
24
+ :status="submissionStatus"
28
25
  class="text-capitalize"
29
- >{{ submissionStatus }}</v-chip>
26
+ />
30
27
  <span v-if="(initialValues as any).block" class="text-caption text-medium-emphasis">
31
28
  Block: <strong>{{ (initialValues as any).block }}</strong>
32
29
  </span>
@@ -37,18 +34,14 @@
37
34
  Unit: <strong>{{ submissionUnit }}</strong>
38
35
  </span>
39
36
  <template v-if="requiresPayment">
40
- <v-chip
37
+ <!-- The payment pair is not a status WORD the shared map knows, so the
38
+ tone is given directly - the same pill, the same measured pair. -->
39
+ <StatusChip
41
40
  v-if="initialManagementValues?.paymentStatus"
42
- color="green"
43
- variant="flat"
44
- size="x-small"
45
- >Payment {{ initialManagementValues.paymentStatus }}</v-chip>
46
- <v-chip
47
- v-else
48
- color="orange"
49
- variant="flat"
50
- size="x-small"
51
- >Payment Not Paid</v-chip>
41
+ tone="ok"
42
+ :label="`Payment ${initialManagementValues.paymentStatus}`"
43
+ />
44
+ <StatusChip v-else tone="warn" label="Payment Not Paid" />
52
45
  <span
53
46
  v-if="initialManagementValues?.paymentOrderId"
54
47
  class="text-caption text-medium-emphasis"
@@ -58,17 +51,21 @@
58
51
 
59
52
  <!-- Scrollable body -->
60
53
  <v-card-text
61
- style="max-height: 76vh; overflow-y: auto"
62
- class="pa-6"
54
+ style="max-height: 76vh"
55
+ class="screen-modal__body pa-6"
63
56
  >
64
- <v-form v-model="validForm">
57
+ <!-- `.filter-field` is a DESCENDANT selector (`.filter-field .v-field`),
58
+ so one class on the form gives every input below it the design's
59
+ 40px/10px/13.5px geometry. Every `:rules` below is untouched, and no
60
+ field becomes an `AppField` - AppField carries no rules, and Submit
61
+ is gated on `validForm`, which only exists because these rules run. -->
62
+ <v-form v-model="validForm" class="filter-field">
65
63
  <template v-for="(block, i) in displayBlocks" :key="i">
66
64
 
67
65
  <!-- ADDRESS BLOCK -->
68
66
  <div
69
67
  v-if="block.type === 'address'"
70
- class="mb-4 pa-3"
71
- style="background: #f5f5f5; border-radius: 6px"
68
+ class="online-form-fill__panel mb-4 pa-3"
72
69
  >
73
70
  <v-row no-gutters>
74
71
  <v-col cols="5">
@@ -91,7 +88,11 @@
91
88
  :key="field.key"
92
89
  :cols="field.cols || 6"
93
90
  >
94
- <InputLabel :title="field.label" />
91
+ <!-- `required` was never passed, so the design system's own
92
+ asterisk never drew: a field the form REFUSES to submit
93
+ without looked identical to an optional one. The rule below
94
+ is keyed on the same `field.required`. -->
95
+ <InputLabel :title="field.label" :required="!readonly && !!field.required" />
95
96
  <v-text-field
96
97
  v-if="field.fieldType === 'text'"
97
98
  v-model="values[field.key]"
@@ -137,7 +138,7 @@
137
138
 
138
139
  <!-- SINGLE FULL-WIDTH FIELD -->
139
140
  <div v-else-if="block.type === 'field'" class="mb-0">
140
- <InputLabel :title="block.field.label" />
141
+ <InputLabel :title="block.field.label" :required="!readonly && !!block.field.required" />
141
142
  <v-text-field
142
143
  v-if="block.field.fieldType === 'text'"
143
144
  v-model="values[block.field.key]"
@@ -233,16 +234,12 @@
233
234
  </div>
234
235
 
235
236
  <!-- PARAGRAPH (terms / info text) -->
237
+ <!-- `color: #333` was a near-black on a dark card: the terms text was
238
+ invisible on the dark theme (measured 1.32:1). `--text` is the
239
+ design's own ink and `--border` the design's own rule. -->
236
240
  <p
237
241
  v-else-if="block.type === 'paragraph'"
238
- class="text-body-2 mb-3"
239
- style="
240
- line-height: 1.65;
241
- color: #333;
242
- text-align: justify;
243
- border-left: 3px solid #ddd;
244
- padding-left: 10px;
245
- "
242
+ class="online-form-fill__para text-body-2 mb-3"
246
243
  >
247
244
  {{ resolveText(block.text) }}
248
245
  </p>
@@ -259,7 +256,7 @@
259
256
  v-for="(item, itemIdx) in block.items"
260
257
  :key="itemIdx"
261
258
  class="text-body-2 mb-1"
262
- style="line-height: 1.55; color: #333"
259
+ style="line-height: 1.55; color: var(--text)"
263
260
  >{{ item }}</li>
264
261
  </ol>
265
262
  </div>
@@ -276,7 +273,7 @@
276
273
  v-for="(item, itemIdx) in block.items"
277
274
  :key="itemIdx"
278
275
  class="text-body-2 mb-1"
279
- style="line-height: 1.55; color: #333"
276
+ style="line-height: 1.55; color: var(--text)"
280
277
  >{{ item }}</li>
281
278
  </ul>
282
279
  </div>
@@ -299,6 +296,12 @@
299
296
  class="mt-3 mb-5"
300
297
  >
301
298
  <span class="text-caption text-medium-emphasis d-block mb-1">{{ block.label }}</span>
299
+ <!-- DELIBERATELY NOT TOKENISED. `detectSigBackground` reads the
300
+ saved PNG and decides whether its ink is black or white; this
301
+ plate has to match THAT ink, not the current theme. A white
302
+ signature on a `--card` plate is invisible on the light theme,
303
+ and a black one on the dark theme - which is the whole reason
304
+ the detection exists. -->
302
305
  <div
303
306
  v-if="initialValues[block.key]"
304
307
  :style="{
@@ -317,15 +320,14 @@
317
320
  </div>
318
321
  <div
319
322
  v-else
320
- style="border-bottom: 1px dashed #bbb; height: 56px; background: #fafafa; border-radius: 4px"
323
+ class="online-form-fill__sig-blank"
321
324
  />
322
325
  </div>
323
326
 
324
327
  <!-- ACKNOWLEDGEMENT -->
325
328
  <div
326
329
  v-else-if="block.type === 'acknowledgement'"
327
- class="mb-4 pa-3"
328
- style="background: #f9f9f9; border-radius: 6px; border: 1px solid #eee"
330
+ class="online-form-fill__panel online-form-fill__panel--bordered mb-4 pa-3"
329
331
  >
330
332
  <span class="text-body-2 font-weight-medium">{{ resolveText(block.text) }}</span>
331
333
  </div>
@@ -333,10 +335,10 @@
333
335
  <!-- MANAGEMENT USE -->
334
336
  <div
335
337
  v-else-if="block.type === 'management-use'"
336
- class="pa-4 mt-2"
337
- :style="approvalMode && selectedAction === 'approved'
338
- ? 'background: #e8f5e9; border-radius: 6px; border: 1px solid #a5d6a7'
339
- : 'background: #f0f0f0; border-radius: 6px; opacity: 0.75'"
338
+ class="online-form-fill__panel pa-4 mt-2"
339
+ :class="approvalMode && selectedAction === 'approved'
340
+ ? 'online-form-fill__panel--ok'
341
+ : 'online-form-fill__panel--dim'"
340
342
  >
341
343
  <v-row no-gutters class="mb-2">
342
344
  <span class="text-caption font-weight-bold text-medium-emphasis">
@@ -360,7 +362,7 @@
360
362
  />
361
363
  <div
362
364
  v-else
363
- style="border-bottom: 1px solid #aaa; min-width: 120px; height: 22px; font-size: 13px; padding-top: 2px"
365
+ class="online-form-fill__mgmt-line"
364
366
  >{{ managementValues[item] || '' }}</div>
365
367
  </div>
366
368
  </v-row>
@@ -378,7 +380,7 @@
378
380
  <!-- Approval action panel -->
379
381
  <div
380
382
  v-if="readonly && approvalMode"
381
- style="border-top: 1px solid #e0e0e0; padding: 12px 16px; background: #fafafa; display: flex; flex-direction: column; gap: 10px"
383
+ class="online-form-fill__approve"
382
384
  >
383
385
  <v-text-field
384
386
  v-model="adminRemarks"
@@ -389,35 +391,35 @@
389
391
  :error="remarksRequired && adminRemarks.trim().length === 0"
390
392
  :error-messages="remarksRequired && adminRemarks.trim().length === 0 ? 'Remarks are required' : ''"
391
393
  />
392
- <div class="d-flex align-center ga-2 justify-end">
393
- <v-btn variant="text" class="text-none" size="small" @click="emit('cancel')">Close</v-btn>
394
+ <!-- The three verdicts keep their colour coding - it is what tells them
395
+ apart - but on the theme's own status colours rather than fixed
396
+ palette entries, so both the tonal and the flat state follow the
397
+ theme. Confirm was a hardcoded `grey-darken-3` fill and is now the
398
+ design's primary button, which is what it is. -->
399
+ <div class="d-flex align-center ga-2 justify-end flex-wrap">
400
+ <v-btn variant="outlined" class="screen-btn-ghost" @click="emit('cancel')">Close</v-btn>
394
401
  <v-btn
395
402
  :variant="selectedAction === 'resubmission' ? 'flat' : 'tonal'"
396
- color="orange-darken-1"
403
+ color="warning"
397
404
  class="text-none"
398
- size="small"
399
405
  @click="selectAction('resubmission')"
400
406
  >Resubmit</v-btn>
401
407
  <v-btn
402
408
  :variant="selectedAction === 'rejected' ? 'flat' : 'tonal'"
403
- color="red"
409
+ color="error"
404
410
  class="text-none"
405
- size="small"
406
411
  @click="selectAction('rejected')"
407
412
  >Reject</v-btn>
408
413
  <v-btn
409
414
  :variant="selectedAction === 'approved' ? 'flat' : 'tonal'"
410
- color="green"
415
+ color="success"
411
416
  class="text-none"
412
- size="small"
413
417
  @click="selectAction('approved')"
414
418
  >Approve</v-btn>
415
419
  <v-btn
416
420
  v-if="selectedAction"
417
421
  variant="flat"
418
- color="grey-darken-3"
419
- class="text-none"
420
- size="small"
422
+ class="screen-btn-primary"
421
423
  :disabled="!canConfirm"
422
424
  :loading="updatingStatus"
423
425
  @click="confirmStatusUpdate"
@@ -425,24 +427,25 @@
425
427
  </div>
426
428
  </div>
427
429
 
428
- <!-- Normal action bar -->
429
- <v-toolbar v-else density="compact">
430
- <v-row no-gutters>
431
- <template v-if="readonly">
432
- <v-col cols="12">
433
- <v-btn tile block variant="text" class="text-none" size="48" @click="emit('cancel')">Close</v-btn>
434
- </v-col>
435
- </template>
436
- <template v-else>
437
- <v-col cols="6">
438
- <v-btn tile block variant="text" class="text-none" size="48" :disabled="submitting" @click="emit('cancel')">Cancel</v-btn>
439
- </v-col>
440
- <v-col cols="6">
441
- <v-btn tile block variant="flat" color="black" class="text-none" size="48" :disabled="!validForm || submitting" :loading="submitting" @click="submit">Submit</v-btn>
442
- </v-col>
443
- </template>
444
- </v-row>
445
- </v-toolbar>
430
+ <!-- The design's footer: equal-width buttons, ghost then primary, on a
431
+ bordered strip. Submit was a hardcoded `color="black"` block fill. Its
432
+ `:disabled="!validForm || submitting"` is untouched - the gate is still
433
+ the form's own validity. -->
434
+ <div v-else class="screen-modal__footer">
435
+ <template v-if="readonly">
436
+ <v-btn variant="outlined" class="screen-btn-ghost" @click="emit('cancel')">Close</v-btn>
437
+ </template>
438
+ <template v-else>
439
+ <v-btn variant="outlined" class="screen-btn-ghost" :disabled="submitting" @click="emit('cancel')">Cancel</v-btn>
440
+ <v-btn
441
+ variant="flat"
442
+ class="screen-btn-primary"
443
+ :disabled="!validForm || submitting"
444
+ :loading="submitting"
445
+ @click="submit"
446
+ >Submit</v-btn>
447
+ </template>
448
+ </div>
446
449
  </v-card>
447
450
  </template>
448
451
  <script setup lang="ts">
@@ -621,15 +624,10 @@ function confirmStatusUpdate() {
621
624
 
622
625
  const requiredRule = (v: any) => (v !== null && v !== undefined && v !== "") || "This field is required";
623
626
 
624
- const submissionStatusColor = computed(() => {
625
- switch (props.submissionStatus?.toLowerCase()) {
626
- case "approved": return "green";
627
- case "pending": return "orange";
628
- case "resubmission": return "orange";
629
- case "rejected": return "red";
630
- default: return "grey";
631
- }
632
- });
627
+ // The local status -> colour switch this file carried is gone: `StatusChip`
628
+ // maps the word through the shared `utils/status.ts`, which already had
629
+ // approved/pending/rejected and now has resubmission too. One mapping, not a
630
+ // second private copy that could drift from it.
633
631
 
634
632
  const requiresPayment = computed(() => {
635
633
  if (props.paymentAmount <= 0) return false;
@@ -771,3 +769,74 @@ async function submit() {
771
769
  }
772
770
  }
773
771
  </script>
772
+
773
+ <style scoped>
774
+ /*
775
+ * Every surface below was a hardcoded near-white hex - `#f5f5f5`, `#f9f9f9`,
776
+ * `#f0f0f0`, `#fafafa` - with THEMED text on top of it. On the dark theme that
777
+ * put the design's near-white ink on a near-white plate: the address strip read
778
+ * 1.05:1, the acknowledgement 1.08:1 and the management panel 1:1, i.e. text
779
+ * that was there and could not be seen. `--bg` is the design's own recessed
780
+ * surface and follows the theme, so the same rows read correctly on both.
781
+ */
782
+ .online-form-fill__meta {
783
+ flex-wrap: wrap;
784
+ background: var(--bg);
785
+ border-bottom: 1px solid var(--border);
786
+ }
787
+
788
+ .online-form-fill__panel {
789
+ background: var(--bg);
790
+ border-radius: var(--r-inner);
791
+ }
792
+
793
+ .online-form-fill__panel--bordered {
794
+ border: 1px solid var(--border);
795
+ }
796
+
797
+ /* The approved state of the management panel. `--ok-bg` / `--ok` are the
798
+ design's own success pair; it was `#e8f5e9` on `#a5d6a7`, the same greens on
799
+ both themes. */
800
+ .online-form-fill__panel--ok {
801
+ background: var(--ok-bg);
802
+ border: 1px solid var(--ok);
803
+ }
804
+
805
+ .online-form-fill__panel--dim {
806
+ opacity: 0.75;
807
+ }
808
+
809
+ .online-form-fill__para {
810
+ line-height: 1.65;
811
+ color: var(--text);
812
+ text-align: justify;
813
+ border-left: 3px solid var(--border);
814
+ padding-left: 10px;
815
+ }
816
+
817
+ /* The ruled line a management entry is written on, and the empty signature
818
+ plate - both were fixed greys. */
819
+ .online-form-fill__mgmt-line {
820
+ border-bottom: 1px solid var(--border);
821
+ min-width: 120px;
822
+ height: 22px;
823
+ font-size: var(--fs-cell);
824
+ padding-top: 2px;
825
+ }
826
+
827
+ .online-form-fill__sig-blank {
828
+ border-bottom: 1px dashed var(--border);
829
+ height: 56px;
830
+ background: var(--bg);
831
+ border-radius: var(--r-inner-sm);
832
+ }
833
+
834
+ .online-form-fill__approve {
835
+ display: flex;
836
+ flex-direction: column;
837
+ gap: 10px;
838
+ padding: 12px 16px;
839
+ background: var(--bg);
840
+ border-top: 1px solid var(--border);
841
+ }
842
+ </style>
@@ -5,25 +5,25 @@
5
5
  width="560"
6
6
  persistent
7
7
  >
8
- <v-card max-height="85vh" class="d-flex flex-column">
9
- <v-toolbar color="grey-darken-3" class="flex-grow-0">
10
- <v-toolbar-title class="text-subtitle-1 font-weight-bold text-white text-uppercase">
11
- {{ form?.formType }}
12
- </v-toolbar-title>
13
- </v-toolbar>
8
+ <!-- The title row was a `grey-darken-3` v-toolbar: the same hardcoded grey
9
+ slab on both themes, with white text sitting on it. A `v-card-title` on
10
+ `.screen-modal` is the same row on the card's own surface, so it
11
+ follows the theme. -->
12
+ <v-card class="screen-modal d-flex flex-column">
13
+ <v-card-title class="flex-grow-0">{{ form?.formType }}</v-card-title>
14
14
 
15
- <v-card-subtitle class="px-6 pt-3 pb-0 text-caption text-medium-emphasis flex-grow-0">
15
+ <v-card-subtitle class="screen-hint px-6 pb-0 flex-grow-0">
16
16
  Form preview — fields are read-only
17
17
  </v-card-subtitle>
18
18
 
19
- <v-card-text class="px-6 py-4 flex-grow-1" style="overflow-y: auto">
19
+ <v-card-text class="screen-modal__body px-6 py-4 flex-grow-1">
20
20
  <template v-for="(input, index) in form?.inputs" :key="index">
21
21
  <v-text-field
22
22
  v-if="input.dataType === 'Text Input'"
23
23
  :label="input.label"
24
24
  density="comfortable"
25
25
  variant="outlined"
26
- class="mb-1"
26
+ class="filter-field mb-1"
27
27
  :hint="input.required ? 'Required' : ''"
28
28
  persistent-hint
29
29
  readonly
@@ -34,7 +34,7 @@
34
34
  density="comfortable"
35
35
  variant="outlined"
36
36
  rows="3"
37
- class="mb-1"
37
+ class="filter-field mb-1"
38
38
  :hint="input.required ? 'Required' : ''"
39
39
  persistent-hint
40
40
  readonly
@@ -45,7 +45,7 @@
45
45
  type="date"
46
46
  density="comfortable"
47
47
  variant="outlined"
48
- class="mb-1"
48
+ class="filter-field mb-1"
49
49
  :hint="input.required ? 'Required' : ''"
50
50
  persistent-hint
51
51
  readonly
@@ -56,7 +56,7 @@
56
56
  :items="input.options || []"
57
57
  density="comfortable"
58
58
  variant="outlined"
59
- class="mb-1"
59
+ class="filter-field mb-1"
60
60
  :hint="input.required ? 'Required' : ''"
61
61
  persistent-hint
62
62
  readonly
@@ -65,29 +65,24 @@
65
65
  v-else-if="input.dataType === 'Checkbox'"
66
66
  :label="input.label"
67
67
  density="comfortable"
68
- class="mb-1"
68
+ class="filter-field mb-1"
69
69
  readonly
70
70
  />
71
71
  </template>
72
72
  </v-card-text>
73
73
 
74
- <v-toolbar density="compact" class="flex-grow-0">
75
- <v-row no-gutters>
76
- <v-col cols="12">
77
- <v-btn
78
- tile
79
- block
80
- variant="flat"
81
- color="black"
82
- class="text-none"
83
- height="48"
84
- @click="$emit('update:modelValue', false)"
85
- >
86
- Close Preview
87
- </v-btn>
88
- </v-col>
89
- </v-row>
90
- </v-toolbar>
74
+ <!-- The design's modal footer. The Close button was a `color="black"`
75
+ square-cornered block fill - the same black on both themes, and the
76
+ shape of a primary action for what is only a dismiss. -->
77
+ <div class="screen-modal__footer flex-grow-0">
78
+ <v-btn
79
+ variant="outlined"
80
+ class="screen-btn-ghost"
81
+ @click="$emit('update:modelValue', false)"
82
+ >
83
+ Close Preview
84
+ </v-btn>
85
+ </div>
91
86
  </v-card>
92
87
  </v-dialog>
93
88
  </template>
@@ -22,24 +22,23 @@
22
22
  <AppCard class="table-card">
23
23
  <CardToolbar :count="pageRange">
24
24
  <template #left>
25
- <v-btn
26
- fab
27
- icon
28
- density="comfortable"
25
+ <!-- The design's 32px icon button, the same one every other
26
+ converted table toolbar uses, instead of Vuetify's tinted
27
+ fab. -->
28
+ <AppButton
29
+ variant="icon"
30
+ icon="mdi-refresh"
31
+ aria-label="Refresh"
29
32
  @click="getOnlineFormConfigurations"
30
- >
31
- <v-icon>mdi-refresh</v-icon>
32
- </v-btn>
33
- <v-btn
34
- fab
35
- icon
36
- density="comfortable"
37
- :color="sortAlpha ? 'primary' : undefined"
33
+ />
34
+ <AppButton
35
+ variant="icon"
36
+ icon="mdi-sort-alphabetical-ascending"
37
+ class="online-forms-config__sort"
38
+ :class="{ 'online-forms-config__sort--on': sortAlpha }"
38
39
  :title="sortAlpha ? 'Sorted A–Z' : 'Sort A–Z'"
39
40
  @click="sortAlpha = !sortAlpha"
40
- >
41
- <v-icon>mdi-sort-alphabetical-ascending</v-icon>
42
- </v-btn>
41
+ />
43
42
  <AppField
44
43
  v-model="search"
45
44
  search
@@ -109,12 +108,15 @@
109
108
  @cancel="previewDialog = false"
110
109
  >
111
110
  <template #title-actions>
111
+ <!-- `color="white"` was correct only while the fill's title bar was a
112
+ hardcoded dark slab. The bar now paints from `--card`, so the
113
+ pencil takes the card's own ink; `error` is a theme colour and
114
+ follows both themes. -->
112
115
  <v-btn
113
116
  v-if="canUpdate && canUpdateOnlineFormConfiguration"
114
117
  icon
115
118
  size="small"
116
119
  variant="text"
117
- color="white"
118
120
  @click="openEditDialog()"
119
121
  >
120
122
  <v-icon>mdi-pencil</v-icon>
@@ -133,34 +135,38 @@
133
135
  </OnlineFormFill>
134
136
 
135
137
  <!-- Fallback: no block definition found for this form type -->
136
- <v-card v-else width="100%">
137
- <v-card-text class="text-center pa-10">
138
- <v-icon size="48" class="mb-3 text-medium-emphasis">mdi-file-question-outline</v-icon>
139
- <div class="text-body-2 text-medium-emphasis">
140
- No preview available for "{{ selectedFormType }}".
141
- </div>
142
- </v-card-text>
143
- <v-toolbar density="compact">
144
- <v-btn tile block variant="text" class="text-none" size="48" @click="previewDialog = false">
138
+ <v-card v-else class="screen-modal" width="100%">
139
+ <!-- The design's empty block, the same one every other "nothing to
140
+ show here" surface in the layer draws. -->
141
+ <div class="screen-empty">
142
+ <v-icon size="48">mdi-file-question-outline</v-icon>
143
+ <div>No preview available for "{{ selectedFormType }}".</div>
144
+ </div>
145
+ <div class="screen-modal__footer">
146
+ <v-btn variant="outlined" class="screen-btn-ghost" @click="previewDialog = false">
145
147
  Close
146
148
  </v-btn>
147
- </v-toolbar>
149
+ </div>
148
150
  </v-card>
149
151
  </v-dialog>
150
152
 
151
153
  <!-- Delete Dialog -->
154
+ <!-- 520px is the handoff's `--modal-w`. The two `v-toolbar`s this drew for
155
+ its title row and its footer were Vuetify `surface` slabs - a visible
156
+ grey bar on the dark card; a `v-card-title` and the shared
157
+ `.screen-modal__footer` are the same rows on the card's own surface.
158
+ The confirming button was a hardcoded `color="black"` fill, the same
159
+ black on both themes. -->
152
160
  <v-dialog
153
161
  v-model="confirmDialog"
154
162
  :loading="deleteLoading"
155
- width="450"
163
+ width="520"
156
164
  persistent
157
165
  >
158
- <v-card width="100%">
159
- <v-toolbar density="compact" class="pl-4">
160
- <span class="font-weight-medium text-h5">Delete Online Form</span>
161
- </v-toolbar>
162
- <v-card-text>
163
- <p class="text-subtitle-2 text-center">
166
+ <v-card class="screen-modal" width="100%">
167
+ <v-card-title>Delete Online Form</v-card-title>
168
+ <v-card-text class="screen-modal__body">
169
+ <p class="text-center">
164
170
  Are you sure you want to delete this online form? This action cannot
165
171
  be undone.
166
172
  </p>
@@ -171,38 +177,25 @@
171
177
  </span>
172
178
  </v-row>
173
179
  </v-card-text>
174
- <v-toolbar density="compact">
175
- <v-row no-gutters>
176
- <v-col cols="6">
177
- <v-btn
178
- tile
179
- block
180
- size="48"
181
- variant="text"
182
- class="text-none"
183
- @click="confirmDialog = false"
184
- :disabled="deleteLoading"
185
- >
186
- Close
187
- </v-btn>
188
- </v-col>
189
- <v-col cols="6">
190
- <v-btn
191
- tile
192
- block
193
- size="48"
194
- color="black"
195
- variant="flat"
196
- class="text-none"
197
- :loading="deleteLoading"
198
- :disabled="deleteLoading"
199
- @click="handleDeleteOnlineFormConfiguration()"
200
- >
201
- Delete Online Form
202
- </v-btn>
203
- </v-col>
204
- </v-row>
205
- </v-toolbar>
180
+ <div class="screen-modal__footer">
181
+ <v-btn
182
+ variant="outlined"
183
+ class="screen-btn-ghost"
184
+ :disabled="deleteLoading"
185
+ @click="confirmDialog = false"
186
+ >
187
+ Close
188
+ </v-btn>
189
+ <v-btn
190
+ variant="flat"
191
+ class="screen-btn-primary"
192
+ :loading="deleteLoading"
193
+ :disabled="deleteLoading"
194
+ @click="handleDeleteOnlineFormConfiguration()"
195
+ >
196
+ Delete Online Form
197
+ </v-btn>
198
+ </div>
206
199
  </v-card>
207
200
  </v-dialog>
208
201
 
@@ -433,4 +426,12 @@ function successUpdate() {
433
426
  .online-forms-config__search {
434
427
  max-width: 240px;
435
428
  }
429
+
430
+ /* The sort toggle is the only control on this toolbar with an on/off state.
431
+ `--accent-strong` is the design's own accent; the icon button has no
432
+ "selected" variant, so the state is one declaration rather than a variant
433
+ nothing else would use. */
434
+ .online-forms-config__sort--on {
435
+ color: var(--accent-strong);
436
+ }
436
437
  </style>
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.159",
5
+ "version": "3.2.2-staging.161",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
package/utils/status.ts CHANGED
@@ -58,6 +58,11 @@ const TONES: Record<string, TStatusTone> = {
58
58
  resolved: "ok", // was #4CAF50, green
59
59
  active: "ok", // was `success`
60
60
 
61
+ // An online-form submission sent back to the resident to correct and send
62
+ // again. Unlisted it would come out neutral, which would make "needs your
63
+ // attention" look the same as "nothing is happening".
64
+ resubmission: "warn",
65
+
61
66
  assigned: "info", // was `primary`, on the access-card details dialog
62
67
  replaced: "warn", // was `orange`, same screen
63
68
  returned: "warn", // was `orange`, same screen - the card came back