@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,373 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="pa-8" justify="center">
|
|
3
|
+
<v-col cols="12" md="10">
|
|
4
|
+
<v-row no-gutters>
|
|
5
|
+
<v-col cols="12">
|
|
6
|
+
<PageHeader title="Notifications" />
|
|
7
|
+
</v-col>
|
|
8
|
+
|
|
9
|
+
<v-col cols="12" md="8" class="screen-hint intro-copy">
|
|
10
|
+
Choose what you want to hear about, and how. These settings are yours
|
|
11
|
+
alone and apply everywhere you use iService — on this site and on your
|
|
12
|
+
phone.
|
|
13
|
+
</v-col>
|
|
14
|
+
|
|
15
|
+
<!-- loading -->
|
|
16
|
+
<v-col v-if="loading" cols="12" class="mt-8">
|
|
17
|
+
<v-skeleton-loader type="list-item-two-line@4" />
|
|
18
|
+
</v-col>
|
|
19
|
+
|
|
20
|
+
<!-- could not load -->
|
|
21
|
+
<v-col v-else-if="loadError" cols="12" class="mt-8">
|
|
22
|
+
<v-alert type="error" variant="tonal" rounded="lg">
|
|
23
|
+
{{ loadError }}
|
|
24
|
+
</v-alert>
|
|
25
|
+
</v-col>
|
|
26
|
+
|
|
27
|
+
<!-- nothing to offer -->
|
|
28
|
+
<v-col v-else-if="!categories.length" cols="12" class="mt-8">
|
|
29
|
+
<v-alert type="info" variant="tonal" rounded="lg">
|
|
30
|
+
You are not set up to receive any notifications yet. Once you are
|
|
31
|
+
given access to a part of the system, its settings will appear here.
|
|
32
|
+
</v-alert>
|
|
33
|
+
</v-col>
|
|
34
|
+
|
|
35
|
+
<template v-else>
|
|
36
|
+
<!-- everything, at once -->
|
|
37
|
+
<v-col cols="12" class="mt-8">
|
|
38
|
+
<AppCard>
|
|
39
|
+
<v-row no-gutters align="center" class="pa-6">
|
|
40
|
+
<v-col cols="12" sm="8">
|
|
41
|
+
<div class="screen-card-title">Everything</div>
|
|
42
|
+
<div class="screen-hint summary-line mt-1">
|
|
43
|
+
{{ everythingSummary }}
|
|
44
|
+
</div>
|
|
45
|
+
</v-col>
|
|
46
|
+
<v-col cols="12" sm="4" class="d-flex justify-sm-end mt-3 mt-sm-0">
|
|
47
|
+
<AppButton
|
|
48
|
+
variant="ghost"
|
|
49
|
+
:disabled="saving"
|
|
50
|
+
@click="setAll(!allOn)"
|
|
51
|
+
>
|
|
52
|
+
{{ allOn ? "Turn everything off" : "Turn everything on" }}
|
|
53
|
+
</AppButton>
|
|
54
|
+
</v-col>
|
|
55
|
+
</v-row>
|
|
56
|
+
</AppCard>
|
|
57
|
+
</v-col>
|
|
58
|
+
|
|
59
|
+
<!-- one panel per module, collapsed by default so a person with many
|
|
60
|
+
modules gets a short list rather than a wall of switches -->
|
|
61
|
+
<v-col cols="12" class="mt-4">
|
|
62
|
+
<v-expansion-panels variant="accordion" multiple class="screen-accordion">
|
|
63
|
+
<v-expansion-panel
|
|
64
|
+
v-for="category in categories"
|
|
65
|
+
:key="category.key"
|
|
66
|
+
:value="category.key"
|
|
67
|
+
elevation="0"
|
|
68
|
+
>
|
|
69
|
+
<v-expansion-panel-title>
|
|
70
|
+
<v-row no-gutters align="center">
|
|
71
|
+
<v-col cols="12" sm="6">
|
|
72
|
+
<span>{{ category.label }}</span>
|
|
73
|
+
<!-- "Safety" is a TAG, not a status word, so it takes
|
|
74
|
+
the handoff's tag chip - "small 7px-radius label" -
|
|
75
|
+
which is the dotless shape, on the `info` tone the
|
|
76
|
+
handoff assigns to "Role & info tags". A leading dot
|
|
77
|
+
here would draw it as a status the screen does not
|
|
78
|
+
have. -->
|
|
79
|
+
<StatusChip
|
|
80
|
+
v-if="category.safety"
|
|
81
|
+
tone="info"
|
|
82
|
+
label="Safety"
|
|
83
|
+
:dot="false"
|
|
84
|
+
class="ml-2"
|
|
85
|
+
/>
|
|
86
|
+
</v-col>
|
|
87
|
+
<v-col cols="12" sm="6" class="screen-hint summary-line">
|
|
88
|
+
{{ summaryFor(category) }}
|
|
89
|
+
</v-col>
|
|
90
|
+
</v-row>
|
|
91
|
+
</v-expansion-panel-title>
|
|
92
|
+
|
|
93
|
+
<v-expansion-panel-text>
|
|
94
|
+
<div class="screen-hint summary-line mb-4">
|
|
95
|
+
{{ category.description }}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<AppButton
|
|
99
|
+
variant="ghost"
|
|
100
|
+
class="mb-4"
|
|
101
|
+
:disabled="saving || !switchableIn(category).length"
|
|
102
|
+
@click="setCategory(category, !categoryAllOn(category))"
|
|
103
|
+
>
|
|
104
|
+
{{
|
|
105
|
+
categoryAllOn(category)
|
|
106
|
+
? "Turn all off for " + category.label
|
|
107
|
+
: "Turn all on for " + category.label
|
|
108
|
+
}}
|
|
109
|
+
</AppButton>
|
|
110
|
+
|
|
111
|
+
<!-- one channel per row: a switch must never sit beside the
|
|
112
|
+
NEXT channel's label, which is what a three-up grid does -->
|
|
113
|
+
<v-row no-gutters>
|
|
114
|
+
<v-col
|
|
115
|
+
v-for="(channel, index) in category.channels"
|
|
116
|
+
:key="channel.key"
|
|
117
|
+
cols="12"
|
|
118
|
+
:class="index < category.channels.length - 1 ? 'border-b-thin' : ''"
|
|
119
|
+
>
|
|
120
|
+
<div
|
|
121
|
+
class="d-flex align-center justify-space-between py-2"
|
|
122
|
+
:class="{ 'channel-unavailable': !channel.supported }"
|
|
123
|
+
>
|
|
124
|
+
<div class="pr-4">
|
|
125
|
+
<div class="channel-label">{{ channel.label }}</div>
|
|
126
|
+
<div v-if="!channel.supported" class="channel-note mt-1">
|
|
127
|
+
{{ channel.note }}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<v-switch
|
|
132
|
+
:model-value="channel.enabled"
|
|
133
|
+
:disabled="!channel.supported || saving"
|
|
134
|
+
color="primary"
|
|
135
|
+
hide-details
|
|
136
|
+
density="compact"
|
|
137
|
+
:aria-label="`${channel.label} for ${category.label}`"
|
|
138
|
+
@update:model-value="
|
|
139
|
+
(value) => onToggle(category, channel, !!value)
|
|
140
|
+
"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</v-col>
|
|
144
|
+
</v-row>
|
|
145
|
+
</v-expansion-panel-text>
|
|
146
|
+
</v-expansion-panel>
|
|
147
|
+
</v-expansion-panels>
|
|
148
|
+
</v-col>
|
|
149
|
+
</template>
|
|
150
|
+
</v-row>
|
|
151
|
+
</v-col>
|
|
152
|
+
|
|
153
|
+
<!-- switching a safety alert off: say plainly what stops arriving -->
|
|
154
|
+
<v-dialog v-model="confirmOpen" max-width="460" persistent>
|
|
155
|
+
<v-card class="screen-modal pa-2">
|
|
156
|
+
<v-card-title>{{ confirmText.title }}</v-card-title>
|
|
157
|
+
<v-card-text class="screen-modal__body">
|
|
158
|
+
{{ confirmText.body }}
|
|
159
|
+
</v-card-text>
|
|
160
|
+
<v-card-actions class="justify-end">
|
|
161
|
+
<AppButton variant="ghost" @click="cancelConfirm">
|
|
162
|
+
{{ confirmText.cancel }}
|
|
163
|
+
</AppButton>
|
|
164
|
+
<AppButton @click="acceptConfirm">
|
|
165
|
+
{{ confirmText.confirm }}
|
|
166
|
+
</AppButton>
|
|
167
|
+
</v-card-actions>
|
|
168
|
+
</v-card>
|
|
169
|
+
</v-dialog>
|
|
170
|
+
|
|
171
|
+
<v-snackbar v-model="noticeOpen" :timeout="4000" location="bottom">
|
|
172
|
+
{{ notice }}
|
|
173
|
+
</v-snackbar>
|
|
174
|
+
</v-row>
|
|
175
|
+
</template>
|
|
176
|
+
|
|
177
|
+
<script setup lang="ts">
|
|
178
|
+
import type {
|
|
179
|
+
TNotificationCategorySetting,
|
|
180
|
+
TNotificationChannelSetting,
|
|
181
|
+
} from "../composables/useNotificationPreference";
|
|
182
|
+
|
|
183
|
+
const { getPreferences, savePreferences } = useNotificationPreference();
|
|
184
|
+
|
|
185
|
+
const categories = ref<TNotificationCategorySetting[]>([]);
|
|
186
|
+
const loading = ref(true);
|
|
187
|
+
const saving = ref(false);
|
|
188
|
+
const loadError = ref("");
|
|
189
|
+
const notice = ref("");
|
|
190
|
+
const noticeOpen = ref(false);
|
|
191
|
+
|
|
192
|
+
const confirmOpen = ref(false);
|
|
193
|
+
const confirmText = ref({ title: "", body: "", confirm: "", cancel: "" });
|
|
194
|
+
let confirmAccept: (() => void) | null = null;
|
|
195
|
+
|
|
196
|
+
/** the channels a person can actually move on this category */
|
|
197
|
+
const switchableIn = (category: TNotificationCategorySetting) =>
|
|
198
|
+
category.channels.filter((c) => c.supported);
|
|
199
|
+
|
|
200
|
+
const categoryAllOn = (category: TNotificationCategorySetting) =>
|
|
201
|
+
switchableIn(category).every((c) => c.enabled);
|
|
202
|
+
|
|
203
|
+
const allOn = computed(
|
|
204
|
+
() =>
|
|
205
|
+
categories.value.length > 0 &&
|
|
206
|
+
categories.value.every((category) => categoryAllOn(category)),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
/** what is on, in words — so a collapsed panel still says what it is doing */
|
|
210
|
+
function summaryFor(category: TNotificationCategorySetting) {
|
|
211
|
+
const on = switchableIn(category).filter((c) => c.enabled);
|
|
212
|
+
if (!on.length) return "Off";
|
|
213
|
+
if (on.length === switchableIn(category).length) return "On everywhere";
|
|
214
|
+
return `On: ${on.map((c) => c.label).join(", ")}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const everythingSummary = computed(() => {
|
|
218
|
+
const total = categories.value.length;
|
|
219
|
+
const on = categories.value.filter((c) => categoryAllOn(c)).length;
|
|
220
|
+
if (on === total) return "Everything is on.";
|
|
221
|
+
if (on === 0) return "Everything is off.";
|
|
222
|
+
return `${on} of ${total} fully on.`;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
async function load() {
|
|
226
|
+
loading.value = true;
|
|
227
|
+
loadError.value = "";
|
|
228
|
+
try {
|
|
229
|
+
const res = await getPreferences();
|
|
230
|
+
categories.value = res.data.categories;
|
|
231
|
+
} catch {
|
|
232
|
+
loadError.value =
|
|
233
|
+
"We could not load your notification settings. Please refresh the page and try again.";
|
|
234
|
+
} finally {
|
|
235
|
+
loading.value = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Save the whole screen after any change, and put it back the way it was if the
|
|
241
|
+
* save fails — so what is on screen is never a setting the server did not take.
|
|
242
|
+
*/
|
|
243
|
+
async function persist(previous: TNotificationCategorySetting[]) {
|
|
244
|
+
saving.value = true;
|
|
245
|
+
try {
|
|
246
|
+
const res = await savePreferences({ channels: [], categories: categories.value });
|
|
247
|
+
categories.value = res.data.categories;
|
|
248
|
+
notice.value = "Saved.";
|
|
249
|
+
} catch {
|
|
250
|
+
categories.value = previous;
|
|
251
|
+
notice.value = "We could not save that change. Please try again.";
|
|
252
|
+
} finally {
|
|
253
|
+
noticeOpen.value = true;
|
|
254
|
+
saving.value = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const snapshot = () =>
|
|
259
|
+
JSON.parse(JSON.stringify(categories.value)) as TNotificationCategorySetting[];
|
|
260
|
+
|
|
261
|
+
function apply(mutate: () => void) {
|
|
262
|
+
const previous = snapshot();
|
|
263
|
+
mutate();
|
|
264
|
+
void persist(previous);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function onToggle(
|
|
268
|
+
category: TNotificationCategorySetting,
|
|
269
|
+
channel: TNotificationChannelSetting,
|
|
270
|
+
value: boolean,
|
|
271
|
+
) {
|
|
272
|
+
if (!channel.supported) return;
|
|
273
|
+
|
|
274
|
+
const commit = () => apply(() => (channel.enabled = value));
|
|
275
|
+
|
|
276
|
+
// switching a safety alert OFF is the only thing worth stopping to confirm
|
|
277
|
+
if (category.safety && !value) {
|
|
278
|
+
confirmText.value = safetyOffConfirmation(category.label, channel.label);
|
|
279
|
+
confirmAccept = commit;
|
|
280
|
+
confirmOpen.value = true;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
commit();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function setCategory(category: TNotificationCategorySetting, value: boolean) {
|
|
288
|
+
const commit = () =>
|
|
289
|
+
apply(() => {
|
|
290
|
+
for (const channel of switchableIn(category)) channel.enabled = value;
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (category.safety && !value) {
|
|
294
|
+
confirmText.value = safetyOffConfirmation(category.label, "every channel");
|
|
295
|
+
confirmAccept = commit;
|
|
296
|
+
confirmOpen.value = true;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
commit();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function setAll(value: boolean) {
|
|
304
|
+
const commit = () =>
|
|
305
|
+
apply(() => {
|
|
306
|
+
for (const category of categories.value) {
|
|
307
|
+
for (const channel of switchableIn(category)) channel.enabled = value;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (!value && categories.value.some((c) => c.safety)) {
|
|
312
|
+
confirmText.value = safetyOffConfirmation("safety and security", "every channel");
|
|
313
|
+
confirmAccept = commit;
|
|
314
|
+
confirmOpen.value = true;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
commit();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function acceptConfirm() {
|
|
322
|
+
confirmOpen.value = false;
|
|
323
|
+
confirmAccept?.();
|
|
324
|
+
confirmAccept = null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function cancelConfirm() {
|
|
328
|
+
confirmOpen.value = false;
|
|
329
|
+
confirmAccept = null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
onMounted(load);
|
|
333
|
+
</script>
|
|
334
|
+
|
|
335
|
+
<style scoped>
|
|
336
|
+
/* The handoff names no type for long-form intro copy or a summary line, so
|
|
337
|
+
both take the design's own secondary sizes rather than Vuetify's `text-h6`
|
|
338
|
+
/ `text-body-2` scale. */
|
|
339
|
+
.intro-copy {
|
|
340
|
+
font-size: var(--fs-cell);
|
|
341
|
+
margin-top: 6px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.summary-line {
|
|
345
|
+
font-size: var(--fs-breadcrumb);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.channel-label {
|
|
349
|
+
font-family: var(--font-sans);
|
|
350
|
+
font-size: var(--fs-cell);
|
|
351
|
+
font-weight: var(--fw-cell);
|
|
352
|
+
color: var(--text);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.channel-note {
|
|
356
|
+
font-family: var(--font-sans);
|
|
357
|
+
font-size: var(--fs-breadcrumb);
|
|
358
|
+
color: var(--text2);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Unavailable channels are shown and explained, never hidden.
|
|
362
|
+
*
|
|
363
|
+
* They are NOT faded. The disabled switch already says the control cannot be
|
|
364
|
+
* moved, and the sentence beside it is the only place a person is told why —
|
|
365
|
+
* dimming that sentence is dimming the explanation. A blanket opacity here
|
|
366
|
+
* measured 2.3:1 against the card, which is unreadable; the row is now marked
|
|
367
|
+
* by a rule down its edge instead, and every word stays at full strength. */
|
|
368
|
+
.channel-unavailable {
|
|
369
|
+
border-left: 2px solid rgb(var(--v-border-color));
|
|
370
|
+
padding-left: 12px;
|
|
371
|
+
opacity: 1;
|
|
372
|
+
}
|
|
373
|
+
</style>
|
|
@@ -4,11 +4,18 @@
|
|
|
4
4
|
<v-row>
|
|
5
5
|
<v-col cols="6">
|
|
6
6
|
<InputLabel class="text-capitalize font-weight-bold" :title="title" :required="required" />
|
|
7
|
-
<v-text-field v-model.number="count" type="number" density="comfortable" :rules="rules" :readonly="readOnly" />
|
|
7
|
+
<v-text-field v-model.number="count" type="number" class="filter-field" density="comfortable" :rules="rules" :readonly="readOnly" />
|
|
8
8
|
</v-col>
|
|
9
9
|
|
|
10
10
|
<v-col cols="6">
|
|
11
|
-
|
|
11
|
+
<!-- Was `color="primary" size="large"`. On a `v-btn`,
|
|
12
|
+
`primary` is this theme's FOREGROUND token (links,
|
|
13
|
+
icons, text buttons) - the fill is `primary-button`, so
|
|
14
|
+
a flat button asking for `primary` painted itself with
|
|
15
|
+
an ink colour. `.screen-btn-primary` is the design's
|
|
16
|
+
40px primary and carries the correct fill, so the
|
|
17
|
+
colour prop and the Vuetify size both go. -->
|
|
18
|
+
<v-btn v-if="!readOnly" class="text-none mt-6 screen-btn-primary" variant="flat"
|
|
12
19
|
:disabled="!valid || disabled" :loading="loading || updating" text="Save" @click="handleSave" />
|
|
13
20
|
</v-col>
|
|
14
21
|
</v-row>
|