@7365admin1/layer-common 3.2.2-staging.93 → 3.2.2-staging.95
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 +14 -0
- package/components/Avatar/Main.vue +41 -1
- package/components/BillingMain.vue +25 -27
- package/components/EntryPassMain.vue +46 -24
- package/components/EquipmentItemMain.vue +1 -1
- package/components/EquipmentManagementMain.vue +1 -1
- package/components/RolePermissionMain.vue +44 -49
- package/components/ScheduleTastTicketMain.vue +12 -20
- package/components/TableMain.vue +65 -29
- package/package.json +1 -1
package/assets/css/screens.css
CHANGED
|
@@ -361,6 +361,20 @@
|
|
|
361
361
|
border-bottom: 1px solid var(--border);
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
/**
|
|
365
|
+
* The callers' filter row (`#extension`). It was a `v-toolbar` extension, i.e.
|
|
366
|
+
* a fixed-height second bar; it is now a plain row under the toolbar with the
|
|
367
|
+
* same rule under it, so it can size to whatever the caller puts in it.
|
|
368
|
+
*/
|
|
369
|
+
.table-card__extension {
|
|
370
|
+
border-bottom: 1px solid var(--border);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.table-card__loading {
|
|
374
|
+
position: absolute;
|
|
375
|
+
z-index: 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
364
378
|
.table-card__range {
|
|
365
379
|
font-size: 12px;
|
|
366
380
|
font-weight: 600;
|
|
@@ -55,6 +55,39 @@ const initials = computed(() => {
|
|
|
55
55
|
|
|
56
56
|
const fontSize = computed(() => Math.round(props.size * 0.5))
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* WHITE INITIALS ON A HASHED COLOUR - THE ESTATE'S WORST CONTRAST DEFECT.
|
|
60
|
+
*
|
|
61
|
+
* The circle's fill is derived from the name, and Vuetify then picks a WHITE
|
|
62
|
+
* label for it. Measured across the whole space this generator can produce
|
|
63
|
+
* (360 hues x 20 saturations x 15 lightnesses): **70.6% of names fail AA**,
|
|
64
|
+
* and the worst - a yellow, `hsl(60,79%,59%)` - reads **1.30:1**. The 3.12:1
|
|
65
|
+
* seen on one screen was a mild sample, not the floor.
|
|
66
|
+
*
|
|
67
|
+
* The name's colour is its identity, so the HUE and SATURATION are kept
|
|
68
|
+
* exactly; only the lightness is walked down until white clears 4.5:1 on it.
|
|
69
|
+
* A yellow therefore darkens a long way and a blue barely moves, which is what
|
|
70
|
+
* makes a single fixed lightness impossible here.
|
|
71
|
+
*/
|
|
72
|
+
const AA = 4.5
|
|
73
|
+
|
|
74
|
+
const srgb = (c: number) => {
|
|
75
|
+
const v = c / 255
|
|
76
|
+
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Relative luminance of an `hsl()` triple, without going through the DOM. */
|
|
80
|
+
function luminance(h: number, s: number, l: number): number {
|
|
81
|
+
const sat = s / 100
|
|
82
|
+
const light = l / 100
|
|
83
|
+
const a = sat * Math.min(light, 1 - light)
|
|
84
|
+
const f = (n: number) => {
|
|
85
|
+
const k = (n + h / 30) % 12
|
|
86
|
+
return light - a * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)))
|
|
87
|
+
}
|
|
88
|
+
return 0.2126 * srgb(f(0) * 255) + 0.7152 * srgb(f(8) * 255) + 0.0722 * srgb(f(4) * 255)
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
function getColor(str?: string): string {
|
|
59
92
|
if(!str) return "grey"
|
|
60
93
|
|
|
@@ -66,7 +99,14 @@ function getColor(str?: string): string {
|
|
|
66
99
|
|
|
67
100
|
const hue = Math.abs(hash) % 360
|
|
68
101
|
const saturation = 60 + (Math.abs((hash >> 3) % 20)) // 60–80%
|
|
69
|
-
|
|
102
|
+
let lightness = 45 + (Math.abs((hash >> 5) % 15)) // 45–60%
|
|
103
|
+
|
|
104
|
+
// ponytail: linear 1% walk, not a solve - the range is at most 45 steps and
|
|
105
|
+
// this runs once per avatar. Lightness bottoms out at 0, where every hue is
|
|
106
|
+
// black and white is 21:1, so the loop always terminates on a pass.
|
|
107
|
+
while (lightness > 0 && 1.05 / (luminance(hue, saturation, lightness) + 0.05) < AA) {
|
|
108
|
+
lightness -= 1
|
|
109
|
+
}
|
|
70
110
|
|
|
71
111
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
|
72
112
|
}
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
</v-toolbar>
|
|
2
|
+
<div>
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S PAGE SCAFFOLD (same shape as Service Providers).
|
|
5
|
+
|
|
6
|
+
Placement only: the same refresh control, the same range text, the same
|
|
7
|
+
pager bound to the same `page`, and the table, its columns and its cell
|
|
8
|
+
templates are untouched. The screen simply gains the page title it has
|
|
9
|
+
never had, and its card + toolbar are the design's rather than Vuetify's.
|
|
10
|
+
-->
|
|
11
|
+
<PageHeader title="Billing" />
|
|
12
|
+
|
|
13
|
+
<AppCard class="table-card">
|
|
14
|
+
<CardToolbar :count="pageRange">
|
|
15
|
+
<template #left>
|
|
16
|
+
<AppButton variant="icon" icon="mdi-refresh" aria-label="Refresh" />
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<template #right>
|
|
20
|
+
<local-pagination v-model="page" :length="pages" />
|
|
21
|
+
</template>
|
|
22
|
+
</CardToolbar>
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
<v-data-table
|
|
26
25
|
:headers="headers"
|
|
27
26
|
:items="items"
|
|
28
27
|
item-value="_id"
|
|
@@ -44,10 +43,9 @@
|
|
|
44
43
|
{{ item.nature }}
|
|
45
44
|
</span>
|
|
46
45
|
</template>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</v-row>
|
|
46
|
+
</v-data-table>
|
|
47
|
+
</AppCard>
|
|
48
|
+
</div>
|
|
51
49
|
</template>
|
|
52
50
|
|
|
53
51
|
<script setup lang="ts">
|
|
@@ -5,28 +5,41 @@
|
|
|
5
5
|
:height="'200px'"
|
|
6
6
|
v-if="loading"
|
|
7
7
|
/>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
<div v-if="!loading">
|
|
9
|
+
<!--
|
|
10
|
+
THE DESIGN'S CARD + TAB ROW - AND DELIBERATELY NO `PageHeader`.
|
|
11
|
+
|
|
12
|
+
This screen owns no table; it is a settings screen of switches and fields,
|
|
13
|
+
so it takes `AppCard` + `CardToolbar`'s tabs row and no pager, count or
|
|
14
|
+
refresh, because it has never had any.
|
|
15
|
+
|
|
16
|
+
It gets NO page title, unlike the other screens in this pass: its only
|
|
17
|
+
caller mounts it INSIDE a `v-expansion-panel` on property-management's
|
|
18
|
+
Settings page whose panel header already reads "Entry Pass". A
|
|
19
|
+
`PageHeader` here would print that title twice.
|
|
20
|
+
|
|
21
|
+
The same four tabs switch the same `v-window` on the same `activeTab`,
|
|
22
|
+
keeping their icons; every switch, field, button and save handler is
|
|
23
|
+
untouched.
|
|
24
|
+
-->
|
|
25
|
+
<AppCard class="screen-card">
|
|
26
|
+
<CardToolbar>
|
|
27
|
+
<template #tabs>
|
|
28
|
+
<button
|
|
29
|
+
v-for="tab in entryPassTabs"
|
|
30
|
+
:key="tab.value"
|
|
31
|
+
type="button"
|
|
32
|
+
role="tab"
|
|
33
|
+
class="app-tab"
|
|
34
|
+
:class="{ 'app-tab--active': activeTab === tab.value }"
|
|
35
|
+
:aria-selected="activeTab === tab.value"
|
|
36
|
+
@click="activeTab = tab.value"
|
|
37
|
+
>
|
|
38
|
+
<v-icon start size="16">{{ tab.icon }}</v-icon>
|
|
39
|
+
{{ tab.label }}
|
|
40
|
+
</button>
|
|
41
|
+
</template>
|
|
42
|
+
</CardToolbar>
|
|
30
43
|
|
|
31
44
|
<v-window v-model="activeTab">
|
|
32
45
|
<!-- ─── Tab 1: General ─────────────────────────────────── -->
|
|
@@ -379,7 +392,7 @@
|
|
|
379
392
|
</v-row>
|
|
380
393
|
</v-window-item>
|
|
381
394
|
</v-window>
|
|
382
|
-
</
|
|
395
|
+
</AppCard>
|
|
383
396
|
|
|
384
397
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
385
398
|
|
|
@@ -388,7 +401,7 @@
|
|
|
388
401
|
:header="currentEntryPassSettings.qrCodeTemplateHeader"
|
|
389
402
|
:sub-text="currentEntryPassSettings.qrCodeTemplateHeaderSubText"
|
|
390
403
|
/>
|
|
391
|
-
</
|
|
404
|
+
</div>
|
|
392
405
|
</template>
|
|
393
406
|
|
|
394
407
|
<script lang="ts" setup>
|
|
@@ -400,6 +413,15 @@ const prop = defineProps({
|
|
|
400
413
|
|
|
401
414
|
const activeTab = ref("general");
|
|
402
415
|
|
|
416
|
+
/** The same four tabs, in the same order, moved out of the template so the
|
|
417
|
+
design's tab row can `v-for` them. Values unchanged - they key `v-window`. */
|
|
418
|
+
const entryPassTabs = [
|
|
419
|
+
{ value: "general", label: "General", icon: "mdi-cog-outline" },
|
|
420
|
+
{ value: "qrcode", label: "QR Code", icon: "mdi-qrcode" },
|
|
421
|
+
{ value: "printer", label: "Printer", icon: "mdi-printer" },
|
|
422
|
+
{ value: "entrypass", label: "Entrypass", icon: "mdi-link-variant" },
|
|
423
|
+
];
|
|
424
|
+
|
|
403
425
|
const currentEntryPassSettings = ref<Record<string, any>>({
|
|
404
426
|
_id: "",
|
|
405
427
|
site: "",
|
|
@@ -1,50 +1,46 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S PAGE SCAFFOLD (same shape as Service Providers).
|
|
5
|
+
|
|
6
|
+
`Create role` moves from a bare row above the card onto the title line,
|
|
7
|
+
on the right, where the design puts a screen's primary action. It is the
|
|
8
|
+
same button under the same `canCreateRole` permission opening the same
|
|
9
|
+
dialog. The refresh, the range, the pager and the table are unchanged.
|
|
10
|
+
-->
|
|
11
|
+
<PageHeader title="Roles and Permissions">
|
|
12
|
+
<template #actions>
|
|
13
|
+
<AppButton v-if="canCreateRole" @click="createDialog = true">
|
|
11
14
|
Create role
|
|
12
|
-
</
|
|
13
|
-
</
|
|
14
|
-
</
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@update:value="getRoles()"
|
|
42
|
-
/>
|
|
43
|
-
</v-row>
|
|
44
|
-
</template>
|
|
45
|
-
</v-toolbar>
|
|
46
|
-
|
|
47
|
-
<v-data-table
|
|
15
|
+
</AppButton>
|
|
16
|
+
</template>
|
|
17
|
+
</PageHeader>
|
|
18
|
+
|
|
19
|
+
<AppCard class="table-card">
|
|
20
|
+
<!-- `v-card`'s `:loading` bar, drawn explicitly: a loading screen must
|
|
21
|
+
still say so now that the card is not a `v-card`. -->
|
|
22
|
+
<v-progress-linear v-if="loading" indeterminate height="2" />
|
|
23
|
+
|
|
24
|
+
<CardToolbar :count="pageRange">
|
|
25
|
+
<template #left>
|
|
26
|
+
<AppButton
|
|
27
|
+
variant="icon"
|
|
28
|
+
icon="mdi-refresh"
|
|
29
|
+
aria-label="Refresh"
|
|
30
|
+
@click="getRoles()"
|
|
31
|
+
/>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<template #right>
|
|
35
|
+
<local-pagination
|
|
36
|
+
v-model="page"
|
|
37
|
+
:length="pages"
|
|
38
|
+
@update:value="getRoles()"
|
|
39
|
+
/>
|
|
40
|
+
</template>
|
|
41
|
+
</CardToolbar>
|
|
42
|
+
|
|
43
|
+
<v-data-table
|
|
48
44
|
:headers="props.headers"
|
|
49
45
|
:items="items"
|
|
50
46
|
item-value="_id"
|
|
@@ -78,9 +74,8 @@
|
|
|
78
74
|
</v-list>
|
|
79
75
|
</v-menu>
|
|
80
76
|
</template>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
</v-col>
|
|
77
|
+
</v-data-table>
|
|
78
|
+
</AppCard>
|
|
84
79
|
|
|
85
80
|
<!-- dialogs -->
|
|
86
81
|
<v-dialog v-model="createDialog" width="500" persistent>
|
|
@@ -196,7 +191,7 @@
|
|
|
196
191
|
</v-dialog>
|
|
197
192
|
|
|
198
193
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
199
|
-
</
|
|
194
|
+
</div>
|
|
200
195
|
</template>
|
|
201
196
|
|
|
202
197
|
<script setup lang="ts">
|
|
@@ -32,13 +32,18 @@
|
|
|
32
32
|
</template>
|
|
33
33
|
|
|
34
34
|
<template #item.createdBy="{ item }">
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
<!--
|
|
36
|
+
THE 3.12:1 `HS` CHIP.
|
|
37
|
+
|
|
38
|
+
This was a hand-rolled copy of `AvatarMain` with two problems: its
|
|
39
|
+
fill was Vuetify's MATERIAL `blue`/`purple`/`grey` (not a theme
|
|
40
|
+
token, so no `on-*` pair applied to it) and its `white--text` is
|
|
41
|
+
Vuetify 2 syntax that does nothing in Vuetify 3 - leaving Vuetify to
|
|
42
|
+
derive WHITE, which reads 3.12:1 on that blue in BOTH themes.
|
|
43
|
+
`AvatarMain` draws the same circle with the same initials and now
|
|
44
|
+
guarantees its label clears 4.5:1.
|
|
45
|
+
-->
|
|
46
|
+
<AvatarMain :name="item.createdBy" :size="32" class="mr-2" />
|
|
42
47
|
{{ item.createdBy || "N/A" }}
|
|
43
48
|
</template>
|
|
44
49
|
|
|
@@ -131,19 +136,6 @@ const headers = [
|
|
|
131
136
|
|
|
132
137
|
const { formatDate } = useUtils();
|
|
133
138
|
|
|
134
|
-
function getInitials(name: string) {
|
|
135
|
-
if (!name) return "";
|
|
136
|
-
return name
|
|
137
|
-
.split(" ")
|
|
138
|
-
.map((n) => n[0])
|
|
139
|
-
.join("")
|
|
140
|
-
.toUpperCase();
|
|
141
|
-
}
|
|
142
|
-
function getAvatarColor(name: string) {
|
|
143
|
-
if (name === "Demo Smith") return "purple";
|
|
144
|
-
if (name === "Hygiene SP") return "blue";
|
|
145
|
-
return "grey";
|
|
146
|
-
}
|
|
147
139
|
function acceptTask(item: any) {
|
|
148
140
|
selectedTask.value = item;
|
|
149
141
|
showAcceptDialog.value = true;
|
package/components/TableMain.vue
CHANGED
|
@@ -15,6 +15,24 @@
|
|
|
15
15
|
-->
|
|
16
16
|
<template>
|
|
17
17
|
<v-row no-gutters>
|
|
18
|
+
<!--
|
|
19
|
+
THE DESIGN'S PAGE TITLE (Phase 9).
|
|
20
|
+
|
|
21
|
+
The design opens every screen with a 22px/800 heading. This shared table
|
|
22
|
+
card is mounted by 17 module screens, and none of them had one - four of
|
|
23
|
+
them (Units, Areas, Equipment Item, Equipment Management) were already
|
|
24
|
+
PASSING `:title` to this component, which had no `title` prop, so the
|
|
25
|
+
string was landing on the root element as a plain HTML `title` attribute:
|
|
26
|
+
a browser tooltip nobody asked for. It now draws the heading it was
|
|
27
|
+
always meant to draw.
|
|
28
|
+
|
|
29
|
+
Placement only. No caller changes, no slot moves, no behaviour: a screen
|
|
30
|
+
that passes no title renders exactly what it rendered before.
|
|
31
|
+
-->
|
|
32
|
+
<v-col cols="12" v-if="title">
|
|
33
|
+
<PageHeader :title="title" />
|
|
34
|
+
</v-col>
|
|
35
|
+
|
|
18
36
|
<!-- Top Actions -->
|
|
19
37
|
<v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
|
|
20
38
|
<v-row no-gutters>
|
|
@@ -62,43 +80,56 @@
|
|
|
62
80
|
|
|
63
81
|
<!-- Table Card -->
|
|
64
82
|
<v-col cols="12">
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
<!--
|
|
84
|
+
THE DESIGN'S CARD AND ITS IN-CARD TOP ROW (Phase 9).
|
|
85
|
+
|
|
86
|
+
Was a `v-card` + `v-toolbar`. `v-card` brings its own radius, elevation
|
|
87
|
+
and surface at a specificity a class has to fight, and `v-toolbar` puts
|
|
88
|
+
its `extension` on a SECOND stacked row. `AppCard` + `CardToolbar` are
|
|
89
|
+
the primitives the design is drawn with, already proven on
|
|
90
|
+
`ServiceProviderMain` / `MemberMain`.
|
|
91
|
+
|
|
92
|
+
Placement only. The refresh button, the `prepend-additional` slot, the
|
|
93
|
+
page range, the pager, the `extension` slot and the `footer` slot are
|
|
94
|
+
the same controls, emitting the same events, in the same order. The
|
|
95
|
+
`loading` bar `v-card` used to draw is drawn explicitly so a loading
|
|
96
|
+
screen still says so.
|
|
97
|
+
-->
|
|
98
|
+
<AppCard class="table-card">
|
|
99
|
+
<v-progress-linear
|
|
100
|
+
v-if="loading"
|
|
101
|
+
indeterminate
|
|
102
|
+
height="2"
|
|
103
|
+
class="table-card__loading"
|
|
104
|
+
/>
|
|
105
|
+
|
|
71
106
|
<!-- Toolbar -->
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
color="transparent"
|
|
75
|
-
class="table-card__toolbar"
|
|
76
|
-
:extension-height="extensionHeight"
|
|
77
|
-
>
|
|
78
|
-
<template #prepend>
|
|
107
|
+
<CardToolbar :count="pageRange">
|
|
108
|
+
<template #left>
|
|
79
109
|
<v-btn fab icon density="comfortable" variant="text" @click="emits('refresh')">
|
|
80
110
|
<v-icon>mdi-refresh</v-icon>
|
|
81
111
|
</v-btn>
|
|
82
112
|
<slot name="prepend-additional" />
|
|
83
113
|
</template>
|
|
84
114
|
|
|
85
|
-
<template #
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
v-model="internalPage"
|
|
92
|
-
:length="pages"
|
|
93
|
-
@update:value="emits('update:page', internalPage)"
|
|
94
|
-
/>
|
|
95
|
-
</v-row>
|
|
115
|
+
<template #right>
|
|
116
|
+
<local-pagination
|
|
117
|
+
v-model="internalPage"
|
|
118
|
+
:length="pages"
|
|
119
|
+
@update:value="emits('update:page', internalPage)"
|
|
120
|
+
/>
|
|
96
121
|
</template>
|
|
122
|
+
</CardToolbar>
|
|
97
123
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
124
|
+
<!--
|
|
125
|
+
The callers' `extension` is a FILTER row (selects, date ranges), not
|
|
126
|
+
tabs - it is full-width by construction, so it keeps its own line
|
|
127
|
+
under the toolbar rather than being folded into it, which would push
|
|
128
|
+
the pager off the end of the row.
|
|
129
|
+
-->
|
|
130
|
+
<div v-if="$slots.extension" class="table-card__extension">
|
|
131
|
+
<slot name="extension" />
|
|
132
|
+
</div>
|
|
102
133
|
|
|
103
134
|
<!-- Data Table -->
|
|
104
135
|
<v-data-table
|
|
@@ -129,13 +160,18 @@
|
|
|
129
160
|
</template>
|
|
130
161
|
</v-data-table>
|
|
131
162
|
<slot name="footer" />
|
|
132
|
-
</
|
|
163
|
+
</AppCard>
|
|
133
164
|
</v-col>
|
|
134
165
|
</v-row>
|
|
135
166
|
</template>
|
|
136
167
|
|
|
137
168
|
<script lang="ts" setup>
|
|
138
169
|
const props = defineProps({
|
|
170
|
+
// The design's page heading. Empty = no heading, i.e. exactly today's markup.
|
|
171
|
+
title: {
|
|
172
|
+
type: String,
|
|
173
|
+
default: "",
|
|
174
|
+
},
|
|
139
175
|
headers: {
|
|
140
176
|
type: Array as PropType<Array<Record<string, any>>>,
|
|
141
177
|
required: true,
|