@7365admin1/layer-common 3.2.7 → 3.2.8-staging.201
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/primitives.css +1113 -0
- package/assets/css/screens.css +1097 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +236 -0
- package/components/AcceptDialog.vue +17 -18
- package/components/AccessCard/AvailableStats.vue +24 -16
- package/components/AccessCardAddForm.vue +42 -42
- package/components/AccessCardAssignToUnitForm.vue +13 -21
- package/components/AccessCardDeleteDialog.vue +41 -39
- package/components/AccessCardDetailsDialog.vue +125 -79
- package/components/AccessCardPreviewDialog.vue +58 -84
- package/components/AccessCardQrTagging.vue +60 -63
- package/components/AccessCardReplaceForm.vue +27 -23
- package/components/AccessManagement.vue +86 -85
- package/components/AddEqupmentForm.vue +18 -22
- package/components/AddPassKeyToVisitor.vue +95 -73
- package/components/AppButton.vue +62 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +49 -0
- package/components/AppField.vue +76 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +247 -0
- package/components/AreaChecklistHistoryLogs.vue +51 -44
- package/components/AreaChecklistHistoryMain.vue +14 -7
- package/components/AreaFormDialog.vue +71 -59
- package/components/AreaMain.vue +73 -107
- package/components/AttachmentsViewer.vue +84 -22
- package/components/AttendanceCheckInOutDialog.vue +85 -88
- package/components/AttendanceDetailsDialog.vue +57 -63
- package/components/AttendanceMain.vue +4 -3
- package/components/AttendanceMapSearchDialog.vue +42 -44
- package/components/AttendanceSettingsDialog.vue +79 -63
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BtnUploadFile.vue +53 -11
- package/components/BuildingForm.vue +34 -27
- package/components/BuildingManagement/buildings.vue +39 -39
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BuildingUnitFormAdd.vue +20 -23
- package/components/BuildingUnitFormEdit.vue +8 -4
- package/components/BulletinBoardForm.vue +19 -24
- package/components/BulletinBoardManagement.vue +49 -57
- package/components/BulletinBoardView.vue +9 -6
- package/components/BulletinExpirationChip.vue +13 -4
- package/components/Button/Close.vue +2 -2
- package/components/CameraForm.vue +21 -22
- package/components/CameraMain.vue +50 -49
- package/components/Card/DeleteConfirmation.vue +24 -22
- package/components/Card/MemberInfoSummary.vue +28 -3
- package/components/Card/Toggle.vue +15 -3
- package/components/CardToolbar.vue +37 -0
- package/components/Carousel.vue +56 -36
- package/components/Chat/Bubbles.vue +43 -14
- package/components/Chat/Information.vue +49 -32
- package/components/Chat/ListCard.vue +38 -9
- package/components/Chat/Message.vue +41 -15
- package/components/Chat/Navigation.vue +27 -15
- package/components/Chat/SkeletonLoader.vue +9 -2
- package/components/ChecklistItemRow.vue +25 -1
- package/components/CleaningScheduleMain.vue +44 -49
- package/components/ClientDetailForm.vue +81 -56
- package/components/ClientMain.vue +92 -82
- package/components/ConfirmDialog.vue +14 -8
- package/components/Container/Standard.vue +9 -7
- package/components/CreateSubsciptionPlan.vue +148 -89
- package/components/DashboardEmptyState.vue +48 -3
- package/components/DashboardMain.vue +924 -676
- package/components/DashboardPanel.vue +84 -21
- package/components/DeliveryCompany.vue +34 -20
- package/components/Dialog/DeleteConfirmation.vue +20 -21
- package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
- package/components/Dialog/ReusablePrompt.vue +30 -29
- package/components/Dialog/UpdateMoreAction.vue +62 -59
- package/components/DocumentForm.vue +14 -21
- package/components/DocumentManagement.vue +162 -108
- package/components/DocumentViewer.vue +29 -12
- package/components/DrawImage.vue +29 -21
- package/components/EntryPass/QrTemplatePreview.vue +27 -16
- package/components/EntryPassInformation.vue +40 -10
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +84 -97
- package/components/EquipmentManagementMain.vue +81 -103
- package/components/Facility/BookingSetup.vue +43 -52
- package/components/Feedback/Form.vue +26 -12
- package/components/FeedbackMain.vue +35 -49
- package/components/FileInput.vue +75 -58
- package/components/FileInputWithList.vue +59 -12
- package/components/FormDialog.vue +60 -21
- package/components/HidAccessLogDashboard.vue +512 -490
- package/components/HidCameraCaptureDialog.vue +216 -0
- package/components/HidEnabledGate.vue +67 -9
- package/components/HidIntercomManagement.vue +1322 -653
- package/components/HidQrCodeConfiguration.vue +1030 -258
- package/components/HidReaderForm.vue +142 -59
- package/components/HidReaderManagement.vue +211 -323
- package/components/HidServiceSettingsPanel.vue +44 -13
- package/components/HidUserEnrollment.vue +1185 -412
- package/components/HygieneUpdateMoreAction.vue +44 -44
- package/components/IncidentReport/Authorities.vue +30 -18
- package/components/IncidentReport/IncidentInformation.vue +10 -10
- package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
- package/components/IncidentReport/affectedEntities.vue +41 -20
- package/components/Input/Date.vue +3 -0
- package/components/Input/DatePicker.vue +1 -2
- package/components/Input/DateTimePicker.vue +1 -1
- package/components/Input/File.vue +55 -10
- package/components/Input/FileV2.vue +39 -6
- package/components/Input/InputPhoneNumberV2.vue +2 -1
- package/components/Input/ListGroupSelection.vue +56 -8
- package/components/Input/NRICNumber.vue +1 -0
- package/components/Input/NewDate.vue +1 -0
- package/components/Input/Number.vue +1 -0
- package/components/Input/Password.vue +1 -0
- package/components/Input/PhoneNumber.vue +21 -1
- package/components/Input/VehicleNumber.vue +1 -0
- package/components/InputLabel.vue +2 -2
- package/components/InvitationClientForm.vue +21 -19
- package/components/InvitationForm.vue +15 -21
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +263 -36
- package/components/Layout/NavigationDrawer.vue +300 -30
- package/components/ListItem.vue +33 -3
- package/components/LocalPagination.vue +27 -3
- package/components/ManageChecklistMain.vue +136 -86
- package/components/MemberInformation.vue +32 -16
- package/components/MemberMain.vue +113 -93
- package/components/MyAttendanceMain.vue +3 -5
- package/components/NavigationItem.vue +83 -3
- package/components/Nfc/NFCPatrolReportMain.vue +47 -33
- package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
- package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
- package/components/Nfc/NFCPatrolSettings.vue +14 -4
- package/components/Nfc/NFCTagForm.vue +22 -8
- package/components/Nfc/NFCTagMain.vue +41 -43
- package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
- package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
- package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
- package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
- package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
- package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
- package/components/NotificationSettings.vue +373 -0
- package/components/NumberSettingField.vue +9 -2
- package/components/OnlineFormConfigurationForm.vue +725 -237
- package/components/OnlineFormFill.vue +213 -106
- package/components/OnlineFormRenderPreview.vue +25 -30
- package/components/OnlineFormsConfiguration.vue +113 -109
- package/components/OrgViewDialog.vue +34 -16
- package/components/OvernightParkingAvailability.vue +49 -26
- package/components/OvernightParkingManagement.vue +78 -18
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +109 -53
- package/components/PasswordConfirmation.vue +44 -26
- package/components/PeopleForm.vue +41 -20
- package/components/PhotoUpload.vue +110 -48
- package/components/PlaceholderComponent.vue +35 -19
- package/components/PlanActionsMenu.vue +12 -2
- package/components/PlanStatusBadge.vue +7 -8
- package/components/PlateNumberDisplay.vue +9 -6
- package/components/QrTemplate/PrintDialog.vue +66 -43
- package/components/RolePermissionFormCreate.vue +104 -79
- package/components/RolePermissionFormPreviewUpdate.vue +111 -109
- package/components/RolePermissionMain.vue +183 -81
- package/components/ScanVisitorQRCode.vue +99 -35
- package/components/ScheduleAreaMain.vue +34 -59
- package/components/ScheduleTaskForm.vue +61 -42
- package/components/ScheduleTaskMain.vue +56 -69
- package/components/SearchVehicleNumberUser.vue +39 -37
- package/components/ServiceProviderFormCreate.vue +29 -20
- package/components/ServiceProviderInvitationPrompt.vue +155 -0
- package/components/ServiceProviderMain.vue +601 -432
- package/components/Signature.vue +40 -34
- package/components/SignaturePad.vue +5 -29
- package/components/SiteSettings.vue +41 -11
- package/components/SiteSettingsWorkOrder.vue +61 -30
- package/components/SlideCardGroup.vue +35 -11
- package/components/Snackbar.vue +36 -3
- package/components/SpecificAttr.vue +18 -8
- package/components/StatusChip.vue +156 -0
- package/components/StockCard.vue +17 -12
- package/components/SubscriptionPlanTable.vue +207 -140
- package/components/SwitchContext.vue +201 -9
- package/components/TableHygiene.vue +36 -46
- package/components/TableList.vue +10 -10
- package/components/TableListSecondary.vue +105 -54
- package/components/TableMain.vue +251 -78
- package/components/TableV3.vue +61 -70
- package/components/TiptapEditor.vue +33 -40
- package/components/TiptapEditorQr.vue +5 -2
- package/components/Tooltip/Info.vue +69 -24
- package/components/UnitMain.vue +40 -60
- package/components/UnitPersonCard.vue +42 -13
- package/components/VehicleAddSelection.vue +48 -32
- package/components/VehicleForm.vue +112 -129
- package/components/VehicleManagement.vue +26 -12
- package/components/VehicleUpdateMoreAction.vue +59 -46
- package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
- package/components/VisitorForm.vue +774 -210
- package/components/VisitorFormSelection.vue +40 -37
- package/components/VisitorManagement.vue +239 -82
- package/components/VisitorPassKeyQRScanner.vue +65 -18
- package/components/VisitorSearchResultSummary.vue +60 -16
- package/components/VisitorSocketPopUp.vue +22 -14
- package/components/VisitorsReportPreview.vue +44 -29
- package/components/WorkOrder/Create.vue +26 -17
- package/components/WorkOrder/ListView.vue +38 -7
- package/components/WorkOrder/Main.vue +62 -59
- package/composables/useFeedback.ts +3 -3
- package/composables/useHidAmico.ts +269 -0
- package/composables/useHidNavigation.ts +2 -9
- package/composables/useMember.ts +3 -3
- package/composables/useNFCPatrolDailyReport.ts +13 -0
- package/composables/useNFCPatrolMonthlyReport.ts +22 -56
- package/composables/useNFCPatrolReport.ts +13 -0
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useRole.ts +22 -0
- package/composables/useServiceProvider.ts +3 -3
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +336 -0
- package/composables/useSubscriptionPlan.ts +62 -0
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/composables/useWebUsb.ts +127 -37
- package/composables/useWorkOrder.ts +17 -3
- package/error.vue +30 -6
- package/nuxt.config.ts +36 -0
- package/package.json +25 -4
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/pages/notification-settings.vue +19 -0
- package/pages/payment-method-linked.vue +34 -10
- package/pages/require-customer.vue +48 -10
- package/pages/require-organization-membership.vue +32 -8
- package/pages/unauthorized.vue +29 -4
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +69 -2
- package/types/nfc-patrol-report.ts +3 -2
- package/types/site.d.ts +71 -0
- package/types/subscription.ts +18 -11
- package/utils/breadcrumb.test.ts +108 -0
- package/utils/breadcrumb.ts +82 -0
- package/utils/dashboard.test.ts +118 -0
- package/utils/dashboard.ts +53 -0
- package/utils/data.test.ts +95 -0
- package/utils/data.ts +40 -2
- package/utils/hid-discover.test.ts +102 -0
- package/utils/list-page.test.ts +60 -0
- package/utils/list-page.ts +38 -0
- package/utils/materialSymbols.ts +363 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/nfc-patrol-report.test.ts +130 -0
- package/utils/nfc-patrol-report.ts +101 -0
- package/utils/role.test.ts +69 -0
- package/utils/role.ts +10 -0
- package/utils/route-name.test.ts +85 -0
- package/utils/route-name.ts +24 -0
- package/utils/status.test.ts +110 -0
- package/utils/status.ts +135 -0
- package/utils/table-card.test.ts +84 -0
- package/utils/theme-aa-ledger.ts +148 -0
- package/utils/theme.test.ts +485 -7
- package/utils/theme.ts +295 -110
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.editorconfig +0 -12
- package/.github/workflows/main.yml +0 -17
- package/.github/workflows/publish-staging.yml +0 -47
- package/.github/workflows/publish.yml +0 -39
- package/PUBLISHING.md +0 -269
- package/components/HidIdentityMapping.vue +0 -975
- package/components/ScheduleTaskAreaFormDialog.vue +0 -144
- package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
- package/components/ScheduleTastTicketMain.vue +0 -182
- package/components/TableWithButton.vue +0 -94
- package/test/visitor-socket.test.mjs +0 -36
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
THE TEXT FIELD.
|
|
3
|
+
|
|
4
|
+
40px (34px compact), 10px radius, a 1px border, 13.5px/500 - and the label
|
|
5
|
+
OUTSIDE the box as a small muted word, not Vuetify's outlined field with a
|
|
6
|
+
label that floats up through the border. A native `<input>`, so the browser
|
|
7
|
+
keeps the keyboard, autofill and accessibility for free.
|
|
8
|
+
-->
|
|
9
|
+
<template>
|
|
10
|
+
<label
|
|
11
|
+
v-bind="rootAttrs"
|
|
12
|
+
class="app-field"
|
|
13
|
+
:class="[{ 'app-field--compact': compact, 'app-field--search': search }, klass]"
|
|
14
|
+
>
|
|
15
|
+
<v-icon
|
|
16
|
+
v-if="icon || search"
|
|
17
|
+
class="app-field__icon"
|
|
18
|
+
:icon="icon || 'mdi-magnify'"
|
|
19
|
+
size="18"
|
|
20
|
+
/>
|
|
21
|
+
<span v-if="label" class="app-field__label">{{ label }}</span>
|
|
22
|
+
<input
|
|
23
|
+
class="app-field__input"
|
|
24
|
+
v-bind="inputAttrs"
|
|
25
|
+
:type="type"
|
|
26
|
+
:value="model"
|
|
27
|
+
:placeholder="placeholder"
|
|
28
|
+
:disabled="disabled"
|
|
29
|
+
:readonly="readonly"
|
|
30
|
+
@input="model = ($event.target as HTMLInputElement).value"
|
|
31
|
+
/>
|
|
32
|
+
<!-- A trailing control INSIDE the box: the password reveal on the HID PIN,
|
|
33
|
+
SIP password and reader password fields. The handoff draws no password
|
|
34
|
+
field at all, so the eye is this product's, not the design's - but it
|
|
35
|
+
belongs in one place rather than being re-hacked in three screens. -->
|
|
36
|
+
<slot name="trailing" />
|
|
37
|
+
</label>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { computed, useAttrs } from "vue";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The root is a `<label>`, so an `aria-label`, `inputmode` or `autocomplete`
|
|
45
|
+
* left to fall through would land on the LABEL and never reach the input -
|
|
46
|
+
* silently costing the field its accessible name. `class` and `style` still
|
|
47
|
+
* belong on the root (that is what every existing caller styles), so the two
|
|
48
|
+
* are split rather than moved wholesale.
|
|
49
|
+
*/
|
|
50
|
+
defineOptions({ inheritAttrs: false });
|
|
51
|
+
|
|
52
|
+
const attrs = useAttrs();
|
|
53
|
+
const rootAttrs = computed(() => ({ class: attrs.class, style: attrs.style }));
|
|
54
|
+
const inputAttrs = computed(() => {
|
|
55
|
+
const { class: _c, style: _s, ...rest } = attrs;
|
|
56
|
+
return rest;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const model = defineModel({ type: String, default: "" });
|
|
60
|
+
|
|
61
|
+
defineProps({
|
|
62
|
+
label: { type: String, default: "" },
|
|
63
|
+
placeholder: { type: String, default: "" },
|
|
64
|
+
icon: { type: String, default: "" },
|
|
65
|
+
type: { type: String, default: "text" },
|
|
66
|
+
/** The design's search field: a magnifier and `flex:1` in a filter row. */
|
|
67
|
+
search: { type: Boolean, default: false },
|
|
68
|
+
compact: { type: Boolean, default: false },
|
|
69
|
+
disabled: { type: Boolean, default: false },
|
|
70
|
+
/** A field that prints a derived value - a preview, a computed reference -
|
|
71
|
+
and is not typed into. Kept distinct from `disabled`, which also greys
|
|
72
|
+
the box and takes it out of the tab order. */
|
|
73
|
+
readonly: { type: Boolean, default: false },
|
|
74
|
+
klass: { type: String, default: "" },
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
THE KPI TILE.
|
|
3
|
+
|
|
4
|
+
The handoff's tile is three rows: the soft icon square AND the uppercase
|
|
5
|
+
label together on the first, the big tabular number with its percentage pill
|
|
6
|
+
BESIDE it on the second, the sub-line on the third. The product had the icon
|
|
7
|
+
alone on a row of its own, the label under it, and the pill stranded at the
|
|
8
|
+
bottom next to the sub-line - the same figures in a different shape.
|
|
9
|
+
|
|
10
|
+
It draws what it is given and nothing else: no fetch, no formatting, no
|
|
11
|
+
threshold logic. `#action` is the far end of the first row (a filter menu, a
|
|
12
|
+
"View All"); `#default`, when given, replaces the figure rows entirely for
|
|
13
|
+
the tiles that list items instead of counting them.
|
|
14
|
+
-->
|
|
15
|
+
<template>
|
|
16
|
+
<AppCard class="app-kpi d-flex flex-column flex-grow-1">
|
|
17
|
+
<div class="app-kpi__head">
|
|
18
|
+
<span
|
|
19
|
+
v-if="icon"
|
|
20
|
+
class="app-kpi__icon"
|
|
21
|
+
:style="tint ? { backgroundColor: tint } : undefined"
|
|
22
|
+
>
|
|
23
|
+
<v-icon :icon="icon" :color="color || undefined" size="19" />
|
|
24
|
+
</span>
|
|
25
|
+
|
|
26
|
+
<span class="app-kpi__label">{{ label }}</span>
|
|
27
|
+
|
|
28
|
+
<span v-if="$slots.action" class="app-kpi__action">
|
|
29
|
+
<slot name="action" />
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<slot>
|
|
34
|
+
<div class="app-kpi__figure">
|
|
35
|
+
<span class="app-kpi__value">{{ value }}</span>
|
|
36
|
+
|
|
37
|
+
<span
|
|
38
|
+
v-if="chip"
|
|
39
|
+
class="app-kpi__chip"
|
|
40
|
+
:class="{ 'app-kpi__chip--down': down }"
|
|
41
|
+
>
|
|
42
|
+
{{ chip }}
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div v-if="sub" class="app-kpi__sub">{{ sub }}</div>
|
|
47
|
+
</slot>
|
|
48
|
+
</AppCard>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
defineProps({
|
|
53
|
+
icon: { type: String, default: "" },
|
|
54
|
+
/** The icon square's soft fill - a token-derived colour from the caller. */
|
|
55
|
+
tint: { type: String, default: "" },
|
|
56
|
+
color: { type: String, default: "" },
|
|
57
|
+
label: { type: String, default: "" },
|
|
58
|
+
value: { type: [String, Number], default: "" },
|
|
59
|
+
/** The percentage pill's text, already formatted by the caller. */
|
|
60
|
+
chip: { type: String, default: "" },
|
|
61
|
+
/** Draws the pill in the error tone rather than the success one. */
|
|
62
|
+
down: { type: Boolean, default: false },
|
|
63
|
+
sub: { type: String, default: "" },
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
THE SEGMENTED CONTROL - `Today | Week | Month`.
|
|
3
|
+
|
|
4
|
+
A tray of plain `<button>`s, not a `v-btn-toggle`. The toggle brings its own
|
|
5
|
+
height scale, dividers, ripple and overlay layer, and its selected state is
|
|
6
|
+
drawn from `on-surface` - which is why the product's selected segment reads
|
|
7
|
+
grey where the design fills it with the accent.
|
|
8
|
+
|
|
9
|
+
Selection only: it writes the same `v-model` the toggle wrote, and the value
|
|
10
|
+
set is whatever the caller passes. No behaviour is added here.
|
|
11
|
+
-->
|
|
12
|
+
<template>
|
|
13
|
+
<div class="app-seg" role="group">
|
|
14
|
+
<button
|
|
15
|
+
v-for="option in options"
|
|
16
|
+
:key="String(option.value)"
|
|
17
|
+
type="button"
|
|
18
|
+
class="app-seg__item"
|
|
19
|
+
:class="{ 'app-seg__item--on': model === option.value }"
|
|
20
|
+
:aria-pressed="model === option.value"
|
|
21
|
+
@click="model = option.value"
|
|
22
|
+
>
|
|
23
|
+
{{ option.label }}
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
defineProps({
|
|
30
|
+
/** `{ label, value }` - the same option list the toggle was given. */
|
|
31
|
+
options: {
|
|
32
|
+
type: Array as PropType<Array<{ label: string; value: any }>>,
|
|
33
|
+
default: () => [],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const model = defineModel<any>({ default: "" });
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
THE SELECT.
|
|
3
|
+
|
|
4
|
+
The handoff draws it as the text field with the filter's name in muted 12px
|
|
5
|
+
on the left, the chosen value next to it and a chevron on the right. Both
|
|
6
|
+
single and multiple values use the same themed popover, so the option list
|
|
7
|
+
does not fall back to the browser/OS styling.
|
|
8
|
+
|
|
9
|
+
`multiple` is the ONE case where native is the wrong answer. A native
|
|
10
|
+
`<select multiple>` is not a dropdown at all - it is an always-open scrolling
|
|
11
|
+
LIST BOX - so adopting it would turn the daily filter bars into list boxes
|
|
12
|
+
and change how the staff work. So multi opens a POPOVER in the same field
|
|
13
|
+
chrome, with the chosen values as the design's soft removable pills.
|
|
14
|
+
|
|
15
|
+
A single-value consumer that does not use `return-object` still emits the
|
|
16
|
+
same string that the old native `<select>` emitted.
|
|
17
|
+
-->
|
|
18
|
+
<template>
|
|
19
|
+
<v-menu
|
|
20
|
+
v-if="!multiple"
|
|
21
|
+
:close-on-content-click="true"
|
|
22
|
+
:disabled="disabled"
|
|
23
|
+
location="bottom start"
|
|
24
|
+
offset="4"
|
|
25
|
+
>
|
|
26
|
+
<template #activator="{ props: activator }">
|
|
27
|
+
<div
|
|
28
|
+
v-bind="{ ...activator, ...$attrs }"
|
|
29
|
+
class="app-field app-select app-select--single"
|
|
30
|
+
:class="{ 'app-field--compact': compact, 'app-select--disabled': disabled }"
|
|
31
|
+
role="button"
|
|
32
|
+
:aria-disabled="disabled"
|
|
33
|
+
:tabindex="disabled ? -1 : 0"
|
|
34
|
+
>
|
|
35
|
+
<span v-if="label" class="app-field__label">{{ label }}</span>
|
|
36
|
+
<span
|
|
37
|
+
class="app-select__single-value"
|
|
38
|
+
:class="{ 'app-select__placeholder': !hasSingleValue }"
|
|
39
|
+
>
|
|
40
|
+
{{ singleDisplay }}
|
|
41
|
+
</span>
|
|
42
|
+
<button
|
|
43
|
+
v-if="clearable && hasSingleValue"
|
|
44
|
+
type="button"
|
|
45
|
+
class="app-select__clear"
|
|
46
|
+
aria-label="Clear"
|
|
47
|
+
@click.stop="clearSingle"
|
|
48
|
+
>
|
|
49
|
+
<v-icon icon="mdi-close-circle" size="15" />
|
|
50
|
+
</button>
|
|
51
|
+
<v-icon class="app-field__icon" icon="mdi-chevron-down" size="17" />
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<div class="app-select__menu" role="listbox">
|
|
56
|
+
<button
|
|
57
|
+
v-for="opt in normalized"
|
|
58
|
+
:key="String(opt.value)"
|
|
59
|
+
type="button"
|
|
60
|
+
class="app-select__option"
|
|
61
|
+
:class="{ 'app-select__option--selected': isSingleOn(opt) }"
|
|
62
|
+
role="option"
|
|
63
|
+
:aria-selected="isSingleOn(opt)"
|
|
64
|
+
@click="selectSingle(opt)"
|
|
65
|
+
>
|
|
66
|
+
<span class="app-select__option-text">{{ opt.title }}</span>
|
|
67
|
+
<v-icon v-if="isSingleOn(opt)" icon="mdi-check" size="17" />
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</v-menu>
|
|
71
|
+
|
|
72
|
+
<v-menu
|
|
73
|
+
v-else
|
|
74
|
+
:close-on-content-click="false"
|
|
75
|
+
:disabled="disabled"
|
|
76
|
+
location="bottom start"
|
|
77
|
+
offset="4"
|
|
78
|
+
>
|
|
79
|
+
<template #activator="{ props: activator }">
|
|
80
|
+
<div
|
|
81
|
+
v-bind="{ ...activator, ...$attrs }"
|
|
82
|
+
class="app-field app-select"
|
|
83
|
+
:class="{ 'app-field--compact': compact, 'app-select--disabled': disabled }"
|
|
84
|
+
role="button"
|
|
85
|
+
:aria-disabled="disabled"
|
|
86
|
+
:tabindex="disabled ? -1 : 0"
|
|
87
|
+
>
|
|
88
|
+
<span v-if="label" class="app-field__label">{{ label }}</span>
|
|
89
|
+
<span class="app-select__values">
|
|
90
|
+
<StatusChip
|
|
91
|
+
v-for="opt in selectedOptions"
|
|
92
|
+
:key="String(opt.value)"
|
|
93
|
+
tone="neutral"
|
|
94
|
+
pill
|
|
95
|
+
:dot="false"
|
|
96
|
+
>
|
|
97
|
+
{{ opt.title }}
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
class="app-select__x"
|
|
101
|
+
:aria-label="`Remove ${opt.title}`"
|
|
102
|
+
@click.stop="toggle(opt)"
|
|
103
|
+
>
|
|
104
|
+
<v-icon icon="mdi-close" size="12" />
|
|
105
|
+
</button>
|
|
106
|
+
</StatusChip>
|
|
107
|
+
<span v-if="!selectedOptions.length" class="app-select__placeholder">
|
|
108
|
+
{{ placeholder }}
|
|
109
|
+
</span>
|
|
110
|
+
</span>
|
|
111
|
+
<button
|
|
112
|
+
v-if="clearable && selectedOptions.length"
|
|
113
|
+
type="button"
|
|
114
|
+
class="app-select__clear"
|
|
115
|
+
aria-label="Clear"
|
|
116
|
+
@click.stop="clear"
|
|
117
|
+
>
|
|
118
|
+
<v-icon icon="mdi-close-circle" size="15" />
|
|
119
|
+
</button>
|
|
120
|
+
<v-icon class="app-field__icon" icon="mdi-chevron-down" size="17" />
|
|
121
|
+
</div>
|
|
122
|
+
</template>
|
|
123
|
+
|
|
124
|
+
<div class="app-select__menu">
|
|
125
|
+
<button
|
|
126
|
+
v-for="opt in normalized"
|
|
127
|
+
:key="String(opt.value)"
|
|
128
|
+
type="button"
|
|
129
|
+
class="app-select__option"
|
|
130
|
+
:aria-pressed="isOn(opt)"
|
|
131
|
+
@click="toggle(opt)"
|
|
132
|
+
>
|
|
133
|
+
<v-icon
|
|
134
|
+
class="app-select__box"
|
|
135
|
+
:class="{ 'app-select__box--on': isOn(opt) }"
|
|
136
|
+
:icon="isOn(opt) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
|
|
137
|
+
size="17"
|
|
138
|
+
/>
|
|
139
|
+
<span class="app-select__option-text">{{ opt.title }}</span>
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
</v-menu>
|
|
143
|
+
</template>
|
|
144
|
+
|
|
145
|
+
<script setup lang="ts">
|
|
146
|
+
/* `v-menu` has no single root node, so attributes and classes have to be put
|
|
147
|
+
on each activator by hand. */
|
|
148
|
+
defineOptions({ inheritAttrs: false });
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* One model for both shapes: a string when single, an array when multiple.
|
|
152
|
+
* The default stays `""` so an existing single consumer that never sets its
|
|
153
|
+
* v-model starts on the same empty string it starts on today.
|
|
154
|
+
*/
|
|
155
|
+
const model = defineModel<any>({ default: "" });
|
|
156
|
+
|
|
157
|
+
const props = defineProps({
|
|
158
|
+
label: { type: String, default: "" },
|
|
159
|
+
/** Strings, or the `{ title, value }` shape `v-select` already takes. */
|
|
160
|
+
items: { type: Array as PropType<any[]>, default: () => [] },
|
|
161
|
+
itemTitle: { type: String, default: "title" },
|
|
162
|
+
itemValue: { type: String, default: "value" },
|
|
163
|
+
compact: { type: Boolean, default: false },
|
|
164
|
+
disabled: { type: Boolean, default: false },
|
|
165
|
+
/** Popover multi-select with removable pills. Never a native list box. */
|
|
166
|
+
multiple: { type: Boolean, default: false },
|
|
167
|
+
/** Emit the source item rather than its `itemValue`. */
|
|
168
|
+
returnObject: { type: Boolean, default: false },
|
|
169
|
+
clearable: { type: Boolean, default: false },
|
|
170
|
+
placeholder: { type: String, default: "" },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const normalized = computed(() =>
|
|
174
|
+
props.items.map((item: any) =>
|
|
175
|
+
typeof item === "object" && item !== null
|
|
176
|
+
? { title: item[props.itemTitle], value: item[props.itemValue], raw: item }
|
|
177
|
+
: { title: String(item), value: item, raw: item }
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
/* A model entry is either the plain value or - under `return-object` - the
|
|
182
|
+
source item, so everything compares on this one key. */
|
|
183
|
+
const keyOf = (v: any) =>
|
|
184
|
+
String(v !== null && typeof v === "object" ? v[props.itemValue] : v);
|
|
185
|
+
|
|
186
|
+
/* ---- single ---------------------------------------------------------- */
|
|
187
|
+
|
|
188
|
+
const hasSingleValue = computed(
|
|
189
|
+
() => model.value !== "" && model.value !== null && model.value !== undefined
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const selectedSingleOption = computed(() =>
|
|
193
|
+
normalized.value.find((opt) => String(opt.value) === keyOf(model.value))
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const singleDisplay = computed(() =>
|
|
197
|
+
hasSingleValue.value
|
|
198
|
+
? selectedSingleOption.value?.title ?? keyOf(model.value)
|
|
199
|
+
: props.placeholder
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const isSingleOn = (opt: any) =>
|
|
203
|
+
hasSingleValue.value && String(opt.value) === keyOf(model.value);
|
|
204
|
+
|
|
205
|
+
function selectSingle(opt: any) {
|
|
206
|
+
if (props.disabled) return;
|
|
207
|
+
model.value = props.returnObject ? opt.raw : String(opt.value);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function clearSingle() {
|
|
211
|
+
if (props.disabled) return;
|
|
212
|
+
model.value = "";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ---- multiple -------------------------------------------------------- */
|
|
216
|
+
|
|
217
|
+
const selected = computed<any[]>(() =>
|
|
218
|
+
Array.isArray(model.value) ? model.value : []
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const selectedKeys = computed(() => new Set(selected.value.map(keyOf)));
|
|
222
|
+
|
|
223
|
+
const isOn = (opt: any) => selectedKeys.value.has(String(opt.value));
|
|
224
|
+
|
|
225
|
+
/* Fall back to the raw key when a chosen value is not in `items` - an async
|
|
226
|
+
item list can arrive after the model does, and the chip must still print. */
|
|
227
|
+
const selectedOptions = computed(() =>
|
|
228
|
+
selected.value.map(
|
|
229
|
+
(v) =>
|
|
230
|
+
normalized.value.find((o) => String(o.value) === keyOf(v)) ?? {
|
|
231
|
+
title: keyOf(v),
|
|
232
|
+
value: keyOf(v),
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
function toggle(opt: any) {
|
|
238
|
+
if (props.disabled) return;
|
|
239
|
+
model.value = isOn(opt)
|
|
240
|
+
? selected.value.filter((v) => keyOf(v) !== String(opt.value))
|
|
241
|
+
: [...selected.value, props.returnObject ? opt.raw : opt.value];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function clear() {
|
|
245
|
+
model.value = [];
|
|
246
|
+
}
|
|
247
|
+
</script>
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters align="center" justify="center">
|
|
3
3
|
<v-col cols="12">
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
</
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<v-card-text class="pa-0">
|
|
4
|
+
<!-- The screen's name and its Back move out of the `grey-lighten-4` bar -
|
|
5
|
+
a slab that stayed white in dark mode - onto the design's title row.
|
|
6
|
+
All five service apps mount this component bare, so the layer is the
|
|
7
|
+
only place the page name can come from. -->
|
|
8
|
+
<PageHeader title="Area Checklist">
|
|
9
|
+
<template #actions>
|
|
10
|
+
<AppButton variant="ghost" icon="mdi-chevron-left" @click="back()">
|
|
11
|
+
Back
|
|
12
|
+
</AppButton>
|
|
13
|
+
</template>
|
|
14
|
+
</PageHeader>
|
|
15
|
+
|
|
16
|
+
<AppCard>
|
|
17
|
+
<div class="pa-0">
|
|
19
18
|
<v-row no-gutters>
|
|
20
19
|
<v-col
|
|
21
20
|
cols="12"
|
|
22
21
|
md="6"
|
|
23
22
|
lg="5"
|
|
24
23
|
xl="4"
|
|
25
|
-
|
|
24
|
+
class="checklist-detail__split"
|
|
26
25
|
>
|
|
27
26
|
<v-row no-gutters class="pa-6">
|
|
28
27
|
<v-col cols="12" class="mb-4">
|
|
@@ -47,11 +46,11 @@
|
|
|
47
46
|
|
|
48
47
|
<v-col cols="12" class="mb-3">
|
|
49
48
|
<div class="text-body-2 mb-1">Status:</div>
|
|
50
|
-
<
|
|
51
|
-
class="text-capitalize
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
<StatusChip
|
|
50
|
+
class="text-capitalize"
|
|
51
|
+
:status="status"
|
|
52
|
+
:label="status || 'N/A'"
|
|
53
|
+
/>
|
|
55
54
|
</v-col>
|
|
56
55
|
</v-row>
|
|
57
56
|
</v-col>
|
|
@@ -119,15 +118,15 @@
|
|
|
119
118
|
class="text-subtitle-2 font-weight-bold px-0"
|
|
120
119
|
>
|
|
121
120
|
Set {{ checklistSet.set }}
|
|
122
|
-
<
|
|
121
|
+
<StatusChip
|
|
123
122
|
v-if="checklistSet.isScheduleTask"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class="
|
|
123
|
+
tone="info"
|
|
124
|
+
:dot="false"
|
|
125
|
+
pill
|
|
126
|
+
class="ml-2"
|
|
128
127
|
>
|
|
129
128
|
Schedule Task
|
|
130
|
-
</
|
|
129
|
+
</StatusChip>
|
|
131
130
|
</v-list-subheader>
|
|
132
131
|
|
|
133
132
|
<v-list-item
|
|
@@ -161,22 +160,14 @@
|
|
|
161
160
|
</div>
|
|
162
161
|
</v-col>
|
|
163
162
|
<v-col cols="12" sm="2" class="text-right">
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
: 'grey'
|
|
169
|
-
"
|
|
170
|
-
:variant="
|
|
171
|
-
unit.status?.toLowerCase() === 'completed'
|
|
172
|
-
? 'flat'
|
|
173
|
-
: 'outlined'
|
|
174
|
-
"
|
|
175
|
-
size="small"
|
|
163
|
+
<!-- Same word decides the colour, only now through the
|
|
164
|
+
shared status map, so "Completed" here is the same
|
|
165
|
+
green as on every other screen. -->
|
|
166
|
+
<StatusChip
|
|
176
167
|
class="text-capitalize"
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
168
|
+
:status="unit.status || 'Open'"
|
|
169
|
+
:label="unit.status || 'Open'"
|
|
170
|
+
/>
|
|
180
171
|
</v-col>
|
|
181
172
|
</v-row>
|
|
182
173
|
</v-list-item>
|
|
@@ -191,8 +182,8 @@
|
|
|
191
182
|
</v-row>
|
|
192
183
|
</v-col>
|
|
193
184
|
</v-row>
|
|
194
|
-
</
|
|
195
|
-
</
|
|
185
|
+
</div>
|
|
186
|
+
</AppCard>
|
|
196
187
|
</v-col>
|
|
197
188
|
</v-row>
|
|
198
189
|
</template>
|
|
@@ -242,3 +233,19 @@ const timeOut = ref<string>("N/A");
|
|
|
242
233
|
const status = ref<string>("N/A");
|
|
243
234
|
const signatureUrl = ref<string>("");
|
|
244
235
|
</script>
|
|
236
|
+
|
|
237
|
+
<style scoped>
|
|
238
|
+
/* Was an inline `rgba(0,0,0,.12)`, which is the same near-black rule in dark
|
|
239
|
+
mode. The token follows the theme. At `md` and below the two halves stack, so
|
|
240
|
+
the rule moves under the first one. */
|
|
241
|
+
.checklist-detail__split {
|
|
242
|
+
border-bottom: 1px solid var(--border);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@media (min-width: 960px) {
|
|
246
|
+
.checklist-detail__split {
|
|
247
|
+
border-bottom: none;
|
|
248
|
+
border-right: 1px solid var(--border);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
</style>
|
|
@@ -32,11 +32,12 @@
|
|
|
32
32
|
|
|
33
33
|
<template #extension>
|
|
34
34
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
<!--
|
|
36
|
+
A bare `v-card` here paints Vuetify's own `surface`, a shade off
|
|
37
|
+
the table card it sits inside. A plain row on the card's own
|
|
38
|
+
background, with both filters on the design's field.
|
|
39
|
+
-->
|
|
40
|
+
<div class="w-100 filter-bar d-flex align-center ga-5">
|
|
40
41
|
<v-text-field
|
|
41
42
|
v-model="searchInput"
|
|
42
43
|
density="compact"
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
max-width="300"
|
|
46
47
|
append-inner-icon="mdi-magnify"
|
|
47
48
|
hide-details
|
|
49
|
+
class="filter-field"
|
|
48
50
|
/>
|
|
49
51
|
<v-select
|
|
50
52
|
v-model="status"
|
|
@@ -55,8 +57,9 @@
|
|
|
55
57
|
clearable
|
|
56
58
|
hide-details
|
|
57
59
|
max-width="200"
|
|
60
|
+
class="filter-field"
|
|
58
61
|
/>
|
|
59
|
-
</
|
|
62
|
+
</div>
|
|
60
63
|
</v-row>
|
|
61
64
|
</template>
|
|
62
65
|
|
|
@@ -80,7 +83,11 @@
|
|
|
80
83
|
</div>
|
|
81
84
|
</template>
|
|
82
85
|
<template #item.status="{ value }">
|
|
83
|
-
<
|
|
86
|
+
<StatusChip
|
|
87
|
+
class="text-capitalize"
|
|
88
|
+
:status="value"
|
|
89
|
+
:label="value || 'No Status'"
|
|
90
|
+
/>
|
|
84
91
|
</template>
|
|
85
92
|
</TableMain></v-col
|
|
86
93
|
>
|