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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,21 @@
1
1
  <template>
2
- <v-card width="100%" :loading="processing">
3
- <v-toolbar>
4
- <v-row
5
- no-gutters
6
- class="fill-height px-6 d-flex justify-space-between align-center"
7
- align="center"
8
- >
9
- <span class="font-weight-bold text-subtitle-1"
10
- >{{ isEditMode ? "Edit" : "Assign" }} Pass &amp; Keys</span
11
- >
12
- <ButtonClose @click="emit('close')" />
13
- </v-row>
14
- </v-toolbar>
15
-
16
- <v-card-text>
17
- <v-row no-gutters class="ga-1">
2
+ <v-card width="100%" class="screen-modal passkey" :loading="processing">
3
+ <div class="passkey__head">
4
+ <v-card-title>{{ isEditMode ? "Edit" : "Assign" }} Pass &amp; Keys</v-card-title>
5
+ <AppButton
6
+ variant="icon"
7
+ icon="mdi-close"
8
+ aria-label="Close"
9
+ @click="emit('close')"
10
+ />
11
+ </div>
12
+
13
+ <div class="screen-modal__body passkey__body">
14
+ <!-- `.filter-field` is the design's input, and it is a descendant
15
+ selector, so both autocompletes get it from this one class. -->
16
+ <v-row no-gutters class="ga-1 filter-field">
18
17
  <v-col cols="12">
19
- <span class="text-subtitle-2 text-medium-emphasis">
20
- Name: {{ prop.visitor.name }}
21
- </span>
18
+ <span class="passkey__name">Name: {{ prop.visitor.name }}</span>
22
19
  </v-col>
23
20
 
24
21
  <!-- Pass section -->
@@ -27,15 +24,11 @@
27
24
 
28
25
  <!-- Existing pass -->
29
26
  <div v-if="existingPass" class="d-flex align-center ga-2 mb-2">
30
- <v-chip
31
- prepend-icon="mdi-card-bulleted-outline"
32
- color="blue"
33
- variant="tonal"
34
- size="small"
35
- >
27
+ <StatusChip tone="info" :dot="false">
28
+ <v-icon icon="mdi-card-bulleted-outline" size="13" />
36
29
  {{ existingPass.prefixAndName }}
37
- </v-chip>
38
- <span class="text-caption text-medium-emphasis">(current)</span>
30
+ </StatusChip>
31
+ <span class="passkey__current">(current)</span>
39
32
  </div>
40
33
 
41
34
  <!-- New pass selector -->
@@ -79,21 +72,26 @@
79
72
  v-if="existingKeys.length > 0"
80
73
  class="d-flex flex-wrap ga-1 mb-2"
81
74
  >
82
- <v-chip
75
+ <!-- The design's tag chip, keeping the remove control the product
76
+ already had: the tone and the shape are the shared ones, only
77
+ the close button is drawn inside rather than by Vuetify. -->
78
+ <StatusChip
83
79
  v-for="key in existingKeys.filter((k) => k.status != 'Removed')"
84
80
  :key="key.keyId"
85
- prepend-icon="mdi-key"
86
- color="orange"
87
- variant="tonal"
88
- size="small"
89
- closable
90
- @click:close="removeExistingKey(key)"
81
+ tone="warn"
82
+ :dot="false"
91
83
  >
84
+ <v-icon icon="mdi-key" size="13" />
92
85
  {{ key.prefixAndName }}
93
- </v-chip>
94
- <span class="text-caption text-medium-emphasis align-self-center">
95
- (current)
96
- </span>
86
+ <button
87
+ class="passkey__remove"
88
+ :aria-label="`Remove ${key.prefixAndName}`"
89
+ @click="removeExistingKey(key)"
90
+ >
91
+ <v-icon icon="mdi-close-circle" size="14" />
92
+ </button>
93
+ </StatusChip>
94
+ <span class="passkey__current align-self-center">(current)</span>
97
95
  </div>
98
96
 
99
97
  <!-- New keys selector -->
@@ -132,41 +130,16 @@
132
130
  </v-row>
133
131
 
134
132
  <v-row v-if="errorMessage" no-gutters class="mt-2">
135
- <p class="text-error text-subtitle-2 w-100 text-center">
136
- {{ errorMessage }}
137
- </p>
138
- </v-row>
139
- </v-card-text>
140
-
141
- <v-toolbar density="compact">
142
- <v-row no-gutters>
143
- <v-col cols="6">
144
- <v-btn
145
- tile
146
- block
147
- variant="text"
148
- class="text-none"
149
- size="48"
150
- text="Cancel"
151
- @click="emit('close')"
152
- />
153
- </v-col>
154
- <v-col cols="6">
155
- <v-btn
156
- tile
157
- block
158
- variant="flat"
159
- color="black"
160
- class="text-none"
161
- size="48"
162
- :text="isEditMode ? 'Save' : 'Assign'"
163
- :disabled="!canSubmit"
164
- :loading="processing"
165
- @click="handleSubmit"
166
- />
167
- </v-col>
133
+ <p class="passkey__error">{{ errorMessage }}</p>
168
134
  </v-row>
169
- </v-toolbar>
135
+ </div>
136
+
137
+ <div class="passkey__footer">
138
+ <AppButton variant="ghost" @click="emit('close')">Cancel</AppButton>
139
+ <AppButton :disabled="!canSubmit" @click="handleSubmit">
140
+ {{ isEditMode ? "Save" : "Assign" }}
141
+ </AppButton>
142
+ </div>
170
143
  </v-card>
171
144
  </template>
172
145
 
@@ -448,4 +421,53 @@ async function handleSubmit() {
448
421
  }
449
422
  </script>
450
423
 
451
- <style scoped></style>
424
+ <style scoped>
425
+ .passkey__head {
426
+ display: flex;
427
+ align-items: center;
428
+ justify-content: space-between;
429
+ padding-right: 12px;
430
+ }
431
+
432
+ .passkey__body {
433
+ padding: 4px 20px 0;
434
+ }
435
+
436
+ .passkey__name,
437
+ .passkey__current {
438
+ font-size: var(--fs-cell);
439
+ color: var(--text2);
440
+ }
441
+
442
+ .passkey__error {
443
+ width: 100%;
444
+ margin: 0;
445
+ text-align: center;
446
+ font-size: var(--fs-cell);
447
+ color: var(--err);
448
+ }
449
+
450
+ /* The chip's own remove control - `currentColor`, so it stays on whatever
451
+ tone the chip carries rather than picking a second colour. */
452
+ .passkey__remove {
453
+ display: inline-flex;
454
+ margin-left: 2px;
455
+ padding: 0;
456
+ border: none;
457
+ background: none;
458
+ color: inherit;
459
+ cursor: pointer;
460
+ opacity: 0.75;
461
+ }
462
+
463
+ .passkey__remove:hover {
464
+ opacity: 1;
465
+ }
466
+
467
+ .passkey__footer {
468
+ display: flex;
469
+ justify-content: flex-end;
470
+ gap: 8px;
471
+ padding: 16px 20px 20px;
472
+ }
473
+ </style>
@@ -1,14 +1,17 @@
1
1
  <template>
2
- <v-card width="100%" :loading="processing">
2
+ <v-card width="100%" class="screen-modal" :loading="processing">
3
3
  <v-toolbar>
4
4
  <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-subtitle-1 text-capitalize">
5
+ <span class="screen-card-title text-capitalize">
6
6
  Preview Announcement
7
7
  </span>
8
8
  </v-row>
9
9
  </v-toolbar>
10
10
 
11
- <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0 my-3">
11
+ <v-card-text
12
+ style="max-height: 100vh; overflow-y: auto"
13
+ class="screen-modal__body pb-0 my-3"
14
+ >
12
15
  <v-row no-gutters>
13
16
  <template v-for="(label, key) in formattedFields" :key="key">
14
17
  <v-col cols="12" class="mb-5">
@@ -39,7 +42,7 @@
39
42
  <v-col class="font-weight-bold">Attachments:</v-col>
40
43
  <v-col cols="9" class="px-10">
41
44
  <template v-if="videosArray.length > 0">
42
- <div class="text-caption text-medium-emphasis mb-1">
45
+ <div class="text-caption screen-hint mb-1">
43
46
  Videos ({{ videosArray.length }})
44
47
  </div>
45
48
  <v-list density="compact" class="pa-0 rounded-lg border mb-3">
@@ -71,7 +74,7 @@
71
74
 
72
75
  <template v-if="photosArray.length > 0">
73
76
  <div
74
- class="text-caption text-medium-emphasis mb-1"
77
+ class="text-caption screen-hint mb-1"
75
78
  :class="{ 'mt-3': videosArray.length > 0 }"
76
79
  >
77
80
  Photos ({{ photosArray.length }})
@@ -110,7 +113,7 @@
110
113
 
111
114
  <template v-if="filesArray.length > 0">
112
115
  <div
113
- class="text-caption text-medium-emphasis mb-1"
116
+ class="text-caption screen-hint mb-1"
114
117
  :class="{
115
118
  'mt-3': videosArray.length > 0 || photosArray.length > 0,
116
119
  }"
@@ -1,57 +1,45 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
3
  <v-col cols="12">
4
- <v-card width="100%" variant="outlined" border="thin" rounded="lg">
5
- <v-toolbar density="compact" color="grey-lighten-4 pl-2 pr-4">
6
- <template #prepend>
7
- <v-btn fab icon density="comfortable" @click="getCameraRefresh()">
8
- <v-icon>mdi-refresh</v-icon>
9
- </v-btn>
10
- </template>
11
-
12
- <template #append>
13
- <v-btn
14
- v-if="!prop.readOnly"
15
- variant="flat"
16
- color="primary"
17
- class="text-none"
18
- @click="dialogAdd = true"
19
- >
4
+ <!-- Two `grey-lighten-4` bars - one above the table, one below - are one
5
+ CardToolbar on the card's own surface. The literal grey stayed white
6
+ on the dark theme's card; measured as four 48px white slabs. -->
7
+ <AppCard class="table-card">
8
+ <CardToolbar :count="pageRange">
9
+ <template #left>
10
+ <AppButton
11
+ variant="icon"
12
+ icon="mdi-refresh"
13
+ aria-label="Refresh"
14
+ @click="getCameraRefresh()"
15
+ />
16
+ <AppButton v-if="!prop.readOnly" @click="dialogAdd = true">
20
17
  Add
21
- </v-btn>
18
+ </AppButton>
22
19
  </template>
23
- </v-toolbar>
24
-
25
- <v-card-text class="pa-0 bg-red">
26
- <v-data-table
27
- :headers="computedHeaders"
28
- :items="items"
29
- fixed-header
30
- hide-default-footer
31
- @click:row="handleRowClick"
32
- style="max-height: 300px"
33
- >
34
- <template #item.host="{ value }">
35
- {{ maskHostUrl(value) }}
36
- </template>
37
- </v-data-table>
38
- </v-card-text>
39
20
 
40
- <v-toolbar density="compact" color="grey-lighten-4">
41
- <template #append>
42
- <v-row no-gutters justify="end" align="center">
43
- <span class="mr-2 text-caption text-font gray">
44
- {{ pageRange }}
45
- </span>
46
- <local-pagination
47
- v-model="page"
48
- :length="pages"
49
- @update:value="$emit('update:value', $event)"
50
- />
51
- </v-row>
21
+ <template #right>
22
+ <local-pagination
23
+ v-model="page"
24
+ :length="pages"
25
+ @update:value="$emit('update:value', $event)"
26
+ />
52
27
  </template>
53
- </v-toolbar>
54
- </v-card>
28
+ </CardToolbar>
29
+
30
+ <v-data-table
31
+ :headers="computedHeaders"
32
+ :items="items"
33
+ fixed-header
34
+ hide-default-footer
35
+ @click:row="handleRowClick"
36
+ style="max-height: 300px"
37
+ >
38
+ <template #item.host="{ value }">
39
+ {{ maskHostUrl(value) }}
40
+ </template>
41
+ </v-data-table>
42
+ </AppCard>
55
43
  </v-col>
56
44
 
57
45
  <!-- dialog -->
@@ -1,22 +1,27 @@
1
1
  <template>
2
2
  <v-row no-gutters class="pa-3">
3
3
  <v-col cols="12">
4
- <v-card width="100%" variant="outlined" border="thin" rounded="lg" max-width="700" max-height="500"
5
- style="overflow-y: auto;">
6
- <v-toolbar density="compact" color="grey-lighten-4 pl-2 pr-4">
7
- <v-row v-if="!hideTitle" no-gutters class="px-3 font-weight-bold">
8
- Delivery Companies
9
- </v-row>
4
+ <AppCard class="delivery-company__card">
5
+ <!--
6
+ The one toolbar the design puts on top of the card. It was a
7
+ hardcoded grey-lighten-4 bar, which stays light in dark mode
8
+ and painted as a white slab there.
9
+ -->
10
+ <CardToolbar>
11
+ <template #left>
12
+ <span v-if="!hideTitle" class="screen-card-title">
13
+ Delivery Companies
14
+ </span>
15
+ </template>
10
16
 
11
- <template #append>
12
- <v-btn v-if="!props.readOnly" variant="flat" color="primary" class="text-none"
13
- @click="handleAdd">
17
+ <template #right>
18
+ <AppButton v-if="!props.readOnly" @click="handleAdd">
14
19
  Add
15
- </v-btn>
20
+ </AppButton>
16
21
  </template>
17
- </v-toolbar>
22
+ </CardToolbar>
18
23
 
19
- <v-card-text class="pa-0" style="min-height: 100px;">
24
+ <div class="pa-0" style="min-height: 100px;">
20
25
  <template v-if="companies.length > 0">
21
26
  <draggable :list="companies" item-key="index" class="drag-area"
22
27
  :disabled="editingIndex !== null">
@@ -60,11 +65,11 @@
60
65
  </draggable>
61
66
  </template>
62
67
 
63
- <div v-else class="text-center py-5">
68
+ <div v-else class="screen-empty text-center py-5">
64
69
  No delivery companies added yet.
65
70
  </div>
66
- </v-card-text>
67
- </v-card>
71
+ </div>
72
+ </AppCard>
68
73
  </v-col>
69
74
 
70
75
  <v-dialog v-model="dialog.add" persistent width="450">
@@ -82,12 +87,13 @@
82
87
  </v-card-text>
83
88
 
84
89
  <v-toolbar>
85
- <v-row no-gutters justify="end" align="center" class="px-5">
86
- <v-btn variant="text" text="Cancel" @click="dialog.add = false" />
87
- <v-btn variant="flat" color="primary" class="text-none ml-2"
88
- @click="saveEdit(companies.length)">
90
+ <v-row no-gutters justify="end" align="center" class="px-5 ga-2">
91
+ <AppButton variant="ghost" @click="dialog.add = false">
92
+ Cancel
93
+ </AppButton>
94
+ <AppButton @click="saveEdit(companies.length)">
89
95
  Add
90
- </v-btn>
96
+ </AppButton>
91
97
  </v-row>
92
98
  </v-toolbar>
93
99
  </v-card>
@@ -237,4 +243,12 @@ watch(initialCompanies, (val) => {
237
243
  .drag-handle {
238
244
  cursor: grab;
239
245
  }
246
+
247
+ /* The size cap the v-card carried as max-width/max-height attributes, kept
248
+ verbatim so the panel does not grow to the full page width. */
249
+ .delivery-company__card {
250
+ max-width: 700px;
251
+ max-height: 500px;
252
+ overflow-y: auto;
253
+ }
240
254
  </style>
@@ -2,37 +2,39 @@
2
2
  <v-row no-gutters class="px-5 pt-4">
3
3
  <!-- Authorities (Police/SCDF/Ambulance called in at the scene) -->
4
4
  <v-col cols="12" class="border-b pb-5 mb-5 mt-1">
5
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
5
+ <p class="screen-card-title mb-2">
6
6
  Authorities (Police/SCDF/Ambulance called in at the scene)?
7
7
  </p>
8
8
  <p
9
9
  v-if="authorities?.authoritiesValue == 'no'"
10
- class="mt-2 text-h6 text-capitalize"
10
+ class="report-answer mt-2 text-capitalize"
11
11
  >
12
12
  {{ authorities?.authoritiesValue }}
13
13
  </p>
14
14
 
15
- <v-data-table
16
- v-else
17
- :headers="authoritiesTableHeader"
18
- :items="authorities?.authoritiesCalled"
19
- fixed-header
20
- hide-default-footer
21
- :hide-default-header="false"
22
- items-per-page="100"
23
- style="max-height: calc(100vh - (200px))"
24
- class="border mt-5"
25
- >
26
- </v-data-table>
15
+ <AppCard v-else class="table-card mt-5">
16
+ <v-data-table
17
+ :headers="authoritiesTableHeader"
18
+ :items="authorities?.authoritiesCalled"
19
+ fixed-header
20
+ hide-default-footer
21
+ :hide-default-header="false"
22
+ items-per-page="100"
23
+ style="max-height: calc(100vh - (200px))"
24
+ >
25
+ </v-data-table>
26
+ </AppCard>
27
27
  </v-col>
28
- <div class="w-100 my-8 mx-2">
29
- <v-table class="responsive-table border elevation-0">
28
+ <!-- `w-100` + `mx-2` made the card 16px wider than its column, so its right
29
+ corner was clipped off. The card supplies its own edge now. -->
30
+ <AppCard class="table-card w-100 my-8">
31
+ <v-table class="responsive-table elevation-0">
30
32
  <thead>
31
33
  <tr>
32
34
  <th
33
35
  v-for="(item, index) in concernHeader"
34
36
  :key="index"
35
- class="text-left font-weight-bold text-black border-e"
37
+ class="text-left border-e"
36
38
  >
37
39
  {{ item }}
38
40
  </th>
@@ -196,10 +198,20 @@
196
198
  </tr>
197
199
  </tbody>
198
200
  </v-table>
199
- </div>
201
+ </AppCard>
200
202
  </v-row>
201
203
  </template>
202
204
 
205
+ <style scoped>
206
+ /* The prototype states no size for a plain answer line, so it takes the body
207
+ row's own type rather than Vuetify's `text-h6`. */
208
+ .report-answer {
209
+ font-size: var(--fs-cell);
210
+ font-weight: var(--fw-cell);
211
+ color: var(--text);
212
+ }
213
+ </style>
214
+
203
215
  <script lang="ts" setup>
204
216
  definePageMeta({
205
217
  middleware: ["01-auth", "02-org"],
@@ -1,9 +1,12 @@
1
1
  <template>
2
2
  <v-row no-gutters class="w-100">
3
- <v-card class="w-100">
4
- <v-card-text>
5
- <v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize"
6
- prepend-icon="mdi-qrcode" @click="handleScanQRPass" />
3
+ <AppCard class="w-100 pa-4">
4
+ <!-- GHOST, not primary: the handoff names "Scan QR Code" in its
5
+ ghost/secondary list, alongside Bulk Upload, Download Report and
6
+ Export. Stays a `v-btn` because `block` has no AppButton
7
+ equivalent, so the design's button is applied as a class. -->
8
+ <v-btn block :height="38" text="Scan QR Code" variant="outlined"
9
+ class="screen-btn-ghost" prepend-icon="mdi-qrcode" @click="handleScanQRPass" />
7
10
  <v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorScanPassMessage = ''">
8
11
  <v-row no-gutters class="pt-5 ga-2">
9
12
  <v-col cols="12">
@@ -81,22 +84,26 @@
81
84
 
82
85
  <v-row class="pt-3" justify="space-between">
83
86
  <v-col cols="6">
84
- <v-btn text="Add Member" :disabled="!validForm" prepend-icon="mdi-plus" color="primary-button"
85
- class="text-capitalize" @click="handleAddMember" />
87
+ <!-- The primitive rather than a class on a v-btn: these two
88
+ need nothing Vuetify owns, and AppButton is the only way
89
+ they draw at the handoff's 40px / 38px. -->
90
+ <AppButton icon="mdi-plus" :disabled="!validForm" @click="handleAddMember">
91
+ Add Member
92
+ </AppButton>
86
93
  </v-col>
87
94
  <v-col cols="6" align="end">
88
- <v-btn text="Clear Form" prepend-icon="mdi-refresh" color="primary-button" class="text-capitalize"
89
- @click.stop="handleClearForm" />
95
+ <AppButton variant="ghost" icon="mdi-refresh" @click.stop="handleClearForm">
96
+ Clear Form
97
+ </AppButton>
90
98
  </v-col>
91
99
  </v-row>
92
100
  </v-row>
93
101
  </v-form>
94
- </v-card-text>
95
- </v-card>
102
+ </AppCard>
96
103
  <visitor-pass-key-q-r-scanner :dialog="dialog.scanQRCode" v-model:scannedValue="scannedPassValue"
97
104
  @close-dialog="dialog.scanQRCode = false" />
98
105
  <v-dialog v-model="dialog.memberAutofillOptions" max-width="430">
99
- <v-card v-if="memberSelectedSearchResult" class="nric-options-card">
106
+ <v-card v-if="memberSelectedSearchResult" class="nric-options-card screen-modal">
100
107
  <v-card-actions class="justify-end pa-1">
101
108
  <v-btn icon="mdi-close" variant="text" size="small" @click="memberCloseAutofillOptions" />
102
109
  </v-card-actions>
@@ -127,10 +134,10 @@
127
134
  </v-card-text>
128
135
 
129
136
  <v-card-actions class="justify-end px-4 pb-4">
130
- <v-btn variant="text" class="text-none" size="small" @click="memberCloseAutofillOptions">
137
+ <v-btn variant="outlined" class="screen-btn-ghost" size="small" @click="memberCloseAutofillOptions">
131
138
  Cancel
132
139
  </v-btn>
133
- <v-btn color="primary-button" variant="flat" class="text-none px-5" size="small"
140
+ <v-btn variant="flat" class="screen-btn-primary px-5" size="small"
134
141
  @click="memberConfirmAutofillOptions">
135
142
  Select
136
143
  </v-btn>
@@ -626,13 +633,13 @@ function handleRemoveMember(index: number) {
626
633
  }
627
634
 
628
635
  .nric-options-title {
629
- color: #06253b;
636
+ color: var(--text);
630
637
  font-size: 1.45rem;
631
638
  line-height: 1.2;
632
639
  }
633
640
 
634
641
  .nric-options-section-title {
635
- color: #06253b;
642
+ color: var(--text);
636
643
  font-size: 1.05rem;
637
644
  line-height: 1.25;
638
645
  }
@@ -640,7 +647,7 @@ function handleRemoveMember(index: number) {
640
647
  .nric-selected-person {
641
648
  border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
642
649
  border-radius: 12px;
643
- color: #06253b;
650
+ color: var(--text);
644
651
  font-size: 0.95rem;
645
652
  min-height: 52px;
646
653
  padding: 10px 14px;