@7365admin1/layer-common 1.10.0 → 1.10.2

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 (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AcceptDialog.vue +44 -0
  3. package/components/AccessCard/AvailableStats.vue +55 -0
  4. package/components/AccessCardAddForm.vue +284 -19
  5. package/components/AccessCardAssignToUnitForm.vue +440 -0
  6. package/components/AccessManagement.vue +218 -85
  7. package/components/AddSupplyForm.vue +165 -0
  8. package/components/AreaChecklistHistoryLogs.vue +235 -0
  9. package/components/AreaChecklistHistoryMain.vue +176 -0
  10. package/components/AreaFormDialog.vue +266 -0
  11. package/components/AreaMain.vue +863 -0
  12. package/components/AttendanceCheckInOutDialog.vue +416 -0
  13. package/components/AttendanceDetailsDialog.vue +184 -0
  14. package/components/AttendanceMain.vue +155 -0
  15. package/components/AttendanceMapSearchDialog.vue +393 -0
  16. package/components/AttendanceSettingsDialog.vue +398 -0
  17. package/components/BuildingManagement/buildings.vue +5 -5
  18. package/components/BuildingManagement/units.vue +5 -5
  19. package/components/BulletinBoardManagement.vue +322 -0
  20. package/components/ChecklistItemRow.vue +54 -0
  21. package/components/CheckoutItemMain.vue +705 -0
  22. package/components/CleaningScheduleMain.vue +271 -0
  23. package/components/DocumentManagement.vue +4 -0
  24. package/components/EntryPass/QrTemplatePreview.vue +104 -0
  25. package/components/EntryPassMain.vue +252 -200
  26. package/components/HygieneUpdateMoreAction.vue +238 -0
  27. package/components/ManageChecklistMain.vue +384 -0
  28. package/components/MemberMain.vue +48 -20
  29. package/components/MyAttendanceMain.vue +224 -0
  30. package/components/OnlineFormsConfiguration.vue +9 -2
  31. package/components/PhotoUpload.vue +410 -0
  32. package/components/ScheduleAreaMain.vue +313 -0
  33. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  34. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  35. package/components/ScheduleTaskForm.vue +471 -0
  36. package/components/ScheduleTaskMain.vue +345 -0
  37. package/components/ScheduleTastTicketMain.vue +182 -0
  38. package/components/SignaturePad.vue +17 -5
  39. package/components/StockCard.vue +191 -0
  40. package/components/SupplyManagementMain.vue +557 -0
  41. package/components/TableHygiene.vue +617 -0
  42. package/components/UnitMain.vue +451 -0
  43. package/components/VisitorManagement.vue +28 -15
  44. package/composables/useAccessManagement.ts +163 -0
  45. package/composables/useAreaPermission.ts +51 -0
  46. package/composables/useAreas.ts +99 -0
  47. package/composables/useAttendance.ts +89 -0
  48. package/composables/useAttendancePermission.ts +68 -0
  49. package/composables/useBuilding.ts +2 -2
  50. package/composables/useBuildingUnit.ts +2 -2
  51. package/composables/useBulletin.ts +82 -0
  52. package/composables/useCard.ts +2 -0
  53. package/composables/useCheckout.ts +61 -0
  54. package/composables/useCheckoutPermission.ts +80 -0
  55. package/composables/useCleaningPermission.ts +229 -0
  56. package/composables/useCleaningSchedulePermission.ts +58 -0
  57. package/composables/useCleaningSchedules.ts +233 -0
  58. package/composables/useCountry.ts +8 -0
  59. package/composables/useDashboardData.ts +2 -2
  60. package/composables/useFeedback.ts +1 -1
  61. package/composables/useLocation.ts +78 -0
  62. package/composables/useOnlineForm.ts +16 -9
  63. package/composables/usePeople.ts +87 -72
  64. package/composables/useQR.ts +29 -0
  65. package/composables/useScheduleTask.ts +89 -0
  66. package/composables/useScheduleTaskArea.ts +85 -0
  67. package/composables/useScheduleTaskPermission.ts +68 -0
  68. package/composables/useSiteEntryPassSettings.ts +4 -15
  69. package/composables/useStock.ts +45 -0
  70. package/composables/useSupply.ts +63 -0
  71. package/composables/useSupplyPermission.ts +92 -0
  72. package/composables/useUnitPermission.ts +51 -0
  73. package/composables/useUnits.ts +82 -0
  74. package/composables/useWebUsb.ts +389 -0
  75. package/composables/useWorkOrder.ts +1 -1
  76. package/nuxt.config.ts +3 -0
  77. package/package.json +4 -1
  78. package/types/area.d.ts +22 -0
  79. package/types/attendance.d.ts +38 -0
  80. package/types/checkout-item.d.ts +27 -0
  81. package/types/cleaner-schedule.d.ts +54 -0
  82. package/types/location.d.ts +42 -0
  83. package/types/schedule-task.d.ts +18 -0
  84. package/types/stock.d.ts +16 -0
  85. package/types/supply.d.ts +11 -0
  86. package/utils/acm-crypto.ts +30 -0
@@ -0,0 +1,271 @@
1
+ <template>
2
+ <v-row no-gutters align="center" justify="center">
3
+ <v-col cols="12" lg="12">
4
+ <TableMain
5
+ v-if="canViewSchedules"
6
+ :title="'Cleaner Checklist'"
7
+ :items="items"
8
+ :headers="headers"
9
+ :loading="loading"
10
+ :show-header="true"
11
+ v-model:page="page"
12
+ :pages="pages"
13
+ :pageRange="pageRange"
14
+ :no-data-text="'No Cleaner Checklist found'"
15
+ @refresh="getCleanerChecklistRefresh"
16
+ @row-click="onRowClick"
17
+ >
18
+ <template #extension>
19
+ <v-row no-gutters class="w-100 d-flex flex-column">
20
+ <v-card
21
+ class="w-100 px-3 d-flex align-center ga-5 py-2"
22
+ flat
23
+ :height="60"
24
+ >
25
+ <InputDateTimePicker
26
+ v-model:utc="startDate"
27
+ density="compact"
28
+ hide-details
29
+ />
30
+ <InputDateTimePicker
31
+ v-model:utc="endDate"
32
+ density="compact"
33
+ hide-details
34
+ />
35
+ <v-select
36
+ v-model="status"
37
+ :items="statusOptions"
38
+ density="compact"
39
+ hide-details
40
+ class="mx-3"
41
+ style="max-width: 160px"
42
+ item-title="title"
43
+ item-value="value"
44
+ label="Status"
45
+ />
46
+ </v-card>
47
+ </v-row>
48
+ </template>
49
+
50
+ <template #item.createdAt="{ value }">
51
+ {{
52
+ formatDate
53
+ ? formatDate(value)
54
+ : value
55
+ ? new Date(value).toLocaleString()
56
+ : ""
57
+ }}
58
+ </template>
59
+ <template #item.closeIn="{ item }">
60
+ <!-- <v-chip class="text-capitalize">{{ value || "No Status" }}</v-chip> -->
61
+ <v-chip class="text-capitalize" variant="flat" color="primary">{{
62
+ remainingTime[item._id] || "No Status"
63
+ }}</v-chip>
64
+ </template>
65
+ <template #item.status="{ value }">
66
+ <v-chip
67
+ class="text-capitalize"
68
+ :color="getStatusColor(value)"
69
+ variant="flat"
70
+ pill
71
+ >
72
+ {{ value || "No Status" }}
73
+ </v-chip>
74
+ </template>
75
+ <template #item.download="{ item }">
76
+ <v-btn
77
+ v-if="canDownloadSchedule"
78
+ variant="outlined"
79
+ size="medium"
80
+ class="text-capitalize px-4 py-2"
81
+ :loading="downloadingId === item._id"
82
+ @click.stop="downloadItem(item)"
83
+ >
84
+ <v-icon left>mdi-download</v-icon>
85
+ Download
86
+ </v-btn>
87
+ </template>
88
+ </TableMain>
89
+ <div v-else class="pa-4">
90
+ <v-card variant="outlined" class="pa-4">
91
+ <div class="text-subtitle-1">
92
+ You do not have permission to view cleaning schedules.
93
+ </div>
94
+ </v-card>
95
+ </div>
96
+ </v-col>
97
+ </v-row>
98
+ </template>
99
+
100
+ <script lang="ts" setup>
101
+ import { useCleaningSchedulePermission } from "../composables/useCleaningSchedulePermission";
102
+ import useCleaningSchedules from "../composables/useCleaningSchedules";
103
+
104
+ const props = defineProps({
105
+ orgId: { type: String, required: true },
106
+ site: { type: String, required: true },
107
+ type: { type: String, required: true },
108
+ });
109
+
110
+ const startDate = ref("");
111
+ const endDate = ref("");
112
+ const status = ref<TScheduleAreaStatus>("All");
113
+ const statusOptions = [
114
+ { title: "All", value: "all" },
115
+ { title: "Ready", value: "ready" },
116
+ { title: "Ongoing", value: "ongoing" },
117
+ { title: "Completed", value: "completed" },
118
+ ] as const;
119
+
120
+ const items = ref<Array<Record<string, any>>>([]);
121
+ const page = ref(1);
122
+ const pages = ref(0);
123
+ const pageRange = ref("-- - -- of --");
124
+
125
+ const headers = [
126
+ { title: "Created Date", value: "createdAt" },
127
+ { title: "Close in", value: "closeIn" },
128
+ { title: "Status", value: "status" },
129
+ { title: "Completion Date", value: "completionDate" },
130
+ { title: "", value: "download" },
131
+ ];
132
+ const remainingTime = ref({} as any);
133
+ const downloadingId = ref<string | null>(null);
134
+
135
+ const { getCleaningSchedules, downloadChecklistPdf } = useCleaningSchedules();
136
+ const { formatDate } = useUtils();
137
+
138
+ const getStatusColor = (status: unknown): string => {
139
+ if (!status) return "grey";
140
+
141
+ const normalized = String(status).toLowerCase();
142
+
143
+ switch (normalized) {
144
+ case "ready":
145
+ return "grey";
146
+ case "ongoing":
147
+ return "primary";
148
+ case "completed":
149
+ case "accepted":
150
+ return "success";
151
+ default:
152
+ return "secondary";
153
+ }
154
+ };
155
+
156
+ const {
157
+ canDownloadSchedule,
158
+ canViewSchedules,
159
+ canViewScheduleDetails,
160
+ canManageScheduleTasks,
161
+ } = useCleaningSchedulePermission();
162
+
163
+ const {
164
+ data: getCleanerChecklistReq,
165
+ refresh: getCleanerChecklistRefresh,
166
+ pending: loading,
167
+ } = await useLazyAsyncData(
168
+ "get-all-areas",
169
+ () =>
170
+ getCleaningSchedules({
171
+ page: page.value,
172
+ site: props.site,
173
+ startDate: startDate.value,
174
+ endDate: endDate.value,
175
+ status: status.value === "All" ? undefined : status.value,
176
+ }),
177
+ {
178
+ watch: [() => page.value, () => props.site, startDate, endDate, status],
179
+ }
180
+ );
181
+
182
+ function calculateRemainingTime(
183
+ item: Date | string | number | null | undefined
184
+ ) {
185
+ if (!item) return -1;
186
+ const _date = new Date(item);
187
+ if (isNaN(_date.getTime())) return -1;
188
+ const creationTime = _date.getTime();
189
+ const currentTime = Date.now();
190
+ const differenceInMillis = currentTime - creationTime;
191
+ const differenceInSeconds = Math.floor(differenceInMillis / 1000);
192
+ const desiredDurationInSeconds = 24 * 60 * 60;
193
+ const remainingTimeInSeconds = desiredDurationInSeconds - differenceInSeconds;
194
+ return remainingTimeInSeconds;
195
+ }
196
+
197
+ const formatTime = (seconds: number) => {
198
+ if (seconds <= 0 || isNaN(seconds)) return "00h 00m";
199
+ const hours = Math.floor(seconds / 3600);
200
+ const minutes = Math.floor((seconds % 3600) / 60);
201
+ return `${String(hours).padStart(2, "0")}h ${String(minutes).padStart(
202
+ 2,
203
+ "0"
204
+ )}m`;
205
+ };
206
+
207
+ const updateRemainingTime = () => {
208
+ console.log(items.value);
209
+ items.value.forEach((item) => {
210
+ const itemId = item._id as string;
211
+ const _time = calculateRemainingTime(item.date as string);
212
+ remainingTime.value[itemId] = _time < 0 ? "00h 00m" : formatTime(_time);
213
+ });
214
+ };
215
+
216
+ watchEffect(() => {
217
+ if (getCleanerChecklistReq.value) {
218
+ items.value = getCleanerChecklistReq.value.items;
219
+ pages.value = getCleanerChecklistReq.value.pages;
220
+ pageRange.value = getCleanerChecklistReq.value.pageRange;
221
+ }
222
+
223
+ updateRemainingTime();
224
+ });
225
+
226
+ async function downloadItem(item: any) {
227
+ const id = item?._id;
228
+ if (!id) return;
229
+
230
+ try {
231
+ downloadingId.value = id;
232
+ await downloadChecklistPdf(id);
233
+ } catch (err) {
234
+ console.error("Failed to download checklist PDF", err);
235
+ } finally {
236
+ downloadingId.value = null;
237
+ }
238
+ }
239
+
240
+ function onRowClick(data: any) {
241
+ const item = data?.item ?? data;
242
+ const id = item?._id || item?.id || item?.areaId;
243
+ if (id) {
244
+ if (!canViewScheduleDetails.value) {
245
+ showMessage(
246
+ "You do not have permission to view schedule details.",
247
+ "error"
248
+ );
249
+ return;
250
+ }
251
+
252
+ if (props.type === "toilet") {
253
+ const path = `/${props.orgId}/${props.site}/toilet-checklist/${id}`;
254
+ navigateTo(path);
255
+ return;
256
+ }
257
+ const path = `/${props.orgId}/${props.site}/cleaning-schedule/${id}`;
258
+ navigateTo(path);
259
+ } else {
260
+ console.warn("Row clicked but no id found to navigate:", item);
261
+ }
262
+ }
263
+
264
+ function showMessage(msg: string, color: string = "error") {
265
+ try {
266
+ console.warn(msg);
267
+ } catch (e) {
268
+ alert(msg);
269
+ }
270
+ }
271
+ </script>
@@ -264,6 +264,9 @@ const messageColor = ref("");
264
264
 
265
265
  const items = ref<Array<Record<string, any>>>([]);
266
266
 
267
+ const route = useRoute();
268
+ const siteId = route.params.site as string;
269
+
267
270
  const {
268
271
  data: getDocumentReq,
269
272
  refresh: getDocuments,
@@ -274,6 +277,7 @@ const {
274
277
  _getAllDocuments({
275
278
  page: page.value,
276
279
  search: headerSearch.value,
280
+ site: siteId,
277
281
  }),
278
282
  {
279
283
  watch: [page, headerSearch],
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <v-dialog
3
+ :model-value="modelValue"
4
+ transition="dialog-right-transition"
5
+ width="550"
6
+ persistent
7
+ @update:model-value="emit('update:modelValue', $event)"
8
+ >
9
+ <v-card>
10
+ <v-toolbar
11
+ style="height: 12px"
12
+ :color="isDark ? 'grey-darken-4' : 'white'"
13
+ >
14
+ <v-spacer></v-spacer>
15
+ <v-btn
16
+ color="grey-darken-1"
17
+ icon="mdi-close"
18
+ @click="emit('update:modelValue', false)"
19
+ ></v-btn>
20
+ </v-toolbar>
21
+
22
+ <v-card-text style="padding: 0 !important">
23
+ <v-container>
24
+ <v-row no-gutters class="text-center">
25
+ <v-col cols="12">
26
+ <p class="font-weight-bold text-h5 mb-3">{{ header }}</p>
27
+ <p>{{ subText }}</p>
28
+ </v-col>
29
+
30
+ <v-col cols="12" class="mt-5">
31
+ <!-- UPPER QR -->
32
+ <v-row no-gutters>
33
+ <v-col cols="6">
34
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
35
+ </v-col>
36
+ <v-col cols="6">
37
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
38
+ </v-col>
39
+ </v-row>
40
+
41
+ <!-- CENTER QR -->
42
+ <img v-if="qrLarge" :src="qrLarge" width="130" height="130" class="my-2" />
43
+
44
+ <!-- LOWER QR -->
45
+ <v-row no-gutters>
46
+ <v-col cols="6">
47
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
48
+ </v-col>
49
+ <v-col cols="6">
50
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
51
+ </v-col>
52
+ </v-row>
53
+ </v-col>
54
+
55
+ <v-col cols="12" class="mt-5">
56
+ <p class="font-weight-bold text-h5 mb-2">Company Name</p>
57
+ <p class="font-weight-bold text-h5 mb-3">BL 01/03/24</p>
58
+ <p class="mb-1">{{ `Date: ${dateStr} ${timeStr}` }}</p>
59
+ <p class="mb-1">Access: Door</p>
60
+ <p>Scan and press level 1. One time use.</p>
61
+ </v-col>
62
+ </v-row>
63
+ </v-container>
64
+ </v-card-text>
65
+ </v-card>
66
+ </v-dialog>
67
+ </template>
68
+
69
+ <script lang="ts" setup>
70
+ import { useTheme } from "vuetify";
71
+ import QRCode from "qrcode";
72
+
73
+ defineProps({
74
+ modelValue: {
75
+ type: Boolean,
76
+ default: false,
77
+ },
78
+ header: {
79
+ type: String,
80
+ default: "",
81
+ },
82
+ subText: {
83
+ type: String,
84
+ default: "",
85
+ },
86
+ });
87
+
88
+ const emit = defineEmits(["update:modelValue"]);
89
+
90
+ const { global: themeGlobal } = useTheme();
91
+ const isDark = computed(() => themeGlobal.current.value.dark);
92
+
93
+ const qrSmall = ref<string>("");
94
+ const qrLarge = ref<string>("");
95
+
96
+ onMounted(async () => {
97
+ qrSmall.value = await QRCode.toDataURL("123123", { width: 80, margin: 1 });
98
+ qrLarge.value = await QRCode.toDataURL("123123", { width: 130, margin: 1 });
99
+ });
100
+
101
+ const now = new Date();
102
+ const dateStr = now.toLocaleDateString();
103
+ const timeStr = now.toLocaleTimeString();
104
+ </script>