@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.
- package/CHANGELOG.md +6 -0
- package/components/AcceptDialog.vue +44 -0
- package/components/AccessCardAddForm.vue +101 -13
- package/components/AccessManagement.vue +130 -47
- package/components/AddSupplyForm.vue +165 -0
- package/components/AreaChecklistHistoryLogs.vue +235 -0
- package/components/AreaChecklistHistoryMain.vue +176 -0
- package/components/AreaFormDialog.vue +266 -0
- package/components/AreaMain.vue +841 -0
- package/components/AttendanceCheckInOutDialog.vue +416 -0
- package/components/AttendanceDetailsDialog.vue +184 -0
- package/components/AttendanceMain.vue +155 -0
- package/components/AttendanceMapSearchDialog.vue +393 -0
- package/components/AttendanceSettingsDialog.vue +398 -0
- package/components/BuildingManagement/buildings.vue +5 -5
- package/components/BuildingManagement/units.vue +5 -5
- package/components/ChecklistItemRow.vue +54 -0
- package/components/CheckoutItemMain.vue +705 -0
- package/components/CleaningScheduleMain.vue +271 -0
- package/components/DocumentManagement.vue +4 -0
- package/components/EntryPass/QrTemplatePreview.vue +104 -0
- package/components/EntryPassMain.vue +252 -200
- package/components/HygieneUpdateMoreAction.vue +238 -0
- package/components/ManageChecklistMain.vue +384 -0
- package/components/MemberMain.vue +48 -20
- package/components/MyAttendanceMain.vue +224 -0
- package/components/OnlineFormsConfiguration.vue +9 -2
- package/components/PhotoUpload.vue +410 -0
- package/components/ScheduleAreaMain.vue +313 -0
- package/components/ScheduleTaskAreaFormDialog.vue +144 -0
- package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
- package/components/ScheduleTaskForm.vue +471 -0
- package/components/ScheduleTaskMain.vue +345 -0
- package/components/ScheduleTastTicketMain.vue +182 -0
- package/components/StockCard.vue +191 -0
- package/components/SupplyManagementMain.vue +557 -0
- package/components/TableHygiene.vue +617 -0
- package/components/UnitMain.vue +451 -0
- package/components/VisitorManagement.vue +28 -15
- package/composables/useAccessManagement.ts +90 -0
- package/composables/useAreaPermission.ts +51 -0
- package/composables/useAreas.ts +99 -0
- package/composables/useAttendance.ts +89 -0
- package/composables/useAttendancePermission.ts +68 -0
- package/composables/useBuilding.ts +2 -2
- package/composables/useBuildingUnit.ts +2 -2
- package/composables/useCard.ts +2 -0
- package/composables/useCheckout.ts +61 -0
- package/composables/useCheckoutPermission.ts +80 -0
- package/composables/useCleaningPermission.ts +229 -0
- package/composables/useCleaningSchedulePermission.ts +58 -0
- package/composables/useCleaningSchedules.ts +233 -0
- package/composables/useCountry.ts +8 -0
- package/composables/useDashboardData.ts +2 -2
- package/composables/useFeedback.ts +1 -1
- package/composables/useLocation.ts +78 -0
- package/composables/useOnlineForm.ts +16 -9
- package/composables/usePeople.ts +87 -72
- package/composables/useQR.ts +29 -0
- package/composables/useScheduleTask.ts +89 -0
- package/composables/useScheduleTaskArea.ts +85 -0
- package/composables/useScheduleTaskPermission.ts +68 -0
- package/composables/useSiteEntryPassSettings.ts +4 -15
- package/composables/useStock.ts +45 -0
- package/composables/useSupply.ts +63 -0
- package/composables/useSupplyPermission.ts +92 -0
- package/composables/useUnitPermission.ts +51 -0
- package/composables/useUnits.ts +82 -0
- package/composables/useWebUsb.ts +389 -0
- package/composables/useWorkOrder.ts +1 -1
- package/nuxt.config.ts +3 -0
- package/package.json +4 -1
- package/types/area.d.ts +22 -0
- package/types/attendance.d.ts +38 -0
- package/types/checkout-item.d.ts +27 -0
- package/types/cleaner-schedule.d.ts +54 -0
- package/types/location.d.ts +42 -0
- package/types/schedule-task.d.ts +18 -0
- package/types/stock.d.ts +16 -0
- package/types/supply.d.ts +11 -0
- 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>
|