@7365admin1/layer-common 1.11.22 → 1.11.23
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/AttendanceMain.vue +6 -6
- package/components/CleaningScheduleMain.vue +8 -7
- package/components/ManageChecklistMain.vue +4 -3
- package/components/MemberInformation.vue +49 -13
- package/components/MyAttendanceMain.vue +6 -3
- package/components/PassInformation.vue +197 -144
- package/components/ScanVisitorQRCode.vue +47 -12
- package/components/ScheduleAreaMain.vue +6 -3
- package/components/ScheduleTaskMain.vue +11 -8
- package/components/VisitorDataFromScannedQRCodeForm.vue +262 -0
- package/components/VisitorManagement.vue +126 -13
- package/components/VisitorPassKeyQRScanner.vue +138 -0
- package/composables/useVisitor.ts +89 -28
- package/package.json +2 -1
- package/types/visitor.d.ts +45 -15
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog v-model="props.dialog" transition="dialog-bottom-transition" width="700" max-width="700" persistent
|
|
3
|
+
@after-enter="handleScanVisitorQRCode">
|
|
4
|
+
<v-container class="d-flex justify-center" max-height="90vh">
|
|
5
|
+
<!-- QR code reader container -->
|
|
6
|
+
<v-card elevation="2" class="d-flex flex-column align-center pa-2 w-100 h-100">
|
|
7
|
+
<v-toolbar>
|
|
8
|
+
<v-card-title class="text-h5"> Scan QR Code </v-card-title>
|
|
9
|
+
<v-spacer />
|
|
10
|
+
<v-btn color="grey-darken-1" icon="mdi-close" @click="closeDialog()" />
|
|
11
|
+
</v-toolbar>
|
|
12
|
+
|
|
13
|
+
<div id="reader" class="d-flex justify-center align-center w-100 h-100"
|
|
14
|
+
style="height: calc(100vh - 64px)">
|
|
15
|
+
<!-- The QR code scanner box -->
|
|
16
|
+
<!-- This is where the QR code scanner will be displayed -->
|
|
17
|
+
</div>
|
|
18
|
+
<!-- Show Error Messages -->
|
|
19
|
+
<div v-if="showNotFoundMessage" class="text-error mt-8">
|
|
20
|
+
<p>{{ errorMessage }}</p>
|
|
21
|
+
</div>
|
|
22
|
+
<!-- Switch camera button inside the reader box -->
|
|
23
|
+
<v-btn color="primary" icon class="mt-4" @click="switchCamera()">
|
|
24
|
+
<v-icon icon="mdi-camera-switch" large />
|
|
25
|
+
</v-btn>
|
|
26
|
+
</v-card>
|
|
27
|
+
</v-container>
|
|
28
|
+
</v-dialog>
|
|
29
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
30
|
+
</template>
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
const { $Html5Qrcode }: any = useNuxtApp();
|
|
33
|
+
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
dialog: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const scannedValue = defineModel<string>("scannedValue", {
|
|
42
|
+
default: "",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits(["closeDialog"]);
|
|
46
|
+
|
|
47
|
+
const message = ref("");
|
|
48
|
+
const messageColor = ref("");
|
|
49
|
+
const messageSnackbar = ref(false);
|
|
50
|
+
|
|
51
|
+
function showMessage(msg: string, color: string) {
|
|
52
|
+
message.value = msg;
|
|
53
|
+
messageColor.value = color;
|
|
54
|
+
messageSnackbar.value = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const html5QrCodeInstance = ref<any>(null);
|
|
58
|
+
const cameraFacingMode = ref<string>("environment");
|
|
59
|
+
|
|
60
|
+
const showNotFoundMessage = ref<boolean>(false);
|
|
61
|
+
const errorMessage = ref<string>("");
|
|
62
|
+
|
|
63
|
+
const handleScanVisitorQRCode = async () => {
|
|
64
|
+
if (html5QrCodeInstance.value) return;
|
|
65
|
+
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
|
|
66
|
+
html5QrCodeInstance.value = new $Html5Qrcode("reader");
|
|
67
|
+
html5QrCodeInstance.value
|
|
68
|
+
.start(
|
|
69
|
+
{ facingMode: cameraFacingMode.value },
|
|
70
|
+
config,
|
|
71
|
+
async (qrCodeMessage: string) => {
|
|
72
|
+
showNotFoundMessage.value = false;
|
|
73
|
+
scannedValue.value = qrCodeMessage;
|
|
74
|
+
console.log(`QR Code scanned: ${qrCodeMessage}`);
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
closeDialog();
|
|
77
|
+
}, 100);
|
|
78
|
+
},
|
|
79
|
+
(msg: string) => {
|
|
80
|
+
if (msg === "QR Code no longer in front") {
|
|
81
|
+
showNotFoundMessage.value = true;
|
|
82
|
+
errorMessage.value = msg;
|
|
83
|
+
showMessage(msg, "error");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
.catch((err: any) => {
|
|
88
|
+
showMessage(`Unable to start scanning, error: ${err}`, "error");
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const isValidUrl = (string: string) => {
|
|
93
|
+
try {
|
|
94
|
+
new URL(string);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (_) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const switchCamera = async () => {
|
|
102
|
+
await stopScanner();
|
|
103
|
+
cameraFacingMode.value =
|
|
104
|
+
cameraFacingMode.value === "environment" ? "user" : "environment";
|
|
105
|
+
showMessage(
|
|
106
|
+
`Switched to ${cameraFacingMode.value === "environment" ? "Back" : "Front"
|
|
107
|
+
} Camera`,
|
|
108
|
+
"info"
|
|
109
|
+
);
|
|
110
|
+
handleScanVisitorQRCode();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const closeDialog = () => {
|
|
114
|
+
emit("closeDialog");
|
|
115
|
+
stopScanner();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const stopScanner = () => {
|
|
119
|
+
return new Promise<void>((resolve, reject) => {
|
|
120
|
+
if (html5QrCodeInstance.value) {
|
|
121
|
+
html5QrCodeInstance.value
|
|
122
|
+
.stop()
|
|
123
|
+
.then(() => {
|
|
124
|
+
html5QrCodeInstance.value.clear();
|
|
125
|
+
html5QrCodeInstance.value = null;
|
|
126
|
+
showNotFoundMessage.value = false;
|
|
127
|
+
resolve(); // Resolve the promise when the scanner is fully stopped
|
|
128
|
+
})
|
|
129
|
+
.catch((err: any) => {
|
|
130
|
+
showMessage("Unable to stop scanning.", err);
|
|
131
|
+
reject(err); // Reject the promise if there's an error
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
resolve(); // Resolve immediately if no scanner instance exists
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
</script>
|
|
@@ -4,7 +4,7 @@ export default function () {
|
|
|
4
4
|
value: TVisitorType;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
const visitorSelection: TVisitorSelection[] =[
|
|
7
|
+
const visitorSelection: TVisitorSelection[] = [
|
|
8
8
|
{ label: "Drive-in", value: "guest" },
|
|
9
9
|
{ label: "Contractor", value: "contractor" },
|
|
10
10
|
{ label: "Walk-In", value: "walk-in" },
|
|
@@ -13,14 +13,62 @@ export default function () {
|
|
|
13
13
|
{ label: "Drop-Off", value: "drop-off" },
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const typeFieldMap: Record<
|
|
17
|
+
TVisitorType,
|
|
18
|
+
(keyof TVisitorPayload | "delivery-company")[]
|
|
19
|
+
> = {
|
|
20
|
+
guest: [
|
|
21
|
+
"name",
|
|
22
|
+
"nric",
|
|
23
|
+
"contact",
|
|
24
|
+
"block",
|
|
25
|
+
"plateNumber",
|
|
26
|
+
"level",
|
|
27
|
+
"unit",
|
|
28
|
+
"unitName",
|
|
29
|
+
"remarks",
|
|
30
|
+
],
|
|
31
|
+
contractor: [
|
|
32
|
+
"contractorType",
|
|
33
|
+
"name",
|
|
34
|
+
"nric",
|
|
35
|
+
"company",
|
|
36
|
+
"contact",
|
|
37
|
+
"plateNumber",
|
|
38
|
+
"block",
|
|
39
|
+
"level",
|
|
40
|
+
"unit",
|
|
41
|
+
"unitName",
|
|
42
|
+
"remarks",
|
|
43
|
+
],
|
|
44
|
+
"walk-in": [
|
|
45
|
+
"name",
|
|
46
|
+
"company",
|
|
47
|
+
"nric",
|
|
48
|
+
"contact",
|
|
49
|
+
"block",
|
|
50
|
+
"level",
|
|
51
|
+
"unit",
|
|
52
|
+
"unitName",
|
|
53
|
+
"remarks",
|
|
54
|
+
],
|
|
55
|
+
delivery: [
|
|
56
|
+
"attachments",
|
|
57
|
+
"name",
|
|
58
|
+
"deliveryType",
|
|
59
|
+
"delivery-company",
|
|
60
|
+
"nric",
|
|
61
|
+
"contact",
|
|
62
|
+
"plateNumber",
|
|
63
|
+
"block",
|
|
64
|
+
"level",
|
|
65
|
+
"unit",
|
|
66
|
+
"unitName",
|
|
67
|
+
"remarks",
|
|
68
|
+
],
|
|
69
|
+
"pick-up": ["plateNumber", "block", "remarks"],
|
|
70
|
+
"drop-off": ["plateNumber", "block", "remarks"],
|
|
71
|
+
};
|
|
24
72
|
|
|
25
73
|
const contractorTypes = [
|
|
26
74
|
{ title: "Estate Contractor", value: "estate-contractor" },
|
|
@@ -29,21 +77,20 @@ export default function () {
|
|
|
29
77
|
{ title: "House Mover", value: "house-mover" },
|
|
30
78
|
];
|
|
31
79
|
|
|
32
|
-
|
|
33
80
|
type GetVisitorsParams = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
81
|
+
page?: number;
|
|
82
|
+
limit?: number;
|
|
83
|
+
order?: "asc" | "desc";
|
|
84
|
+
search?: string;
|
|
85
|
+
org?: string;
|
|
86
|
+
site?: string;
|
|
87
|
+
dateTo?: string;
|
|
88
|
+
dateFrom?: string;
|
|
89
|
+
type?: string;
|
|
90
|
+
status?: string;
|
|
91
|
+
checkedOut?: boolean;
|
|
92
|
+
plateNumber?: string;
|
|
93
|
+
};
|
|
47
94
|
|
|
48
95
|
async function getVisitors({
|
|
49
96
|
page = 1,
|
|
@@ -58,7 +105,7 @@ export default function () {
|
|
|
58
105
|
status = "",
|
|
59
106
|
// status = "registered"
|
|
60
107
|
plateNumber = "",
|
|
61
|
-
checkedOut
|
|
108
|
+
checkedOut,
|
|
62
109
|
}: GetVisitorsParams = {}) {
|
|
63
110
|
return await useNuxtApp().$api<Record<string, any>>(
|
|
64
111
|
"/api/visitor-transactions",
|
|
@@ -76,7 +123,7 @@ export default function () {
|
|
|
76
123
|
status,
|
|
77
124
|
checkedOut,
|
|
78
125
|
plateNumber,
|
|
79
|
-
order
|
|
126
|
+
order,
|
|
80
127
|
},
|
|
81
128
|
}
|
|
82
129
|
);
|
|
@@ -111,7 +158,13 @@ export default function () {
|
|
|
111
158
|
);
|
|
112
159
|
}
|
|
113
160
|
|
|
114
|
-
|
|
161
|
+
function validateVisitorQrCode(site: string, id: string) {
|
|
162
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
163
|
+
`/api/visitors/visitor-qr-code/${id}?site=${site}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function updateVisitorPassKey(_id: string, payload: TPassKeyPayload[]) {
|
|
115
168
|
return await useNuxtApp().$api<Record<string, any>>(
|
|
116
169
|
`/api/visitor-transactions/replace-keys/id/${_id}`,
|
|
117
170
|
{
|
|
@@ -120,7 +173,13 @@ export default function () {
|
|
|
120
173
|
}
|
|
121
174
|
);
|
|
122
175
|
}
|
|
123
|
-
|
|
176
|
+
|
|
177
|
+
function visitorDataFromScannedQRCodeCheckInOut(payload: any) {
|
|
178
|
+
return useNuxtApp().$api<Record<string, any>>("/api/visitors/update-one", {
|
|
179
|
+
method: "PUT",
|
|
180
|
+
body: payload,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
124
183
|
|
|
125
184
|
return {
|
|
126
185
|
typeFieldMap,
|
|
@@ -130,6 +189,8 @@ export default function () {
|
|
|
130
189
|
getVisitors,
|
|
131
190
|
updateVisitor,
|
|
132
191
|
deleteVisitor,
|
|
133
|
-
|
|
192
|
+
validateVisitorQrCode,
|
|
193
|
+
updateVisitorPassKey,
|
|
194
|
+
visitorDataFromScannedQRCodeCheckInOut,
|
|
134
195
|
};
|
|
135
196
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.11.
|
|
5
|
+
"version": "1.11.23",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"publishConfig": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@types/qrcode": "^1.5.6",
|
|
35
35
|
"ckeditor5": "^47.2.0",
|
|
36
36
|
"html2pdf.js": "^0.10.2",
|
|
37
|
+
"html5-qrcode": "^2.3.8",
|
|
37
38
|
"moment-timezone": "^0.6.0",
|
|
38
39
|
"nuxt-signature-pad": "^1.8.0",
|
|
39
40
|
"qrcode": "^1.5.4",
|
package/types/visitor.d.ts
CHANGED
|
@@ -16,39 +16,69 @@ declare type TVisitor = {
|
|
|
16
16
|
nric?: string;
|
|
17
17
|
contact?: string;
|
|
18
18
|
remarks?: string;
|
|
19
|
+
purpose?: string;
|
|
19
20
|
attachments: string[];
|
|
20
21
|
org: string;
|
|
21
|
-
site: string
|
|
22
|
+
site: string;
|
|
22
23
|
manualCheckout?: boolean;
|
|
23
24
|
visitorPass?: TPassKeyPayload[];
|
|
24
|
-
passKeys?: TPassKeyPayload[]
|
|
25
|
+
passKeys?: TPassKeyPayload[];
|
|
25
26
|
checkInRemarks?: string;
|
|
26
27
|
checkOutRemarks?: string;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
declare type TVisitorType =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
declare type TVisitorType =
|
|
31
|
+
| "contractor"
|
|
32
|
+
| "delivery"
|
|
33
|
+
| "walk-in"
|
|
34
|
+
| "pick-up"
|
|
35
|
+
| "drop-off"
|
|
36
|
+
| "guest";
|
|
35
37
|
|
|
38
|
+
declare type TVisitorPayload = Pick<
|
|
39
|
+
TVisitor,
|
|
40
|
+
| "name"
|
|
41
|
+
| "type"
|
|
42
|
+
| "company"
|
|
43
|
+
| "block"
|
|
44
|
+
| "level"
|
|
45
|
+
| "unit"
|
|
46
|
+
| "unitName"
|
|
47
|
+
| "contact"
|
|
48
|
+
| "plateNumber"
|
|
49
|
+
| "checkOut"
|
|
50
|
+
| "contractorType"
|
|
51
|
+
| "deliveryType"
|
|
52
|
+
| "nric"
|
|
53
|
+
| "contact"
|
|
54
|
+
| "remarks"
|
|
55
|
+
| "attachments"
|
|
56
|
+
| "org"
|
|
57
|
+
| "site"
|
|
58
|
+
| "status"
|
|
59
|
+
| "visitorPass"
|
|
60
|
+
| "passKeys"
|
|
61
|
+
| "checkInRemarks"
|
|
62
|
+
| "checkOutRemarks"
|
|
63
|
+
> & {
|
|
64
|
+
members?: TMemberInfo[];
|
|
65
|
+
};
|
|
36
66
|
|
|
37
67
|
declare type TDefaultOptionObj = {
|
|
38
68
|
title: string;
|
|
39
69
|
value: any;
|
|
40
|
-
}
|
|
70
|
+
};
|
|
41
71
|
|
|
42
72
|
declare type TMemberInfo = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
name: string;
|
|
74
|
+
nric: string;
|
|
75
|
+
visitorPass?: { keyId: string }[];
|
|
76
|
+
passKeys?: TPassKeyPayload[];
|
|
77
|
+
contact: string
|
|
48
78
|
}
|
|
49
79
|
|
|
50
80
|
declare type TPassKeyPayload = {
|
|
51
81
|
keyId: string;
|
|
52
82
|
status?: string;
|
|
53
83
|
remarks?: string;
|
|
54
|
-
}
|
|
84
|
+
};
|