@7365admin1/layer-common 1.9.0 → 1.10.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AcceptDialog.vue +44 -0
  3. package/components/AccessCardAddForm.vue +101 -13
  4. package/components/AccessManagement.vue +130 -47
  5. package/components/AddSupplyForm.vue +165 -0
  6. package/components/AreaChecklistHistoryLogs.vue +235 -0
  7. package/components/AreaChecklistHistoryMain.vue +176 -0
  8. package/components/AreaFormDialog.vue +266 -0
  9. package/components/AreaMain.vue +841 -0
  10. package/components/AttendanceCheckInOutDialog.vue +416 -0
  11. package/components/AttendanceDetailsDialog.vue +184 -0
  12. package/components/AttendanceMain.vue +155 -0
  13. package/components/AttendanceMapSearchDialog.vue +393 -0
  14. package/components/AttendanceSettingsDialog.vue +398 -0
  15. package/components/BuildingManagement/buildings.vue +5 -5
  16. package/components/BuildingManagement/units.vue +5 -5
  17. package/components/ChecklistItemRow.vue +54 -0
  18. package/components/CheckoutItemMain.vue +705 -0
  19. package/components/CleaningScheduleMain.vue +271 -0
  20. package/components/DocumentManagement.vue +8 -9
  21. package/components/EntryPass/QrTemplatePreview.vue +104 -0
  22. package/components/EntryPassMain.vue +252 -200
  23. package/components/HygieneUpdateMoreAction.vue +238 -0
  24. package/components/IncidentReport/Authorities.vue +226 -0
  25. package/components/IncidentReport/IncidentInformation.vue +258 -0
  26. package/components/IncidentReport/affectedEntities.vue +167 -0
  27. package/components/InvitationMain.vue +19 -17
  28. package/components/ManageChecklistMain.vue +384 -0
  29. package/components/MemberMain.vue +48 -20
  30. package/components/MyAttendanceMain.vue +224 -0
  31. package/components/OnlineFormsConfiguration.vue +9 -2
  32. package/components/PasswordConfirmation.vue +95 -0
  33. package/components/PhotoUpload.vue +410 -0
  34. package/components/RolePermissionMain.vue +17 -15
  35. package/components/ScheduleAreaMain.vue +313 -0
  36. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  37. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  38. package/components/ScheduleTaskForm.vue +471 -0
  39. package/components/ScheduleTaskMain.vue +345 -0
  40. package/components/ScheduleTastTicketMain.vue +182 -0
  41. package/components/ServiceProviderMain.vue +27 -7
  42. package/components/StockCard.vue +191 -0
  43. package/components/SupplyManagementMain.vue +557 -0
  44. package/components/TableHygiene.vue +617 -0
  45. package/components/UnitMain.vue +451 -0
  46. package/components/VisitorManagement.vue +28 -15
  47. package/composables/useAccessManagement.ts +90 -0
  48. package/composables/useAreaPermission.ts +51 -0
  49. package/composables/useAreas.ts +99 -0
  50. package/composables/useAttendance.ts +89 -0
  51. package/composables/useAttendancePermission.ts +68 -0
  52. package/composables/useBuilding.ts +2 -2
  53. package/composables/useBuildingUnit.ts +2 -2
  54. package/composables/useCard.ts +2 -0
  55. package/composables/useCheckout.ts +61 -0
  56. package/composables/useCheckoutPermission.ts +80 -0
  57. package/composables/useCleaningPermission.ts +229 -0
  58. package/composables/useCleaningSchedulePermission.ts +58 -0
  59. package/composables/useCleaningSchedules.ts +233 -0
  60. package/composables/useCountry.ts +8 -0
  61. package/composables/useDOBEntries.ts +13 -0
  62. package/composables/useDashboardData.ts +2 -2
  63. package/composables/useDocument.ts +3 -2
  64. package/composables/useFeedback.ts +1 -1
  65. package/composables/useFile.ts +4 -6
  66. package/composables/useLocation.ts +78 -0
  67. package/composables/useOnlineForm.ts +16 -9
  68. package/composables/usePeople.ts +87 -72
  69. package/composables/useQR.ts +29 -0
  70. package/composables/useRole.ts +3 -2
  71. package/composables/useScheduleTask.ts +89 -0
  72. package/composables/useScheduleTaskArea.ts +85 -0
  73. package/composables/useScheduleTaskPermission.ts +68 -0
  74. package/composables/useSiteEntryPassSettings.ts +4 -15
  75. package/composables/useStock.ts +45 -0
  76. package/composables/useSupply.ts +63 -0
  77. package/composables/useSupplyPermission.ts +92 -0
  78. package/composables/useUnitPermission.ts +51 -0
  79. package/composables/useUnits.ts +82 -0
  80. package/composables/useWebUsb.ts +389 -0
  81. package/composables/useWorkOrder.ts +1 -1
  82. package/nuxt.config.ts +3 -0
  83. package/package.json +4 -1
  84. package/types/area.d.ts +22 -0
  85. package/types/attendance.d.ts +38 -0
  86. package/types/checkout-item.d.ts +27 -0
  87. package/types/cleaner-schedule.d.ts +54 -0
  88. package/types/location.d.ts +42 -0
  89. package/types/schedule-task.d.ts +18 -0
  90. package/types/stock.d.ts +16 -0
  91. package/types/supply.d.ts +11 -0
  92. package/types/verification.d.ts +1 -1
  93. package/utils/acm-crypto.ts +30 -0
@@ -56,8 +56,13 @@
56
56
  hide-default-footer
57
57
  @click:row="tableRowClickHandler"
58
58
  style="max-height: calc(100vh - (200px))"
59
- ></v-data-table> </v-card
60
- ></v-col>
59
+ >
60
+ <template v-slot:item.createdAt="{ item }">
61
+ {{ formatDateDDMMYYYYLocal(item.createdAt) }}
62
+ </template></v-data-table
63
+ >
64
+ </v-card></v-col
65
+ >
61
66
 
62
67
  <!-- Create Dialog -->
63
68
  <v-dialog v-model="createDialog" width="450" persistent>
@@ -306,6 +311,8 @@ const orgId = route.params.org as string;
306
311
 
307
312
  const items = ref<Array<Record<string, any>>>([]);
308
313
 
314
+ const { formatDateDDMMYYYYLocal } = useUtils();
315
+
309
316
  const {
310
317
  data: getOnlineFormConfigurationReq,
311
318
  refresh: getOnlineFormConfigurations,
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <v-card width="100%" :disabled="loading || processingPassword" :loading="loading || processingPassword">
3
+ <v-toolbar density="compact">
4
+ <v-row no-gutters class="pa-3 text-h6 font-weight-bold">
5
+ <span>{{ promptTitle }}</span>
6
+ </v-row>
7
+ </v-toolbar>
8
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5 px-7 text-center">
9
+ <v-row no-gutters class="w-100">
10
+ <v-col cols="12">
11
+ <v-icon icon="mdi-lock-outline" :color="'gray'" class="text-h2" />
12
+ </v-col>
13
+ <v-col cols="12" v-if="message" class="mt-2">
14
+ {{ message }}
15
+ </v-col>
16
+ <v-col cols="12" class="mt-5 text-start">
17
+ <v-text-field v-model="passwordInput" label="Password" type="password" density="compact" :error-messages="errorMessage"></v-text-field>
18
+ </v-col>
19
+ </v-row>
20
+ </v-card-text>
21
+
22
+ <v-toolbar class="pa-0" density="compact">
23
+ <v-row no-gutters>
24
+ <v-col cols="6" class="pa-0">
25
+ <v-btn block variant="text" class="text-none" size="large" tile @click="emit('cancel')" height="48">
26
+ Cancel
27
+ </v-btn>
28
+ </v-col>
29
+
30
+ <v-col cols="6" class="pa-0">
31
+ <v-btn block tile variant="flat" class="text-none" size="large" height="48" color="red" :disabled="!passwordInput"
32
+ @click="handleConfirm">
33
+ Confirm
34
+ </v-btn>
35
+ </v-col>
36
+ </v-row>
37
+ </v-toolbar>
38
+ </v-card>
39
+ </template>
40
+
41
+ <script setup lang="ts">
42
+
43
+ const props = defineProps({
44
+ message: {
45
+ type: String,
46
+ default: "Confirm your digital signature with password to proceed."
47
+ },
48
+ promptTitle: {
49
+ type: String,
50
+ default: "Confirm Action"
51
+ },
52
+ loading: {
53
+ type: Boolean,
54
+ default: false
55
+ },
56
+ })
57
+
58
+ const { confirmPassword } = useDOBEntries();
59
+ const { currentUser } = useLocalAuth();
60
+
61
+ const emit = defineEmits(["cancel", "confirm"])
62
+
63
+ const processingPassword = ref(false)
64
+ const errorMessage = ref('')
65
+
66
+
67
+ const passwordInput = ref('')
68
+
69
+
70
+
71
+ async function handleConfirm() {
72
+ errorMessage.value = ''
73
+ try {
74
+ processingPassword.value = true;
75
+ const userId = currentUser.value?._id;
76
+ if (!userId) {
77
+ throw new Error('User not found. Please login again.');
78
+ }
79
+ const res = await confirmPassword({ userId, password: passwordInput.value });
80
+ if (res) {
81
+ emit('confirm');
82
+ }
83
+
84
+ } catch (err: any) {
85
+ const errMessage = err?.response?._data?.message || 'Something went wrong. Please try again.';
86
+ console.log('[ERROR]', err)
87
+ errorMessage.value = errMessage;
88
+ } finally {
89
+ processingPassword.value = false;
90
+ }
91
+ }
92
+
93
+ </script>
94
+
95
+ <style scoped></style>
@@ -0,0 +1,410 @@
1
+ <template>
2
+ <v-row no-gutters class="mb-6">
3
+ <v-col cols="12">
4
+ <div class="text-subtitle-2 font-weight-bold mb-3">
5
+ {{ title }}
6
+ <span v-if="required" class="text-error">*</span>
7
+ </div>
8
+
9
+ <v-sheet
10
+ border
11
+ rounded="lg"
12
+ class="pa-8 text-center upload-area"
13
+ :class="{ 'upload-disabled': uploading }"
14
+ style="cursor: pointer; transition: all 0.2s"
15
+ @click="!uploading && (photoOptionsDialog = true)"
16
+ >
17
+ <v-icon
18
+ :icon="uploading ? 'mdi-loading mdi-spin' : 'mdi-camera-plus-outline'"
19
+ size="56"
20
+ :color="uploading ? 'grey' : 'primary'"
21
+ class="mb-3"
22
+ />
23
+ <div class="text-body-1 font-weight-medium mb-1">
24
+ {{ uploading ? "Uploading Photo..." : "Add Photo" }}
25
+ </div>
26
+ <div class="text-caption text-medium-emphasis">
27
+ {{ uploading ? "Please wait" : "Take a photo or upload from device" }}
28
+ </div>
29
+ </v-sheet>
30
+ </v-col>
31
+ </v-row>
32
+
33
+ <v-row v-if="photos.length > 0" no-gutters>
34
+ <v-col
35
+ v-for="(photo, index) in photos"
36
+ :key="index"
37
+ cols="auto"
38
+ class="pa-1"
39
+ >
40
+ <div class="position-relative" style="display: inline-block">
41
+ <v-sheet
42
+ rounded="sm"
43
+ style="width: 88px; height: 64px; overflow: hidden; cursor: pointer"
44
+ class="photo-thumb"
45
+ @click="viewPhoto(photo)"
46
+ >
47
+ <v-img :src="photo" height="64" width="88" cover />
48
+ </v-sheet>
49
+ <v-btn
50
+ icon="mdi-close"
51
+ size="x-small"
52
+ variant="tonal"
53
+ color="error"
54
+ class="delete-btn"
55
+ @click.stop="removePhoto(index)"
56
+ style="position: absolute; top: -8px; right: -8px"
57
+ />
58
+ </div>
59
+ </v-col>
60
+ </v-row>
61
+
62
+ <v-dialog v-model="photoOptionsDialog" max-width="420">
63
+ <v-card>
64
+ <v-card-title class="d-flex align-center pa-4">
65
+ <span class="text-h6 font-weight-bold">Add Photo</span>
66
+ <v-spacer />
67
+ <v-btn
68
+ icon="mdi-close"
69
+ variant="text"
70
+ size="small"
71
+ @click="photoOptionsDialog = false"
72
+ />
73
+ </v-card-title>
74
+
75
+ <v-divider />
76
+
77
+ <v-card-text class="pa-4">
78
+ <v-list class="pa-0" bg-color="transparent">
79
+ <v-list-item class="rounded-lg mb-2" @click="openCameraFromOptions">
80
+ <template v-slot:prepend>
81
+ <v-avatar color="primary" variant="tonal" size="48">
82
+ <v-icon icon="mdi-camera" color="primary" />
83
+ </v-avatar>
84
+ </template>
85
+ <v-list-item-title class="font-weight-medium mb-1">
86
+ Take a Photo
87
+ </v-list-item-title>
88
+ <v-list-item-subtitle class="text-caption">
89
+ Use device camera to capture an image
90
+ </v-list-item-subtitle>
91
+ </v-list-item>
92
+
93
+ <v-list-item class="rounded-lg" @click="triggerFileInput">
94
+ <template v-slot:prepend>
95
+ <v-avatar color="primary" variant="tonal" size="48">
96
+ <v-icon icon="mdi-upload" color="primary" />
97
+ </v-avatar>
98
+ </template>
99
+ <v-list-item-title class="font-weight-medium mb-1">
100
+ Upload a Photo
101
+ </v-list-item-title>
102
+ <v-list-item-subtitle class="text-caption">
103
+ Select an image from your device
104
+ </v-list-item-subtitle>
105
+ </v-list-item>
106
+ </v-list>
107
+ </v-card-text>
108
+ </v-card>
109
+ </v-dialog>
110
+
111
+ <v-dialog v-model="showCameraDialog" max-width="720">
112
+ <v-card>
113
+ <v-toolbar density="compact">
114
+ <v-toolbar-title class="text-subtitle-1 font-weight-medium">
115
+ Camera
116
+ </v-toolbar-title>
117
+ <v-spacer />
118
+ <v-btn icon @click="closeCameraDialog">
119
+ <v-icon>mdi-close</v-icon>
120
+ </v-btn>
121
+ </v-toolbar>
122
+ <v-card-text>
123
+ <div class="d-flex flex-column align-center">
124
+ <div style="width: 100%; max-width: 640px">
125
+ <video
126
+ ref="videoRef"
127
+ autoplay
128
+ muted
129
+ playsinline
130
+ style="width: 100%; border-radius: 8px; background: #000"
131
+ />
132
+ </div>
133
+ <canvas ref="canvasRef" style="display: none"></canvas>
134
+ <div class="text-caption grey--text mt-2">
135
+ Tip: Allow camera permissions to use this feature.
136
+ </div>
137
+ </div>
138
+ </v-card-text>
139
+ <v-card-actions>
140
+ <v-btn
141
+ text
142
+ @click="closeCameraDialog"
143
+ class="text-none font-weight-medium"
144
+ >
145
+ CANCEL
146
+ </v-btn>
147
+ <v-spacer />
148
+ <v-btn
149
+ block
150
+ variant="flat"
151
+ color="black"
152
+ class="text-none font-weight-bold rounded-0"
153
+ height="56"
154
+ size="large"
155
+ @click="capturePhoto"
156
+ >
157
+ <v-icon class="mr-2">mdi-camera</v-icon>
158
+ CAPTURE
159
+ </v-btn>
160
+ </v-card-actions>
161
+ </v-card>
162
+ </v-dialog>
163
+
164
+ <input
165
+ ref="fileInputRef"
166
+ type="file"
167
+ accept="image/*"
168
+ style="display: none"
169
+ @change="onFileSelected"
170
+ />
171
+ </template>
172
+
173
+ <script setup lang="ts">
174
+ const props = defineProps({
175
+ title: {
176
+ type: String,
177
+ default: "Photos (Optional)",
178
+ },
179
+ required: {
180
+ type: Boolean,
181
+ default: false,
182
+ },
183
+ modelValue: {
184
+ type: Array as PropType<string[]>,
185
+ default: () => [],
186
+ },
187
+ photoIds: {
188
+ type: Array as PropType<string[]>,
189
+ default: () => [],
190
+ },
191
+ });
192
+
193
+ const emit = defineEmits(["update:modelValue", "update:photoIds", "error"]);
194
+
195
+ const photos = ref<string[]>([...props.modelValue]);
196
+ const photosIds = ref<string[]>([...props.photoIds]);
197
+ const photoOptionsDialog = ref(false);
198
+ const showCameraDialog = ref(false);
199
+ const videoRef = ref<HTMLVideoElement | null>(null);
200
+ const canvasRef = ref<HTMLCanvasElement | null>(null);
201
+ const fileInputRef = ref<HTMLInputElement | null>(null);
202
+ let mediaStream: MediaStream | null = null;
203
+ const uploading = ref(false);
204
+
205
+ const { addFile } = useFile();
206
+
207
+ function viewPhoto(photoData: string) {
208
+ const link = document.createElement("a");
209
+ link.href = photoData;
210
+ link.target = "_blank";
211
+ link.click();
212
+ }
213
+
214
+ watch(
215
+ () => props.modelValue,
216
+ (newVal) => {
217
+ if (JSON.stringify(newVal) !== JSON.stringify(photos.value)) {
218
+ photos.value = [...newVal];
219
+ }
220
+ },
221
+ );
222
+
223
+ watch(
224
+ () => props.photoIds,
225
+ (newVal) => {
226
+ if (JSON.stringify(newVal) !== JSON.stringify(photosIds.value)) {
227
+ photosIds.value = [...newVal];
228
+ }
229
+ },
230
+ );
231
+
232
+ watch(
233
+ photos,
234
+ (newVal) => {
235
+ if (JSON.stringify(newVal) !== JSON.stringify(props.modelValue)) {
236
+ emit("update:modelValue", newVal);
237
+ }
238
+ },
239
+ { deep: true },
240
+ );
241
+
242
+ watch(
243
+ photosIds,
244
+ (newVal) => {
245
+ if (JSON.stringify(newVal) !== JSON.stringify(props.photoIds)) {
246
+ emit("update:photoIds", newVal);
247
+ }
248
+ },
249
+ { deep: true },
250
+ );
251
+
252
+ function triggerFileInput() {
253
+ photoOptionsDialog.value = false;
254
+ fileInputRef.value?.click();
255
+ }
256
+
257
+ function onFileSelected(e: Event) {
258
+ const input = e.target as HTMLInputElement;
259
+ const file = input.files?.[0];
260
+ if (!file) return;
261
+
262
+ const reader = new FileReader();
263
+ reader.onload = async () => {
264
+ try {
265
+ if (typeof reader.result === "string") {
266
+ photos.value = [...photos.value, reader.result];
267
+ }
268
+ uploading.value = true;
269
+ const uploadRes = await addFile(file);
270
+ if (uploadRes?.id) {
271
+ photosIds.value = [...photosIds.value, uploadRes.id];
272
+ }
273
+ } catch (err) {
274
+ console.error("File upload error", err);
275
+ emit("error", "Failed to upload image");
276
+
277
+ if (photos.value.length > photosIds.value.length) {
278
+ photos.value.pop();
279
+ }
280
+ } finally {
281
+ uploading.value = false;
282
+ }
283
+ };
284
+ reader.readAsDataURL(file);
285
+ input.value = "";
286
+ }
287
+
288
+ async function openCameraFromOptions() {
289
+ photoOptionsDialog.value = false;
290
+ showCameraDialog.value = true;
291
+ try {
292
+ mediaStream = await navigator.mediaDevices.getUserMedia({
293
+ video: true,
294
+ audio: false,
295
+ });
296
+ if (videoRef.value) {
297
+ videoRef.value.srcObject = mediaStream;
298
+ await videoRef.value.play();
299
+ }
300
+ } catch (err) {
301
+ console.error("Camera error", err);
302
+ emit("error", "Unable to access camera");
303
+ showCameraDialog.value = false;
304
+ }
305
+ }
306
+
307
+ function capturePhoto() {
308
+ if (!videoRef.value) return;
309
+
310
+ const video = videoRef.value;
311
+ const canvas = canvasRef.value || document.createElement("canvas");
312
+ canvas.width = video.videoWidth || 640;
313
+ canvas.height = video.videoHeight || 480;
314
+ const ctx = canvas.getContext("2d");
315
+ if (!ctx) return;
316
+
317
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
318
+ const data = canvas.toDataURL("image/jpeg", 0.9);
319
+ photos.value = [...photos.value, data];
320
+
321
+ // Convert to blob and upload
322
+ canvas.toBlob(async (blob) => {
323
+ if (!blob) {
324
+ closeCameraDialog();
325
+ return;
326
+ }
327
+
328
+ const file = new File([blob], `photo-${Date.now()}.jpg`, {
329
+ type: "image/jpeg",
330
+ });
331
+
332
+ try {
333
+ uploading.value = true;
334
+ const uploadRes = await addFile(file);
335
+ if (uploadRes?.id) {
336
+ photosIds.value = [...photosIds.value, uploadRes.id];
337
+ }
338
+ } catch (err) {
339
+ console.error("Capture upload error", err);
340
+ emit("error", "Failed to upload captured image");
341
+ // Remove the preview if upload failed
342
+ if (photos.value.length > photosIds.value.length) {
343
+ photos.value.pop();
344
+ }
345
+ } finally {
346
+ uploading.value = false;
347
+ closeCameraDialog();
348
+ }
349
+ }, "image/jpeg");
350
+ }
351
+
352
+ function closeCameraDialog() {
353
+ showCameraDialog.value = false;
354
+ if (mediaStream) {
355
+ mediaStream.getTracks().forEach((t) => t.stop());
356
+ mediaStream = null;
357
+ }
358
+ if (videoRef.value) {
359
+ videoRef.value.pause();
360
+ videoRef.value.srcObject = null;
361
+ }
362
+ }
363
+
364
+ function removePhoto(index: number) {
365
+ if (index < 0 || index >= photos.value.length) return;
366
+ photos.value.splice(index, 1);
367
+ if (photosIds.value && photosIds.value.length > index) {
368
+ photosIds.value.splice(index, 1);
369
+ }
370
+ }
371
+
372
+ onBeforeUnmount(() => {
373
+ if (mediaStream) {
374
+ mediaStream.getTracks().forEach((t) => t.stop());
375
+ mediaStream = null;
376
+ }
377
+ });
378
+ </script>
379
+
380
+ <style scoped>
381
+ .upload-area {
382
+ &:hover:not(.upload-disabled) {
383
+ border-color: rgb(var(--v-theme-primary)) !important;
384
+ background-color: rgba(var(--v-theme-primary), 0.04);
385
+ }
386
+ }
387
+
388
+ .upload-disabled {
389
+ cursor: not-allowed !important;
390
+ opacity: 0.6;
391
+ }
392
+
393
+ .photo-thumb {
394
+ cursor: pointer;
395
+ transition: transform 0.2s;
396
+ }
397
+
398
+ .photo-thumb:hover {
399
+ transform: scale(1.05);
400
+ }
401
+
402
+ .delete-btn {
403
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
404
+ transition: all 0.2s;
405
+ }
406
+
407
+ .delete-btn:hover {
408
+ transform: scale(1.1);
409
+ }
410
+ </style>
@@ -119,21 +119,23 @@
119
119
  </template>
120
120
 
121
121
  <template #footer>
122
- <v-btn
123
- variant="text"
124
- @click="confirmDialog = false"
125
- :disabled="deleteLoading"
126
- >
127
- Close
128
- </v-btn>
129
- <v-btn
130
- color="primary"
131
- variant="flat"
132
- @click="handleDeleteRole"
133
- :loading="deleteLoading"
134
- >
135
- Delete Role
136
- </v-btn>
122
+ <div class="d-flex justify-center ga-3 w-100">
123
+ <v-btn
124
+ variant="text"
125
+ @click="confirmDialog = false"
126
+ :disabled="deleteLoading"
127
+ >
128
+ Close
129
+ </v-btn>
130
+ <v-btn
131
+ color="primary"
132
+ variant="flat"
133
+ @click="handleDeleteRole"
134
+ :loading="deleteLoading"
135
+ >
136
+ Delete Role
137
+ </v-btn>
138
+ </div>
137
139
  </template>
138
140
  </ConfirmDialog>
139
141