@7365admin1/layer-common 1.10.8 → 1.10.10

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AccessCardAddForm.vue +1 -1
  3. package/components/AccessCardAssignToUnitForm.vue +1 -1
  4. package/components/AccessManagement.vue +1 -1
  5. package/components/BulletinBoardManagement.vue +18 -8
  6. package/components/Carousel.vue +474 -0
  7. package/components/DeliveryCompany.vue +240 -0
  8. package/components/DrawImage.vue +172 -0
  9. package/components/EntryPassInformation.vue +70 -10
  10. package/components/EquipmentItemMain.vue +9 -4
  11. package/components/Feedback/Form.vue +4 -4
  12. package/components/FeedbackMain.vue +734 -146
  13. package/components/FileInput.vue +289 -0
  14. package/components/IncidentReport/Authorities.vue +189 -151
  15. package/components/IncidentReport/IncidentInformation.vue +14 -10
  16. package/components/IncidentReport/IncidentInformationDownload.vue +212 -0
  17. package/components/IncidentReport/affectedEntities.vue +8 -57
  18. package/components/SiteSettings.vue +285 -0
  19. package/components/StockCard.vue +11 -7
  20. package/components/Tooltip/Info.vue +33 -0
  21. package/components/VisitorForm.vue +176 -45
  22. package/components/VisitorManagement.vue +23 -6
  23. package/composables/useAccessManagement.ts +60 -18
  24. package/composables/useBulletin.ts +8 -3
  25. package/composables/useBulletinBoardPermission.ts +48 -0
  26. package/composables/useCleaningPermission.ts +2 -0
  27. package/composables/useCommonPermission.ts +29 -1
  28. package/composables/useEquipmentManagement.ts +63 -0
  29. package/composables/useFeedback.ts +53 -21
  30. package/composables/useFile.ts +6 -0
  31. package/composables/useLocalAuth.ts +29 -1
  32. package/composables/useSiteSettings.ts +1 -1
  33. package/composables/useUploadFiles.ts +94 -0
  34. package/composables/useUtils.ts +152 -53
  35. package/composables/useVisitor.ts +9 -6
  36. package/constants/app.ts +12 -0
  37. package/nuxt.config.ts +2 -0
  38. package/package.json +3 -1
  39. package/plugins/vue-draggable-next.client.ts +5 -0
  40. package/types/feedback.d.ts +5 -2
  41. package/types/site.d.ts +2 -1
  42. package/types/user.d.ts +1 -0
@@ -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>
@@ -25,158 +25,178 @@
25
25
  >
26
26
  </v-data-table>
27
27
  </v-col>
28
+ <div class="w-100 my-8 mx-2">
29
+ <v-table class="responsive-table border elevation-0">
30
+ <thead>
31
+ <tr>
32
+ <th
33
+ v-for="(item, index) in concernHeader"
34
+ :key="index"
35
+ class="text-left font-weight-bold text-black border-e"
36
+ >
37
+ {{ item }}
38
+ </th>
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ <!-- 1 -->
43
+ <tr>
44
+ <td class="border-e">1.</td>
45
+ <td class="border-e">
46
+ What was done to address the incident thereafter?
47
+ </td>
48
+ <td class="border-e pa-2">
49
+ {{ authorities?.incidentThereAfter.actionTaken }}
50
+ </td>
51
+ <td class="border-e pa-2">
52
+ {{ authorities?.incidentThereAfter.status }}
53
+ </td>
54
+ <td class="pa-2">
55
+ {{ authorities?.incidentThereAfter.time }}
56
+ </td>
57
+ </tr>
28
58
 
29
- <!-- Input lists -->
30
- <v-col cols="12" class="pb-3 mb-5 mt-1">
31
- <v-row no-gutters>
32
- <v-col cols="12" class="px-1 mb-7">
33
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
34
- What was done to address the incident thereafter?
35
- </p>
36
- <p class="mt-2 text-h6 text-capitalize">
37
- {{ authorities?.incidentThereAfter }}
38
- </p>
39
- </v-col>
40
-
41
- <v-col cols="12" class="px-1 mb-7">
42
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
43
- Were the management notified and who was notified?
44
- </p>
45
- <v-row no-gutters>
46
- <v-col cols="12" sm="6" class="px-1">
47
- <InputLabel class="text-capitalize" title="Action Taken" />
48
- <p class="mt-2 text-h6 text-capitalize">
49
- {{ authorities?.managementNotified?.actionTaken }}
50
- </p>
51
- </v-col>
52
- <v-col cols="12" sm="6" class="px-1">
53
- <InputLabel
54
- class="text-capitalize"
55
- title="Management Notified Time"
56
- />
57
- <p class="mt-2 text-h6 text-capitalize">
58
- {{ authorities?.managementNotified?.time }}
59
- </p>
60
- </v-col>
61
- </v-row>
62
- </v-col>
63
-
64
- <v-col cols="12" class="px-1 mb-7">
65
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
66
- How the incident resolved?
67
- </p>
68
- <p class="mt-2 text-h6 text-capitalize">
69
- {{ authorities?.incidentResolved }}
70
- </p>
71
- </v-col>
72
-
73
- <v-col cols="12" class="px-1 mb-7">
74
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
75
- What was the cause of the incident?
76
- </p>
77
- <p class="mt-2 text-h6 text-capitalize">
78
- {{ authorities?.causeOfIncident }}
79
- </p>
80
- </v-col>
81
-
82
- <v-col cols="12" class="px-1 mb-6">
83
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
84
- Any system used to verify incident?
85
- </p>
86
- <p class="mt-2 text-h6 text-capitalize">
87
- {{ authorities?.systemUsed }}
88
- </p>
89
- </v-col>
90
-
91
- <v-col cols="12" class="px-1 mb-6">
92
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
93
- Any cctv records or picture taken?
94
- </p>
95
- <p class="mt-2 text-h6 text-capitalize">
96
- {{ authorities?.cctvRecord }}
97
- </p>
98
- </v-col>
99
-
100
- <v-col cols="12" class="px-1 mb-6">
101
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
102
- Particulars of tenant/Owner (if any)
103
- </p>
104
- <p class="mt-2 text-h6 text-capitalize">
105
- {{ authorities?.particularsOwner }}
106
- </p>
107
- </v-col>
108
-
109
- <v-col cols="12" class="px-1 mb-6">
110
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
111
- When was the incident resolved?
112
- </p>
113
- <v-row no-gutters>
114
- <v-col cols="12" sm="6" class="px-1">
115
- <InputLabel class="text-capitalize" title="Action Taken" />
116
- <p class="mt-2 text-h6 text-capitalize">
117
- {{ authorities?.whenIncidentResolve?.actionTaken }}
118
- </p>
119
- </v-col>
120
- <v-col cols="12" sm="6" class="px-1">
121
- <InputLabel
122
- class="text-capitalize"
123
- title="Incident Resolve Time"
124
- />
125
- <p class="mt-2 text-h6 text-capitalize">
126
- {{ authorities?.whenIncidentResolve?.time }}
127
- </p>
128
- </v-col>
129
- </v-row>
130
- </v-col>
131
-
132
- <v-col cols="12" class="px-1 mb-6">
133
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
134
- Name of shift in charge?
135
- </p>
136
- <v-row no-gutters>
137
- <v-col cols="12" sm="6" class="px-1">
138
- <InputLabel class="text-capitalize" title="Person in Charge" />
139
- <p class="mt-2 text-h6 text-capitalize">
140
- {{ authorities?.nameOfShiftIncharge?.personInCharge }}
141
- </p>
142
- </v-col>
143
- <v-col cols="12" sm="6" class="px-1">
144
- <InputLabel class="text-capitalize" title="Action Taken" />
145
- <p class="mt-2 text-h6 text-capitalize">
146
- {{ authorities?.nameOfShiftIncharge?.actionTaken }}
147
- </p>
148
- </v-col>
149
- </v-row>
150
- </v-col>
151
-
152
- <v-col cols="12" class="px-1 mb-6">
153
- <p class="mb-2" style="font-size: 17px; font-weight: 600"></p>
154
- <v-row no-gutters>
155
- <v-col cols="12" sm="6" class="px-1">
156
- <InputLabel class="text-capitalize" title="Shift Start" />
157
- <p class="mt-2 text-h6 text-capitalize">
158
- {{ authorities?.nameOfShiftIncharge?.shiftStart }}
159
- </p>
160
- </v-col>
161
- <v-col cols="12" sm="6" class="px-1">
162
- <InputLabel class="text-capitalize" title="Shift End" />
163
- <p class="mt-2 text-h6 text-capitalize">
164
- {{ authorities?.nameOfShiftIncharge?.shiftEnd }}
165
- </p>
166
- </v-col>
167
- </v-row>
168
- </v-col>
169
-
170
- <v-col cols="12" class="px-1">
171
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
172
- Any security implication due to the incident?
173
- </p>
174
- <p class="mt-2 text-h6 text-capitalize">
175
- {{ authorities?.securityImplication }}
176
- </p>
177
- </v-col>
178
- </v-row>
179
- </v-col>
59
+ <!-- 2 -->
60
+ <tr>
61
+ <td class="border-e">2.</td>
62
+ <td class="border-e">
63
+ Were the management notified and who was notified?
64
+ </td>
65
+ <td class="border-e pa-2">
66
+ {{ authorities?.managementNotified.actionTaken }}
67
+ </td>
68
+ <td class="border-e pa-2">
69
+ {{ authorities?.managementNotified.status }}
70
+ </td>
71
+ <td class="pa-2">
72
+ {{ authorities?.managementNotified.time }}
73
+ </td>
74
+ </tr>
75
+
76
+ <!-- 3 -->
77
+ <tr>
78
+ <td class="border-e">3.</td>
79
+ <td class="border-e">How was the incident resolved?</td>
80
+ <td class="border-e pa-2">
81
+ {{ authorities?.incidentResolved.actionTaken }}
82
+ </td>
83
+ <td class="border-e pa-2">
84
+ {{ authorities?.incidentResolved.status }}
85
+ </td>
86
+ <td class="pa-2">
87
+ {{ formatDate(authorities?.incidentResolved.time) }}
88
+ </td>
89
+ </tr>
90
+
91
+ <!-- 4 -->
92
+ <tr>
93
+ <td class="border-e">4.</td>
94
+ <td class="border-e">What was the cause of the incident?</td>
95
+ <td class="border-e pa-2">
96
+ {{ authorities?.causeOfIncident.actionTaken }}
97
+ </td>
98
+ <td class="border-e pa-2">
99
+ {{ authorities?.causeOfIncident.status }}
100
+ </td>
101
+ <td class="pa-2">
102
+ {{ authorities?.causeOfIncident.time }}
103
+ </td>
104
+ </tr>
105
+
106
+ <!-- 5 -->
107
+ <tr>
108
+ <td class="border-e">5.</td>
109
+ <td class="border-e">Any systems used to verify the incident?</td>
110
+ <td class="border-e pa-2">
111
+ {{ authorities?.systemUsed.actionTaken }}
112
+ </td>
113
+ <td class="border-e pa-2">
114
+ {{ authorities?.systemUsed.status }}
115
+ </td>
116
+ <td class="pa-2">
117
+ {{ formatDate(authorities?.systemUsed.time) }}
118
+ </td>
119
+ </tr>
120
+
121
+ <!-- 6 -->
122
+ <tr>
123
+ <td class="border-e">6.</td>
124
+ <td class="border-e">Any CCTV records or pictures taken?</td>
125
+ <td class="border-e pa-2">
126
+ {{ authorities?.cctvRecord.actionTaken }}
127
+ </td>
128
+ <td class="border-e pa-2">
129
+ {{ authorities?.cctvRecord.status }}
130
+ </td>
131
+ <td class="pa-2">
132
+ {{ formatDate(authorities?.cctvRecord.time) }}
133
+ </td>
134
+ </tr>
135
+
136
+ <!-- 7 -->
137
+ <tr>
138
+ <td class="border-e">7.</td>
139
+ <td class="border-e">Particulars of tenant / owner (if any)</td>
140
+ <td class="border-e pa-2">
141
+ {{ authorities?.particularsOwner.actionTaken }}
142
+ </td>
143
+ <td class="border-e pa-2">
144
+ {{ authorities?.particularsOwner.status }}
145
+ </td>
146
+ <td class="pa-2">
147
+ {{ authorities?.particularsOwner.time }}
148
+ </td>
149
+ </tr>
150
+
151
+ <!-- 8 -->
152
+ <tr>
153
+ <td class="border-e">8.</td>
154
+ <td class="border-e">When was the incident resolved?</td>
155
+ <td class="border-e pa-2">
156
+ {{ authorities?.whenIncidentResolve.actionTaken }}
157
+ </td>
158
+ <td class="border-e pa-2">
159
+ {{ authorities?.whenIncidentResolve.status }}
160
+ </td>
161
+ <td class="pa-2">
162
+ {{ authorities?.whenIncidentResolve.time }}
163
+ </td>
164
+ </tr>
165
+
166
+ <!-- 9 -->
167
+ <tr>
168
+ <td class="border-e">9.</td>
169
+ <td class="border-e">Name of Shift In charge</td>
170
+ <td class="border-e pa-2">
171
+ {{ authorities?.nameOfShiftIncharge.actionTaken }}
172
+ </td>
173
+ <td class="border-e pa-2">
174
+ {{ authorities?.nameOfShiftIncharge.status }}
175
+ </td>
176
+ <td class="pa-2">
177
+ {{ authorities?.nameOfShiftIncharge.time }}
178
+ </td>
179
+ </tr>
180
+
181
+ <!-- 10 -->
182
+ <tr>
183
+ <td class="border-e">10.</td>
184
+ <td class="border-e">
185
+ Any security implication due to the incident?
186
+ </td>
187
+ <td class="border-e pa-2">
188
+ {{ authorities?.securityImplication.actionTaken }}
189
+ </td>
190
+ <td class="border-e pa-2">
191
+ {{ authorities?.securityImplication.status }}
192
+ </td>
193
+ <td class="pa-2">
194
+ {{ formatDate(authorities?.securityImplication.time) }}
195
+ </td>
196
+ </tr>
197
+ </tbody>
198
+ </v-table>
199
+ </div>
180
200
  </v-row>
181
201
  </template>
182
202
 
@@ -223,4 +243,22 @@ const authoritiesTableHeader = [
223
243
  value: "description",
224
244
  },
225
245
  ];
246
+
247
+ const concernHeader = [
248
+ "S/No",
249
+ "Areas of Concern",
250
+ "Actions Taken",
251
+ "Status",
252
+ "Time",
253
+ ];
254
+
255
+ const formatDate = (date: Date | string | null) => {
256
+ if (!date) return "";
257
+
258
+ const d = typeof date === "string" ? new Date(date) : date;
259
+
260
+ if (isNaN(d.getTime())) return ""; // safeguard for invalid date
261
+
262
+ return new Intl.DateTimeFormat("en-GB").format(d);
263
+ };
226
264
  </script>