@7365admin1/layer-common 1.10.0 → 1.10.1

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/AcceptDialog.vue +44 -0
  3. package/components/AccessCardAddForm.vue +101 -13
  4. package/components/AccessManagement.vue +130 -47
  5. package/components/AddSupplyForm.vue +165 -0
  6. package/components/AreaChecklistHistoryLogs.vue +235 -0
  7. package/components/AreaChecklistHistoryMain.vue +176 -0
  8. package/components/AreaFormDialog.vue +266 -0
  9. package/components/AreaMain.vue +841 -0
  10. package/components/AttendanceCheckInOutDialog.vue +416 -0
  11. package/components/AttendanceDetailsDialog.vue +184 -0
  12. package/components/AttendanceMain.vue +155 -0
  13. package/components/AttendanceMapSearchDialog.vue +393 -0
  14. package/components/AttendanceSettingsDialog.vue +398 -0
  15. package/components/BuildingManagement/buildings.vue +5 -5
  16. package/components/BuildingManagement/units.vue +5 -5
  17. package/components/ChecklistItemRow.vue +54 -0
  18. package/components/CheckoutItemMain.vue +705 -0
  19. package/components/CleaningScheduleMain.vue +271 -0
  20. package/components/DocumentManagement.vue +4 -0
  21. package/components/EntryPass/QrTemplatePreview.vue +104 -0
  22. package/components/EntryPassMain.vue +252 -200
  23. package/components/HygieneUpdateMoreAction.vue +238 -0
  24. package/components/ManageChecklistMain.vue +384 -0
  25. package/components/MemberMain.vue +48 -20
  26. package/components/MyAttendanceMain.vue +224 -0
  27. package/components/OnlineFormsConfiguration.vue +9 -2
  28. package/components/PhotoUpload.vue +410 -0
  29. package/components/ScheduleAreaMain.vue +313 -0
  30. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  31. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  32. package/components/ScheduleTaskForm.vue +471 -0
  33. package/components/ScheduleTaskMain.vue +345 -0
  34. package/components/ScheduleTastTicketMain.vue +182 -0
  35. package/components/StockCard.vue +191 -0
  36. package/components/SupplyManagementMain.vue +557 -0
  37. package/components/TableHygiene.vue +617 -0
  38. package/components/UnitMain.vue +451 -0
  39. package/components/VisitorManagement.vue +28 -15
  40. package/composables/useAccessManagement.ts +90 -0
  41. package/composables/useAreaPermission.ts +51 -0
  42. package/composables/useAreas.ts +99 -0
  43. package/composables/useAttendance.ts +89 -0
  44. package/composables/useAttendancePermission.ts +68 -0
  45. package/composables/useBuilding.ts +2 -2
  46. package/composables/useBuildingUnit.ts +2 -2
  47. package/composables/useCard.ts +2 -0
  48. package/composables/useCheckout.ts +61 -0
  49. package/composables/useCheckoutPermission.ts +80 -0
  50. package/composables/useCleaningPermission.ts +229 -0
  51. package/composables/useCleaningSchedulePermission.ts +58 -0
  52. package/composables/useCleaningSchedules.ts +233 -0
  53. package/composables/useCountry.ts +8 -0
  54. package/composables/useDashboardData.ts +2 -2
  55. package/composables/useFeedback.ts +1 -1
  56. package/composables/useLocation.ts +78 -0
  57. package/composables/useOnlineForm.ts +16 -9
  58. package/composables/usePeople.ts +87 -72
  59. package/composables/useQR.ts +29 -0
  60. package/composables/useScheduleTask.ts +89 -0
  61. package/composables/useScheduleTaskArea.ts +85 -0
  62. package/composables/useScheduleTaskPermission.ts +68 -0
  63. package/composables/useSiteEntryPassSettings.ts +4 -15
  64. package/composables/useStock.ts +45 -0
  65. package/composables/useSupply.ts +63 -0
  66. package/composables/useSupplyPermission.ts +92 -0
  67. package/composables/useUnitPermission.ts +51 -0
  68. package/composables/useUnits.ts +82 -0
  69. package/composables/useWebUsb.ts +389 -0
  70. package/composables/useWorkOrder.ts +1 -1
  71. package/nuxt.config.ts +3 -0
  72. package/package.json +4 -1
  73. package/types/area.d.ts +22 -0
  74. package/types/attendance.d.ts +38 -0
  75. package/types/checkout-item.d.ts +27 -0
  76. package/types/cleaner-schedule.d.ts +54 -0
  77. package/types/location.d.ts +42 -0
  78. package/types/schedule-task.d.ts +18 -0
  79. package/types/stock.d.ts +16 -0
  80. package/types/supply.d.ts +11 -0
  81. package/utils/acm-crypto.ts +30 -0
@@ -0,0 +1,398 @@
1
+ <template>
2
+ <v-dialog v-model="showDialog" max-width="600" persistent>
3
+ <v-card>
4
+ <v-toolbar>
5
+ <v-row no-gutters class="fill-height px-6" align="center">
6
+ <span class="font-weight-bold text-h6">Attendance Settings</span>
7
+ </v-row>
8
+ </v-toolbar>
9
+
10
+ <v-card-text class="pa-6">
11
+ <v-alert
12
+ v-if="error"
13
+ type="error"
14
+ variant="tonal"
15
+ density="compact"
16
+ closable
17
+ class="mb-4"
18
+ @click:close="error = ''"
19
+ >
20
+ {{ error }}
21
+ </v-alert>
22
+
23
+ <v-form ref="formRef" v-model="valid" @submit.prevent="onSubmit">
24
+ <v-row dense>
25
+ <v-col cols="12">
26
+ <v-checkbox
27
+ v-model="isGeofencingEnabled"
28
+ label="Enable Geofencing"
29
+ density="comfortable"
30
+ hide-details
31
+ />
32
+ <span
33
+ v-if="isGeofencingEnabled"
34
+ class="text-body-2 text-medium-emphasis"
35
+ >
36
+ Enabling this setting will require users to take attendance
37
+ within a certain radius via QR code or within the site location.
38
+ </span>
39
+ </v-col>
40
+
41
+ <template v-if="isGeofencingEnabled">
42
+ <v-col cols="12" class="mt-4">
43
+ <v-checkbox
44
+ v-model="isQrEnabled"
45
+ label="Enable QR Code"
46
+ density="comfortable"
47
+ hide-details
48
+ />
49
+ </v-col>
50
+
51
+ <v-col v-if="isQrEnabled" cols="12">
52
+ <v-card
53
+ variant="flat"
54
+ class="pa-4 d-flex flex-column align-center"
55
+ >
56
+ <QrcodeVue
57
+ :value="site"
58
+ :level="'M'"
59
+ :render-as="'svg'"
60
+ :size="300"
61
+ />
62
+ <v-btn
63
+ variant="text"
64
+ color="primary"
65
+ class="mt-2"
66
+ @click="downloadQrCode"
67
+ >
68
+ <v-icon class="mr-2">mdi-download</v-icon>
69
+ Download QR Code
70
+ </v-btn>
71
+ </v-card>
72
+ </v-col>
73
+
74
+ <v-col cols="12" class="mt-4">
75
+ <InputLabel title="Site Location" />
76
+ <v-btn
77
+ variant="outlined"
78
+ class="mt-2"
79
+ @click="isMapSearchVisible = true"
80
+ >
81
+ <v-icon class="mr-2">mdi-map-search</v-icon>
82
+ Search Location
83
+ </v-btn>
84
+ </v-col>
85
+
86
+ <v-col v-if="geolocation" cols="12">
87
+ <LMap
88
+ :key="`map-${geolocation.latitude}-${geolocation.longitude}`"
89
+ style="height: 350px; border-radius: 8px"
90
+ :zoom="15"
91
+ :center="[geolocation.latitude, geolocation.longitude]"
92
+ :use-global-leaflet="false"
93
+ >
94
+ <LTileLayer
95
+ url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
96
+ attribution='&copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
97
+ />
98
+ <LMarker
99
+ :lat-lng="[geolocation.latitude, geolocation.longitude]"
100
+ :draggable="true"
101
+ @update:lat-lng="onMarkerUpdate"
102
+ />
103
+ <LCircle
104
+ v-if="radius"
105
+ :lat-lng="[geolocation.latitude, geolocation.longitude]"
106
+ :radius="radiusInMeters"
107
+ :color="'#1867C0'"
108
+ :fill-color="'#1867C0'"
109
+ :fill-opacity="0.2"
110
+ />
111
+ </LMap>
112
+ </v-col>
113
+
114
+ <v-col cols="12" class="mt-2">
115
+ <InputLabel title="Radius (miles)" />
116
+ <v-text-field
117
+ v-model.number="radius"
118
+ type="number"
119
+ density="comfortable"
120
+ placeholder="Enter radius in miles"
121
+ :rules="[(v) => v > 0 || 'Radius must be greater than 0']"
122
+ hide-details="auto"
123
+ />
124
+ </v-col>
125
+ </template>
126
+ </v-row>
127
+ </v-form>
128
+ </v-card-text>
129
+
130
+ <v-toolbar class="pa-0" density="compact">
131
+ <v-row no-gutters>
132
+ <v-col cols="6" class="pa-0">
133
+ <v-btn
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>
162
+ </v-card>
163
+ </v-dialog>
164
+
165
+ <AttendanceMapSearchDialog
166
+ v-model="isMapSearchVisible"
167
+ :initialPostalCode="postalCode"
168
+ :initialCountry="country"
169
+ :initialCity="city"
170
+ :initialAddress="address"
171
+ @location-selected="onLocationSelected"
172
+ />
173
+
174
+ <Snackbar v-model="snackbar" :text="snackbarText" :color="snackbarColor" />
175
+ </template>
176
+
177
+ <script setup lang="ts">
178
+ import QrcodeVue from "qrcode.vue";
179
+
180
+ const showDialog = defineModel({ type: Boolean, default: false });
181
+
182
+ const props = defineProps({
183
+ site: { type: String, required: true },
184
+ });
185
+
186
+ const emit = defineEmits(["saved", "close"]);
187
+
188
+ const formRef = ref<any>(null);
189
+ const valid = ref(false);
190
+ const submitting = ref(false);
191
+ const error = ref("");
192
+
193
+ const isGeofencingEnabled = ref(false);
194
+ const isQrEnabled = ref(false);
195
+ const geolocation = ref<{ latitude: number; longitude: number } | null>(null);
196
+ const radius = ref(0.5);
197
+ const isMapSearchVisible = ref(false);
198
+
199
+ const radiusInMeters = computed(() => radius.value * 1609.34);
200
+
201
+ const { getAttendanceSettings, updateAttendanceSettings } = useAttendance();
202
+
203
+ const postalCode = ref("");
204
+ const country = ref("");
205
+ const city = ref("");
206
+ const address = ref("");
207
+
208
+ const {
209
+ data: getAttendanceSettingsReq,
210
+ refresh: getAttendanceSettingsRefresh,
211
+ pending: loading,
212
+ } = await useLazyAsyncData(
213
+ `attendance-settings-${props.site}`,
214
+ () => getAttendanceSettings(props.site),
215
+ {
216
+ watch: [() => props.site],
217
+ immediate: false,
218
+ }
219
+ );
220
+
221
+ watchEffect(() => {
222
+ if (getAttendanceSettingsReq.value) {
223
+ isGeofencingEnabled.value =
224
+ getAttendanceSettingsReq.value.isGeofencingEnabled || false;
225
+
226
+ if (getAttendanceSettingsReq.value.location) {
227
+ geolocation.value = {
228
+ latitude: getAttendanceSettingsReq.value.location.latitude,
229
+ longitude: getAttendanceSettingsReq.value.location.longitude,
230
+ };
231
+ }
232
+
233
+ if (getAttendanceSettingsReq.value.mile) {
234
+ radius.value = getAttendanceSettingsReq.value.mile;
235
+ }
236
+
237
+ if (getAttendanceSettingsReq.value.postalCode) {
238
+ postalCode.value = getAttendanceSettingsReq.value.postalCode;
239
+ }
240
+
241
+ if (getAttendanceSettingsReq.value.country) {
242
+ country.value = getAttendanceSettingsReq.value.country;
243
+ }
244
+
245
+ if (getAttendanceSettingsReq.value.city) {
246
+ city.value = getAttendanceSettingsReq.value.city;
247
+ }
248
+
249
+ if (getAttendanceSettingsReq.value.address) {
250
+ address.value = getAttendanceSettingsReq.value.address;
251
+ }
252
+ }
253
+ });
254
+
255
+ onMounted(() => {
256
+ if (navigator.geolocation && !geolocation.value) {
257
+ navigator.geolocation.getCurrentPosition((position) => {
258
+ if (!geolocation.value) {
259
+ geolocation.value = {
260
+ latitude: position.coords.latitude,
261
+ longitude: position.coords.longitude,
262
+ };
263
+ }
264
+ });
265
+ }
266
+ });
267
+
268
+ const onMarkerUpdate = ({ lat, lng }: { lat: number; lng: number }) => {
269
+ geolocation.value = { latitude: lat, longitude: lng };
270
+ };
271
+
272
+ const onLocationSelected = (payload: {
273
+ latitude: number;
274
+ longitude: number;
275
+ address?: string;
276
+ city?: string;
277
+ country?: string;
278
+ postalCode?: string;
279
+ }) => {
280
+ geolocation.value = { latitude: payload.latitude, longitude: payload.longitude };
281
+ if (payload.postalCode) postalCode.value = payload.postalCode;
282
+ if (payload.country) country.value = payload.country;
283
+ if (payload.city) city.value = payload.city;
284
+ if (payload.address) address.value = payload.address;
285
+
286
+ isMapSearchVisible.value = false;
287
+ };
288
+
289
+ const downloadQrCode = () => {
290
+ const svg = document.querySelector("svg");
291
+ if (!svg) return;
292
+
293
+ const svgData = new XMLSerializer().serializeToString(svg);
294
+ const canvas = document.createElement("canvas");
295
+ const ctx = canvas.getContext("2d");
296
+ const img = new Image();
297
+
298
+ const padding = 40;
299
+
300
+ img.onload = () => {
301
+ canvas.width = img.width + padding * 2;
302
+ canvas.height = img.height + padding * 2;
303
+
304
+ if (ctx) {
305
+ ctx.fillStyle = "#ffffff";
306
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
307
+ ctx.drawImage(img, padding, padding);
308
+
309
+ const pngFile = canvas.toDataURL("image/png");
310
+ const downloadLink = document.createElement("a");
311
+ downloadLink.href = pngFile;
312
+ downloadLink.download = `attendance-qr-${props.site}.png`;
313
+ downloadLink.click();
314
+ }
315
+ };
316
+
317
+ img.src =
318
+ "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData)));
319
+ };
320
+
321
+ const snackbar = ref(false);
322
+ const snackbarText = ref("");
323
+ const snackbarColor = ref("success");
324
+
325
+ const showSnackbar = (text: string, color: string = "success") => {
326
+ snackbarText.value = text;
327
+ snackbarColor.value = color;
328
+ snackbar.value = true;
329
+ };
330
+
331
+ const onSubmit = async () => {
332
+ if (!formRef.value?.validate()) return;
333
+
334
+ if (isGeofencingEnabled.value && !geolocation.value) {
335
+ error.value = "Please set a location before enabling geofencing.";
336
+ return;
337
+ }
338
+
339
+ submitting.value = true;
340
+ error.value = "";
341
+
342
+ try {
343
+ const payload: any = {
344
+ site: props.site,
345
+ isLocationEnabled: isGeofencingEnabled.value,
346
+ };
347
+
348
+ if (isGeofencingEnabled.value) {
349
+ payload.isGeofencingEnabled = true;
350
+
351
+ if (geolocation.value) {
352
+ payload.location = {
353
+ latitude: geolocation.value.latitude,
354
+ longitude: geolocation.value.longitude,
355
+ };
356
+ }
357
+ if (postalCode.value) payload.postalCode = postalCode.value;
358
+ if (country.value) payload.country = country.value;
359
+ if (city.value) payload.city = city.value;
360
+ if (address.value) payload.address = address.value;
361
+
362
+ payload.mile = radius.value;
363
+ } else {
364
+ payload.isGeofencingEnabled = false;
365
+ }
366
+
367
+ await updateAttendanceSettings(props.site, payload);
368
+ await getAttendanceSettingsRefresh();
369
+
370
+ showSnackbar("Settings updated successfully");
371
+
372
+ emit("saved", payload);
373
+ close();
374
+ } catch (err: any) {
375
+ console.error("Error saving attendance settings:", err);
376
+ const errorMessage =
377
+ err.statusMessage ||
378
+ err.message ||
379
+ "Failed to save settings. Please try again.";
380
+ error.value = errorMessage;
381
+ showSnackbar(errorMessage, "error");
382
+ } finally {
383
+ submitting.value = false;
384
+ }
385
+ };
386
+
387
+ const close = () => {
388
+ error.value = "";
389
+ showDialog.value = false;
390
+ emit("close");
391
+ };
392
+
393
+ watch(showDialog, (newVal) => {
394
+ if (newVal) {
395
+ getAttendanceSettingsRefresh();
396
+ }
397
+ });
398
+ </script>
@@ -171,11 +171,11 @@ const props = defineProps({
171
171
  title: "Name",
172
172
  value: "name",
173
173
  },
174
- {
175
- title: "Director",
176
- value: "directorName",
177
- },
178
- { title: "Action", value: "action-table" },
174
+ // {
175
+ // title: "Director",
176
+ // value: "directorName",
177
+ // },
178
+ // { title: "Action", value: "action-table" },
179
179
  ],
180
180
  },
181
181
  canCreate: {
@@ -188,11 +188,11 @@ const props = defineProps({
188
188
  title: "Category",
189
189
  value: "category",
190
190
  },
191
- {
192
- title: "Type",
193
- value: "type",
194
- },
195
- { title: "Action", value: "action-table" },
191
+ // {
192
+ // title: "Type",
193
+ // value: "type",
194
+ // },
195
+ // { title: "Action", value: "action-table" },
196
196
  ],
197
197
  },
198
198
  canCreate: {
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <v-row no-gutters class="py-3 px-2" align="center">
3
+ <v-col cols="auto">
4
+ <v-btn
5
+ :variant="
6
+ item?._action === 'rejected' || item?.reject ? 'tonal' : 'outlined'
7
+ "
8
+ :color="
9
+ item?._action === 'rejected' || item?.reject ? 'error' : undefined
10
+ "
11
+ :disabled="readOnly"
12
+ size="small"
13
+ class="me-2"
14
+ icon
15
+ @click.stop="handleReject"
16
+ >
17
+ <v-icon>mdi-close</v-icon>
18
+ </v-btn>
19
+ </v-col>
20
+ <v-col cols="auto">
21
+ <v-btn
22
+ :variant="
23
+ item?._action === 'accepted' || item?.approve ? 'tonal' : 'outlined'
24
+ "
25
+ :color="
26
+ item?._action === 'accepted' || item?.approve ? 'success' : undefined
27
+ "
28
+ :disabled="readOnly"
29
+ size="small"
30
+ icon
31
+ @click.stop="handleApprove"
32
+ >
33
+ <v-icon>mdi-check</v-icon>
34
+ </v-btn>
35
+ </v-col>
36
+ </v-row>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ const props = defineProps({
41
+ item: { type: Object, required: true },
42
+ readOnly: { type: Boolean, default: false },
43
+ });
44
+
45
+ const emit = defineEmits(["reject", "approve"]);
46
+
47
+ function handleReject() {
48
+ emit("reject", props.item);
49
+ }
50
+
51
+ function handleApprove() {
52
+ emit("approve", props.item);
53
+ }
54
+ </script>