@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
|
@@ -1,46 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-card width="100%">
|
|
3
|
-
<v-
|
|
4
|
-
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
-
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
-
{{ mode }} Visitor
|
|
7
|
-
</span>
|
|
8
|
-
</v-row>
|
|
9
|
-
</v-toolbar>
|
|
2
|
+
<v-card width="100%" class="screen-modal visitor-select">
|
|
3
|
+
<v-card-title class="text-capitalize">{{ mode }} Visitor</v-card-title>
|
|
10
4
|
|
|
11
|
-
<
|
|
12
|
-
<
|
|
5
|
+
<div class="screen-modal__body visitor-select__body">
|
|
6
|
+
<p class="visitor-select__hint">Please Select Visitor Type</p>
|
|
13
7
|
<template v-for="item in selection" :key="item.value">
|
|
14
|
-
<
|
|
8
|
+
<AppButton
|
|
15
9
|
v-if="showSelection(item)"
|
|
16
|
-
|
|
17
|
-
block
|
|
18
|
-
variant="flat"
|
|
19
|
-
rounded="md"
|
|
20
|
-
size="48"
|
|
21
|
-
:text="item.label"
|
|
10
|
+
class="visitor-select__option"
|
|
22
11
|
@click="select(item.value)"
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
>
|
|
13
|
+
{{ item.label }}
|
|
14
|
+
</AppButton>
|
|
25
15
|
</template>
|
|
26
|
-
</
|
|
16
|
+
</div>
|
|
27
17
|
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
<v-btn
|
|
32
|
-
tile
|
|
33
|
-
block
|
|
34
|
-
variant="text"
|
|
35
|
-
class="text-none"
|
|
36
|
-
size="48"
|
|
37
|
-
@click="cancel"
|
|
38
|
-
>
|
|
39
|
-
Cancel
|
|
40
|
-
</v-btn>
|
|
41
|
-
</v-col>
|
|
42
|
-
</v-row>
|
|
43
|
-
</v-toolbar>
|
|
18
|
+
<div class="visitor-select__footer">
|
|
19
|
+
<AppButton variant="ghost" @click="cancel">Cancel</AppButton>
|
|
20
|
+
</div>
|
|
44
21
|
</v-card>
|
|
45
22
|
</template>
|
|
46
23
|
|
|
@@ -69,6 +46,32 @@ function showSelection(item: any) {
|
|
|
69
46
|
if(item.value === 'walk-in' && prop.mode === 'register') {
|
|
70
47
|
return false ;
|
|
71
48
|
} return true;
|
|
72
|
-
|
|
49
|
+
|
|
73
50
|
}
|
|
74
51
|
</script>
|
|
52
|
+
|
|
53
|
+
<style scoped>
|
|
54
|
+
/* Same shape as the Add Vehicle prompt: full-width choices, one per row. */
|
|
55
|
+
.visitor-select__body {
|
|
56
|
+
padding: 20px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.visitor-select__hint {
|
|
60
|
+
margin: 0 0 14px;
|
|
61
|
+
text-align: center;
|
|
62
|
+
font-size: var(--fs-cell);
|
|
63
|
+
color: var(--muted);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.visitor-select__option {
|
|
67
|
+
display: flex;
|
|
68
|
+
width: 100%;
|
|
69
|
+
margin-bottom: 10px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.visitor-select__footer {
|
|
73
|
+
display: flex;
|
|
74
|
+
justify-content: flex-end;
|
|
75
|
+
padding: 0 20px 20px;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -1,50 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@row-click="handleRowClick"
|
|
16
|
-
>
|
|
17
|
-
<template #actions>
|
|
18
|
-
<v-row no-gutters>
|
|
19
|
-
<v-btn
|
|
20
|
-
v-if="canAddVisitor"
|
|
21
|
-
class="text-none"
|
|
22
|
-
rounded="pill"
|
|
23
|
-
variant="tonal"
|
|
24
|
-
size="large"
|
|
25
|
-
@click="handleAddNew"
|
|
26
|
-
text="Add Visitor"
|
|
27
|
-
/>
|
|
28
|
-
<v-btn
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S PAGE TITLE ROW (Phase 9).
|
|
5
|
+
|
|
6
|
+
Drawn here rather than through `TableMain`'s `title` prop because the
|
|
7
|
+
design puts the screen's actions on the title line, and `TableMain`
|
|
8
|
+
renders its `actions` slot as a separate row under the heading. Same
|
|
9
|
+
three buttons, same handlers, same permission gates - moved, not changed.
|
|
10
|
+
-->
|
|
11
|
+
<v-col cols="12">
|
|
12
|
+
<PageHeader title="Visitor Mgmt">
|
|
13
|
+
<template #actions>
|
|
14
|
+
<AppButton
|
|
29
15
|
v-if="canScanVisitorQRCode"
|
|
30
|
-
|
|
31
|
-
class="text-none ml-2"
|
|
32
|
-
rounded="pill"
|
|
33
|
-
variant="tonal"
|
|
34
|
-
size="large"
|
|
16
|
+
variant="ghost"
|
|
35
17
|
@click="dialog.scanVisitorQRCode = true"
|
|
36
|
-
|
|
18
|
+
>
|
|
19
|
+
Scan QR Code
|
|
20
|
+
</AppButton>
|
|
37
21
|
<v-menu>
|
|
38
22
|
<template #activator="{ props }">
|
|
39
|
-
<v-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class="text-none ml-2"
|
|
43
|
-
prepend-icon="mdi-download"
|
|
44
|
-
rounded="pill"
|
|
45
|
-
variant="tonal"
|
|
46
|
-
size="large"
|
|
47
|
-
/>
|
|
23
|
+
<AppButton v-bind="props" variant="ghost" icon="mdi-download">
|
|
24
|
+
Download Report
|
|
25
|
+
</AppButton>
|
|
48
26
|
</template>
|
|
49
27
|
<v-list>
|
|
50
28
|
<v-list-item
|
|
@@ -59,8 +37,27 @@
|
|
|
59
37
|
/>
|
|
60
38
|
</v-list>
|
|
61
39
|
</v-menu>
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
<AppButton v-if="canAddVisitor" icon="mdi-plus" @click="handleAddNew">
|
|
41
|
+
Add Visitor
|
|
42
|
+
</AppButton>
|
|
43
|
+
</template>
|
|
44
|
+
</PageHeader>
|
|
45
|
+
</v-col>
|
|
46
|
+
|
|
47
|
+
<TableMain
|
|
48
|
+
:headers="headers"
|
|
49
|
+
:items="items"
|
|
50
|
+
:loading="getVisitorPending"
|
|
51
|
+
:page="page"
|
|
52
|
+
:pages="pages"
|
|
53
|
+
:extension-height="110"
|
|
54
|
+
:offset="300"
|
|
55
|
+
:pageRange="pageRange"
|
|
56
|
+
@refresh="getVisitorRefresh"
|
|
57
|
+
@update:page="handleUpdatePage"
|
|
58
|
+
show-header
|
|
59
|
+
@row-click="handleRowClick"
|
|
60
|
+
>
|
|
64
61
|
<template #extension>
|
|
65
62
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
66
63
|
<v-tabs
|
|
@@ -109,32 +106,18 @@
|
|
|
109
106
|
hide-details
|
|
110
107
|
/>
|
|
111
108
|
<InputDatePicker v-model="dateTo" density="compact" hide-details />
|
|
112
|
-
<
|
|
109
|
+
<AppSelect
|
|
113
110
|
v-if="activeTab == 'registered'"
|
|
114
111
|
v-model="filterTypes"
|
|
115
112
|
label="Filter by types"
|
|
116
113
|
item-title="label"
|
|
117
114
|
item-value="value"
|
|
118
115
|
:items="visitorSelection"
|
|
119
|
-
|
|
116
|
+
class="visitor-type-filter"
|
|
117
|
+
compact
|
|
120
118
|
clearable
|
|
121
119
|
multiple
|
|
122
|
-
|
|
123
|
-
hide-details
|
|
124
|
-
>
|
|
125
|
-
<template v-slot:selection="{ item, index }">
|
|
126
|
-
<div class="d-flex align-center text-caption text-nowrap">
|
|
127
|
-
<v-chip
|
|
128
|
-
v-if="index === 0"
|
|
129
|
-
color="error"
|
|
130
|
-
:text="filterTypeSelectionLabel()"
|
|
131
|
-
size="x-small"
|
|
132
|
-
variant="tonal"
|
|
133
|
-
class="ml-2"
|
|
134
|
-
/>
|
|
135
|
-
</div>
|
|
136
|
-
</template>
|
|
137
|
-
</v-select>
|
|
120
|
+
/>
|
|
138
121
|
</v-card>
|
|
139
122
|
</v-row>
|
|
140
123
|
</template>
|
|
@@ -303,6 +286,18 @@
|
|
|
303
286
|
>
|
|
304
287
|
</div>
|
|
305
288
|
|
|
289
|
+
<v-btn
|
|
290
|
+
v-if="hidQrEnabled"
|
|
291
|
+
icon="mdi-qrcode"
|
|
292
|
+
size="x-small"
|
|
293
|
+
variant="text"
|
|
294
|
+
color="primary"
|
|
295
|
+
title="View HID QR code"
|
|
296
|
+
aria-label="View HID QR code"
|
|
297
|
+
class="align-self-start mt-1"
|
|
298
|
+
@click.stop="openVisitorHidQr(item)"
|
|
299
|
+
/>
|
|
300
|
+
|
|
306
301
|
<v-menu :close-on-content-click="true">
|
|
307
302
|
<template v-slot:activator="{ props: menuProps }">
|
|
308
303
|
<div
|
|
@@ -618,15 +613,21 @@
|
|
|
618
613
|
class="d-flex align-center"
|
|
619
614
|
>
|
|
620
615
|
<strong>{{ label }}:</strong>
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
616
|
+
<!-- The design's check-in / check-out pair. Was Vuetify's
|
|
617
|
+
`color="success"` beside a hardcoded `color="red"` - two
|
|
618
|
+
different systems on one field list, and neither of them a
|
|
619
|
+
token. `:loading` is dropped rather than re-invented: the
|
|
620
|
+
same flag already drives `:disabled`, which is the
|
|
621
|
+
precedent set when the scanned-visitor form converted. -->
|
|
622
|
+
<AppButton
|
|
623
|
+
variant="ok"
|
|
624
|
+
icon="mdi-check"
|
|
625
|
+
class="ml-3"
|
|
626
626
|
:disabled="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
627
627
|
@click="handleCheckin(selectedVisitorDataObject)"
|
|
628
|
-
|
|
629
|
-
|
|
628
|
+
>
|
|
629
|
+
Check In
|
|
630
|
+
</AppButton>
|
|
630
631
|
</span>
|
|
631
632
|
<span
|
|
632
633
|
v-if="
|
|
@@ -638,14 +639,15 @@
|
|
|
638
639
|
class="d-flex align-center"
|
|
639
640
|
>
|
|
640
641
|
<strong>{{ label }}:</strong>
|
|
641
|
-
<
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
text="Checkout"
|
|
642
|
+
<AppButton
|
|
643
|
+
variant="err"
|
|
644
|
+
icon="mdi-close"
|
|
645
|
+
class="ml-3"
|
|
646
646
|
:disabled="loading.checkingOut"
|
|
647
647
|
@click="handleCheckout(selectedVisitorId as string)"
|
|
648
|
-
|
|
648
|
+
>
|
|
649
|
+
Check Out
|
|
650
|
+
</AppButton>
|
|
649
651
|
</span>
|
|
650
652
|
|
|
651
653
|
<span
|
|
@@ -1177,12 +1179,52 @@
|
|
|
1177
1179
|
</v-card>
|
|
1178
1180
|
</v-dialog>
|
|
1179
1181
|
|
|
1182
|
+
<v-dialog v-model="hidQrDialog" max-width="440" persistent>
|
|
1183
|
+
<v-card>
|
|
1184
|
+
<v-toolbar color="transparent" density="comfortable" class="px-2">
|
|
1185
|
+
<v-toolbar-title class="text-subtitle-1 font-weight-bold">Visitor QR Code</v-toolbar-title>
|
|
1186
|
+
<v-btn icon="mdi-close" variant="text" title="Close" @click="hidQrDialog = false" />
|
|
1187
|
+
</v-toolbar>
|
|
1188
|
+
<v-card-text class="px-8 pb-6 text-center">
|
|
1189
|
+
<v-progress-circular v-if="hidQrLoading" indeterminate color="primary" class="my-10" />
|
|
1190
|
+
<template v-else-if="hidQrImageUrl">
|
|
1191
|
+
<div class="text-h6 font-weight-bold">{{ hidQrVisitor?.name || "Visitor Access Pass" }}</div>
|
|
1192
|
+
<div class="visitor-hid-qr my-5">
|
|
1193
|
+
<v-img :src="hidQrImageUrl" width="280" height="280" alt="Visitor HID QR code" />
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="text-caption text-medium-emphasis">
|
|
1196
|
+
Valid until {{ formatHidQrExpiry(hidQrExpiresAt) }}
|
|
1197
|
+
</div>
|
|
1198
|
+
<v-chip class="mt-2" size="small" :color="hidQrExpired ? 'error' : 'success'" variant="tonal">
|
|
1199
|
+
{{ hidQrExpired ? "Expired" : `Expires in ${formatHidQrCountdown(hidQrRemainingSeconds)}` }}
|
|
1200
|
+
</v-chip>
|
|
1201
|
+
</template>
|
|
1202
|
+
<v-alert v-if="hidQrError" type="warning" variant="tonal" density="compact" class="mt-4 text-left">
|
|
1203
|
+
{{ hidQrError }}
|
|
1204
|
+
</v-alert>
|
|
1205
|
+
<v-btn
|
|
1206
|
+
v-if="!hidQrLoading && (!hidQrImageUrl || hidQrExpired)"
|
|
1207
|
+
block
|
|
1208
|
+
color="primary-button"
|
|
1209
|
+
variant="flat"
|
|
1210
|
+
class="mt-4 text-none"
|
|
1211
|
+
prepend-icon="mdi-refresh"
|
|
1212
|
+
:loading="hidQrGenerating"
|
|
1213
|
+
@click="generateVisitorHidQr"
|
|
1214
|
+
>
|
|
1215
|
+
{{ hidQrExpired ? "Generate QR again" : "Generate HID QR code" }}
|
|
1216
|
+
</v-btn>
|
|
1217
|
+
</v-card-text>
|
|
1218
|
+
</v-card>
|
|
1219
|
+
</v-dialog>
|
|
1220
|
+
|
|
1180
1221
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
1181
1222
|
</v-row>
|
|
1182
1223
|
</template>
|
|
1183
1224
|
|
|
1184
1225
|
<script setup lang="ts">
|
|
1185
1226
|
import moment from "moment-timezone";
|
|
1227
|
+
import QRCode from "qrcode";
|
|
1186
1228
|
|
|
1187
1229
|
definePageMeta({
|
|
1188
1230
|
middleware: ["01-auth", "02-org"],
|
|
@@ -1244,6 +1286,8 @@ const {
|
|
|
1244
1286
|
} = useUtils();
|
|
1245
1287
|
const { formatLocation } = useSecurityUtils();
|
|
1246
1288
|
const { getFileUrlAnpr } = useFile();
|
|
1289
|
+
const { getSiteById } = useSite();
|
|
1290
|
+
const { getIdentities, issueVisitorQr } = useHidAmico();
|
|
1247
1291
|
// const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
|
|
1248
1292
|
|
|
1249
1293
|
const routeName = route.name;
|
|
@@ -1255,6 +1299,106 @@ const pageRange = ref("-- - -- of --");
|
|
|
1255
1299
|
const activeTab = ref("registered");
|
|
1256
1300
|
const activeVisitorFormType = ref<TVisitorType | null>(null);
|
|
1257
1301
|
const selectedVisitorId = ref<string | null>(""); // selected visitor for viewing/actions
|
|
1302
|
+
const hidQrDialog = ref(false);
|
|
1303
|
+
const hidQrLoading = ref(false);
|
|
1304
|
+
const hidQrGenerating = ref(false);
|
|
1305
|
+
const hidQrError = ref("");
|
|
1306
|
+
const hidQrImageUrl = ref("");
|
|
1307
|
+
const hidQrExpiresAt = ref("");
|
|
1308
|
+
const hidQrNow = ref(Date.now());
|
|
1309
|
+
const hidQrVisitor = ref<Record<string, any> | null>(null);
|
|
1310
|
+
const hidQrConfig = ref({ enabled: false, readerId: "", validityMinutes: 60 });
|
|
1311
|
+
let hidQrTimer: ReturnType<typeof setInterval> | null = null;
|
|
1312
|
+
const hidQrEnabled = computed(() => hidQrConfig.value.enabled && Boolean(hidQrConfig.value.readerId));
|
|
1313
|
+
const hidQrRemainingSeconds = computed(() => {
|
|
1314
|
+
const expiry = new Date(hidQrExpiresAt.value).getTime();
|
|
1315
|
+
return Number.isFinite(expiry) ? Math.max(0, Math.ceil((expiry - hidQrNow.value) / 1000)) : 0;
|
|
1316
|
+
});
|
|
1317
|
+
const hidQrExpired = computed(() => Boolean(hidQrExpiresAt.value) && hidQrRemainingSeconds.value === 0);
|
|
1318
|
+
|
|
1319
|
+
async function loadHidQrConfig() {
|
|
1320
|
+
try {
|
|
1321
|
+
const response: any = await getSiteById(siteId);
|
|
1322
|
+
const site = response?.data ?? response;
|
|
1323
|
+
const config = site?.metadata?.hidQrCodePass ?? {};
|
|
1324
|
+
hidQrConfig.value = {
|
|
1325
|
+
enabled: Boolean(config.enabled && config.onlineMode !== false),
|
|
1326
|
+
readerId: String(config.readerId || ""),
|
|
1327
|
+
validityMinutes: Number(config.validityMinutes) > 0 ? Number(config.validityMinutes) : 60,
|
|
1328
|
+
};
|
|
1329
|
+
} catch {
|
|
1330
|
+
hidQrConfig.value = { enabled: false, readerId: "", validityMinutes: 60 };
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function extractIdentityItems(response: any) {
|
|
1335
|
+
return response?.items ?? response?.data?.items ?? [];
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
async function openVisitorHidQr(visitor: Record<string, any>) {
|
|
1339
|
+
hidQrVisitor.value = visitor;
|
|
1340
|
+
hidQrDialog.value = true;
|
|
1341
|
+
hidQrLoading.value = true;
|
|
1342
|
+
hidQrError.value = "";
|
|
1343
|
+
hidQrImageUrl.value = "";
|
|
1344
|
+
hidQrExpiresAt.value = "";
|
|
1345
|
+
try {
|
|
1346
|
+
const response = await getIdentities(hidQrConfig.value.readerId, { type: "visitor", limit: 100 });
|
|
1347
|
+
const identity = extractIdentityItems(response).find(
|
|
1348
|
+
(item: any) => String(item.visitor || item.metadata?.visitor || "") === String(visitor._id),
|
|
1349
|
+
);
|
|
1350
|
+
const qrValue = String(identity?.metadata?.qrValue || identity?.cardNo || "");
|
|
1351
|
+
hidQrExpiresAt.value = String(identity?.metadata?.expiresAt || "");
|
|
1352
|
+
if (!qrValue || !hidQrExpiresAt.value) {
|
|
1353
|
+
hidQrError.value = "This visitor does not have an HID QR code yet.";
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
hidQrImageUrl.value = await QRCode.toDataURL(qrValue, { width: 360, margin: 2, errorCorrectionLevel: "M" });
|
|
1357
|
+
hidQrNow.value = Date.now();
|
|
1358
|
+
} catch (error: unknown) {
|
|
1359
|
+
hidQrError.value = errorConverter(error) || "Unable to load the visitor HID QR code.";
|
|
1360
|
+
} finally {
|
|
1361
|
+
hidQrLoading.value = false;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
async function generateVisitorHidQr() {
|
|
1366
|
+
if (!hidQrVisitor.value?._id || !hidQrConfig.value.readerId) return;
|
|
1367
|
+
hidQrGenerating.value = true;
|
|
1368
|
+
hidQrError.value = "";
|
|
1369
|
+
try {
|
|
1370
|
+
const response = await issueVisitorQr(hidQrConfig.value.readerId, {
|
|
1371
|
+
visitorId: String(hidQrVisitor.value._id),
|
|
1372
|
+
validityMinutes: hidQrConfig.value.validityMinutes,
|
|
1373
|
+
});
|
|
1374
|
+
const credential = "data" in response ? response.data : response;
|
|
1375
|
+
hidQrImageUrl.value = await QRCode.toDataURL(credential.qrValue, { width: 360, margin: 2, errorCorrectionLevel: "M" });
|
|
1376
|
+
hidQrExpiresAt.value = credential.expiresAt;
|
|
1377
|
+
hidQrNow.value = Date.now();
|
|
1378
|
+
} catch (error: unknown) {
|
|
1379
|
+
hidQrError.value = errorConverter(error) || "Unable to generate the visitor HID QR code.";
|
|
1380
|
+
} finally {
|
|
1381
|
+
hidQrGenerating.value = false;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function formatHidQrExpiry(value: string) {
|
|
1386
|
+
const date = new Date(value);
|
|
1387
|
+
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function formatHidQrCountdown(totalSeconds: number) {
|
|
1391
|
+
const safe = Math.max(0, totalSeconds);
|
|
1392
|
+
const hours = Math.floor(safe / 3600);
|
|
1393
|
+
const minutes = Math.floor((safe % 3600) / 60);
|
|
1394
|
+
const seconds = safe % 60;
|
|
1395
|
+
return [hours, minutes, seconds].map((part) => String(part).padStart(2, "0")).join(":");
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
watch(hidQrDialog, (open) => {
|
|
1399
|
+
if (hidQrTimer) clearInterval(hidQrTimer);
|
|
1400
|
+
hidQrTimer = open ? setInterval(() => { hidQrNow.value = Date.now(); }, 1000) : null;
|
|
1401
|
+
});
|
|
1258
1402
|
|
|
1259
1403
|
//filter states
|
|
1260
1404
|
const searchInput = ref("");
|
|
@@ -1505,11 +1649,6 @@ const formattedFields = {
|
|
|
1505
1649
|
attachments: "Attachments",
|
|
1506
1650
|
} as const;
|
|
1507
1651
|
|
|
1508
|
-
function filterTypeSelectionLabel() {
|
|
1509
|
-
const length = filterTypes.value.length;
|
|
1510
|
-
return `${length} selected ${length === 1 ? "type" : "types"}` as string;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
1652
|
const passStatusOptions = [
|
|
1514
1653
|
{ label: "Returned", value: "Returned" },
|
|
1515
1654
|
{ label: "Not Returned", value: "In Use" },
|
|
@@ -1601,7 +1740,6 @@ const {
|
|
|
1601
1740
|
watch(getVisitorReq, (newData: any) => {
|
|
1602
1741
|
if (newData) {
|
|
1603
1742
|
items.value = newData.items ?? [];
|
|
1604
|
-
// items.value = [{ _id: "testid", name: "John Doe", type: "Contractor", company: "ABC Corp", location: "Block A, Level 1, Unit 101", contact: "12345678", plateNumber: "SGX1234A", checkIn: new Date().toISOString(), checkOut: null, status: "registered", visitorPass: [{ _id: "1", keyId: "pass1", prefixAndName: "VIP Pass" }, { _id: "2", keyId: "pass2", prefixAndName: "VIP Pass 2" }], passKeys: [{ _id: "1", keyId: "key2", prefixAndName: "Master Key" }] }];
|
|
1605
1743
|
pages.value = newData.pages ?? 0;
|
|
1606
1744
|
pageRange.value = newData?.pageRange ?? "-- - -- of --";
|
|
1607
1745
|
}
|
|
@@ -2347,7 +2485,14 @@ const syncVisitorFiltersFromRoute = () => {
|
|
|
2347
2485
|
}
|
|
2348
2486
|
};
|
|
2349
2487
|
|
|
2350
|
-
onMounted(
|
|
2488
|
+
onMounted(() => {
|
|
2489
|
+
syncVisitorFiltersFromRoute();
|
|
2490
|
+
void loadHidQrConfig();
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
onBeforeUnmount(() => {
|
|
2494
|
+
if (hidQrTimer) clearInterval(hidQrTimer);
|
|
2495
|
+
});
|
|
2351
2496
|
|
|
2352
2497
|
watch(() => route.query, syncVisitorFiltersFromRoute, { deep: true });
|
|
2353
2498
|
|
|
@@ -2398,4 +2543,16 @@ watchEffect(async () => {
|
|
|
2398
2543
|
});
|
|
2399
2544
|
</script>
|
|
2400
2545
|
|
|
2401
|
-
<style scoped
|
|
2546
|
+
<style scoped>
|
|
2547
|
+
/* The width the type filter carried as `max-width="200"` on the v-select. */
|
|
2548
|
+
.visitor-type-filter {
|
|
2549
|
+
max-width: 200px;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
.visitor-hid-qr {
|
|
2553
|
+
display: inline-flex;
|
|
2554
|
+
padding: 12px;
|
|
2555
|
+
border: 1px solid #dce3e8;
|
|
2556
|
+
background: #fff;
|
|
2557
|
+
}
|
|
2558
|
+
</style>
|
|
@@ -3,26 +3,27 @@
|
|
|
3
3
|
@after-enter="handleScanVisitorQRCode">
|
|
4
4
|
<v-container class="d-flex justify-center" max-height="90vh">
|
|
5
5
|
<!-- QR code reader container -->
|
|
6
|
-
<v-card
|
|
7
|
-
<
|
|
8
|
-
<v-card-title
|
|
9
|
-
<
|
|
10
|
-
<v-btn color="grey-darken-1" icon="mdi-close" @click="closeDialog()" />
|
|
11
|
-
</v-toolbar>
|
|
12
|
-
|
|
13
|
-
<div id="reader" class="d-flex justify-center align-center w-100 h-100"
|
|
14
|
-
style="height: calc(100vh - 64px)">
|
|
15
|
-
<!-- The QR code scanner box -->
|
|
16
|
-
<!-- This is where the QR code scanner will be displayed -->
|
|
6
|
+
<v-card class="screen-modal qr-scanner">
|
|
7
|
+
<div class="qr-scanner__head">
|
|
8
|
+
<v-card-title>Scan QR Code</v-card-title>
|
|
9
|
+
<AppButton variant="icon" icon="mdi-close" @click="closeDialog()" />
|
|
17
10
|
</div>
|
|
18
|
-
|
|
19
|
-
<div
|
|
20
|
-
<
|
|
11
|
+
|
|
12
|
+
<div class="screen-modal__body qr-scanner__body">
|
|
13
|
+
<div id="reader" class="qr-scanner__reader">
|
|
14
|
+
<!-- The QR code scanner box -->
|
|
15
|
+
<!-- This is where the QR code scanner will be displayed -->
|
|
16
|
+
</div>
|
|
17
|
+
<!-- Show Error Messages -->
|
|
18
|
+
<p v-if="showNotFoundMessage" class="qr-scanner__error">{{ errorMessage }}</p>
|
|
19
|
+
<!-- Switch camera button inside the reader box -->
|
|
20
|
+
<AppButton
|
|
21
|
+
icon="mdi-camera-switch"
|
|
22
|
+
class="qr-scanner__switch"
|
|
23
|
+
aria-label="Switch camera"
|
|
24
|
+
@click="switchCamera()"
|
|
25
|
+
/>
|
|
21
26
|
</div>
|
|
22
|
-
<!-- Switch camera button inside the reader box -->
|
|
23
|
-
<v-btn color="primary" icon class="mt-4" @click="switchCamera()">
|
|
24
|
-
<v-icon icon="mdi-camera-switch" large />
|
|
25
|
-
</v-btn>
|
|
26
27
|
</v-card>
|
|
27
28
|
</v-container>
|
|
28
29
|
</v-dialog>
|
|
@@ -136,3 +137,49 @@ const stopScanner = () => {
|
|
|
136
137
|
});
|
|
137
138
|
};
|
|
138
139
|
</script>
|
|
140
|
+
|
|
141
|
+
<style scoped>
|
|
142
|
+
/* The card used to be `w-100` inside the centring container; keep that width
|
|
143
|
+
or the dialog collapses to the width of the reader box. */
|
|
144
|
+
.qr-scanner {
|
|
145
|
+
width: 100%;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.qr-scanner__head {
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: space-between;
|
|
152
|
+
padding-right: 12px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.qr-scanner__body {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 16px;
|
|
160
|
+
padding: 4px 20px 20px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* The scanner paints its own video into this box; only the frame is ours. */
|
|
164
|
+
.qr-scanner__reader {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
width: 100%;
|
|
169
|
+
min-height: 320px;
|
|
170
|
+
border: 1px solid var(--border);
|
|
171
|
+
border-radius: var(--r-inner);
|
|
172
|
+
background: var(--thead);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.qr-scanner__error {
|
|
176
|
+
margin: 0;
|
|
177
|
+
font-size: var(--fs-cell);
|
|
178
|
+
color: var(--err);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.qr-scanner__switch {
|
|
182
|
+
width: var(--btn-h);
|
|
183
|
+
padding: 0;
|
|
184
|
+
}
|
|
185
|
+
</style>
|