@7365admin1/layer-common 3.2.1 → 3.2.2-staging.100
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/.changeset/camera-wall-gated-endpoint.md +47 -0
- package/.changeset/camera-wall-plain-english.md +60 -0
- package/.changeset/camera-wall-selection-and-guidance.md +65 -0
- package/.changeset/dark-primary-contrast.md +39 -0
- package/.changeset/dashboard-dark-theme-contrast.md +15 -0
- package/.changeset/notification-preferences.md +17 -0
- package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
- package/.changeset/service-provider-invitation-actions.md +28 -0
- package/assets/css/primitives.css +680 -0
- package/assets/css/screens.css +541 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +220 -0
- package/components/AccessCardQrTagging.vue +1 -1
- package/components/AccessManagement.vue +53 -72
- package/components/AppButton.vue +52 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +35 -0
- package/components/AppField.vue +46 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +203 -0
- package/components/AreaChecklistHistoryMain.vue +6 -2
- package/components/AreaMain.vue +9 -17
- package/components/AttendanceMain.vue +4 -3
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BuildingManagement/buildings.vue +27 -38
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BulletinBoardManagement.vue +23 -10
- package/components/CameraWall.vue +1034 -0
- package/components/CameraWallTile.vue +818 -0
- package/components/CardToolbar.vue +37 -0
- package/components/CleaningScheduleMain.vue +29 -46
- package/components/ClientMain.vue +90 -78
- package/components/DashboardEmptyState.vue +11 -1
- package/components/DashboardMain.vue +349 -275
- package/components/DashboardPanel.vue +1 -1
- package/components/DocumentManagement.vue +129 -93
- package/components/EntryPassInformation.vue +4 -4
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +4 -6
- package/components/EquipmentManagementMain.vue +9 -7
- package/components/FeedbackMain.vue +42 -56
- package/components/FormDialog.vue +60 -21
- package/components/HidAccessLogDashboard.vue +119 -44
- package/components/HidCameraCaptureDialog.vue +199 -0
- package/components/HidIntercomManagement.vue +825 -202
- package/components/HidQrCodeConfiguration.vue +927 -93
- package/components/HidServiceSettingsPanel.vue +20 -1
- package/components/HidUserEnrollment.vue +454 -106
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +289 -37
- package/components/Layout/NavigationDrawer.vue +340 -30
- package/components/ManageChecklistMain.vue +17 -17
- package/components/MemberMain.vue +79 -60
- package/components/MyAttendanceMain.vue +3 -5
- package/components/NavigationItem.vue +9 -0
- package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
- package/components/NotificationSettings.vue +361 -0
- package/components/OnlineFormConfigurationForm.vue +620 -224
- package/components/OnlineFormFill.vue +36 -8
- package/components/OnlineFormsConfiguration.vue +7 -3
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +104 -45
- package/components/QrTemplate/CreditCardLandscape.vue +19 -12
- package/components/QrTemplate/PrintDialog.vue +209 -196
- package/components/RolePermissionMain.vue +184 -91
- package/components/ScanVisitorQRCode.vue +35 -7
- package/components/ScheduleAreaMain.vue +15 -49
- package/components/ScheduleTaskMain.vue +23 -26
- package/components/ScheduleTastTicketMain.vue +18 -43
- package/components/ServiceProviderInvitationPrompt.vue +150 -0
- package/components/ServiceProviderMain.vue +346 -157
- package/components/SiteSettings.vue +25 -10
- package/components/SpecificAttr.vue +10 -2
- package/components/StatusChip.vue +93 -0
- package/components/SwitchContext.vue +33 -1
- package/components/TableHygiene.vue +29 -46
- package/components/TableList.vue +1 -1
- package/components/TableListSecondary.vue +1 -1
- package/components/TableMain.vue +161 -77
- package/components/TableV3.vue +61 -66
- package/components/UnitMain.vue +9 -17
- package/components/VehicleManagement.vue +20 -9
- package/components/VisitorForm.vue +294 -78
- package/components/VisitorManagement.vue +221 -71
- package/components/VisitorSocketPopUp.vue +56 -2
- package/components/WorkOrder/Main.vue +42 -51
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +151 -0
- package/composables/useHidNavigation.ts +0 -7
- package/composables/useLocalAuth.ts +9 -36
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useOrg.ts +1 -1
- package/composables/useRole.ts +22 -0
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +336 -0
- package/composables/useSiteSettings.ts +50 -0
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/composables/useVisitorSocket.ts +26 -0
- package/composables/useWebUsb.ts +127 -37
- package/nuxt.config.ts +31 -0
- package/package.json +4 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/pages/notification-settings.vue +19 -0
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +68 -9
- package/test/visitor-socket.test.mjs +36 -0
- package/types/org.d.ts +2 -0
- package/types/site.d.ts +71 -0
- package/utils/camera-wall.test.ts +571 -0
- package/utils/camera-wall.ts +926 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/status.test.ts +77 -0
- package/utils/status.ts +90 -0
- package/utils/theme.test.ts +534 -0
- package/utils/theme.ts +294 -0
- package/components/HidIdentityMapping.vue +0 -975
- package/middleware/member.ts +0 -4
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
|
@@ -1,40 +1,117 @@
|
|
|
1
1
|
<!-- LayoutNavigationDrawer.vue -->
|
|
2
2
|
<template>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
<!--
|
|
4
|
+
`bg-brand-surface`, not `bg-primary`: `primary` is a foreground colour on a
|
|
5
|
+
dark page (it has to carry `class="text-primary"` sentences at 4.5:1), and a
|
|
6
|
+
fill that carries a label cannot also be that. `brand-surface` is a real
|
|
7
|
+
surface in each theme - see `utils/theme.ts` - so the drawer belongs to the
|
|
8
|
+
application it is in rather than being the one panel the theme switch never
|
|
9
|
+
reached. Its label reads 15.43:1 light, 13.21:1 dark.
|
|
10
|
+
|
|
11
|
+
NO HARDCODED LABEL COLOUR ANYWHERE IN HERE. Vuetify paints the drawer with
|
|
12
|
+
`on-brand-surface` and the list inside it inherits that through
|
|
13
|
+
`bg-transparent`, which the drawer supplies to every `v-list` it contains.
|
|
14
|
+
A `text-white` in here (or in an app's `#action` / `#services` slot) is
|
|
15
|
+
white on a light rail at 1.13:1.
|
|
16
|
+
|
|
17
|
+
`floating` dropped so the drawer draws its own edge, and the three
|
|
18
|
+
opacities below are raised on this element only:
|
|
19
|
+
|
|
20
|
+
- `border-opacity` 0.45. A surface is a low-contrast panel by design
|
|
21
|
+
(1.13:1 light, 1.19:1 dark against the page), so the edge is what carries
|
|
22
|
+
the boundary: 3.27:1 light, 4.33:1 dark. The theme-wide value stays where
|
|
23
|
+
it is; raising that would redraw every table and card edge in eleven
|
|
24
|
+
applications.
|
|
25
|
+
- `hover-opacity` 0.08. Material's 0.04 is 1.11:1 on this rail, which is
|
|
26
|
+
not feedback. 0.08 is 1.17:1 light, 1.24:1 dark.
|
|
27
|
+
- `activated-opacity` 0.18. The active item is `primary` text on a
|
|
28
|
+
primary-tinted fill (see `NavigationItem.vue`); 0.12 left the fill at
|
|
29
|
+
1.15:1, 0.18 is 1.44:1 light, 1.33:1 dark.
|
|
30
|
+
-->
|
|
31
|
+
<!--
|
|
32
|
+
RESPONSIVE: THE RAIL BECOMES AN OVERLAY BELOW 1024px.
|
|
33
|
+
|
|
34
|
+
`permanent` was hardcoded, which is why the prototype's own behaviour - and
|
|
35
|
+
ours - was a 252px rail still sitting there on a 360px phone with the
|
|
36
|
+
content squeezed into what was left. Dropping `permanent` and naming the
|
|
37
|
+
breakpoint hands the whole behaviour to Vuetify: at >= 1024 the drawer is
|
|
38
|
+
part of the layout exactly as it is today, and below it the same drawer
|
|
39
|
+
becomes temporary, floats OVER the content with a scrim, and closes on the
|
|
40
|
+
scrim, on Escape and on a navigation.
|
|
41
|
+
|
|
42
|
+
1024 rather than Vuetify's default 1280: at 1024 the rail still leaves a
|
|
43
|
+
772px content column, which is a usable table. Below that it does not.
|
|
10
44
|
|
|
11
|
-
|
|
45
|
+
Nothing about the menu changes - same items, same order, same permissions,
|
|
46
|
+
same routes. Only where the panel is drawn.
|
|
47
|
+
-->
|
|
48
|
+
<!--
|
|
49
|
+
PHASE 2 - THE GROUPED SIDEBAR.
|
|
50
|
+
|
|
51
|
+
The rail is 252px as drawn, the brand row is the design's 34px tile plus
|
|
52
|
+
the product name, and the menu is printed in labelled sections.
|
|
53
|
+
|
|
54
|
+
The MENU ITSELF IS UNTOUCHED. Each application still builds its own array,
|
|
55
|
+
gated item by item on its own permissions, and it is rendered in exactly
|
|
56
|
+
that order; `utils/nav-sections.ts` only decides which label to print above
|
|
57
|
+
which item, and an item whose title it does not know keeps its place under
|
|
58
|
+
the heading it follows. Nothing here can add, hide, move or rename a menu
|
|
59
|
+
entry, and no route, permission or click handler is involved.
|
|
60
|
+
-->
|
|
61
|
+
<v-navigation-drawer
|
|
62
|
+
v-model="drawer"
|
|
63
|
+
:mobile-breakpoint="1024"
|
|
64
|
+
:width="252"
|
|
65
|
+
class="bg-brand-surface brand-drawer"
|
|
66
|
+
>
|
|
67
|
+
<div class="brand-drawer__brand">
|
|
68
|
+
<div class="brand-drawer__logo">
|
|
69
|
+
<v-icon :icon="brandIcon" size="19" />
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="brand-drawer__names">
|
|
73
|
+
<div class="brand-drawer__app">{{ props.title || APP_NAME }}</div>
|
|
74
|
+
<div class="brand-drawer__suite">iService 365</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<v-list>
|
|
79
|
+
<!--
|
|
80
|
+
The site selector, in the design's bordered card. The slot's CONTENTS
|
|
81
|
+
are whatever the application puts there, still inside the same
|
|
82
|
+
`v-list` it has always been inside - this only draws the box around it.
|
|
83
|
+
-->
|
|
84
|
+
<div v-if="$slots.action" class="brand-drawer__context">
|
|
85
|
+
<slot name="action" />
|
|
86
|
+
</div>
|
|
12
87
|
|
|
13
88
|
<!-- NAVIGATION ITEMS -->
|
|
14
|
-
<
|
|
15
|
-
v-for="(item, index) in
|
|
89
|
+
<template
|
|
90
|
+
v-for="({ item, section }, index) in sectionedItems"
|
|
16
91
|
:key="`${item.title}-${index}`"
|
|
17
|
-
location="right"
|
|
18
|
-
:disabled="!item.disabled"
|
|
19
92
|
>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
93
|
+
<div v-if="section" class="brand-drawer__section">{{ section }}</div>
|
|
94
|
+
|
|
95
|
+
<v-tooltip location="right" :disabled="!item.disabled">
|
|
96
|
+
<template #activator="{ props: tooltipProps }">
|
|
97
|
+
<div v-bind="tooltipProps">
|
|
98
|
+
<NavigationItem
|
|
99
|
+
:title="item.title"
|
|
100
|
+
:icon="item.icon"
|
|
101
|
+
:route="item.route"
|
|
102
|
+
:children="item.children"
|
|
103
|
+
:link="item.link"
|
|
104
|
+
:class="{
|
|
105
|
+
'opacity-50 pointer-events-none': item.disabled,
|
|
106
|
+
}"
|
|
107
|
+
@click="() => handleClick(item)"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<span>Please complete onboarding first</span>
|
|
113
|
+
</v-tooltip>
|
|
114
|
+
</template>
|
|
38
115
|
|
|
39
116
|
<!-- SERVICES SLOT -->
|
|
40
117
|
<slot name="services" />
|
|
@@ -43,6 +120,10 @@
|
|
|
43
120
|
</template>
|
|
44
121
|
|
|
45
122
|
<script setup lang="ts">
|
|
123
|
+
import { useDisplay } from "vuetify";
|
|
124
|
+
|
|
125
|
+
import { withSections } from "../../utils/nav-sections";
|
|
126
|
+
|
|
46
127
|
const props = defineProps({
|
|
47
128
|
navigationItems: { type: Array, required: true },
|
|
48
129
|
title: { type: String, default: "" },
|
|
@@ -53,6 +134,32 @@ const emit = defineEmits(["navigate"]);
|
|
|
53
134
|
const { drawer } = useLocal();
|
|
54
135
|
const { APP_NAME } = useRuntimeConfig().public;
|
|
55
136
|
const route = useRoute();
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `drawer` is shared application state and it defaults to OPEN, which is right
|
|
140
|
+
* for a rail and wrong for an overlay - a phone would load with the menu
|
|
141
|
+
* covering the page behind a scrim. So the default follows the viewport, and a
|
|
142
|
+
* user who resizes past the breakpoint gets the state that suits where they
|
|
143
|
+
* landed.
|
|
144
|
+
*
|
|
145
|
+
* Only the DEFAULT is managed here. Once the hamburger has been used, the
|
|
146
|
+
* user's choice stands until the layout itself changes underneath them.
|
|
147
|
+
*/
|
|
148
|
+
const { mobile } = useDisplay({ mobileBreakpoint: 1024 });
|
|
149
|
+
|
|
150
|
+
watch(mobile, (isOverlay) => (drawer.value = !isOverlay), { immediate: true });
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* An overlay drawer that survives a navigation leaves the scrim over the page
|
|
154
|
+
* the user just asked for. A rail must NOT close - that would be a step added
|
|
155
|
+
* to every desktop navigation.
|
|
156
|
+
*/
|
|
157
|
+
watch(
|
|
158
|
+
() => route.fullPath,
|
|
159
|
+
() => {
|
|
160
|
+
if (mobile.value) drawer.value = false;
|
|
161
|
+
}
|
|
162
|
+
);
|
|
56
163
|
const orgId = computed(() => String(route.params.org ?? ""));
|
|
57
164
|
const siteId = computed(() => String(route.params.site ?? ""));
|
|
58
165
|
const { useHidFaceReaderMenu } = useHidNavigation();
|
|
@@ -86,8 +193,211 @@ const navigationItems = computed(() => {
|
|
|
86
193
|
return items;
|
|
87
194
|
});
|
|
88
195
|
|
|
196
|
+
const sectionedItems = computed(() => withSections(navigationItems.value));
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The design's brand tile carries the product's own mark. One icon for eleven
|
|
200
|
+
* applications would be one application's icon on ten of them, so the tile
|
|
201
|
+
* follows `APP_NAME` - the same value the row already prints - and falls back
|
|
202
|
+
* to the suite's shield for anything not listed.
|
|
203
|
+
*/
|
|
204
|
+
const BRAND_ICONS: Record<string, string> = {
|
|
205
|
+
security: "mdi-shield-check",
|
|
206
|
+
"property-management": "mdi-office-building",
|
|
207
|
+
hygiene: "mdi-broom",
|
|
208
|
+
landscape: "mdi-flower",
|
|
209
|
+
"pest-control": "mdi-bug",
|
|
210
|
+
"pool-mgmt": "mdi-pool",
|
|
211
|
+
"mechanical-electrical": "mdi-flash",
|
|
212
|
+
admin: "mdi-shield-crown",
|
|
213
|
+
org: "mdi-domain",
|
|
214
|
+
account: "mdi-account-circle",
|
|
215
|
+
main: "mdi-view-grid",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const brandIcon = computed(() => {
|
|
219
|
+
// `APP_NAME` is an environment value and is spelled for people ("Property
|
|
220
|
+
// Management"), so it is normalised rather than matched literally.
|
|
221
|
+
const key = String(APP_NAME ?? "")
|
|
222
|
+
.toLowerCase()
|
|
223
|
+
.trim()
|
|
224
|
+
.replace(/[\s_]+/g, "-");
|
|
225
|
+
|
|
226
|
+
return BRAND_ICONS[key] ?? "mdi-shield-check";
|
|
227
|
+
});
|
|
228
|
+
|
|
89
229
|
const handleClick = (item: any) => {
|
|
90
230
|
if (item.disabled) return;
|
|
91
231
|
emit("navigate", item);
|
|
92
232
|
};
|
|
93
233
|
</script>
|
|
234
|
+
|
|
235
|
+
<style scoped>
|
|
236
|
+
/* Scoped to the drawer: none of these reach the rest of the application. */
|
|
237
|
+
.brand-drawer {
|
|
238
|
+
--v-border-opacity: 0.45;
|
|
239
|
+
--v-hover-opacity: 0.08;
|
|
240
|
+
--v-activated-opacity: 0.18;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Phase 1 loaded Manrope and named it `--font-sans` but deliberately set it
|
|
244
|
+
* on nothing: switching eleven applications to a new typeface before a
|
|
245
|
+
* single surface had been rebuilt would have left the product looking
|
|
246
|
+
* half-migrated. The shell IS the surface Phase 2 rebuilds, so it is the
|
|
247
|
+
* first thing to wear it.
|
|
248
|
+
*/
|
|
249
|
+
font-family: var(--font-sans);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ---------------------------------------------------------------- */
|
|
253
|
+
/* The brand row: 34px accent tile + product name + suite name. */
|
|
254
|
+
/* ---------------------------------------------------------------- */
|
|
255
|
+
|
|
256
|
+
.brand-drawer__brand {
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
gap: 10px;
|
|
260
|
+
padding: 14px 16px 10px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.brand-drawer__logo {
|
|
264
|
+
flex: 0 0 34px;
|
|
265
|
+
width: 34px;
|
|
266
|
+
height: 34px;
|
|
267
|
+
display: flex;
|
|
268
|
+
align-items: center;
|
|
269
|
+
justify-content: center;
|
|
270
|
+
border-radius: var(--r-inner);
|
|
271
|
+
/* A white glyph, so the fill has to be the one accent that carries white:
|
|
272
|
+
`--accent` is 3.23:1 under it, `--accent-strong` is 4.57:1. */
|
|
273
|
+
background: var(--accent-strong);
|
|
274
|
+
color: var(--on-accent-strong);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.brand-drawer__names {
|
|
278
|
+
min-width: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.brand-drawer__app {
|
|
282
|
+
font-size: var(--fs-card-title);
|
|
283
|
+
font-weight: var(--fw-card-title);
|
|
284
|
+
line-height: 1.2;
|
|
285
|
+
color: var(--text);
|
|
286
|
+
/* The design prints the application's name in full ("Property Management").
|
|
287
|
+
Truncating it to "Property Managem…" in a 252px rail loses the word that
|
|
288
|
+
tells the user which product they are in, so it wraps instead. */
|
|
289
|
+
overflow-wrap: anywhere;
|
|
290
|
+
text-transform: capitalize;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.brand-drawer__suite {
|
|
294
|
+
font-size: 11.5px;
|
|
295
|
+
font-weight: 600;
|
|
296
|
+
line-height: 1.3;
|
|
297
|
+
color: var(--muted);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The organisation / site selector, drawn as the design's bordered card.
|
|
302
|
+
*
|
|
303
|
+
* Two applications have no selector to draw: `web-app-admin` passes no slot at
|
|
304
|
+
* all (handled by the `v-if`), and `web-app-org`'s super-admin layout passes an
|
|
305
|
+
* EMPTY one - `<template #action> </template>` - which the `v-if` cannot see.
|
|
306
|
+
* An empty bordered box on that rail would be a box around nothing, so a card
|
|
307
|
+
* with no element inside it is not drawn.
|
|
308
|
+
*/
|
|
309
|
+
.brand-drawer__context:not(:has(*)) {
|
|
310
|
+
display: none;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.brand-drawer__context {
|
|
314
|
+
margin: 2px 4px 4px;
|
|
315
|
+
border: 1px solid var(--border);
|
|
316
|
+
border-radius: var(--r-inner-lg);
|
|
317
|
+
background: var(--card);
|
|
318
|
+
overflow: hidden;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* ---------------------------------------------------------------- */
|
|
322
|
+
/* Section labels and the menu items under them. */
|
|
323
|
+
/* ---------------------------------------------------------------- */
|
|
324
|
+
|
|
325
|
+
.brand-drawer__section {
|
|
326
|
+
padding: 14px 12px 6px;
|
|
327
|
+
font-size: var(--fs-section-label);
|
|
328
|
+
font-weight: var(--fw-section-label);
|
|
329
|
+
letter-spacing: var(--ls-section-label);
|
|
330
|
+
line-height: 1.2;
|
|
331
|
+
text-transform: uppercase;
|
|
332
|
+
color: var(--muted);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* The first label sits directly under the site selector, so it does not need
|
|
336
|
+
the separating space the ones between two menu blocks do. */
|
|
337
|
+
.brand-drawer__section:first-child {
|
|
338
|
+
padding-top: 4px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* `:deep` because the items are `NavigationItem`s - a child component - and
|
|
343
|
+
* the design's item shape (10px radius, 8px 12px, 13px label) belongs to the
|
|
344
|
+
* rail rather than to that component, which is also rendered elsewhere.
|
|
345
|
+
*
|
|
346
|
+
* ONLY shape and type. The active state's colour still comes from
|
|
347
|
+
* `color="primary"` on the item itself, which is `--accent-text`.
|
|
348
|
+
*/
|
|
349
|
+
.brand-drawer :deep(.v-list) {
|
|
350
|
+
padding: 0 8px 12px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The two `!important`s are not carelessness: Vuetify puts `rounded-0` and
|
|
355
|
+
* `text-subtitle-2` ON the item, and both of those are utility classes that
|
|
356
|
+
* are themselves `!important`. Nothing weaker can reach the design's 10px
|
|
357
|
+
* radius or its 13px label. Both are confined to `.brand-drawer`.
|
|
358
|
+
*/
|
|
359
|
+
.brand-drawer :deep(.v-list-item) {
|
|
360
|
+
border-radius: var(--r-inner) !important;
|
|
361
|
+
min-height: 38px;
|
|
362
|
+
padding-top: 8px;
|
|
363
|
+
padding-bottom: 8px;
|
|
364
|
+
padding-inline: 12px;
|
|
365
|
+
font-size: var(--fs-nav) !important;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.brand-drawer :deep(.v-list-item__prepend > .v-icon) {
|
|
369
|
+
font-size: 19px;
|
|
370
|
+
opacity: 1;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Vuetify's own 16px gap between the icon and the label, brought to the 10px
|
|
374
|
+
the design draws. The spacer element is what holds that distance - a margin
|
|
375
|
+
on the icon leaves it where it was. */
|
|
376
|
+
.brand-drawer :deep(.v-list-item__spacer) {
|
|
377
|
+
width: 10px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.brand-drawer :deep(.v-list-item--active) {
|
|
381
|
+
font-weight: var(--fw-section-label);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The design's active item is a 12% accent wash, which is exactly what
|
|
386
|
+
* `--accent-soft` is. Vuetify paints the same idea with an overlay whose
|
|
387
|
+
* opacity is theme-wide, so the fill is stated here instead of leaving the
|
|
388
|
+
* rail's active row to a number tuned for tables and cards.
|
|
389
|
+
*/
|
|
390
|
+
.brand-drawer :deep(.v-list-item--active > .v-list-item__overlay) {
|
|
391
|
+
opacity: 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.brand-drawer :deep(.v-list-item--active) {
|
|
395
|
+
background: var(--accent-soft);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* A child row indents by the icon slot it does not have, so the labels of a
|
|
399
|
+
group line up with the labels of its siblings rather than with their icons. */
|
|
400
|
+
.brand-drawer :deep(.v-list-group__items .v-list-item) {
|
|
401
|
+
padding-inline-start: 41px !important;
|
|
402
|
+
}
|
|
403
|
+
</style>
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
</v-col>
|
|
11
11
|
<v-spacer />
|
|
12
12
|
<v-col v-if="isScheduleClosed" cols="auto" class="d-flex align-center">
|
|
13
|
-
<
|
|
14
|
-
<v-icon
|
|
13
|
+
<StatusChip status="Closed" class="text-capitalize">
|
|
14
|
+
<v-icon size="14">mdi-lock</v-icon>
|
|
15
15
|
Closed – View Only
|
|
16
|
-
</
|
|
16
|
+
</StatusChip>
|
|
17
17
|
</v-col>
|
|
18
18
|
</v-row>
|
|
19
19
|
<TableHygiene
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
<v-sheet
|
|
86
86
|
border
|
|
87
87
|
rounded="lg"
|
|
88
|
-
class="d-inline-flex flex-column pa-3
|
|
88
|
+
class="d-inline-flex flex-column pa-3"
|
|
89
89
|
style="max-width: 420px"
|
|
90
90
|
>
|
|
91
91
|
<div class="d-flex align-center ga-2 mb-2">
|
|
@@ -93,9 +93,7 @@
|
|
|
93
93
|
<span class="text-caption font-weight-bold">
|
|
94
94
|
Rejection details
|
|
95
95
|
</span>
|
|
96
|
-
<
|
|
97
|
-
Rejected
|
|
98
|
-
</v-chip>
|
|
96
|
+
<StatusChip status="Rejected" :dot="false" />
|
|
99
97
|
</div>
|
|
100
98
|
<div
|
|
101
99
|
v-if="item.remarks"
|
|
@@ -173,22 +171,24 @@
|
|
|
173
171
|
<template #group-header-append="{ group }">
|
|
174
172
|
<v-row no-gutters align="center" class="ga-2">
|
|
175
173
|
<v-col v-if="isGroupAnyApproved(group)" cols="auto">
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
:
|
|
179
|
-
|
|
180
|
-
:prepend-icon="
|
|
181
|
-
isGroupComplete(group)
|
|
182
|
-
? 'mdi-check-circle-outline'
|
|
183
|
-
: 'mdi-progress-clock'
|
|
184
|
-
"
|
|
174
|
+
<StatusChip
|
|
175
|
+
:status="isGroupComplete(group) ? 'Completed' : 'Ongoing'"
|
|
176
|
+
:tone="isGroupComplete(group) ? 'ok' : 'warn'"
|
|
177
|
+
:dot="false"
|
|
185
178
|
class="text-none"
|
|
186
179
|
>
|
|
180
|
+
<v-icon size="12">
|
|
181
|
+
{{
|
|
182
|
+
isGroupComplete(group)
|
|
183
|
+
? "mdi-check-circle-outline"
|
|
184
|
+
: "mdi-progress-clock"
|
|
185
|
+
}}
|
|
186
|
+
</v-icon>
|
|
187
187
|
{{ isGroupComplete(group) ? "Completed" : "Ongoing" }}
|
|
188
188
|
<template v-if="getGroupCompletedByName(group)">
|
|
189
189
|
· {{ getGroupCompletedByName(group) }}
|
|
190
190
|
</template>
|
|
191
|
-
</
|
|
191
|
+
</StatusChip>
|
|
192
192
|
</v-col>
|
|
193
193
|
<v-col
|
|
194
194
|
v-if="group.attachments && group.attachments.length > 0"
|
|
@@ -1,65 +1,65 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S PAGE SCAFFOLD.
|
|
5
|
+
|
|
6
|
+
The handoff opens Members with a 22px title row and puts the tabs INSIDE
|
|
7
|
+
the card's first row, with the pagination on the far end of that same
|
|
8
|
+
line. Before this the screen had no title at all - the page name existed
|
|
9
|
+
only in the rail and the browser tab - and the tabs sat in a toolbar
|
|
10
|
+
extension, i.e. a second stacked bar below a mostly empty one.
|
|
11
|
+
|
|
12
|
+
Nothing here changes what the screen does: the same two tabs route the
|
|
13
|
+
same way, the same refresh and pager call the same `getAll()`, the same
|
|
14
|
+
`headers` prop drives the same columns, and the same menu carries the
|
|
15
|
+
same four actions behind the same permission conditions.
|
|
16
|
+
-->
|
|
17
|
+
<PageHeader :title="props.title">
|
|
18
|
+
<template v-if="props.seatManagement !== 'index'" #actions>
|
|
19
|
+
<AppButton
|
|
10
20
|
:to="{
|
|
11
21
|
name: props.seatManagement,
|
|
12
22
|
params: { organization: props.orgId },
|
|
13
23
|
}"
|
|
14
|
-
size="large"
|
|
15
24
|
>
|
|
16
25
|
Manage seats
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
:key="i"
|
|
55
|
-
class="text-capitalize"
|
|
56
|
-
>
|
|
57
|
-
{{ tab.name }}
|
|
58
|
-
</v-tab>
|
|
59
|
-
</v-tabs>
|
|
60
|
-
</template>
|
|
61
|
-
</v-toolbar>
|
|
62
|
-
|
|
26
|
+
</AppButton>
|
|
27
|
+
</template>
|
|
28
|
+
</PageHeader>
|
|
29
|
+
|
|
30
|
+
<AppCard class="table-card">
|
|
31
|
+
<CardToolbar :count="pageRange">
|
|
32
|
+
<template #tabs>
|
|
33
|
+
<button
|
|
34
|
+
v-for="(tab, i) in tabOptions"
|
|
35
|
+
:key="i"
|
|
36
|
+
type="button"
|
|
37
|
+
role="tab"
|
|
38
|
+
class="app-tab"
|
|
39
|
+
:class="{ 'app-tab--active': selectedStatus === tab.status }"
|
|
40
|
+
:aria-selected="selectedStatus === tab.status"
|
|
41
|
+
@click="toRoute(tab.status)"
|
|
42
|
+
>
|
|
43
|
+
{{ tab.name }}
|
|
44
|
+
</button>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<template #right>
|
|
48
|
+
<AppButton
|
|
49
|
+
variant="icon"
|
|
50
|
+
icon="mdi-refresh"
|
|
51
|
+
aria-label="Refresh"
|
|
52
|
+
@click="getAll()"
|
|
53
|
+
/>
|
|
54
|
+
<local-pagination
|
|
55
|
+
v-model="page"
|
|
56
|
+
:length="pages"
|
|
57
|
+
@update:value="getAll()"
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
60
|
+
</CardToolbar>
|
|
61
|
+
|
|
62
|
+
<div class="app-table-scroll">
|
|
63
63
|
<v-data-table
|
|
64
64
|
:headers="props.headers"
|
|
65
65
|
:items="items"
|
|
@@ -73,6 +73,13 @@
|
|
|
73
73
|
<template #item.nature="{ item }">
|
|
74
74
|
{{ replaceMatch(item.nature, "_", " ") }}
|
|
75
75
|
</template>
|
|
76
|
+
<!-- The role is a soft pill in the design, not a plain word. Same
|
|
77
|
+
text, same column, same source field. -->
|
|
78
|
+
<template #item.roleName="{ item }">
|
|
79
|
+
<StatusChip v-if="item.roleName" :dot="false" pill tone="info">
|
|
80
|
+
{{ item.roleName }}
|
|
81
|
+
</StatusChip>
|
|
82
|
+
</template>
|
|
76
83
|
<template #item.dateInvited="{ item }">
|
|
77
84
|
{{ formatDateInvited(item.dateInvited) }}
|
|
78
85
|
</template>
|
|
@@ -89,8 +96,13 @@
|
|
|
89
96
|
offset-y
|
|
90
97
|
width="150"
|
|
91
98
|
>
|
|
92
|
-
<template v-slot:activator="{ props }">
|
|
93
|
-
<
|
|
99
|
+
<template v-slot:activator="{ props: menuProps }">
|
|
100
|
+
<AppButton
|
|
101
|
+
v-bind="menuProps"
|
|
102
|
+
variant="row"
|
|
103
|
+
icon="mdi-dots-vertical"
|
|
104
|
+
aria-label="Member actions"
|
|
105
|
+
/>
|
|
94
106
|
</template>
|
|
95
107
|
<v-list>
|
|
96
108
|
<v-list-item
|
|
@@ -128,8 +140,9 @@
|
|
|
128
140
|
</v-menu>
|
|
129
141
|
</template>
|
|
130
142
|
</v-data-table>
|
|
131
|
-
</
|
|
132
|
-
</
|
|
143
|
+
</div>
|
|
144
|
+
</AppCard>
|
|
145
|
+
|
|
133
146
|
<ConfirmDialog
|
|
134
147
|
v-model="confirmDialog"
|
|
135
148
|
:loading="updateLoading"
|
|
@@ -232,11 +245,17 @@
|
|
|
232
245
|
</v-card-text>
|
|
233
246
|
</v-card>
|
|
234
247
|
</v-dialog>
|
|
235
|
-
</
|
|
248
|
+
</div>
|
|
236
249
|
</template>
|
|
237
250
|
|
|
238
251
|
<script setup lang="ts">
|
|
239
252
|
const props = defineProps({
|
|
253
|
+
/** The design's page title. Every app that mounts this screen calls it
|
|
254
|
+
Members; a caller that names it otherwise passes its own. */
|
|
255
|
+
title: {
|
|
256
|
+
type: String,
|
|
257
|
+
default: "Members",
|
|
258
|
+
},
|
|
240
259
|
orgId: {
|
|
241
260
|
type: String,
|
|
242
261
|
default: "",
|
|
@@ -17,10 +17,8 @@
|
|
|
17
17
|
<v-row no-gutters align="center" class="w-100">
|
|
18
18
|
<v-col cols="auto" v-if="canCheckInOut">
|
|
19
19
|
<v-btn
|
|
20
|
-
class="text-none"
|
|
21
|
-
|
|
22
|
-
variant="tonal"
|
|
23
|
-
size="large"
|
|
20
|
+
class="text-none screen-btn-primary"
|
|
21
|
+
variant="flat"
|
|
24
22
|
@click="onCreateItem"
|
|
25
23
|
>
|
|
26
24
|
{{ hasCheckedInToday ? "Check Out" : "Check In" }}
|
|
@@ -28,7 +26,7 @@
|
|
|
28
26
|
</v-col>
|
|
29
27
|
<v-spacer />
|
|
30
28
|
|
|
31
|
-
<v-col cols="auto">
|
|
29
|
+
<v-col cols="auto" class="filter-field">
|
|
32
30
|
<v-text-field
|
|
33
31
|
v-model="searchInput"
|
|
34
32
|
density="compact"
|