@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,13 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-dialog v-model="showDialog" max-width="600" persistent>
|
|
3
|
-
<v-card>
|
|
4
|
-
<
|
|
5
|
-
<v-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<v-card-text class="pa-6">
|
|
3
|
+
<v-card class="screen-modal attendance-settings">
|
|
4
|
+
<div class="attendance-settings__head">
|
|
5
|
+
<v-card-title>Attendance Settings</v-card-title>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="screen-modal__body attendance-settings__body filter-field">
|
|
11
9
|
<v-alert
|
|
12
10
|
v-if="error"
|
|
13
11
|
type="error"
|
|
@@ -26,12 +24,13 @@
|
|
|
26
24
|
<v-checkbox
|
|
27
25
|
v-model="isGeofencingEnabled"
|
|
28
26
|
label="Enable Geofencing"
|
|
27
|
+
color="accent"
|
|
29
28
|
density="comfortable"
|
|
30
29
|
hide-details
|
|
31
30
|
/>
|
|
32
31
|
<span
|
|
33
32
|
v-if="isGeofencingEnabled"
|
|
34
|
-
class="
|
|
33
|
+
class="attendance-settings__hint"
|
|
35
34
|
>
|
|
36
35
|
Enabling this setting will require users to take attendance
|
|
37
36
|
within a certain radius via QR code or within the site location.
|
|
@@ -43,44 +42,32 @@
|
|
|
43
42
|
<v-checkbox
|
|
44
43
|
v-model="isQrEnabled"
|
|
45
44
|
label="Enable QR Code"
|
|
45
|
+
color="accent"
|
|
46
46
|
density="comfortable"
|
|
47
47
|
hide-details
|
|
48
48
|
/>
|
|
49
49
|
</v-col>
|
|
50
50
|
|
|
51
51
|
<v-col v-if="isQrEnabled" cols="12">
|
|
52
|
-
<
|
|
53
|
-
variant="flat"
|
|
54
|
-
class="pa-4 d-flex flex-column align-center"
|
|
55
|
-
>
|
|
52
|
+
<div class="attendance-settings__qr">
|
|
56
53
|
<QrcodeVue
|
|
57
54
|
:value="site"
|
|
58
55
|
:level="'M'"
|
|
59
56
|
:render-as="'svg'"
|
|
60
57
|
:size="300"
|
|
61
58
|
/>
|
|
62
|
-
<
|
|
63
|
-
variant="text"
|
|
64
|
-
color="primary"
|
|
65
|
-
class="mt-2"
|
|
66
|
-
@click="downloadQrCode"
|
|
67
|
-
>
|
|
68
|
-
<v-icon class="mr-2">mdi-download</v-icon>
|
|
59
|
+
<AppButton variant="ghost" icon="mdi-download" class="mt-2" @click="downloadQrCode">
|
|
69
60
|
Download QR Code
|
|
70
|
-
</
|
|
71
|
-
</
|
|
61
|
+
</AppButton>
|
|
62
|
+
</div>
|
|
72
63
|
</v-col>
|
|
73
64
|
|
|
74
65
|
<v-col cols="12" class="mt-4">
|
|
75
66
|
<InputLabel title="Site Location" />
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
class="mt-2"
|
|
79
|
-
@click="isMapSearchVisible = true"
|
|
80
|
-
>
|
|
81
|
-
<v-icon class="mr-2">mdi-map-search</v-icon>
|
|
67
|
+
<AppButton variant="ghost" icon="mdi-map-search" class="mt-2"
|
|
68
|
+
@click="isMapSearchVisible = true">
|
|
82
69
|
Search Location
|
|
83
|
-
</
|
|
70
|
+
</AppButton>
|
|
84
71
|
</v-col>
|
|
85
72
|
|
|
86
73
|
<v-col v-if="geolocation" cols="12">
|
|
@@ -125,40 +112,12 @@
|
|
|
125
112
|
</template>
|
|
126
113
|
</v-row>
|
|
127
114
|
</v-form>
|
|
128
|
-
</
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
block
|
|
135
|
-
variant="text"
|
|
136
|
-
class="text-none"
|
|
137
|
-
size="large"
|
|
138
|
-
@click="close"
|
|
139
|
-
height="56"
|
|
140
|
-
:disabled="submitting"
|
|
141
|
-
>
|
|
142
|
-
Cancel
|
|
143
|
-
</v-btn>
|
|
144
|
-
</v-col>
|
|
145
|
-
<v-col cols="6" class="pa-0">
|
|
146
|
-
<v-btn
|
|
147
|
-
block
|
|
148
|
-
variant="flat"
|
|
149
|
-
color="black"
|
|
150
|
-
class="text-none font-weight-bold rounded-0"
|
|
151
|
-
height="56"
|
|
152
|
-
size="large"
|
|
153
|
-
:loading="submitting"
|
|
154
|
-
@click="onSubmit"
|
|
155
|
-
>
|
|
156
|
-
<v-icon class="mr-2">mdi-check</v-icon>
|
|
157
|
-
Update
|
|
158
|
-
</v-btn>
|
|
159
|
-
</v-col>
|
|
160
|
-
</v-row>
|
|
161
|
-
</v-toolbar>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="attendance-settings__footer">
|
|
118
|
+
<AppButton variant="ghost" :disabled="submitting" @click="close">Cancel</AppButton>
|
|
119
|
+
<AppButton icon="mdi-check" :disabled="submitting" @click="onSubmit">Update</AppButton>
|
|
120
|
+
</div>
|
|
162
121
|
</v-card>
|
|
163
122
|
</v-dialog>
|
|
164
123
|
|
|
@@ -210,6 +169,7 @@ const {
|
|
|
210
169
|
data: getAttendanceSettingsReq,
|
|
211
170
|
refresh: getAttendanceSettingsRefresh,
|
|
212
171
|
pending: loading,
|
|
172
|
+
error: getAttendanceSettingsError,
|
|
213
173
|
} = await useLazyAsyncData(
|
|
214
174
|
`attendance-settings-${props.site}`,
|
|
215
175
|
() => getAttendanceSettings(props.site, props.serviceType),
|
|
@@ -220,6 +180,22 @@ const {
|
|
|
220
180
|
);
|
|
221
181
|
|
|
222
182
|
watchEffect(() => {
|
|
183
|
+
// A failed read used to be indistinguishable from a site with no settings
|
|
184
|
+
// saved: the dialog just opened with geofencing off and blank fields, and
|
|
185
|
+
// the operator had no way to tell it was showing them nothing rather than
|
|
186
|
+
// the truth. Worse, a request answered by the SPA fallback resolves with a
|
|
187
|
+
// 200 and a STRING, so nothing threw - every field read off it was simply
|
|
188
|
+
// undefined. Anything that is not an object is a failure, and it is said.
|
|
189
|
+
const res: any = getAttendanceSettingsReq.value;
|
|
190
|
+
if (
|
|
191
|
+
getAttendanceSettingsError.value ||
|
|
192
|
+
(res !== null && res !== undefined && typeof res !== "object")
|
|
193
|
+
) {
|
|
194
|
+
error.value =
|
|
195
|
+
"Could not load the saved settings - what you see below is not what is saved. Close and reopen to try again.";
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
223
199
|
if (getAttendanceSettingsReq.value) {
|
|
224
200
|
isGeofencingEnabled.value =
|
|
225
201
|
getAttendanceSettingsReq.value.isGeofencingEnabled || false;
|
|
@@ -397,3 +373,43 @@ watch(showDialog, (newVal) => {
|
|
|
397
373
|
}
|
|
398
374
|
});
|
|
399
375
|
</script>
|
|
376
|
+
|
|
377
|
+
<style scoped>
|
|
378
|
+
.attendance-settings__head {
|
|
379
|
+
display: flex;
|
|
380
|
+
align-items: center;
|
|
381
|
+
justify-content: space-between;
|
|
382
|
+
padding-right: 12px;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.attendance-settings__body {
|
|
386
|
+
padding: 0 20px;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.attendance-settings__hint {
|
|
390
|
+
font-size: var(--fs-cell);
|
|
391
|
+
color: var(--text2);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* Was a `variant="flat"` v-card, i.e. a second card inside the modal card. It
|
|
395
|
+
is a frame around the QR, so it takes the design's inner surface. */
|
|
396
|
+
.attendance-settings__qr {
|
|
397
|
+
display: flex;
|
|
398
|
+
flex-direction: column;
|
|
399
|
+
align-items: center;
|
|
400
|
+
padding: 16px;
|
|
401
|
+
border: 1px solid var(--border);
|
|
402
|
+
border-radius: var(--r-inner);
|
|
403
|
+
background: var(--thead);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* The design's dialog footer. Deliberately not the shared
|
|
407
|
+
`.screen-modal__footer` - that class is on the batch-D branch (#1150), not
|
|
408
|
+
on this base, and a second copy in `screens.css` would collide with it. */
|
|
409
|
+
.attendance-settings__footer {
|
|
410
|
+
display: flex;
|
|
411
|
+
justify-content: flex-end;
|
|
412
|
+
gap: 8px;
|
|
413
|
+
padding: 16px 20px 20px;
|
|
414
|
+
}
|
|
415
|
+
</style>
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-avatar :color="getColor(name || id)" :size="size">
|
|
3
|
-
|
|
3
|
+
<!--
|
|
4
|
+
A stored profile photo that 404s left the circle EMPTY - `v-img`
|
|
5
|
+
draws nothing when the source fails, and there was no fallback under
|
|
6
|
+
it. The initials the avatar already computes stand in, so the circle
|
|
7
|
+
is never blank.
|
|
8
|
+
-->
|
|
9
|
+
<v-img v-if="imageSrc" :alt="name" :src="imageUrl">
|
|
10
|
+
<template #error>
|
|
11
|
+
<div class="d-flex align-center justify-center fill-height">
|
|
12
|
+
<span :style="{ fontSize: `${fontSize}px` }">{{ initials }}</span>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
</v-img>
|
|
4
16
|
<span v-else :style="{
|
|
5
17
|
fontSize: `${fontSize}px`,
|
|
6
18
|
lineHeight: `${size}px`
|
|
@@ -43,6 +55,39 @@ const initials = computed(() => {
|
|
|
43
55
|
|
|
44
56
|
const fontSize = computed(() => Math.round(props.size * 0.5))
|
|
45
57
|
|
|
58
|
+
/**
|
|
59
|
+
* WHITE INITIALS ON A HASHED COLOUR - THE ESTATE'S WORST CONTRAST DEFECT.
|
|
60
|
+
*
|
|
61
|
+
* The circle's fill is derived from the name, and Vuetify then picks a WHITE
|
|
62
|
+
* label for it. Measured across the whole space this generator can produce
|
|
63
|
+
* (360 hues x 20 saturations x 15 lightnesses): **70.6% of names fail AA**,
|
|
64
|
+
* and the worst - a yellow, `hsl(60,79%,59%)` - reads **1.30:1**. The 3.12:1
|
|
65
|
+
* seen on one screen was a mild sample, not the floor.
|
|
66
|
+
*
|
|
67
|
+
* The name's colour is its identity, so the HUE and SATURATION are kept
|
|
68
|
+
* exactly; only the lightness is walked down until white clears 4.5:1 on it.
|
|
69
|
+
* A yellow therefore darkens a long way and a blue barely moves, which is what
|
|
70
|
+
* makes a single fixed lightness impossible here.
|
|
71
|
+
*/
|
|
72
|
+
const AA = 4.5
|
|
73
|
+
|
|
74
|
+
const srgb = (c: number) => {
|
|
75
|
+
const v = c / 255
|
|
76
|
+
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Relative luminance of an `hsl()` triple, without going through the DOM. */
|
|
80
|
+
function luminance(h: number, s: number, l: number): number {
|
|
81
|
+
const sat = s / 100
|
|
82
|
+
const light = l / 100
|
|
83
|
+
const a = sat * Math.min(light, 1 - light)
|
|
84
|
+
const f = (n: number) => {
|
|
85
|
+
const k = (n + h / 30) % 12
|
|
86
|
+
return light - a * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)))
|
|
87
|
+
}
|
|
88
|
+
return 0.2126 * srgb(f(0) * 255) + 0.7152 * srgb(f(8) * 255) + 0.0722 * srgb(f(4) * 255)
|
|
89
|
+
}
|
|
90
|
+
|
|
46
91
|
function getColor(str?: string): string {
|
|
47
92
|
if(!str) return "grey"
|
|
48
93
|
|
|
@@ -54,7 +99,14 @@ function getColor(str?: string): string {
|
|
|
54
99
|
|
|
55
100
|
const hue = Math.abs(hash) % 360
|
|
56
101
|
const saturation = 60 + (Math.abs((hash >> 3) % 20)) // 60–80%
|
|
57
|
-
|
|
102
|
+
let lightness = 45 + (Math.abs((hash >> 5) % 15)) // 45–60%
|
|
103
|
+
|
|
104
|
+
// ponytail: linear 1% walk, not a solve - the range is at most 45 steps and
|
|
105
|
+
// this runs once per avatar. Lightness bottoms out at 0, where every hue is
|
|
106
|
+
// black and white is 21:1, so the loop always terminates on a pass.
|
|
107
|
+
while (lightness > 0 && 1.05 / (luminance(hue, saturation, lightness) + 0.05) < AA) {
|
|
108
|
+
lightness -= 1
|
|
109
|
+
}
|
|
58
110
|
|
|
59
111
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
|
60
112
|
}
|
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
</v-row>
|
|
24
|
-
</template>
|
|
25
|
-
</v-toolbar>
|
|
2
|
+
<div>
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S PAGE SCAFFOLD (same shape as Service Providers).
|
|
5
|
+
|
|
6
|
+
Placement only: the same refresh control, the same range text, the same
|
|
7
|
+
pager bound to the same `page`, and the table, its columns and its cell
|
|
8
|
+
templates are untouched. The screen simply gains the page title it has
|
|
9
|
+
never had, and its card + toolbar are the design's rather than Vuetify's.
|
|
10
|
+
-->
|
|
11
|
+
<PageHeader title="Billing" />
|
|
12
|
+
|
|
13
|
+
<AppCard class="table-card">
|
|
14
|
+
<CardToolbar :count="pageRange">
|
|
15
|
+
<template #left>
|
|
16
|
+
<AppButton variant="icon" icon="mdi-refresh" aria-label="Refresh" />
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<template #right>
|
|
20
|
+
<local-pagination v-model="page" :length="pages" />
|
|
21
|
+
</template>
|
|
22
|
+
</CardToolbar>
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
<v-data-table
|
|
28
25
|
:headers="headers"
|
|
29
26
|
:items="items"
|
|
30
27
|
item-value="_id"
|
|
@@ -38,7 +35,7 @@
|
|
|
38
35
|
<span class="text-caption font-weight-bold text-capitalize">
|
|
39
36
|
permissions
|
|
40
37
|
</span>
|
|
41
|
-
<
|
|
38
|
+
<StatusChip :dot="false" tone="neutral" :label="`${value.length}`" />
|
|
42
39
|
</template>
|
|
43
40
|
|
|
44
41
|
<template #item.nature="{ item }">
|
|
@@ -46,10 +43,9 @@
|
|
|
46
43
|
{{ item.nature }}
|
|
47
44
|
</span>
|
|
48
45
|
</template>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</v-row>
|
|
46
|
+
</v-data-table>
|
|
47
|
+
</AppCard>
|
|
48
|
+
</div>
|
|
53
49
|
</template>
|
|
54
50
|
|
|
55
51
|
<script setup lang="ts">
|
|
@@ -10,12 +10,17 @@
|
|
|
10
10
|
<v-row no-gutters>
|
|
11
11
|
<v-col v-if="value || readOnly" cols="12">
|
|
12
12
|
<v-row no-gutters>
|
|
13
|
+
<!-- The strip is one control split in two: the file name, and the
|
|
14
|
+
cross that clears it. `rounded=""` on the read-only card was
|
|
15
|
+
not "no radius" - Vuetify's `useRounded` treats an empty string
|
|
16
|
+
as `true` and adds `.rounded`, i.e. its own 4px. The design's
|
|
17
|
+
radius is stated here instead, on both ends. -->
|
|
13
18
|
<v-card
|
|
14
19
|
variant="outlined"
|
|
15
20
|
border="thin"
|
|
16
|
-
class="
|
|
17
|
-
:
|
|
18
|
-
height="
|
|
21
|
+
class="btn-input-file__name"
|
|
22
|
+
:class="{ 'btn-input-file__name--solo': readOnly }"
|
|
23
|
+
height="40"
|
|
19
24
|
@click="emit('click:view')"
|
|
20
25
|
>
|
|
21
26
|
<v-row no-gutters class="fill-height" align="center">
|
|
@@ -26,24 +31,28 @@
|
|
|
26
31
|
v-if="!readOnly"
|
|
27
32
|
variant="outlined"
|
|
28
33
|
border="s-0 thin"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
height="36"
|
|
34
|
+
class="btn-input-file__clear"
|
|
35
|
+
height="40"
|
|
32
36
|
@click="emit('click:close')"
|
|
33
37
|
>
|
|
34
38
|
<v-row no-gutters class="fill-height" align="center">
|
|
35
|
-
<v-icon size="
|
|
39
|
+
<v-icon size="18"> mdi-close </v-icon>
|
|
36
40
|
</v-row>
|
|
37
41
|
</v-card>
|
|
38
42
|
</v-row>
|
|
39
43
|
</v-col>
|
|
40
44
|
|
|
41
45
|
<v-col v-else cols="12">
|
|
46
|
+
<!-- This arm is the `v-else` of `v-if="value || readOnly"`, so
|
|
47
|
+
`value` is always falsy by the time it renders: the old
|
|
48
|
+
`:prepend-icon="value ? '' : ..."` had an arm that could never be
|
|
49
|
+
taken, and the icon is unconditional. `screen-btn-ghost` is the
|
|
50
|
+
design's 40px bordered button while keeping Vuetify's `loading`
|
|
51
|
+
spinner, which the upload needs and `AppButton` does not have. -->
|
|
42
52
|
<v-btn
|
|
43
|
-
variant="
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
:prepend-icon="value ? '' : 'mdi-tray-arrow-up'"
|
|
53
|
+
variant="flat"
|
|
54
|
+
class="text-none screen-btn-ghost"
|
|
55
|
+
prepend-icon="mdi-tray-arrow-up"
|
|
47
56
|
:disabled="loading"
|
|
48
57
|
:loading="loading"
|
|
49
58
|
@click="selectAttachment"
|
|
@@ -137,3 +146,36 @@ const errorMessage = computed(() => {
|
|
|
137
146
|
return null;
|
|
138
147
|
});
|
|
139
148
|
</script>
|
|
149
|
+
|
|
150
|
+
<style scoped>
|
|
151
|
+
/* The design's control box on the attachment strip: 40px, `--r-inner` on the
|
|
152
|
+
outer corners only (the two cards butt together), a `--line` edge and the
|
|
153
|
+
13.5px/500 cell type - not Vuetify's `text-caption` scale and 4px radius.
|
|
154
|
+
`:deep` because the box being styled is the `v-card` root Vuetify renders. */
|
|
155
|
+
.btn-input-file__name,
|
|
156
|
+
.btn-input-file__clear {
|
|
157
|
+
font-family: var(--font-sans);
|
|
158
|
+
font-size: var(--fs-cell);
|
|
159
|
+
font-weight: var(--fw-cell);
|
|
160
|
+
color: var(--text2);
|
|
161
|
+
border-color: var(--border);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.btn-input-file__name {
|
|
165
|
+
padding-inline: 14px;
|
|
166
|
+
border-start-start-radius: var(--r-inner);
|
|
167
|
+
border-end-start-radius: var(--r-inner);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Read-only: there is no cross beside it, so it is a single rounded box. */
|
|
171
|
+
.btn-input-file__name--solo {
|
|
172
|
+
border-radius: var(--r-inner);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.btn-input-file__clear {
|
|
176
|
+
display: inline;
|
|
177
|
+
padding-inline: 12px;
|
|
178
|
+
border-start-end-radius: var(--r-inner);
|
|
179
|
+
border-end-end-radius: var(--r-inner);
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-card width="100%">
|
|
3
|
-
<v-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</v-row>
|
|
9
|
-
</v-toolbar>
|
|
10
|
-
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-0">
|
|
2
|
+
<v-card width="100%" class="screen-modal">
|
|
3
|
+
<v-card-title class="text-capitalize">
|
|
4
|
+
{{ prop.mode }} Block {{ (prop.mode === "edit" && building.block) ? `- ${building.block}` : "" }}
|
|
5
|
+
</v-card-title>
|
|
6
|
+
|
|
7
|
+
<v-card-text class="screen-modal__body pa-0">
|
|
11
8
|
<v-form v-model="validForm" :disabled="disable">
|
|
12
9
|
<v-row no-gutters class="px-6 pt-4">
|
|
13
10
|
<v-col cols="12" class="mt-2">
|
|
14
11
|
<v-row no-gutters>
|
|
15
12
|
<InputLabel class="text-capitalize font-weight-bold" title="Block Name" required />
|
|
16
13
|
<v-col cols="12">
|
|
17
|
-
<v-text-field v-model="building.name" density="comfortable" :rules="[requiredRule]"></v-text-field>
|
|
14
|
+
<v-text-field v-model="building.name" class="filter-field" density="comfortable" :rules="[requiredRule]"></v-text-field>
|
|
18
15
|
</v-col>
|
|
19
16
|
</v-row>
|
|
20
17
|
</v-col>
|
|
@@ -26,7 +23,7 @@
|
|
|
26
23
|
<InputLabel class="text-capitalize font-weight-bold" title="Block No." required />
|
|
27
24
|
<v-col cols="12">
|
|
28
25
|
<v-select v-model.number="building.block" :items="blocks" item-title="label" item-value="value"
|
|
29
|
-
density="comfortable" :rules="[
|
|
26
|
+
class="filter-field" density="comfortable" :rules="[
|
|
30
27
|
requiredRule,
|
|
31
28
|
() =>
|
|
32
29
|
(building.block && building.block > 0) ||
|
|
@@ -45,7 +42,7 @@
|
|
|
45
42
|
<v-row no-gutters>
|
|
46
43
|
<InputLabel class="text-capitalize font-weight-bold" title="No. of Levels" required />
|
|
47
44
|
<v-col cols="12">
|
|
48
|
-
<v-text-field v-model.number="buildingLevels" density="comfortable" :rules="[requiredRule]"
|
|
45
|
+
<v-text-field v-model.number="buildingLevels" class="filter-field" density="comfortable" :rules="[requiredRule]"
|
|
49
46
|
type="number" @update:model-value="setBuildingLevels()"></v-text-field>
|
|
50
47
|
</v-col>
|
|
51
48
|
</v-row>
|
|
@@ -73,7 +70,7 @@
|
|
|
73
70
|
<v-col cols="12">
|
|
74
71
|
<v-row no-gutters>
|
|
75
72
|
<v-col :cols="prop.mode === 'edit' ? 10 : 12">
|
|
76
|
-
<v-text-field v-model.trim="building.levels[levelIndex].name" density="comfortable"></v-text-field>
|
|
73
|
+
<v-text-field v-model.trim="building.levels[levelIndex].name" class="filter-field" density="comfortable"></v-text-field>
|
|
77
74
|
</v-col>
|
|
78
75
|
<v-col v-if="prop.mode === 'edit'" cols="2" class="d-flex align-center pb-5 justify-end">
|
|
79
76
|
<v-btn icon variant="text" color="error" :disabled="disable"
|
|
@@ -92,7 +89,7 @@
|
|
|
92
89
|
<v-row no-gutters class="px-6 w-100">
|
|
93
90
|
<v-row no-gutters v-for="(newLevelsItem, index) in newLevelsArray" :key="index" :cols="12" class="w-100">
|
|
94
91
|
<v-col :cols="prop.mode === 'edit' ? 10 : 12">
|
|
95
|
-
<v-text-field v-model.trim="newLevelsArray[index]" density="comfortable"></v-text-field>
|
|
92
|
+
<v-text-field v-model.trim="newLevelsArray[index]" class="filter-field" density="comfortable"></v-text-field>
|
|
96
93
|
</v-col>
|
|
97
94
|
<v-col v-if="prop.mode === 'edit'" cols="2" class="d-flex align-center pb-5 justify-end">
|
|
98
95
|
<v-btn icon variant="text" color="error" @click="newLevelsArray.splice(index, 1)">
|
|
@@ -104,7 +101,7 @@
|
|
|
104
101
|
|
|
105
102
|
<v-row v-if="prop.mode === 'edit'" no-gutters class="px-6 w-100">
|
|
106
103
|
<v-col cols="10">
|
|
107
|
-
<v-text-field v-model.trim="activeNewLevelInput"
|
|
104
|
+
<v-text-field v-model.trim="activeNewLevelInput" class="filter-field w-100" density="comfortable"
|
|
108
105
|
placeholder="Add new level" @click="addNewLevelErrorMessage = ''"></v-text-field>
|
|
109
106
|
</v-col>
|
|
110
107
|
<v-col cols="2" class="d-flex align-center pb-5 justify-end">
|
|
@@ -150,22 +147,22 @@
|
|
|
150
147
|
</v-form>
|
|
151
148
|
</v-card-text>
|
|
152
149
|
|
|
153
|
-
<v-
|
|
150
|
+
<v-card-actions class="screen-modal__footer">
|
|
154
151
|
<v-row no-gutters>
|
|
155
|
-
<v-col cols="6">
|
|
156
|
-
<v-btn
|
|
152
|
+
<v-col cols="6" class="pr-2">
|
|
153
|
+
<v-btn block variant="outlined" class="text-none screen-btn-ghost" @click="cancel" :disabled="disable">
|
|
157
154
|
Cancel
|
|
158
155
|
</v-btn>
|
|
159
156
|
</v-col>
|
|
160
157
|
|
|
161
|
-
<v-col cols="6">
|
|
162
|
-
<v-btn
|
|
158
|
+
<v-col cols="6" class="pl-2">
|
|
159
|
+
<v-btn block variant="flat" class="text-none screen-btn-primary" :disabled="!validForm || disable"
|
|
163
160
|
@click="submit" :loading="disable">
|
|
164
161
|
Submit
|
|
165
162
|
</v-btn>
|
|
166
163
|
</v-col>
|
|
167
164
|
</v-row>
|
|
168
|
-
</v-
|
|
165
|
+
</v-card-actions>
|
|
169
166
|
|
|
170
167
|
<ConfirmDialog v-model="confirmDeleteLevelDialog" :loading="loading.deletingLevel" width="450" persistent>
|
|
171
168
|
<template #title>
|
|
@@ -178,16 +175,24 @@
|
|
|
178
175
|
|
|
179
176
|
<template #footer>
|
|
180
177
|
<v-row no-gutters class="w-100">
|
|
181
|
-
<v-col cols="6">
|
|
182
|
-
<v-btn
|
|
178
|
+
<v-col cols="6" class="pr-2">
|
|
179
|
+
<v-btn block variant="outlined" class="text-none screen-btn-ghost" :disabled="loading.deletingLevel"
|
|
183
180
|
@click="closeDeleteLevelPrompt">
|
|
184
181
|
Cancel
|
|
185
182
|
</v-btn>
|
|
186
183
|
</v-col>
|
|
187
184
|
|
|
188
|
-
<v-col cols="6">
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
<v-col cols="6" class="pl-2">
|
|
186
|
+
<!-- Left on Vuetify's own error fill, deliberately. It must NOT
|
|
187
|
+
take `.screen-btn-primary` - that class sets `background` and
|
|
188
|
+
would repaint a delete button accent blue. And the handoff
|
|
189
|
+
draws no modal, so it specifies no destructive button: making
|
|
190
|
+
one out of `--err` puts white on a token that LIGHTENS in dark
|
|
191
|
+
theme, which measured 2.99:1 - the same fill-used-as-ink trap
|
|
192
|
+
as the Upload button. So this keeps today's appearance and only
|
|
193
|
+
loses the 40px height. A destructive button needs a spec. -->
|
|
194
|
+
<v-btn block variant="flat" color="error" class="text-none"
|
|
195
|
+
:loading="loading.deletingLevel" :disabled="loading.deletingLevel" @click="confirmDeleteLevel">
|
|
191
196
|
Delete
|
|
192
197
|
</v-btn>
|
|
193
198
|
</v-col>
|
|
@@ -348,7 +353,9 @@ const buildingFilesNames = ref<Record<string, string>>({});
|
|
|
348
353
|
building.value.site = prop.site;
|
|
349
354
|
if (prop.mode === "edit") {
|
|
350
355
|
building.value = JSON.parse(JSON.stringify(prop.building));
|
|
351
|
-
|
|
356
|
+
// Same missing field as the blocks table: a block record without `levels`
|
|
357
|
+
// made this `.map` throw, which killed the edit dialog before it opened.
|
|
358
|
+
building.value.levels = (building.value.levels ?? []).map((level: any) => ({ _id: level._id, name: level.name })); //temporary
|
|
352
359
|
buildingLevels.value = building.value.levels.length;
|
|
353
360
|
initialBuildingLevels.value = JSON.parse(JSON.stringify(building.value.levels));
|
|
354
361
|
|