@7365admin1/layer-common 3.1.1 → 3.1.2-staging.29
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/.github/workflows/publish-staging.yml +11 -0
- package/components/AddEqupmentForm.vue +52 -1
- package/components/AddPassKeyToVisitor.vue +115 -53
- package/components/DashboardMain.vue +231 -87
- package/components/EquipmentManagementMain.vue +56 -0
- package/components/InvitationMain.vue +4 -3
- package/components/ManageChecklistMain.vue +44 -38
- package/components/Nfc/NFCPatrolReportMain.vue +128 -38
- package/components/Nfc/PatrolReport/DailyReportTab.vue +10 -6
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +19 -9
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +69 -0
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
- package/components/Nfc/PatrolReport/ReportFilters.vue +50 -18
- package/components/PhotoUpload.vue +19 -4
- package/components/ServiceProviderMain.vue +46 -29
- package/components/VisitorManagement.vue +109 -30
- package/composables/useCleaningSchedulePermission.ts +16 -51
- package/composables/useEquipment.ts +2 -0
- package/composables/useLocalAuth.ts +1 -1
- package/composables/useNFCPatrolDailyReport.ts +1 -4
- package/composables/useNFCPatrolMonthlyReport.ts +62 -30
- package/package.json +1 -1
- package/types/customer.d.ts +2 -1
- package/types/equipment.d.ts +5 -1
- package/types/nfc-patrol-report.ts +15 -1
|
@@ -34,3 +34,14 @@ jobs:
|
|
|
34
34
|
|
|
35
35
|
- name: Publish to npm with staging tag
|
|
36
36
|
run: npm publish --tag staging --registry https://registry.npmjs.org
|
|
37
|
+
|
|
38
|
+
- name: Notify Discord on failure
|
|
39
|
+
if: failure()
|
|
40
|
+
env:
|
|
41
|
+
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
|
42
|
+
run: |
|
|
43
|
+
MESSAGE="@everyone :rotating_light: **Publish Staging** failed in \`${{ github.repository }}\` (\`${{ github.workflow }}\`)
|
|
44
|
+
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
45
|
+
curl -s -X POST -H "Content-Type: application/json" \
|
|
46
|
+
-d "$(jq -n --arg content "$MESSAGE" '{content: $content}')" \
|
|
47
|
+
"$DISCORD_WEBHOOK_URL"
|
|
@@ -12,6 +12,24 @@
|
|
|
12
12
|
<v-card-text class="pa-4">
|
|
13
13
|
<v-form ref="formRef" v-model="valid">
|
|
14
14
|
<v-row no-gutters class="pa-0">
|
|
15
|
+
<v-col cols="12" class="pa-0 mb-2">
|
|
16
|
+
<PhotoUpload
|
|
17
|
+
v-model="attachmentPhotos"
|
|
18
|
+
v-model:photo-ids="attachmentIds"
|
|
19
|
+
title="Photo"
|
|
20
|
+
@error="photoUploadError = $event"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<v-alert
|
|
24
|
+
v-if="photoUploadError"
|
|
25
|
+
type="error"
|
|
26
|
+
density="compact"
|
|
27
|
+
variant="tonal"
|
|
28
|
+
class="mt-2"
|
|
29
|
+
>
|
|
30
|
+
{{ photoUploadError }}
|
|
31
|
+
</v-alert>
|
|
32
|
+
</v-col>
|
|
15
33
|
<v-col cols="12" class="pa-0 mb-2">
|
|
16
34
|
<InputLabel for="name" title="Name" required class="mb-1" />
|
|
17
35
|
<v-text-field
|
|
@@ -87,6 +105,10 @@ const unitOfMeasurementModel = defineModel("unitOfMeasurement", {
|
|
|
87
105
|
type: String,
|
|
88
106
|
default: "",
|
|
89
107
|
});
|
|
108
|
+
const attachmentModel = defineModel("attachment", {
|
|
109
|
+
type: String,
|
|
110
|
+
default: "",
|
|
111
|
+
});
|
|
90
112
|
const showDialog = defineModel({ type: Boolean, default: false });
|
|
91
113
|
|
|
92
114
|
const prop = defineProps({
|
|
@@ -105,6 +127,9 @@ const emit = defineEmits(["saved", "close"]);
|
|
|
105
127
|
const formRef = ref<any>(null);
|
|
106
128
|
const valid = ref(false);
|
|
107
129
|
const submitting = ref(false);
|
|
130
|
+
const attachmentPhotos = ref<string[]>([]);
|
|
131
|
+
const attachmentIds = ref<string[]>([]);
|
|
132
|
+
const photoUploadError = ref("");
|
|
108
133
|
|
|
109
134
|
const { requiredRule } = useUtils();
|
|
110
135
|
|
|
@@ -124,6 +149,29 @@ const unitOfMeasurementOptions = [
|
|
|
124
149
|
watchEffect(() => {
|
|
125
150
|
nameModel.value = prop.equipmentData?.name || "";
|
|
126
151
|
unitOfMeasurementModel.value = prop.equipmentData?.unitOfMeasurement || "";
|
|
152
|
+
attachmentModel.value = prop.equipmentData?.attachment || "";
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
watch(
|
|
156
|
+
attachmentIds,
|
|
157
|
+
(photoIds) => {
|
|
158
|
+
const [attachmentId] = photoIds;
|
|
159
|
+
attachmentModel.value = attachmentId || "";
|
|
160
|
+
|
|
161
|
+
if (photoIds.length > 1) {
|
|
162
|
+
attachmentIds.value = attachmentId ? [attachmentId] : [];
|
|
163
|
+
attachmentPhotos.value = attachmentPhotos.value.slice(0, 1);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
{ deep: true }
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
watch(showDialog, (isOpen) => {
|
|
170
|
+
if (!isOpen) {
|
|
171
|
+
attachmentPhotos.value = [];
|
|
172
|
+
attachmentIds.value = [];
|
|
173
|
+
photoUploadError.value = "";
|
|
174
|
+
}
|
|
127
175
|
});
|
|
128
176
|
|
|
129
177
|
function close() {
|
|
@@ -147,12 +195,15 @@ async function submit() {
|
|
|
147
195
|
|
|
148
196
|
emit("saved", {
|
|
149
197
|
name: nameModel.value,
|
|
150
|
-
|
|
151
198
|
unitOfMeasurement: unitOfMeasurementModel.value,
|
|
199
|
+
attachment: attachmentModel.value,
|
|
152
200
|
});
|
|
153
201
|
|
|
154
202
|
nameModel.value = "";
|
|
155
203
|
unitOfMeasurementModel.value = "";
|
|
204
|
+
attachmentModel.value = "";
|
|
205
|
+
attachmentPhotos.value = [];
|
|
206
|
+
attachmentIds.value = [];
|
|
156
207
|
|
|
157
208
|
showDialog.value = false;
|
|
158
209
|
emit("close");
|
|
@@ -32,11 +32,6 @@
|
|
|
32
32
|
color="blue"
|
|
33
33
|
variant="tonal"
|
|
34
34
|
size="small"
|
|
35
|
-
closable
|
|
36
|
-
@click:close="
|
|
37
|
-
existingPassToBeReplaced = existingPass;
|
|
38
|
-
existingPass = null;
|
|
39
|
-
"
|
|
40
35
|
>
|
|
41
36
|
{{ existingPass.prefixAndName }}
|
|
42
37
|
</v-chip>
|
|
@@ -45,9 +40,9 @@
|
|
|
45
40
|
|
|
46
41
|
<!-- New pass selector -->
|
|
47
42
|
<v-autocomplete
|
|
48
|
-
v-if="!existingPass"
|
|
49
43
|
v-model="selectedPass"
|
|
50
44
|
v-model:search="passInput"
|
|
45
|
+
:label="existingPass?.keyId ? 'Change pass' : undefined"
|
|
51
46
|
:hide-no-data="false"
|
|
52
47
|
:items="passItems"
|
|
53
48
|
item-title="prefixAndName"
|
|
@@ -85,20 +80,20 @@
|
|
|
85
80
|
class="d-flex flex-wrap ga-1 mb-2"
|
|
86
81
|
>
|
|
87
82
|
<v-chip
|
|
88
|
-
v-for="key in existingKeys"
|
|
83
|
+
v-for="key in existingKeys.filter((k) => k.status != 'Removed')"
|
|
89
84
|
:key="key.keyId"
|
|
90
85
|
prepend-icon="mdi-key"
|
|
91
86
|
color="orange"
|
|
92
87
|
variant="tonal"
|
|
93
88
|
size="small"
|
|
94
89
|
closable
|
|
95
|
-
@click:close="removeExistingKey(key
|
|
90
|
+
@click:close="removeExistingKey(key)"
|
|
96
91
|
>
|
|
97
92
|
{{ key.prefixAndName }}
|
|
98
93
|
</v-chip>
|
|
99
|
-
<span class="text-caption text-medium-emphasis align-self-center"
|
|
100
|
-
|
|
101
|
-
>
|
|
94
|
+
<span class="text-caption text-medium-emphasis align-self-center">
|
|
95
|
+
(current)
|
|
96
|
+
</span>
|
|
102
97
|
</div>
|
|
103
98
|
|
|
104
99
|
<!-- New keys selector -->
|
|
@@ -116,6 +111,8 @@
|
|
|
116
111
|
density="compact"
|
|
117
112
|
small-chips
|
|
118
113
|
:loading="fetchKeysPending"
|
|
114
|
+
clearable
|
|
115
|
+
@update:model-value="selectKeys"
|
|
119
116
|
>
|
|
120
117
|
<template v-slot:chip="{ props: chipProps, item }">
|
|
121
118
|
<v-chip
|
|
@@ -205,7 +202,7 @@ const processing = ref(false);
|
|
|
205
202
|
const errorMessage = ref("");
|
|
206
203
|
|
|
207
204
|
// New selections
|
|
208
|
-
const selectedPass = ref<string>(
|
|
205
|
+
const selectedPass = ref<string | null>(null);
|
|
209
206
|
const selectedKeys = ref<string[]>([]);
|
|
210
207
|
const passInput = ref("");
|
|
211
208
|
const keyInput = ref("");
|
|
@@ -213,12 +210,15 @@ const passItems = ref<TPassKey[]>([]);
|
|
|
213
210
|
const keyItems = ref<TPassKey[]>([]);
|
|
214
211
|
|
|
215
212
|
// Existing pass/keys (editable)
|
|
216
|
-
const existingPass = ref<{
|
|
217
|
-
const existingPassToBeReplaced = ref<{
|
|
213
|
+
const existingPass = ref<{
|
|
218
214
|
keyId: string;
|
|
219
215
|
prefixAndName: string;
|
|
216
|
+
status?: string;
|
|
220
217
|
} | null>(null);
|
|
221
|
-
|
|
218
|
+
|
|
219
|
+
const existingKeys = ref<
|
|
220
|
+
{ keyId: string; prefixAndName: string; status?: string }[]
|
|
221
|
+
>([]);
|
|
222
222
|
|
|
223
223
|
const isEditMode = computed(() => prop.mode === "edit");
|
|
224
224
|
const showKeys = computed(() => prop.visitor.type === "contractor");
|
|
@@ -237,28 +237,82 @@ const passTypesComputed = computed(() => {
|
|
|
237
237
|
// Initialise existing values in edit mode
|
|
238
238
|
onMounted(() => {
|
|
239
239
|
if (isEditMode.value) {
|
|
240
|
-
const vPass = prop.visitor.visitorPass
|
|
240
|
+
const vPass = prop.visitor.visitorPass?.filter(
|
|
241
|
+
(i) => i.status != "Removed"
|
|
242
|
+
);
|
|
241
243
|
if (Array.isArray(vPass) && vPass.length > 0) {
|
|
242
244
|
const p = vPass[0] as any;
|
|
243
|
-
existingPass.value = {
|
|
245
|
+
existingPass.value = {
|
|
246
|
+
keyId: p.keyId,
|
|
247
|
+
prefixAndName: p.prefixAndName,
|
|
248
|
+
status: p.status,
|
|
249
|
+
};
|
|
244
250
|
}
|
|
245
|
-
const vKeys = prop.visitor.passKeys;
|
|
251
|
+
const vKeys = prop.visitor.passKeys?.filter((i) => i.status != "Removed");
|
|
246
252
|
if (Array.isArray(vKeys) && vKeys.length > 0) {
|
|
247
253
|
existingKeys.value = (vKeys as any[]).map((k) => ({
|
|
248
254
|
keyId: k.keyId,
|
|
249
255
|
prefixAndName: k.prefixAndName,
|
|
256
|
+
status: k.status,
|
|
250
257
|
}));
|
|
251
258
|
}
|
|
252
259
|
}
|
|
253
260
|
});
|
|
254
261
|
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
const previouslyRemovedKeys = ref<any>([]);
|
|
263
|
+
|
|
264
|
+
function removeExistingKey(key: Record<string, any>) {
|
|
265
|
+
const originalKey = existingKeys.value.find((k) => k.keyId === key.keyId);
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
originalKey &&
|
|
269
|
+
!previouslyRemovedKeys.value.some((k: any) => k.keyId === key.keyId)
|
|
270
|
+
) {
|
|
271
|
+
previouslyRemovedKeys.value.push({ ...originalKey });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
existingKeys.value = existingKeys.value.map((i) => {
|
|
275
|
+
if (i.keyId === key.keyId) {
|
|
276
|
+
return {
|
|
277
|
+
...i,
|
|
278
|
+
status: "Removed",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return i;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function selectKeys() {
|
|
286
|
+
const currentSelections = [...selectedKeys.value];
|
|
287
|
+
|
|
288
|
+
currentSelections.forEach((selectedId) => {
|
|
289
|
+
const removedMatch = previouslyRemovedKeys.value.find(
|
|
290
|
+
(k: any) => k.keyId === selectedId
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (removedMatch) {
|
|
294
|
+
existingKeys.value = existingKeys.value.map((i) => {
|
|
295
|
+
if (i.keyId === selectedId) {
|
|
296
|
+
return {
|
|
297
|
+
...i,
|
|
298
|
+
status: removedMatch.status,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return i;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
previouslyRemovedKeys.value = previouslyRemovedKeys.value.filter(
|
|
305
|
+
(k: any) => k.keyId !== selectedId
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
selectedKeys.value = selectedKeys.value.filter((id) => id !== selectedId);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
257
311
|
}
|
|
258
312
|
|
|
259
313
|
// Exclude already-held pass/keys from selectable lists
|
|
260
314
|
const existingPassId = computed(() => existingPass.value?.keyId);
|
|
261
|
-
const existingKeyIds = computed(() => existingKeys.value.map((k) => k.keyId));
|
|
315
|
+
// const existingKeyIds = computed(() => existingKeys.value.map((k) => k.keyId));
|
|
262
316
|
|
|
263
317
|
const {
|
|
264
318
|
data: passesData,
|
|
@@ -294,43 +348,50 @@ const {
|
|
|
294
348
|
})
|
|
295
349
|
);
|
|
296
350
|
|
|
297
|
-
watch(passesData, (data: any) => {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
});
|
|
351
|
+
// watch(passesData, (data: any) => {
|
|
352
|
+
// const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
|
|
353
|
+
// passItems.value = all.filter((p) => p._id !== existingPassId.value);
|
|
354
|
+
// });
|
|
301
355
|
|
|
302
|
-
watch(keysData, (data: any) => {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
});
|
|
356
|
+
// watch(keysData, (data: any) => {
|
|
357
|
+
// const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
|
|
358
|
+
// keyItems.value = all.filter((k) => !existingKeyIds.value.includes(k._id!));
|
|
359
|
+
// });
|
|
306
360
|
|
|
307
361
|
// Re-filter when existing items change
|
|
308
362
|
watch(
|
|
309
|
-
[existingPassId,
|
|
363
|
+
[existingPassId, existingKeys, passesData, keysData],
|
|
310
364
|
() => {
|
|
311
365
|
const rawPasses = (passesData.value as any)?.items ?? [];
|
|
312
366
|
passItems.value = rawPasses.filter(
|
|
313
367
|
(p: TPassKey) => p._id !== existingPassId.value
|
|
314
368
|
);
|
|
369
|
+
|
|
315
370
|
const rawKeys = (keysData.value as any)?.items ?? [];
|
|
316
|
-
|
|
317
|
-
(k
|
|
371
|
+
const manuallyRemovedKeys = existingKeys.value
|
|
372
|
+
.filter((k) => k.status === "Removed")
|
|
373
|
+
.map((k) => ({
|
|
374
|
+
_id: k.keyId,
|
|
375
|
+
prefixAndName: k.prefixAndName,
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
const allExistingKeyIds = existingKeys.value.map((k) => k.keyId);
|
|
379
|
+
|
|
380
|
+
const filteredRawKeys = rawKeys.filter(
|
|
381
|
+
(k: TPassKey) => !allExistingKeyIds.includes(k._id!)
|
|
318
382
|
);
|
|
383
|
+
|
|
384
|
+
keyItems.value = [...filteredRawKeys, ...manuallyRemovedKeys];
|
|
385
|
+
console.log("keyItems: ", keyItems.value);
|
|
319
386
|
},
|
|
320
|
-
{ deep: true }
|
|
387
|
+
{ deep: true, immediate: true }
|
|
321
388
|
);
|
|
322
389
|
|
|
323
390
|
const canSubmit = computed(() => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
selectedPass.value ||
|
|
329
|
-
existingKeys.value.length > 0 ||
|
|
330
|
-
selectedKeys.value.length > 0
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
return !!selectedPass.value || selectedKeys.value.length > 0;
|
|
391
|
+
const hasRemovedKeys = existingKeys.value.some((k) => k.status === "Removed");
|
|
392
|
+
return (
|
|
393
|
+
!!selectedPass.value || selectedKeys.value.length > 0 || hasRemovedKeys
|
|
394
|
+
);
|
|
334
395
|
});
|
|
335
396
|
|
|
336
397
|
async function handleSubmit() {
|
|
@@ -343,16 +404,13 @@ async function handleSubmit() {
|
|
|
343
404
|
|
|
344
405
|
// Build final pass list: kept existing + newly selected
|
|
345
406
|
const finalPasses: { keyId: string; status?: string; add?: boolean }[] = [];
|
|
346
|
-
if (existingPass.value) {
|
|
347
|
-
finalPasses.push({ keyId: existingPass.value.keyId, status: "Removed" });
|
|
348
|
-
}
|
|
349
|
-
if (existingPassToBeReplaced.value) {
|
|
350
|
-
finalPasses.push({
|
|
351
|
-
keyId: existingPassToBeReplaced.value.keyId,
|
|
352
|
-
status: "Removed",
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
407
|
if (selectedPass.value) {
|
|
408
|
+
if (existingPass.value) {
|
|
409
|
+
finalPasses.push({
|
|
410
|
+
keyId: existingPass.value.keyId,
|
|
411
|
+
status: "Removed",
|
|
412
|
+
});
|
|
413
|
+
}
|
|
356
414
|
finalPasses.push({
|
|
357
415
|
keyId: selectedPass.value,
|
|
358
416
|
status: "Not Returned",
|
|
@@ -364,13 +422,17 @@ async function handleSubmit() {
|
|
|
364
422
|
// Build final keys list: kept existing + newly selected
|
|
365
423
|
if (showKeys.value) {
|
|
366
424
|
const finalKeys: { keyId: string; status?: string; add?: boolean }[] = [
|
|
367
|
-
...existingKeys.value.map((k) => ({
|
|
425
|
+
...existingKeys.value.map((k) => ({
|
|
426
|
+
keyId: k.keyId,
|
|
427
|
+
status: k.status,
|
|
428
|
+
})),
|
|
368
429
|
...selectedKeys.value.map((keyId) => ({
|
|
369
430
|
keyId,
|
|
370
431
|
status: "Not Returned",
|
|
371
432
|
add: true,
|
|
372
433
|
})),
|
|
373
434
|
];
|
|
435
|
+
|
|
374
436
|
payload.passKeys = finalKeys;
|
|
375
437
|
}
|
|
376
438
|
|