@7365admin1/layer-common 1.10.7 → 1.10.9
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 +1 -1
- package/components/AccessManagement.vue +1 -1
- package/components/{AddSupplyForm.vue → AddEqupmentForm.vue} +5 -5
- package/components/BuildingManagement/units.vue +2 -2
- package/components/BuildingUnitFormAdd.vue +4 -4
- package/components/BuildingUnitFormEdit.vue +114 -68
- package/components/Carousel.vue +474 -0
- package/components/DrawImage.vue +172 -0
- package/components/EntryPassInformation.vue +283 -29
- package/components/{CheckoutItemMain.vue → EquipmentItemMain.vue} +95 -87
- package/components/{SupplyManagement.vue → EquipmentManagement.vue} +3 -3
- package/components/Feedback/Form.vue +4 -4
- package/components/FeedbackMain.vue +748 -145
- package/components/FileInput.vue +289 -0
- package/components/Input/DateTimePicker.vue +17 -11
- package/components/ManageChecklistMain.vue +379 -41
- package/components/StockCard.vue +11 -7
- package/components/TableHygiene.vue +42 -452
- package/components/UnitPersonCard.vue +74 -14
- package/components/VisitorForm.vue +193 -52
- package/components/VisitorFormSelection.vue +13 -2
- package/components/VisitorManagement.vue +83 -55
- package/composables/useAccessManagement.ts +41 -18
- package/composables/useCleaningPermission.ts +7 -7
- package/composables/useDashboardData.ts +2 -2
- package/composables/useEquipment.ts +63 -0
- package/composables/{useCheckout.ts → useEquipmentItem.ts} +7 -7
- package/composables/{useCheckoutPermission.ts → useEquipmentItemPermission.ts} +13 -13
- package/composables/{useSupply.ts → useEquipmentManagement.ts} +1 -1
- package/composables/useEquipmentManagementPermission.ts +96 -0
- package/composables/{useSupplyPermission.ts → useEquipmentPermission.ts} +9 -9
- package/composables/useFeedback.ts +53 -21
- package/composables/useLocalAuth.ts +29 -1
- package/composables/useUploadFiles.ts +94 -0
- package/composables/useUtils.ts +152 -53
- package/composables/useVehicle.ts +21 -2
- package/composables/useVisitor.ts +9 -7
- package/composables/useWorkOrder.ts +25 -3
- package/package.json +2 -1
- package/types/building.d.ts +1 -1
- package/types/{checkout-item.d.ts → equipment-item.d.ts} +3 -3
- package/types/{supply.d.ts → equipment.d.ts} +2 -2
- package/types/feedback.d.ts +5 -2
- package/types/people.d.ts +3 -1
- package/types/user.d.ts +1 -0
- package/types/vehicle.d.ts +2 -0
- package/types/visitor.d.ts +2 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-file-input
|
|
4
|
+
v-model="files"
|
|
5
|
+
:label="label"
|
|
6
|
+
accept="image/*"
|
|
7
|
+
:prepend-icon="prependIcon"
|
|
8
|
+
:loading="isFileUploading"
|
|
9
|
+
hide-details
|
|
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
|
+
<!-- Camera Dialog -->
|
|
27
|
+
<v-dialog
|
|
28
|
+
v-model="showCameraDialog"
|
|
29
|
+
transition="dialog-bottom-transition"
|
|
30
|
+
width="800"
|
|
31
|
+
max-width="800"
|
|
32
|
+
persistent
|
|
33
|
+
@after-enter="startCamera"
|
|
34
|
+
>
|
|
35
|
+
<v-container
|
|
36
|
+
class="d-flex justify-center"
|
|
37
|
+
max-height="90vh"
|
|
38
|
+
width="800"
|
|
39
|
+
max-width="800"
|
|
40
|
+
>
|
|
41
|
+
<v-card elevation="2" class="d-flex flex-column align-center pa-2">
|
|
42
|
+
<v-toolbar>
|
|
43
|
+
<v-card-title class="text-h5">Take a Picture</v-card-title>
|
|
44
|
+
<v-spacer></v-spacer>
|
|
45
|
+
<v-btn
|
|
46
|
+
color="grey-darken-1"
|
|
47
|
+
icon="mdi-close"
|
|
48
|
+
@click="closeCameraDialog"
|
|
49
|
+
></v-btn>
|
|
50
|
+
</v-toolbar>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
id="reader"
|
|
54
|
+
class="d-flex justify-center align-center"
|
|
55
|
+
style="
|
|
56
|
+
position: relative;
|
|
57
|
+
width: 500px;
|
|
58
|
+
min-width: 400px;
|
|
59
|
+
height: 400px;
|
|
60
|
+
"
|
|
61
|
+
>
|
|
62
|
+
<video
|
|
63
|
+
ref="video"
|
|
64
|
+
style="flex: 1; height: 400px; min-width: 300px"
|
|
65
|
+
class="video-shutter"
|
|
66
|
+
autoplay
|
|
67
|
+
></video>
|
|
68
|
+
<canvas
|
|
69
|
+
ref="canvas"
|
|
70
|
+
style="flex: 1; height: 400px; min-width: 300px; display: none"
|
|
71
|
+
></canvas>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<v-row align="center" justify="center">
|
|
75
|
+
<v-col cols="6">
|
|
76
|
+
<v-btn color="primary" icon class="mt-4" @click="switchCamera">
|
|
77
|
+
<v-icon>mdi-camera-switch</v-icon>
|
|
78
|
+
</v-btn>
|
|
79
|
+
</v-col>
|
|
80
|
+
<v-col cols="6">
|
|
81
|
+
<v-btn
|
|
82
|
+
color="secondary"
|
|
83
|
+
icon
|
|
84
|
+
class="mt-4"
|
|
85
|
+
@click="captureImageFromCamera"
|
|
86
|
+
>
|
|
87
|
+
<v-icon large>mdi-camera-outline</v-icon>
|
|
88
|
+
</v-btn>
|
|
89
|
+
</v-col>
|
|
90
|
+
</v-row>
|
|
91
|
+
</v-card>
|
|
92
|
+
</v-container>
|
|
93
|
+
</v-dialog>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup lang="ts">
|
|
100
|
+
interface FileWithPreview {
|
|
101
|
+
name: string;
|
|
102
|
+
data: File;
|
|
103
|
+
progress: number;
|
|
104
|
+
url: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const props = defineProps({
|
|
108
|
+
label: {
|
|
109
|
+
type: String,
|
|
110
|
+
default: "Select File",
|
|
111
|
+
},
|
|
112
|
+
prependIcon: {
|
|
113
|
+
type: String,
|
|
114
|
+
default: "mdi-paperclip",
|
|
115
|
+
},
|
|
116
|
+
required: {
|
|
117
|
+
type: Boolean,
|
|
118
|
+
default: true,
|
|
119
|
+
},
|
|
120
|
+
initFiles: {
|
|
121
|
+
type: Array,
|
|
122
|
+
},
|
|
123
|
+
hasLabel: {
|
|
124
|
+
type: Boolean,
|
|
125
|
+
default: true,
|
|
126
|
+
},
|
|
127
|
+
hasHideInput: {
|
|
128
|
+
type: Boolean,
|
|
129
|
+
default: false,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const emit = defineEmits<{
|
|
134
|
+
(event: "onFileAttach", payload: Array<{ data: File }>): void;
|
|
135
|
+
(event: "update:files", files: FileWithPreview[]): void;
|
|
136
|
+
(event: "onFileRemoved", payload: { index: number; file: File }): void;
|
|
137
|
+
(event: "onClear"): void;
|
|
138
|
+
}>();
|
|
139
|
+
|
|
140
|
+
const { showUploadedFiles } = useUploadFiles();
|
|
141
|
+
const isFileUploading = ref<boolean>(false);
|
|
142
|
+
|
|
143
|
+
const files = ref<File[]>([]);
|
|
144
|
+
const showCameraDialog = ref(false);
|
|
145
|
+
const video = ref<HTMLVideoElement | null>(null);
|
|
146
|
+
const canvas = ref<HTMLCanvasElement | null>(null);
|
|
147
|
+
const cameraFacingMode = ref<"environment" | "user">("environment");
|
|
148
|
+
|
|
149
|
+
const message = ref("");
|
|
150
|
+
const messageColor = ref("");
|
|
151
|
+
const messageSnackbar = ref(false);
|
|
152
|
+
|
|
153
|
+
function showMessage(msg: string, color: string) {
|
|
154
|
+
message.value = msg;
|
|
155
|
+
messageColor.value = color;
|
|
156
|
+
messageSnackbar.value = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
watchEffect(() => {
|
|
160
|
+
files.value = [...(props.initFiles as typeof files.value)];
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const handleFileSelect = async () => {
|
|
164
|
+
if (files.value && files.value.length > 0) {
|
|
165
|
+
const newFiles = files.value.map((file: File) => ({
|
|
166
|
+
name: file.name,
|
|
167
|
+
data: file,
|
|
168
|
+
progress: 0,
|
|
169
|
+
url: URL.createObjectURL(file),
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
// attachedFiles.value = [...newFiles];
|
|
173
|
+
showUploadedFiles(newFiles);
|
|
174
|
+
|
|
175
|
+
emit("update:files", newFiles);
|
|
176
|
+
} else {
|
|
177
|
+
files.value = [...(props.initFiles as typeof files.value)];
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleClear = () => {
|
|
182
|
+
files.value = [];
|
|
183
|
+
emit("onClear");
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const openCameraDialog = () => {
|
|
187
|
+
showCameraDialog.value = true;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const closeCameraDialog = () => {
|
|
191
|
+
showCameraDialog.value = false;
|
|
192
|
+
stopCamera();
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const startCamera = async () => {
|
|
196
|
+
try {
|
|
197
|
+
const constraints = {
|
|
198
|
+
video: {
|
|
199
|
+
facingMode: cameraFacingMode.value,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
204
|
+
if (video.value) {
|
|
205
|
+
video.value.srcObject = stream;
|
|
206
|
+
video.value.play();
|
|
207
|
+
}
|
|
208
|
+
} catch (error: any) {
|
|
209
|
+
showMessage(error, "error");
|
|
210
|
+
closeCameraDialog();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const stopCamera = () => {
|
|
215
|
+
if (video.value) {
|
|
216
|
+
const stream = video.value.srcObject as MediaStream;
|
|
217
|
+
if (stream) {
|
|
218
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const switchCamera = async () => {
|
|
224
|
+
await stopCamera();
|
|
225
|
+
cameraFacingMode.value =
|
|
226
|
+
cameraFacingMode.value === "environment" ? "user" : "environment";
|
|
227
|
+
showMessage(
|
|
228
|
+
`Switched to ${
|
|
229
|
+
cameraFacingMode.value === "environment" ? "Back Camera" : "Front Camera"
|
|
230
|
+
}`,
|
|
231
|
+
"error"
|
|
232
|
+
);
|
|
233
|
+
startCamera();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const captureImageFromCamera = () => {
|
|
237
|
+
if (!video.value || !canvas.value) return;
|
|
238
|
+
|
|
239
|
+
const context = canvas.value.getContext("2d");
|
|
240
|
+
if (!context) return;
|
|
241
|
+
|
|
242
|
+
// Set canvas dimensions to match video
|
|
243
|
+
canvas.value.width = video.value.videoWidth;
|
|
244
|
+
canvas.value.height = video.value.videoHeight;
|
|
245
|
+
|
|
246
|
+
// Capture the frame
|
|
247
|
+
context.drawImage(video.value, 0, 0, canvas.value.width, canvas.value.height);
|
|
248
|
+
|
|
249
|
+
// Convert to file
|
|
250
|
+
canvas.value.toBlob((blob) => {
|
|
251
|
+
if (!blob) return;
|
|
252
|
+
|
|
253
|
+
const file = new File([blob], `camera-capture-${Date.now()}.png`, {
|
|
254
|
+
type: "image/png",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
files.value = [file];
|
|
258
|
+
handleFileSelect();
|
|
259
|
+
closeCameraDialog();
|
|
260
|
+
}, "image/png");
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const removeFile = (index) => {
|
|
264
|
+
const removedFile = files.value[index];
|
|
265
|
+
files.value = files.value.filter((_, i) => i !== index);
|
|
266
|
+
emit("onFileRemoved", { index, file: removedFile }); // Emit when a file is removed
|
|
267
|
+
// emit("onFilesUpdated", files.value); // Emit updated files list
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// Cleanup
|
|
271
|
+
onUnmounted(() => {
|
|
272
|
+
stopCamera();
|
|
273
|
+
});
|
|
274
|
+
</script>
|
|
275
|
+
|
|
276
|
+
<style scoped>
|
|
277
|
+
.custom-chip {
|
|
278
|
+
max-width: 100%;
|
|
279
|
+
height: auto !important;
|
|
280
|
+
white-space: normal;
|
|
281
|
+
padding: 3px 20px;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.chip-text {
|
|
285
|
+
word-break: break-word;
|
|
286
|
+
white-space: normal;
|
|
287
|
+
line-height: 1.2;
|
|
288
|
+
}
|
|
289
|
+
</style>
|
|
@@ -22,7 +22,7 @@ const prop = defineProps({
|
|
|
22
22
|
},
|
|
23
23
|
placeholder: {
|
|
24
24
|
type: String,
|
|
25
|
-
default: 'MM/
|
|
25
|
+
default: 'DD/MM/YYYY, HH:MM AM/PM'
|
|
26
26
|
}
|
|
27
27
|
})
|
|
28
28
|
|
|
@@ -53,17 +53,23 @@ function validate() {
|
|
|
53
53
|
function convertToReadableFormat(dateStr: string): string {
|
|
54
54
|
|
|
55
55
|
if (!dateStr) return "";
|
|
56
|
+
|
|
56
57
|
const date = new Date(dateStr)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
58
|
+
|
|
59
|
+
const day = String(date.getDate()).padStart(2, '0')
|
|
60
|
+
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
61
|
+
const year = date.getFullYear()
|
|
62
|
+
|
|
63
|
+
let hours = date.getHours()
|
|
64
|
+
const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
65
|
+
|
|
66
|
+
const ampm = hours >= 12 ? 'PM' : 'AM'
|
|
67
|
+
hours = hours % 12
|
|
68
|
+
hours = hours ? hours : 12
|
|
69
|
+
|
|
70
|
+
const formattedTime = `${String(hours).padStart(2, '0')}:${minutes} ${ampm}`
|
|
71
|
+
|
|
72
|
+
return `${day}/${month}/${year}, ${formattedTime}`
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
function handleInitialDate(){
|