@7365admin1/layer-common 1.10.10 → 1.11.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 +12 -0
- package/components/AccessCardAddForm.vue +1 -1
- package/components/AccessCardAssignToUnitForm.vue +10 -13
- package/components/AccessCardQrTagging.vue +2 -2
- package/components/Chat/SkeletonLoader.vue +71 -0
- package/components/DashboardMain.vue +176 -0
- package/components/EntryPassInformation.vue +3 -7
- package/components/FileInputWithList.vue +304 -0
- package/components/IncidentReport/IncidentInformation.vue +14 -2
- package/components/IncidentReport/IncidentInformationDownload.vue +22 -9
- package/components/IncidentReport/affectedEntities.vue +5 -0
- package/components/Signature.vue +133 -0
- package/components/SlideCardGroup.vue +194 -0
- package/components/VisitorForm.vue +17 -1
- package/composables/useAccessManagement.ts +25 -6
- package/composables/useComment.ts +147 -0
- package/composables/useFeedback.ts +79 -29
- package/composables/usePDFDownload.ts +1 -1
- package/composables/useWorkOrder.ts +61 -26
- package/package.json +2 -1
- package/public/default-image.svg +4 -0
- package/public/placeholder-image.svg +6 -0
- package/types/comment.d.ts +38 -0
- package/types/dashboard.d.ts +10 -0
- package/types/feedback.d.ts +56 -20
- package/types/work-order.d.ts +54 -18
- package/utils/data.ts +31 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-file-input
|
|
4
|
+
v-model="files"
|
|
5
|
+
:label="label"
|
|
6
|
+
accept="image/*"
|
|
7
|
+
:prepend-icon="prependIcon"
|
|
8
|
+
hide-details
|
|
9
|
+
show-size
|
|
10
|
+
chips
|
|
11
|
+
multiple
|
|
12
|
+
clearable
|
|
13
|
+
@update:modelValue="handleFileSelect"
|
|
14
|
+
@click:clear="handleClear"
|
|
15
|
+
:hide-input="hasHideInput"
|
|
16
|
+
>
|
|
17
|
+
<template v-slot:append v-if="hasLabel">
|
|
18
|
+
<slot name="append">
|
|
19
|
+
<v-btn color="primary" height="50px" @click="openCameraDialog">
|
|
20
|
+
<v-icon>mdi-camera</v-icon>
|
|
21
|
+
</v-btn>
|
|
22
|
+
</slot>
|
|
23
|
+
</template>
|
|
24
|
+
</v-file-input>
|
|
25
|
+
|
|
26
|
+
<v-row no-gutters v-if="hasLabel">
|
|
27
|
+
<v-col cols="12" class="mt-1">
|
|
28
|
+
<v-chip-group column>
|
|
29
|
+
<template v-for="(file, index) in files" :key="file.name">
|
|
30
|
+
<v-chip
|
|
31
|
+
closable
|
|
32
|
+
class="text-wrap text-caption custom-chip"
|
|
33
|
+
@click:close="removeFile(index)"
|
|
34
|
+
>
|
|
35
|
+
<span class="chip-text">{{ file.name }}</span>
|
|
36
|
+
</v-chip>
|
|
37
|
+
</template>
|
|
38
|
+
</v-chip-group>
|
|
39
|
+
</v-col>
|
|
40
|
+
</v-row>
|
|
41
|
+
|
|
42
|
+
<!-- Camera Dialog -->
|
|
43
|
+
<v-dialog
|
|
44
|
+
v-model="showCameraDialog"
|
|
45
|
+
transition="dialog-bottom-transition"
|
|
46
|
+
width="800"
|
|
47
|
+
max-width="800"
|
|
48
|
+
persistent
|
|
49
|
+
@after-enter="startCamera"
|
|
50
|
+
>
|
|
51
|
+
<v-container
|
|
52
|
+
class="d-flex justify-center"
|
|
53
|
+
max-height="90vh"
|
|
54
|
+
width="800"
|
|
55
|
+
max-width="800"
|
|
56
|
+
>
|
|
57
|
+
<v-card elevation="2" class="d-flex flex-column align-center pa-2">
|
|
58
|
+
<v-toolbar>
|
|
59
|
+
<v-card-title class="text-h5">Take a Picture</v-card-title>
|
|
60
|
+
<v-spacer></v-spacer>
|
|
61
|
+
<v-btn
|
|
62
|
+
color="grey-darken-1"
|
|
63
|
+
icon="mdi-close"
|
|
64
|
+
@click="closeCameraDialog"
|
|
65
|
+
></v-btn>
|
|
66
|
+
</v-toolbar>
|
|
67
|
+
|
|
68
|
+
<div
|
|
69
|
+
id="reader"
|
|
70
|
+
class="d-flex justify-center align-center"
|
|
71
|
+
style="
|
|
72
|
+
position: relative;
|
|
73
|
+
width: 500px;
|
|
74
|
+
min-width: 400px;
|
|
75
|
+
height: 400px;
|
|
76
|
+
"
|
|
77
|
+
>
|
|
78
|
+
<video
|
|
79
|
+
ref="video"
|
|
80
|
+
style="flex: 1; height: 400px; min-width: 300px"
|
|
81
|
+
class="video-shutter"
|
|
82
|
+
autoplay
|
|
83
|
+
></video>
|
|
84
|
+
<canvas
|
|
85
|
+
ref="canvas"
|
|
86
|
+
style="flex: 1; height: 400px; min-width: 300px; display: none"
|
|
87
|
+
></canvas>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<v-row align="center" justify="center">
|
|
91
|
+
<v-col cols="6">
|
|
92
|
+
<v-btn color="primary" icon class="mt-4" @click="switchCamera">
|
|
93
|
+
<v-icon>mdi-camera-switch</v-icon>
|
|
94
|
+
</v-btn>
|
|
95
|
+
</v-col>
|
|
96
|
+
<v-col cols="6">
|
|
97
|
+
<v-btn
|
|
98
|
+
color="secondary"
|
|
99
|
+
icon
|
|
100
|
+
class="mt-4"
|
|
101
|
+
@click="captureImageFromCamera"
|
|
102
|
+
>
|
|
103
|
+
<v-icon large>mdi-camera-outline</v-icon>
|
|
104
|
+
</v-btn>
|
|
105
|
+
</v-col>
|
|
106
|
+
</v-row>
|
|
107
|
+
</v-card>
|
|
108
|
+
</v-container>
|
|
109
|
+
</v-dialog>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<script setup lang="ts">
|
|
114
|
+
interface FileWithPreview {
|
|
115
|
+
name: string;
|
|
116
|
+
data: File;
|
|
117
|
+
progress: number;
|
|
118
|
+
url: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const props = defineProps({
|
|
122
|
+
label: {
|
|
123
|
+
type: String,
|
|
124
|
+
default: "Select File",
|
|
125
|
+
},
|
|
126
|
+
prependIcon: {
|
|
127
|
+
type: String,
|
|
128
|
+
default: "mdi-paperclip",
|
|
129
|
+
},
|
|
130
|
+
required: {
|
|
131
|
+
type: Boolean,
|
|
132
|
+
default: true,
|
|
133
|
+
},
|
|
134
|
+
initFiles: {
|
|
135
|
+
type: Array,
|
|
136
|
+
},
|
|
137
|
+
hasLabel: {
|
|
138
|
+
type: Boolean,
|
|
139
|
+
default: true,
|
|
140
|
+
},
|
|
141
|
+
hasHideInput: {
|
|
142
|
+
type: Boolean,
|
|
143
|
+
default: false,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const emit = defineEmits<{
|
|
148
|
+
(event: "onFileAttach", payload: Array<{ data: File }>): void;
|
|
149
|
+
(event: "update:files", files: FileWithPreview[]): void;
|
|
150
|
+
(event: "onFileRemoved", payload: { index: number; file: File }): void;
|
|
151
|
+
(event: "onClear"): void;
|
|
152
|
+
}>();
|
|
153
|
+
|
|
154
|
+
const { showUploadedFiles } = useUploadFiles();
|
|
155
|
+
|
|
156
|
+
const files = ref<File[]>([]);
|
|
157
|
+
const attachedFiles = ref<FileWithPreview[]>([]);
|
|
158
|
+
const showCameraDialog = ref(false);
|
|
159
|
+
const video = ref<HTMLVideoElement | null>(null);
|
|
160
|
+
const canvas = ref<HTMLCanvasElement | null>(null);
|
|
161
|
+
const cameraFacingMode = ref<"environment" | "user">("environment");
|
|
162
|
+
|
|
163
|
+
const message = ref("");
|
|
164
|
+
const messageColor = ref("");
|
|
165
|
+
const messageSnackbar = ref(false);
|
|
166
|
+
|
|
167
|
+
function showMessage(msg: string, color: string) {
|
|
168
|
+
message.value = msg;
|
|
169
|
+
messageColor.value = color;
|
|
170
|
+
messageSnackbar.value = true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
watchEffect(() => {
|
|
174
|
+
if (Array.isArray(props.initFiles) && props.initFiles.length > 0) {
|
|
175
|
+
files.value = props.initFiles.filter((file) => file && file.name); // Ensure valid files
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const handleFileSelect = async () => {
|
|
179
|
+
if (files.value && files.value.length > 0) {
|
|
180
|
+
const newFiles = files.value.map((file: File) => ({
|
|
181
|
+
name: file.name,
|
|
182
|
+
data: file,
|
|
183
|
+
progress: 0,
|
|
184
|
+
url: URL.createObjectURL(file),
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
// attachedFiles.value = [...newFiles];
|
|
188
|
+
showUploadedFiles(newFiles);
|
|
189
|
+
|
|
190
|
+
emit("update:files", newFiles);
|
|
191
|
+
} else {
|
|
192
|
+
files.value = [...(props.initFiles as typeof files.value)];
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const handleClear = () => {
|
|
197
|
+
files.value = [];
|
|
198
|
+
emit("onClear");
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const openCameraDialog = () => {
|
|
202
|
+
showCameraDialog.value = true;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const closeCameraDialog = () => {
|
|
206
|
+
showCameraDialog.value = false;
|
|
207
|
+
stopCamera();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const startCamera = async () => {
|
|
211
|
+
try {
|
|
212
|
+
const constraints = {
|
|
213
|
+
video: {
|
|
214
|
+
facingMode: cameraFacingMode.value,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
219
|
+
if (video.value) {
|
|
220
|
+
video.value.srcObject = stream;
|
|
221
|
+
video.value.play();
|
|
222
|
+
}
|
|
223
|
+
} catch (error: any) {
|
|
224
|
+
showMessage(`Error accessing camera: ${error.message}`, "error");
|
|
225
|
+
closeCameraDialog();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const stopCamera = () => {
|
|
230
|
+
if (video.value) {
|
|
231
|
+
const stream = video.value.srcObject as MediaStream;
|
|
232
|
+
if (stream) {
|
|
233
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const switchCamera = async () => {
|
|
239
|
+
await stopCamera();
|
|
240
|
+
cameraFacingMode.value =
|
|
241
|
+
cameraFacingMode.value === "environment" ? "user" : "environment";
|
|
242
|
+
showMessage(
|
|
243
|
+
`Switched to ${
|
|
244
|
+
cameraFacingMode.value === "environment" ? "Back Camera" : "Front Camera"
|
|
245
|
+
}`,
|
|
246
|
+
"error"
|
|
247
|
+
);
|
|
248
|
+
startCamera();
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const captureImageFromCamera = () => {
|
|
252
|
+
if (!video.value || !canvas.value) return;
|
|
253
|
+
|
|
254
|
+
const context = canvas.value.getContext("2d");
|
|
255
|
+
if (!context) return;
|
|
256
|
+
|
|
257
|
+
// Set canvas dimensions to match video
|
|
258
|
+
canvas.value.width = video.value.videoWidth;
|
|
259
|
+
canvas.value.height = video.value.videoHeight;
|
|
260
|
+
|
|
261
|
+
// Capture the frame
|
|
262
|
+
context.drawImage(video.value, 0, 0, canvas.value.width, canvas.value.height);
|
|
263
|
+
|
|
264
|
+
// Convert to file
|
|
265
|
+
canvas.value.toBlob((blob) => {
|
|
266
|
+
if (!blob) return;
|
|
267
|
+
|
|
268
|
+
const file = new File([blob], `camera-capture-${Date.now()}.png`, {
|
|
269
|
+
type: "image/png",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
files.value = [file];
|
|
273
|
+
handleFileSelect();
|
|
274
|
+
closeCameraDialog();
|
|
275
|
+
}, "image/png");
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const removeFile = (index) => {
|
|
279
|
+
const removedFile = files.value[index];
|
|
280
|
+
files.value = files.value.filter((_, i) => i !== index);
|
|
281
|
+
emit("onFileRemoved", { index, file: removedFile }); // Emit when a file is removed
|
|
282
|
+
// emit("onFilesUpdated", files.value); // Emit updated files list
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Cleanup
|
|
286
|
+
onUnmounted(() => {
|
|
287
|
+
stopCamera();
|
|
288
|
+
});
|
|
289
|
+
</script>
|
|
290
|
+
|
|
291
|
+
<style scoped>
|
|
292
|
+
.custom-chip {
|
|
293
|
+
max-width: 100%;
|
|
294
|
+
height: auto !important;
|
|
295
|
+
white-space: normal;
|
|
296
|
+
padding: 3px 20px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.chip-text {
|
|
300
|
+
word-break: break-word;
|
|
301
|
+
white-space: normal;
|
|
302
|
+
line-height: 1.2;
|
|
303
|
+
}
|
|
304
|
+
</style>
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
size="small"
|
|
149
149
|
color="blue"
|
|
150
150
|
class="cursor-pointer"
|
|
151
|
-
@click="
|
|
151
|
+
@click="toggleNRICComplainant"
|
|
152
152
|
>
|
|
153
153
|
{{ showNRICComplainant ? "mdi-eye-off" : "mdi-eye" }}
|
|
154
154
|
</v-icon>
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
size="small"
|
|
191
191
|
color="blue"
|
|
192
192
|
class="cursor-pointer"
|
|
193
|
-
@click="
|
|
193
|
+
@click="toggleNRICRecipient"
|
|
194
194
|
>
|
|
195
195
|
{{ showNRICRecipient ? "mdi-eye-off" : "mdi-eye" }}
|
|
196
196
|
</v-icon>
|
|
@@ -262,6 +262,8 @@ const props = defineProps({
|
|
|
262
262
|
},
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
const emit = defineEmits(["showNRICComplainant", "showNRICRecipient"]);
|
|
266
|
+
|
|
265
267
|
// utilities
|
|
266
268
|
const { getSiteById } = useSiteSettings();
|
|
267
269
|
|
|
@@ -293,6 +295,16 @@ function toLocalDate(utcString: string) {
|
|
|
293
295
|
});
|
|
294
296
|
}
|
|
295
297
|
|
|
298
|
+
const toggleNRICComplainant = () => {
|
|
299
|
+
showNRICComplainant.value = !showNRICComplainant.value;
|
|
300
|
+
emit("showNRICComplainant", showNRICComplainant.value);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const toggleNRICRecipient = () => {
|
|
304
|
+
showNRICRecipient.value = !showNRICRecipient.value;
|
|
305
|
+
emit("showNRICRecipient", showNRICRecipient.value);
|
|
306
|
+
};
|
|
307
|
+
|
|
296
308
|
const maskNRIC = (value: any) => {
|
|
297
309
|
if (!value) return "NA";
|
|
298
310
|
|
|
@@ -98,15 +98,11 @@
|
|
|
98
98
|
<v-col cols="4" class="pa-3 border-b">
|
|
99
99
|
<p class="font-weight-bold">NRIC/WP No.</p>
|
|
100
100
|
<p>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@click="showNRICComplainant = !showNRICComplainant"
|
|
107
|
-
>
|
|
108
|
-
{{ showNRICComplainant ? "mdi-eye-off" : "mdi-eye" }}
|
|
109
|
-
</v-icon>
|
|
101
|
+
{{
|
|
102
|
+
showNRICComplainant
|
|
103
|
+
? incidentInformation?.complaintInfo?.nric || "-"
|
|
104
|
+
: maskNRIC(incidentInformation?.complaintInfo?.nric)
|
|
105
|
+
}}
|
|
110
106
|
</p>
|
|
111
107
|
</v-col>
|
|
112
108
|
|
|
@@ -175,6 +171,7 @@ const props = defineProps({
|
|
|
175
171
|
|
|
176
172
|
// utilities
|
|
177
173
|
const { getSiteById } = useSiteSettings();
|
|
174
|
+
const route = useRoute();
|
|
178
175
|
|
|
179
176
|
// state
|
|
180
177
|
const siteName = ref("");
|
|
@@ -184,6 +181,22 @@ const siteId = computed(() => props.incidentInformation?.siteInfo?.site);
|
|
|
184
181
|
const showNRICComplainant = ref(false);
|
|
185
182
|
const showNRICRecipient = ref(false);
|
|
186
183
|
|
|
184
|
+
watch(
|
|
185
|
+
() => route.query.nricComp,
|
|
186
|
+
(val) => {
|
|
187
|
+
showNRICComplainant.value = val === "true";
|
|
188
|
+
},
|
|
189
|
+
{ immediate: true }
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
watch(
|
|
193
|
+
() => route.query.nricRec,
|
|
194
|
+
(val) => {
|
|
195
|
+
showNRICRecipient.value = val === "true";
|
|
196
|
+
},
|
|
197
|
+
{ immediate: true }
|
|
198
|
+
);
|
|
199
|
+
|
|
187
200
|
watch(
|
|
188
201
|
siteId,
|
|
189
202
|
async (newSiteId) => {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<div class="d-flex align-center">
|
|
28
28
|
NRIC
|
|
29
29
|
<v-icon
|
|
30
|
+
v-if="isShowEyeIcon"
|
|
30
31
|
class="cursor-pointer ml-1"
|
|
31
32
|
size="18"
|
|
32
33
|
color="blue"
|
|
@@ -90,6 +91,10 @@ const props = defineProps({
|
|
|
90
91
|
type: Object as PropType<Record<string, any> | null>,
|
|
91
92
|
required: true,
|
|
92
93
|
},
|
|
94
|
+
isShowEyeIcon: {
|
|
95
|
+
type: Boolean,
|
|
96
|
+
required: true,
|
|
97
|
+
},
|
|
93
98
|
});
|
|
94
99
|
|
|
95
100
|
// emits
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog max-width="700" v-model="isDialogVisible" persistent>
|
|
3
|
+
<v-card>
|
|
4
|
+
<v-toolbar>
|
|
5
|
+
<v-toolbar-title>Signature </v-toolbar-title>
|
|
6
|
+
<v-spacer />
|
|
7
|
+
<v-btn icon="mdi-close" @click="hideModal"></v-btn>
|
|
8
|
+
</v-toolbar>
|
|
9
|
+
<v-card-text>
|
|
10
|
+
<v-row no-gutters>
|
|
11
|
+
<v-col cols="12">
|
|
12
|
+
<div class="text-subtitle-1 text-medium-emphasis ml-2">
|
|
13
|
+
Your signature here
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<NuxtSignaturePad
|
|
17
|
+
ref="signature"
|
|
18
|
+
:options="state.option"
|
|
19
|
+
:width="'100%'"
|
|
20
|
+
:height="'400px'"
|
|
21
|
+
:disabled="state.disabled"
|
|
22
|
+
class="border"
|
|
23
|
+
/>
|
|
24
|
+
</v-col>
|
|
25
|
+
|
|
26
|
+
<v-col cols="12" class="text-center">
|
|
27
|
+
<v-row class="d-flex">
|
|
28
|
+
<v-col class="w-50 px-3">
|
|
29
|
+
<v-btn
|
|
30
|
+
text="clear"
|
|
31
|
+
color="warning"
|
|
32
|
+
type="submit"
|
|
33
|
+
class="my-4 w-100 rounded-lg"
|
|
34
|
+
height="40px"
|
|
35
|
+
@click="clear()"
|
|
36
|
+
/>
|
|
37
|
+
</v-col>
|
|
38
|
+
|
|
39
|
+
<v-col class="w-50 px-3">
|
|
40
|
+
<v-btn
|
|
41
|
+
text="submit"
|
|
42
|
+
color="#1867C0"
|
|
43
|
+
type="submit"
|
|
44
|
+
class="my-4 w-100 rounded-lg"
|
|
45
|
+
height="40px"
|
|
46
|
+
:loading="loading"
|
|
47
|
+
@click="submit"
|
|
48
|
+
:disabled="loading"
|
|
49
|
+
/>
|
|
50
|
+
</v-col>
|
|
51
|
+
</v-row>
|
|
52
|
+
</v-col>
|
|
53
|
+
</v-row>
|
|
54
|
+
</v-card-text>
|
|
55
|
+
</v-card>
|
|
56
|
+
</v-dialog>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup lang="ts">
|
|
60
|
+
const loading = ref(false);
|
|
61
|
+
// const { isValid } = useAudit();
|
|
62
|
+
// const { uiRequiredInput, uiSetSnackbar } = useUtils();
|
|
63
|
+
|
|
64
|
+
const message = ref("");
|
|
65
|
+
const messageColor = ref("");
|
|
66
|
+
const messageSnackbar = ref(false);
|
|
67
|
+
|
|
68
|
+
function showMessage(msg: string, color: string) {
|
|
69
|
+
message.value = msg;
|
|
70
|
+
messageColor.value = color;
|
|
71
|
+
messageSnackbar.value = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const signature = ref(null);
|
|
75
|
+
const state = ref({
|
|
76
|
+
count: 0,
|
|
77
|
+
option: {
|
|
78
|
+
penColor: "rgb(0, 0, 0)",
|
|
79
|
+
backgroundColor: "rgb(255,255,255)",
|
|
80
|
+
},
|
|
81
|
+
disabled: false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const emit = defineEmits<{
|
|
85
|
+
(event: "onSubmit", payload: string): void;
|
|
86
|
+
(event: "onCloseDialog"): void;
|
|
87
|
+
}>();
|
|
88
|
+
let props = defineProps({
|
|
89
|
+
isShowDialog: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: false,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const isDialogVisible = computed(() => props.isShowDialog);
|
|
96
|
+
|
|
97
|
+
const hideModal = () => {
|
|
98
|
+
emit("onCloseDialog");
|
|
99
|
+
};
|
|
100
|
+
const file = ref<File | null>(null);
|
|
101
|
+
const { addFile } = useFile();
|
|
102
|
+
const submit = async () => {
|
|
103
|
+
try {
|
|
104
|
+
loading.value = true;
|
|
105
|
+
const base64 = signature.value.saveSignature();
|
|
106
|
+
const blob = await (await fetch(base64)).blob();
|
|
107
|
+
|
|
108
|
+
file.value = new File([blob], "signature.jpg", { type: "image/jpeg" });
|
|
109
|
+
|
|
110
|
+
const uploadItem = {
|
|
111
|
+
data: file.value,
|
|
112
|
+
name: file.value.name,
|
|
113
|
+
url: URL.createObjectURL(file.value),
|
|
114
|
+
progress: 0,
|
|
115
|
+
type: file.value.type,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const response = await addFile(uploadItem.data);
|
|
119
|
+
|
|
120
|
+
if (response && response.length > 0) {
|
|
121
|
+
emit("onSubmit", response[0]._id);
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
showMessage("Error uploading signature. Please try again.", "error");
|
|
125
|
+
} finally {
|
|
126
|
+
loading.value = false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const clear = () => {
|
|
131
|
+
signature.value.clearCanvas();
|
|
132
|
+
};
|
|
133
|
+
</script>
|