@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
|
@@ -6,6 +6,8 @@ type HidReaderPayload = {
|
|
|
6
6
|
username?: string;
|
|
7
7
|
password?: string;
|
|
8
8
|
deviceId?: string;
|
|
9
|
+
portalId?: number;
|
|
10
|
+
portalName?: string;
|
|
9
11
|
monitorPath?: string;
|
|
10
12
|
enabled?: boolean;
|
|
11
13
|
status?: string;
|
|
@@ -28,6 +30,83 @@ type HidIdentityPayload = {
|
|
|
28
30
|
metadata?: Record<string, any>;
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
type HidVisitorQrData = {
|
|
34
|
+
visitorId: string;
|
|
35
|
+
readerId: string;
|
|
36
|
+
hidUserId: number;
|
|
37
|
+
qrValue: string;
|
|
38
|
+
qrFormat: "0";
|
|
39
|
+
portalId: number;
|
|
40
|
+
portalName: string;
|
|
41
|
+
readerName: string;
|
|
42
|
+
issuedAt: string;
|
|
43
|
+
expiresAt: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type HidVisitorQrResponse = HidVisitorQrData | { data: HidVisitorQrData };
|
|
47
|
+
|
|
48
|
+
type HidPermissionListResponse = {
|
|
49
|
+
items: THidPermissionCandidate[];
|
|
50
|
+
page: number;
|
|
51
|
+
pages: number;
|
|
52
|
+
pageRange: string;
|
|
53
|
+
limit: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type HidFacialEnrollmentResult = {
|
|
57
|
+
user_id?: number;
|
|
58
|
+
success?: boolean;
|
|
59
|
+
scores?: Record<string, unknown>;
|
|
60
|
+
errors?: Array<{ code?: number; message?: string }>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type HidPhysicalCardType = "pacs" | "csn";
|
|
64
|
+
|
|
65
|
+
export type HidPhysicalCard = {
|
|
66
|
+
id: string;
|
|
67
|
+
value: string;
|
|
68
|
+
userId: number;
|
|
69
|
+
cardType: HidPhysicalCardType | "unknown";
|
|
70
|
+
facilityCode?: number;
|
|
71
|
+
cardNumber?: number;
|
|
72
|
+
source?: "manual" | "reader" | "device";
|
|
73
|
+
enrolledAt?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type HidPhysicalCardInput =
|
|
77
|
+
| { cardType: "pacs"; facilityCode: number; cardNumber: number }
|
|
78
|
+
| { cardType: "csn"; cardValue: string };
|
|
79
|
+
|
|
80
|
+
export type HidSipAccountData = {
|
|
81
|
+
orgId: string;
|
|
82
|
+
site: string;
|
|
83
|
+
userId: string;
|
|
84
|
+
extension: string;
|
|
85
|
+
username: string;
|
|
86
|
+
password: string;
|
|
87
|
+
sipAddress: string;
|
|
88
|
+
websocketUrl: string;
|
|
89
|
+
status: "active";
|
|
90
|
+
provisionedAt: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* True only for the specific validation error an API that predates core #1878
|
|
95
|
+
* returns when the discover body carries `site`. Deliberately narrow: after
|
|
96
|
+
* #1878 `site` is required, so this message can no longer be produced and the
|
|
97
|
+
* fallback below can never hide a real authorization refusal.
|
|
98
|
+
*/
|
|
99
|
+
export function isUnknownSiteFieldError(error: unknown): boolean {
|
|
100
|
+
const record = typeof error === "object" && error !== null
|
|
101
|
+
? error as Record<string, unknown>
|
|
102
|
+
: {};
|
|
103
|
+
const data = typeof record.data === "object" && record.data !== null
|
|
104
|
+
? record.data as Record<string, unknown>
|
|
105
|
+
: {};
|
|
106
|
+
const message = String(data.message || record.message || "");
|
|
107
|
+
return message.includes("\"site\"") && message.includes("not allowed");
|
|
108
|
+
}
|
|
109
|
+
|
|
31
110
|
export default function useHidAmico() {
|
|
32
111
|
const basePath = "/api/access-management/hid";
|
|
33
112
|
|
|
@@ -78,6 +157,16 @@ export default function useHidAmico() {
|
|
|
78
157
|
});
|
|
79
158
|
}
|
|
80
159
|
|
|
160
|
+
function issueVisitorQr(
|
|
161
|
+
readerId: string,
|
|
162
|
+
payload: { visitorId: string; validityMinutes: number },
|
|
163
|
+
) {
|
|
164
|
+
return useNuxtApp().$api<HidVisitorQrResponse>(`${basePath}/readers/${readerId}/visitor-qr`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
body: payload,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
81
170
|
function getReaderConfiguration(readerId: string, payload: Record<string, string[]> = {}) {
|
|
82
171
|
return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/configuration`, {
|
|
83
172
|
method: "POST",
|
|
@@ -112,6 +201,127 @@ export default function useHidAmico() {
|
|
|
112
201
|
});
|
|
113
202
|
}
|
|
114
203
|
|
|
204
|
+
function setUserImage(
|
|
205
|
+
readerId: string,
|
|
206
|
+
hidUserId: string | number,
|
|
207
|
+
image: Blob,
|
|
208
|
+
options: { timestamp?: number; match?: boolean } = {},
|
|
209
|
+
) {
|
|
210
|
+
return useNuxtApp().$api<{ data: HidFacialEnrollmentResult }>(
|
|
211
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/image`,
|
|
212
|
+
{
|
|
213
|
+
method: "PUT",
|
|
214
|
+
query: {
|
|
215
|
+
...(options.timestamp ? { timestamp: options.timestamp } : {}),
|
|
216
|
+
match: options.match ?? true,
|
|
217
|
+
},
|
|
218
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
219
|
+
body: image,
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function deleteUserImage(readerId: string, hidUserId: string | number) {
|
|
225
|
+
return useNuxtApp().$api<Record<string, unknown>>(
|
|
226
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/image`,
|
|
227
|
+
{ method: "DELETE" },
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getUserCards(readerId: string, hidUserId: string | number) {
|
|
232
|
+
return useNuxtApp().$api<{
|
|
233
|
+
data: { readerId: string; hidUserId: number; cards: HidPhysicalCard[] };
|
|
234
|
+
}>(`${basePath}/readers/${readerId}/users/${hidUserId}/cards`, {
|
|
235
|
+
method: "GET",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function assignUserCard(
|
|
240
|
+
readerId: string,
|
|
241
|
+
hidUserId: string | number,
|
|
242
|
+
payload: HidPhysicalCardInput,
|
|
243
|
+
) {
|
|
244
|
+
return useNuxtApp().$api<{ data: { card: HidPhysicalCard; verified: boolean } }>(
|
|
245
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/cards`,
|
|
246
|
+
{ method: "POST", body: payload },
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function enrollUserCard(
|
|
251
|
+
readerId: string,
|
|
252
|
+
hidUserId: string | number,
|
|
253
|
+
cardType: HidPhysicalCardType,
|
|
254
|
+
timeoutSeconds = 30,
|
|
255
|
+
) {
|
|
256
|
+
return useNuxtApp().$api<{ data: { card: HidPhysicalCard; verified: boolean } }>(
|
|
257
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/cards/enroll`,
|
|
258
|
+
{ method: "POST", body: { cardType, timeoutSeconds } },
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function cancelUserCardEnrollment(readerId: string) {
|
|
263
|
+
return useNuxtApp().$api<{ data: { cancelled: boolean } }>(
|
|
264
|
+
`${basePath}/readers/${readerId}/cards/enrollment/cancel`,
|
|
265
|
+
{ method: "POST" },
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function deleteUserCard(
|
|
270
|
+
readerId: string,
|
|
271
|
+
hidUserId: string | number,
|
|
272
|
+
cardId: string | number,
|
|
273
|
+
) {
|
|
274
|
+
return useNuxtApp().$api<{ data: { deleted: boolean } }>(
|
|
275
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/cards/${cardId}`,
|
|
276
|
+
{ method: "DELETE" },
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function discoverReader(
|
|
281
|
+
payload: Pick<HidReaderPayload, "baseUrl" | "username" | "password" | "site">,
|
|
282
|
+
) {
|
|
283
|
+
const post = (body: Record<string, unknown>) =>
|
|
284
|
+
useNuxtApp().$api<Record<string, unknown>>(`${basePath}/readers/discover`, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
body,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
return await post(payload as Record<string, unknown>);
|
|
291
|
+
} catch (error: unknown) {
|
|
292
|
+
// The discover endpoint only started accepting (and requiring) `site` in
|
|
293
|
+
// core #1878. An API that predates it validates the body with Joi's
|
|
294
|
+
// default allowUnknown:false and answers `"site" is not allowed`, so the
|
|
295
|
+
// web layer would break the moment it shipped ahead of the API. Retry once
|
|
296
|
+
// without `site` so both API versions work.
|
|
297
|
+
// ponytail: remove this fallback once core #1878 is deployed everywhere.
|
|
298
|
+
if (!isUnknownSiteFieldError(error)) throw error;
|
|
299
|
+
const { site: _site, ...legacy } = payload;
|
|
300
|
+
return await post(legacy as Record<string, unknown>);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getUserPinStatus(readerId: string, hidUserId: string | number) {
|
|
305
|
+
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
306
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
307
|
+
{ method: "GET" },
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function setUserPin(readerId: string, hidUserId: string | number, pin: string) {
|
|
312
|
+
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
313
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
314
|
+
{ method: "PUT", body: { pin } },
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function deleteUserPin(readerId: string, hidUserId: string | number) {
|
|
319
|
+
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
320
|
+
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
321
|
+
{ method: "DELETE" },
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
115
325
|
function createIdentity(readerId: string, payload: HidIdentityPayload) {
|
|
116
326
|
return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/identities`, {
|
|
117
327
|
method: "POST",
|
|
@@ -151,18 +361,73 @@ export default function useHidAmico() {
|
|
|
151
361
|
});
|
|
152
362
|
}
|
|
153
363
|
|
|
364
|
+
function getSitePermissions(siteId: string, orgId: string, readerId: string) {
|
|
365
|
+
return useNuxtApp().$api<{ data: THidSitePermissions }>(
|
|
366
|
+
`${basePath}/sites/${siteId}/permissions`,
|
|
367
|
+
{ method: "GET", query: { orgId, readerId } },
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function ensureSipAccount(siteId: string, orgId: string) {
|
|
372
|
+
return useNuxtApp().$api<{ data: HidSipAccountData }>(
|
|
373
|
+
`${basePath}/sites/${siteId}/intercom/sip-account`,
|
|
374
|
+
{ method: "POST", body: { orgId } },
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getPermissionCandidates(
|
|
379
|
+
siteId: string,
|
|
380
|
+
query: {
|
|
381
|
+
orgId: string;
|
|
382
|
+
readerId: string;
|
|
383
|
+
category: THidPermissionCategory | "intercom";
|
|
384
|
+
search?: string;
|
|
385
|
+
page?: number;
|
|
386
|
+
limit?: number;
|
|
387
|
+
},
|
|
388
|
+
) {
|
|
389
|
+
return useNuxtApp().$api<HidPermissionListResponse>(
|
|
390
|
+
`${basePath}/sites/${siteId}/permission-candidates`,
|
|
391
|
+
{ method: "GET", query },
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function updateSitePermissions(
|
|
396
|
+
siteId: string,
|
|
397
|
+
orgId: string,
|
|
398
|
+
readerId: string,
|
|
399
|
+
assignments: Array<Pick<THidPermissionAssignment, "subjectId" | "category" | "intercom">>,
|
|
400
|
+
) {
|
|
401
|
+
return useNuxtApp().$api<{ data: THidSitePermissions; message: string }>(
|
|
402
|
+
`${basePath}/sites/${siteId}/permissions`,
|
|
403
|
+
{ method: "PUT", body: { orgId, readerId, assignments } },
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
154
407
|
return {
|
|
155
408
|
getReaders,
|
|
409
|
+
discoverReader,
|
|
156
410
|
createReader,
|
|
157
411
|
updateReader,
|
|
158
412
|
deleteReader,
|
|
159
413
|
testReader,
|
|
160
414
|
syncReader,
|
|
161
415
|
runObjectOperation,
|
|
416
|
+
issueVisitorQr,
|
|
162
417
|
getReaderConfiguration,
|
|
163
418
|
setReaderConfiguration,
|
|
164
419
|
getLogs,
|
|
165
420
|
getUserImage,
|
|
421
|
+
setUserImage,
|
|
422
|
+
deleteUserImage,
|
|
423
|
+
getUserCards,
|
|
424
|
+
assignUserCard,
|
|
425
|
+
enrollUserCard,
|
|
426
|
+
cancelUserCardEnrollment,
|
|
427
|
+
deleteUserCard,
|
|
428
|
+
getUserPinStatus,
|
|
429
|
+
setUserPin,
|
|
430
|
+
deleteUserPin,
|
|
166
431
|
getIdentities,
|
|
167
432
|
createIdentity,
|
|
168
433
|
updateIdentity,
|
|
@@ -170,5 +435,9 @@ export default function useHidAmico() {
|
|
|
170
435
|
getIntercomStatus,
|
|
171
436
|
makeIntercomCall,
|
|
172
437
|
finalizeIntercomCall,
|
|
438
|
+
ensureSipAccount,
|
|
439
|
+
getSitePermissions,
|
|
440
|
+
getPermissionCandidates,
|
|
441
|
+
updateSitePermissions,
|
|
173
442
|
};
|
|
174
443
|
}
|
|
@@ -28,9 +28,9 @@ export default function useHidNavigation() {
|
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
|
-
title: "
|
|
31
|
+
title: "HID Cards",
|
|
32
32
|
route: {
|
|
33
|
-
name: "org-site-access-mgmt-
|
|
33
|
+
name: "org-site-access-mgmt-hid-cards",
|
|
34
34
|
params: { org, site },
|
|
35
35
|
},
|
|
36
36
|
},
|
|
@@ -41,13 +41,6 @@ export default function useHidNavigation() {
|
|
|
41
41
|
params: { org, site },
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
-
{
|
|
45
|
-
title: "Identity Mapping",
|
|
46
|
-
route: {
|
|
47
|
-
name: "org-site-access-mgmt-identity-mapping",
|
|
48
|
-
params: { org, site },
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
44
|
{
|
|
52
45
|
title: "Access Logs",
|
|
53
46
|
route: {
|
package/composables/useMember.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export default function useMember() {
|
|
2
2
|
const members = useState<Array<TMember>>("members", () => []);
|
|
3
|
-
const page = useState("
|
|
4
|
-
const pages = useState("
|
|
5
|
-
const pageRange = useState("
|
|
3
|
+
const page = useState("memberPage", () => 1);
|
|
4
|
+
const pages = useState("memberPages", () => 0);
|
|
5
|
+
const pageRange = useState("memberPageRange", () => "-- - -- of --");
|
|
6
6
|
|
|
7
7
|
const member = useState<TMember>("member", () => ({
|
|
8
8
|
_id: "",
|
|
@@ -52,16 +52,24 @@ export function useNFCPatrolDailyReport(
|
|
|
52
52
|
const report = ref<NFCPatrolSummaryReport | null>(null);
|
|
53
53
|
const loading = ref(false);
|
|
54
54
|
const notFound = ref(false);
|
|
55
|
+
/**
|
|
56
|
+
* A request that FAILED is not a route with no patrols. Without this the
|
|
57
|
+
* screen answered a 500 with "No patrol log found for this route", which is
|
|
58
|
+
* a statement about the data the server never made.
|
|
59
|
+
*/
|
|
60
|
+
const failed = ref(false);
|
|
55
61
|
|
|
56
62
|
async function fetchReport() {
|
|
57
63
|
if (!site || !filters.date || !filters.route) {
|
|
58
64
|
report.value = null;
|
|
59
65
|
notFound.value = false;
|
|
66
|
+
failed.value = false;
|
|
60
67
|
return;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
loading.value = true;
|
|
64
71
|
notFound.value = false;
|
|
72
|
+
failed.value = false;
|
|
65
73
|
|
|
66
74
|
try {
|
|
67
75
|
const [logsRes, siteInfo] = await Promise.all([
|
|
@@ -92,6 +100,10 @@ export function useNFCPatrolDailyReport(
|
|
|
92
100
|
report.value = null;
|
|
93
101
|
notFound.value = true;
|
|
94
102
|
}
|
|
103
|
+
} catch {
|
|
104
|
+
report.value = null;
|
|
105
|
+
notFound.value = false;
|
|
106
|
+
failed.value = true;
|
|
95
107
|
} finally {
|
|
96
108
|
loading.value = false;
|
|
97
109
|
}
|
|
@@ -101,6 +113,7 @@ export function useNFCPatrolDailyReport(
|
|
|
101
113
|
report,
|
|
102
114
|
loading,
|
|
103
115
|
notFound,
|
|
116
|
+
failed,
|
|
104
117
|
fetchReport,
|
|
105
118
|
};
|
|
106
119
|
}
|
|
@@ -2,11 +2,11 @@ import type {
|
|
|
2
2
|
NFCPatrolReportFilters,
|
|
3
3
|
NFCPatrolSummaryReport,
|
|
4
4
|
NFCPatrolReportSummaryRow,
|
|
5
|
-
NFCPatrolMonthlyReportRow,
|
|
6
5
|
NFCPatrolMonthlyReport,
|
|
7
6
|
} from "../types/nfc-patrol-report";
|
|
8
7
|
import useNFCPatrolLog from "./useNFCPatrolLog";
|
|
9
8
|
import useSite from "./useSite";
|
|
9
|
+
import { collectAllPages, mapLogsToMonthlyRows } from "../utils/nfc-patrol-report";
|
|
10
10
|
|
|
11
11
|
interface NFCPatrolLogListResponse {
|
|
12
12
|
items?: Record<string, any>[];
|
|
@@ -14,55 +14,6 @@ interface NFCPatrolLogListResponse {
|
|
|
14
14
|
pageRange?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const MONTHS = [
|
|
18
|
-
"January",
|
|
19
|
-
"February",
|
|
20
|
-
"March",
|
|
21
|
-
"April",
|
|
22
|
-
"May",
|
|
23
|
-
"June",
|
|
24
|
-
"July",
|
|
25
|
-
"August",
|
|
26
|
-
"September",
|
|
27
|
-
"October",
|
|
28
|
-
"November",
|
|
29
|
-
"December",
|
|
30
|
-
];
|
|
31
|
-
function mapLogsToMonthlyRows(
|
|
32
|
-
logs: Record<string, any>[],
|
|
33
|
-
): NFCPatrolMonthlyReportRow[] {
|
|
34
|
-
const rows = MONTHS.map((month) => ({
|
|
35
|
-
month,
|
|
36
|
-
checked: 0,
|
|
37
|
-
checkedPercentage: 0,
|
|
38
|
-
missed: 0,
|
|
39
|
-
missedPercentage: 0,
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
logs.forEach((log) => {
|
|
43
|
-
const monthIndex = new Date(log.date).getMonth();
|
|
44
|
-
|
|
45
|
-
for (const checkpoint of log.checkPoints ?? []) {
|
|
46
|
-
if (checkpoint.status === "Completed") {
|
|
47
|
-
rows[monthIndex].checked++;
|
|
48
|
-
} else if (checkpoint.status === "Skipped") {
|
|
49
|
-
rows[monthIndex].missed++;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
rows.forEach((row) => {
|
|
55
|
-
const total = row.checked + row.missed;
|
|
56
|
-
|
|
57
|
-
if (total > 0) {
|
|
58
|
-
row.checkedPercentage = Math.round((row.checked / total) * 100);
|
|
59
|
-
row.missedPercentage = Math.round((row.missed / total) * 100);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return rows;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
17
|
function buildAddress(address?: Record<string, string>) {
|
|
67
18
|
if (!address) return undefined;
|
|
68
19
|
|
|
@@ -90,32 +41,42 @@ export function useNFCPatrolMonthlyReport(
|
|
|
90
41
|
const report = ref<NFCPatrolMonthlyReport | null>(null);
|
|
91
42
|
const loading = ref(false);
|
|
92
43
|
const notFound = ref(false);
|
|
44
|
+
/**
|
|
45
|
+
* A request that FAILED is not a route with no patrols. Without this the
|
|
46
|
+
* screen answered a 500 with "No patrol log found for this route", which is
|
|
47
|
+
* a statement about the data the server never made.
|
|
48
|
+
*/
|
|
49
|
+
const failed = ref(false);
|
|
93
50
|
|
|
94
51
|
async function fetchReport() {
|
|
95
52
|
if (!site || !filters.route) {
|
|
96
53
|
report.value = null;
|
|
97
54
|
notFound.value = false;
|
|
55
|
+
failed.value = false;
|
|
98
56
|
return;
|
|
99
57
|
}
|
|
100
58
|
|
|
101
59
|
loading.value = true;
|
|
102
60
|
notFound.value = false;
|
|
61
|
+
failed.value = false;
|
|
103
62
|
const currentYear = new Date().getFullYear().toString();
|
|
104
63
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
64
|
+
// The whole year, not page one of it. `collectAllPages` reads the page
|
|
65
|
+
// count out of the first reply and walks the rest.
|
|
66
|
+
const [logs, siteInfo] = await Promise.all([
|
|
67
|
+
collectAllPages((page) =>
|
|
68
|
+
getPatrolLogs({
|
|
69
|
+
page,
|
|
108
70
|
limit: 100,
|
|
109
71
|
site,
|
|
110
72
|
routeId: filters.route,
|
|
111
73
|
date: currentYear,
|
|
112
74
|
type: "month",
|
|
113
|
-
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
114
77
|
getSiteById(site),
|
|
115
78
|
]);
|
|
116
79
|
|
|
117
|
-
const logs = logsRes.items ?? [];
|
|
118
|
-
|
|
119
80
|
if (logs.length) {
|
|
120
81
|
const routeName = logs[0]?.route?.name ?? "-";
|
|
121
82
|
|
|
@@ -131,6 +92,10 @@ export function useNFCPatrolMonthlyReport(
|
|
|
131
92
|
report.value = null;
|
|
132
93
|
notFound.value = true;
|
|
133
94
|
}
|
|
95
|
+
} catch {
|
|
96
|
+
report.value = null;
|
|
97
|
+
notFound.value = false;
|
|
98
|
+
failed.value = true;
|
|
134
99
|
} finally {
|
|
135
100
|
loading.value = false;
|
|
136
101
|
}
|
|
@@ -140,6 +105,7 @@ export function useNFCPatrolMonthlyReport(
|
|
|
140
105
|
report,
|
|
141
106
|
loading,
|
|
142
107
|
notFound,
|
|
108
|
+
failed,
|
|
143
109
|
fetchReport,
|
|
144
110
|
};
|
|
145
111
|
}
|
|
@@ -91,6 +91,12 @@ export function useNFCPatrolReport(
|
|
|
91
91
|
const report = ref<NFCPatrolReport | null>(null);
|
|
92
92
|
const loading = ref(false);
|
|
93
93
|
const notFound = ref(false);
|
|
94
|
+
/**
|
|
95
|
+
* A request that FAILED is not a route with no patrols. Without this the
|
|
96
|
+
* screen answered a 500 with "No patrol log found for this route", which is
|
|
97
|
+
* a statement about the data the server never made.
|
|
98
|
+
*/
|
|
99
|
+
const failed = ref(false);
|
|
94
100
|
const selectedActivityRemarks = ref("");
|
|
95
101
|
const dialogRemarks = ref(false);
|
|
96
102
|
|
|
@@ -98,11 +104,13 @@ export function useNFCPatrolReport(
|
|
|
98
104
|
if (!site || !filters.date || !filters.route || !filters.timeRange) {
|
|
99
105
|
report.value = null;
|
|
100
106
|
notFound.value = false;
|
|
107
|
+
failed.value = false;
|
|
101
108
|
return;
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
loading.value = true;
|
|
105
112
|
notFound.value = false;
|
|
113
|
+
failed.value = false;
|
|
106
114
|
|
|
107
115
|
try {
|
|
108
116
|
const [logsRes, siteInfo] = await Promise.all([
|
|
@@ -128,6 +136,10 @@ export function useNFCPatrolReport(
|
|
|
128
136
|
report.value = null;
|
|
129
137
|
notFound.value = true;
|
|
130
138
|
}
|
|
139
|
+
} catch {
|
|
140
|
+
report.value = null;
|
|
141
|
+
notFound.value = false;
|
|
142
|
+
failed.value = true;
|
|
131
143
|
} finally {
|
|
132
144
|
loading.value = false;
|
|
133
145
|
}
|
|
@@ -165,6 +177,7 @@ export function useNFCPatrolReport(
|
|
|
165
177
|
report,
|
|
166
178
|
loading,
|
|
167
179
|
notFound,
|
|
180
|
+
failed,
|
|
168
181
|
selectedActivityRemarks,
|
|
169
182
|
dialogRemarks,
|
|
170
183
|
fetchReport,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A person's own notification settings.
|
|
3
|
+
*
|
|
4
|
+
* The server decides everything that matters here: which categories this person
|
|
5
|
+
* is offered, which channels each one can actually reach them on, and what the
|
|
6
|
+
* words are. Nothing on this side maintains a second list — a screen that made
|
|
7
|
+
* up its own module names is how the previous version came to show three
|
|
8
|
+
* switches that governed nothing.
|
|
9
|
+
*
|
|
10
|
+
* There is no user id in either call. Whose settings these are comes from the
|
|
11
|
+
* session, so a person can only ever see and change their own.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type TNotificationChannelSetting = {
|
|
15
|
+
key: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** false = this category cannot reach them this way; drawn, dimmed, explained */
|
|
18
|
+
supported: boolean;
|
|
19
|
+
/** the plain-English reason, present only when unsupported */
|
|
20
|
+
note?: string;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type TNotificationCategorySetting = {
|
|
25
|
+
key: string;
|
|
26
|
+
label: string;
|
|
27
|
+
description: string;
|
|
28
|
+
safety: boolean;
|
|
29
|
+
channels: TNotificationChannelSetting[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TNotificationPreferences = {
|
|
33
|
+
channels: Array<{ key: string; label: string }>;
|
|
34
|
+
categories: TNotificationCategorySetting[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const EMPTY: TNotificationPreferences = { channels: [], categories: [] };
|
|
38
|
+
|
|
39
|
+
export default function useNotificationPreference() {
|
|
40
|
+
function getPreferences() {
|
|
41
|
+
return useNuxtApp().$api<{ data: TNotificationPreferences }>(
|
|
42
|
+
"/api/notification-preferences",
|
|
43
|
+
{ method: "GET" },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Save the whole screen at once.
|
|
49
|
+
*
|
|
50
|
+
* What is sent is the list of switches that are OFF, which is also how the
|
|
51
|
+
* server stores it: somebody who has changed nothing has no stored row at
|
|
52
|
+
* all, and behaves exactly as they did before this screen existed.
|
|
53
|
+
*/
|
|
54
|
+
function savePreferences(preferences: TNotificationPreferences) {
|
|
55
|
+
const off = preferences.categories.flatMap((category) =>
|
|
56
|
+
category.channels
|
|
57
|
+
.filter((channel) => channel.supported && !channel.enabled)
|
|
58
|
+
.map((channel) => ({ category: category.key, channel: channel.key })),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return useNuxtApp().$api<{ data: TNotificationPreferences }>(
|
|
62
|
+
"/api/notification-preferences",
|
|
63
|
+
{ method: "PUT", body: { off } },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { getPreferences, savePreferences, EMPTY_PREFERENCES: EMPTY };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The sentence shown when somebody switches a safety or security alert off.
|
|
72
|
+
*
|
|
73
|
+
* It says what will stop arriving and how to undo it, and it does not argue —
|
|
74
|
+
* the owner's decision is that these can be switched off, so the job here is to
|
|
75
|
+
* make sure nobody does it by accident, not to talk them out of it.
|
|
76
|
+
*/
|
|
77
|
+
export function safetyOffConfirmation(categoryLabel: string, channelLabel: string) {
|
|
78
|
+
return {
|
|
79
|
+
title: `Turn off ${categoryLabel}?`,
|
|
80
|
+
body:
|
|
81
|
+
`You will stop getting ${categoryLabel.toLowerCase()} alerts under "${channelLabel}". ` +
|
|
82
|
+
`These are safety and security alerts, so they are on for everyone by default. ` +
|
|
83
|
+
`You can turn them back on here at any time.`,
|
|
84
|
+
confirm: "Turn it off",
|
|
85
|
+
cancel: "Keep it on",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -20,6 +20,8 @@ export type FormBlock =
|
|
|
20
20
|
| { type: "checkbox-group"; title?: string; single?: boolean; items: CheckboxItemDef[] }
|
|
21
21
|
| { type: "paragraph"; text: string }
|
|
22
22
|
| { type: "section-header"; text: string }
|
|
23
|
+
| { type: "ordered-list"; title?: string; items: string[] }
|
|
24
|
+
| { type: "unordered-list"; title?: string; items: string[] }
|
|
23
25
|
| { type: "divider" }
|
|
24
26
|
| { type: "signature-row"; label: string; key: string }
|
|
25
27
|
| { type: "acknowledgement"; text: string }
|