@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 72d3443: Update Changes
8
+
9
+ ## 1.10.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 70c2b90: get latest changes
14
+
3
15
  ## 1.9.0
4
16
 
5
17
  ### Minor Changes
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <v-dialog
3
+ :model-value="modelValue"
4
+ @update:model-value="val => $emit('update:modelValue', val)"
5
+ max-width="420"
6
+ persistent
7
+ >
8
+ <v-card class="pa-4" style="border-radius:12px;">
9
+ <v-card-title class="headline" style="font-size:1.3rem; font-weight:500;">
10
+ {{ title }}
11
+ </v-card-title>
12
+ <v-card-text style="font-size:1.05rem; margin-bottom:24px;">
13
+ {{ message }}
14
+ </v-card-text>
15
+ <v-card-actions class="justify-end" style="gap:12px;">
16
+ <v-btn
17
+ color="primary"
18
+ variant="outlined"
19
+ style="min-width:70px; background:#e3f2fd; color:#1976d2; font-weight:500;"
20
+ @click="$emit('confirm')"
21
+ >
22
+ Yes
23
+ </v-btn>
24
+ <v-btn
25
+ color="error"
26
+ variant="outlined"
27
+ style="min-width:70px; background:#ffebee; color:#d32f2f; font-weight:500;"
28
+ @click="$emit('cancel')"
29
+ >
30
+ No
31
+ </v-btn>
32
+ </v-card-actions>
33
+ </v-card>
34
+ </v-dialog>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ defineProps<{
39
+ modelValue: boolean,
40
+ title?: string,
41
+ message?: string
42
+ }>();
43
+ defineEmits(['confirm', 'cancel', 'update:modelValue']);
44
+ </script>
@@ -91,7 +91,7 @@
91
91
  <v-col cols="12">
92
92
  <InputLabel
93
93
  class="text-capitalize font-weight-bold"
94
- title="Doors Location"
94
+ title="Door Location"
95
95
  required
96
96
  />
97
97
  <v-select
@@ -115,6 +115,8 @@
115
115
  v-model="card.accessGroup"
116
116
  density="compact"
117
117
  :items="accessGroupItems"
118
+ item-title="name"
119
+ item-value="no"
118
120
  hide-details
119
121
  :rules="[requiredRule]"
120
122
  multiple
@@ -179,7 +181,7 @@
179
181
  hide-details
180
182
  :rules="card.useAsLiftCard ? [requiredRule] : []"
181
183
  item-title="name"
182
- item-value="value"
184
+ item-value="no"
183
185
  />
184
186
  </v-col>
185
187
  <v-col cols="12">
@@ -327,6 +329,15 @@ const prop = defineProps({
327
329
  const { add: _addCard, updateById: _updateCardById } = useCard();
328
330
  const { getAll: _getBuildings } = useBuilding();
329
331
  const { getAllUnits: _getUnits } = useBuildingUnit();
332
+ const {
333
+ getDoorAccessLevels: _getDoorAccessLevels,
334
+ getLiftAccessLevels: _getLiftAccessLevels,
335
+ getAccessGroups: _getAccessGroups,
336
+ addPhysicalCard: _addPhysicalCard,
337
+ addNonPhysicalCard: _addNonPhysicalCard,
338
+ } = useAccessManagement();
339
+
340
+ const config = useRuntimeConfig();
330
341
  const emit = defineEmits(["cancel", "success"]);
331
342
 
332
343
  const validForm = ref(false);
@@ -429,11 +440,32 @@ const buildingItems = ref<{ name: string; value: string }[]>([]);
429
440
  const levelItems = ref<{ name: string; value: string }[]>([]);
430
441
  const unitItems = ref<{ name: string; value: string }[]>([]);
431
442
  const buildingsData = ref<Record<string, any>[]>([]);
443
+ const encryptedAcmUrl = ref("");
432
444
 
433
445
  const route = useRoute();
434
- const siteId = route.params.site as string;
446
+ const siteId = '66ab2f1381856008f1887971' as string;
447
+ // const siteId = route.params.site as string; @TODO
435
448
  const orgId = route.params.org as string;
436
449
 
450
+ onMounted(async () => {
451
+ try {
452
+ const { encrypted: acmUrl } = await $fetch<{ encrypted: string }>(
453
+ "/api/encrypt-acm-url"
454
+ );
455
+ encryptedAcmUrl.value = acmUrl;
456
+ const [doorLevels, liftLevels, groups] = await Promise.all([
457
+ _getDoorAccessLevels(acmUrl),
458
+ _getLiftAccessLevels(acmUrl),
459
+ _getAccessGroups(acmUrl),
460
+ ]);
461
+ accessLevelItems.value = doorLevels.data ?? [];
462
+ liftAccessLevelItems.value = liftLevels.data ?? [];
463
+ accessGroupItems.value = groups.data ?? [];
464
+ } catch (error) {
465
+ console.error("Failed to fetch access management data:", error);
466
+ }
467
+ });
468
+
437
469
  function limitCardNumber() {
438
470
  if (card.value.cardNumber && card.value.cardNumber.toString().length > cardNoMaxLength.value) {
439
471
  card.value.cardNumber = card.value.cardNumber.toString().slice(0, cardNoMaxLength.value);
@@ -543,23 +575,79 @@ function cancel() {
543
575
  emit("cancel");
544
576
  }
545
577
 
578
+ function buildPhysicalCardPayload() {
579
+ const now = new Date().toISOString();
580
+ const doorItem = accessLevelItems.value.find((i: any) => i.no === card.value.door);
581
+ const liftItem = liftAccessLevelItems.value.find((i: any) => i.no === card.value.liftAccessLevel);
582
+
583
+ return {
584
+ site: siteId,
585
+ cardNo: card.value.cardNumber,
586
+ isActivated: card.value.isActivate,
587
+ isAntiPassBack: card.value.isAntiPassBack,
588
+ isLiftCard: card.value.useAsLiftCard,
589
+ userType: "Visitor/Resident",
590
+ accessLevel: card.value.door,
591
+ accessGroup: card.value.accessGroup,
592
+ accessType: card.value.cardType,
593
+ startDate: card.value.startDate,
594
+ endDate: card.value.endDate,
595
+ doorName: doorItem?.name ?? "",
596
+ createdAt: now,
597
+ updatedAt: now,
598
+ ...(card.value.pinNo ? { pin: card.value.pinNo } : {}),
599
+ ...(card.value.useAsLiftCard
600
+ ? {
601
+ liftAccessLevel: card.value.liftAccessLevel,
602
+ liftName: liftItem?.name ?? "",
603
+ }
604
+ : {}),
605
+ ...(card.value.assignUnit ? { unit: [card.value.assignUnit] } : {}),
606
+ ...(card.value.isWinsland !== undefined ? { isWinsland: card.value.isWinsland } : {}),
607
+ };
608
+ }
609
+
610
+ function buildNonPhysicalCardPayload() {
611
+ const now = new Date().toISOString();
612
+ const doorItem = accessLevelItems.value.find((i: any) => i.no === card.value.door);
613
+ const liftItem = liftAccessLevelItems.value.find((i: any) => i.no === card.value.liftAccessLevel);
614
+
615
+ return {
616
+ site: siteId,
617
+ quantity: card.value.quantity,
618
+ accessLevel: card.value.door,
619
+ isLiftCard: card.value.useAsLiftCard,
620
+ accessGroup: card.value.accessGroup,
621
+ userType: "Visitor/Resident",
622
+ doorName: doorItem?.name ?? "",
623
+ startDate: card.value.startDate,
624
+ endDate: card.value.endDate,
625
+ createdAt: now,
626
+ updatedAt: now,
627
+ ...(card.value.useAsLiftCard
628
+ ? {
629
+ liftAccessLevel: card.value.liftAccessLevel,
630
+ liftName: liftItem?.name ?? "",
631
+ }
632
+ : {}),
633
+ ...(card.value.assignUnit ? { unit: [card.value.assignUnit] } : {}),
634
+ ...(card.value.isWinsland !== undefined ? { isWinsland: card.value.isWinsland } : {}),
635
+ };
636
+ }
637
+
546
638
  async function submit() {
547
639
  disable.value = true;
548
640
  try {
549
641
  if (prop.mode === "add") {
550
- const payload = {
551
- ...card.value,
552
- site: siteId,
553
- org: orgId,
554
- };
555
- await _addCard(payload);
642
+ if (isNonPhysicalCard.value) {
643
+ await _addNonPhysicalCard(buildNonPhysicalCardPayload());
644
+ } else {
645
+ await _addPhysicalCard(buildPhysicalCardPayload());
646
+ }
556
647
  }
557
648
 
558
649
  if (prop.mode === "edit") {
559
- const payload = {
560
- ...card.value,
561
- };
562
- await _updateCardById(prop.card._id ?? "", payload);
650
+ await _updateCardById(prop.card._id ?? "", { ...card.value });
563
651
  }
564
652
  emit("success");
565
653
  } catch (error: any) {
@@ -80,7 +80,7 @@
80
80
  mode="edit"
81
81
  @cancel="editDialog = false"
82
82
  @success="successUpdate()"
83
- :card="selectedCard"
83
+ :card="selectedCard as TCard"
84
84
  />
85
85
  </v-dialog>
86
86
 
@@ -90,21 +90,93 @@
90
90
  <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
91
91
  <v-row no-gutters class="mb-4">
92
92
  <v-col cols="12">
93
- <strong>Card:</strong> {{ selectedCard?.cardNumber ?? "N/A" }}
93
+ <strong>Unit:</strong> {{ selectedCard?.name ?? "N/A" }}
94
94
  </v-col>
95
95
  <v-col cols="12">
96
- <strong>Access Type:</strong>
97
- {{ selectedCard?.accessCardType ?? "N/A" }}
96
+ <strong>Block:</strong> {{ selectedCard?.block?.name ?? "N/A" }}
98
97
  </v-col>
99
98
  <v-col cols="12">
100
- <strong>Unit:</strong>
101
- {{ selectedCard?.unit || "N/A" }}
99
+ <strong>Level:</strong> {{ selectedCard?.level?.level ?? "N/A" }}
102
100
  </v-col>
103
- <v-col cols="12">
104
- <strong>Assign:</strong> {{ selectedCard?.assign || "N/A" }}
101
+
102
+ <!-- Available Physical -->
103
+ <v-col cols="12" class="mt-3">
104
+ <strong>Available Physical</strong>
105
+ <div v-if="selectedCard?.available?.physical?.length" class="mt-1">
106
+ <v-chip
107
+ v-for="card in selectedCard.available.physical"
108
+ :key="card._id"
109
+ size="small"
110
+ class="mr-1 mb-1"
111
+ :color="selectedCardInUnit?._id === card._id ? 'primary' : undefined"
112
+ :variant="selectedCardInUnit?._id === card._id ? 'flat' : 'tonal'"
113
+ style="cursor: pointer"
114
+ @click="selectedCardInUnit = selectedCardInUnit?._id === card._id ? null : card"
115
+ >
116
+ {{ card.cardNo }}
117
+ </v-chip>
118
+ </div>
119
+ <span v-else class="text-caption text-grey ml-1">None</span>
105
120
  </v-col>
106
- <v-col cols="12">
107
- <strong>Status:</strong> {{ selectedCard?.status ?? "N/A" }}
121
+
122
+ <!-- Available Non-Physical -->
123
+ <v-col cols="12" class="mt-2">
124
+ <strong>Available Non-Physical</strong>
125
+ <div v-if="selectedCard?.available?.non_physical?.length" class="mt-1">
126
+ <v-chip
127
+ v-for="card in selectedCard.available.non_physical"
128
+ :key="card._id"
129
+ size="small"
130
+ class="mr-1 mb-1"
131
+ :color="selectedCardInUnit?._id === card._id ? 'primary' : undefined"
132
+ :variant="selectedCardInUnit?._id === card._id ? 'flat' : 'tonal'"
133
+ style="cursor: pointer"
134
+ @click="selectedCardInUnit = selectedCardInUnit?._id === card._id ? null : card"
135
+ >
136
+ {{ card.cardNo }}
137
+ </v-chip>
138
+ </div>
139
+ <span v-else class="text-caption text-grey ml-1">None</span>
140
+ </v-col>
141
+
142
+ <!-- Assigned Physical -->
143
+ <v-col cols="12" class="mt-2">
144
+ <strong>Assigned Physical</strong>
145
+ <div v-if="selectedCard?.assigned?.physical?.length" class="mt-1">
146
+ <v-chip
147
+ v-for="card in selectedCard.assigned.physical"
148
+ :key="card._id"
149
+ size="small"
150
+ class="mr-1 mb-1"
151
+ :color="selectedCardInUnit?._id === card._id ? 'primary' : undefined"
152
+ :variant="selectedCardInUnit?._id === card._id ? 'flat' : 'tonal'"
153
+ style="cursor: pointer"
154
+ @click="selectedCardInUnit = selectedCardInUnit?._id === card._id ? null : card"
155
+ >
156
+ {{ card.cardNo }}
157
+ </v-chip>
158
+ </div>
159
+ <span v-else class="text-caption text-grey ml-1">None</span>
160
+ </v-col>
161
+
162
+ <!-- Assigned Non-Physical -->
163
+ <v-col cols="12" class="mt-2">
164
+ <strong>Assigned Non-Physical</strong>
165
+ <div v-if="selectedCard?.assigned?.non_physical?.length" class="mt-1">
166
+ <v-chip
167
+ v-for="card in selectedCard.assigned.non_physical"
168
+ :key="card._id"
169
+ size="small"
170
+ class="mr-1 mb-1"
171
+ :color="selectedCardInUnit?._id === card._id ? 'primary' : undefined"
172
+ :variant="selectedCardInUnit?._id === card._id ? 'flat' : 'tonal'"
173
+ style="cursor: pointer"
174
+ @click="selectedCardInUnit = selectedCardInUnit?._id === card._id ? null : card"
175
+ >
176
+ {{ card.cardNo }}
177
+ </v-chip>
178
+ </div>
179
+ <span v-else class="text-caption text-grey ml-1">None</span>
108
180
  </v-col>
109
181
  </v-row></v-card-text
110
182
  >
@@ -238,18 +310,38 @@ const props = defineProps({
238
310
  type: Array as PropType<Array<Record<string, any>>>,
239
311
  default: () => [
240
312
  {
241
- title: "Card",
242
- value: "cardNumber",
313
+ title: "Unit",
314
+ value: "name",
315
+ },
316
+ {
317
+ title: "Block",
318
+ value: "block.name",
243
319
  },
244
320
  {
245
- title: "Unit",
246
- value: "unit",
321
+ title: "Level",
322
+ value: "level.level",
247
323
  },
248
324
  {
249
- title: "Assign",
250
- value: "assign",
325
+ title: "Avail. (P)",
326
+ value: "cardCounts.available.physical",
251
327
  },
252
- { title: "Action", value: "action-table" },
328
+ {
329
+ title: "Avail. (NP)",
330
+ value: "cardCounts.available.non_physical",
331
+ },
332
+ {
333
+ title: "Asgn. (P)",
334
+ value: "cardCounts.assigned.physical",
335
+ },
336
+ {
337
+ title: "Asgn. (NP)",
338
+ value: "cardCounts.assigned.non_physical",
339
+ },
340
+ {
341
+ title: "Total",
342
+ value: "totalCardCount",
343
+ },
344
+ // { title: "Action", value: "action-table" },
253
345
  ],
254
346
  },
255
347
  canCreate: {
@@ -300,32 +392,21 @@ const confirmDialog = ref(false);
300
392
  const searchText = ref("");
301
393
  const replaceDialog = ref(false);
302
394
 
303
- const selectedCard = ref<TCard>({
304
- _id: "",
305
- name: "",
306
- accessCardType: "",
307
- // visitorType: "",
308
- type: "",
309
- cardNumber: "",
310
- startDate: "",
311
- endDate: "",
312
- door: "",
313
- accessGroup: [],
314
- cardType: "",
315
- pinNo: "",
316
- useAsLiftCard: false,
317
- liftAccessLevel: "",
318
- isActivate: true,
319
- isAntiPassBack: false,
320
- status: "",
321
- org: "",
322
- site: "",
323
- unit: "",
324
- assign: "",
325
- });
395
+ const route = useRoute();
396
+ // const siteId = computed(() => route.params.site as string); @TODO
397
+ const siteId = computed(() => '66ab2f1381856008f1887971' as string);
398
+ const orgId = computed(() => route.params.org as string);
399
+
400
+ const selectedCard = ref<Record<string, any>>({});
401
+ const selectedCardInUnit = ref<Record<string, any> | null>(null);
326
402
  const selectedCardId = ref<string | null>(null);
327
403
 
328
- const { getAll: _getAllCards, deleteById: _deleteCard } = useCard();
404
+ watch(previewDialog, (val) => {
405
+ if (!val) selectedCardInUnit.value = null;
406
+ });
407
+
408
+ const { deleteById: _deleteCard } = useCard();
409
+ const { getUserTypeAccessCards } = useAccessManagement();
329
410
 
330
411
  const {
331
412
  data: getCardReq,
@@ -334,10 +415,12 @@ const {
334
415
  } = useLazyAsyncData(
335
416
  "get-all-cards",
336
417
  () =>
337
- _getAllCards({
418
+ getUserTypeAccessCards({
338
419
  page: page.value,
339
420
  search: searchText.value,
340
- // search: headerSearch.value,
421
+ organization: orgId.value,
422
+ site: siteId.value,
423
+ userType: "Visitor/Resident",
341
424
  }),
342
425
  {
343
426
  watch: [page, searchText],
@@ -347,10 +430,10 @@ const {
347
430
  const loading = computed(() => getAllReqStatus.value === "pending");
348
431
 
349
432
  watchEffect(() => {
350
- if (getCardReq.value) {
351
- items.value = getCardReq.value.items;
352
- pages.value = getCardReq.value.pages;
353
- pageRange.value = getCardReq.value.pageRange;
433
+ if (getCardReq.value?.data) {
434
+ items.value = getCardReq.value.data.items;
435
+ pages.value = getCardReq.value.data.pages;
436
+ pageRange.value = getCardReq.value.data.pageRange;
354
437
  }
355
438
  });
356
439
 
@@ -0,0 +1,165 @@
1
+ <template>
2
+ <v-dialog v-model="showDialog" max-width="450">
3
+ <v-card>
4
+ <v-toolbar>
5
+ <v-row no-gutters class="fill-height px-6" align="center">
6
+ <span class="font-weight-bold text-h6 text-capitalize">
7
+ {{ prop.mode === "edit" ? "Edit Supply" : "Add New Supply" }}
8
+ </span>
9
+ </v-row>
10
+ </v-toolbar>
11
+
12
+ <v-card-text class="pa-4">
13
+ <v-form ref="formRef" v-model="valid">
14
+ <v-row no-gutters class="pa-0">
15
+ <v-col cols="12" class="pa-0 mb-2">
16
+ <InputLabel for="name" title="Name" required class="mb-1" />
17
+ <v-text-field
18
+ v-model="nameModel"
19
+ :rules="[requiredRule]"
20
+ density="comfortable"
21
+ variant="outlined"
22
+ placeholder="Enter supply name"
23
+ hide-details="auto"
24
+ class="mb-0"
25
+ />
26
+ </v-col>
27
+
28
+ <v-col cols="12" class="pa-0 mb-0">
29
+ <InputLabel
30
+ for="unitOfMeasurement"
31
+ title="Unit of Measurement"
32
+ required
33
+ class="mb-1"
34
+ />
35
+ <v-select
36
+ v-model="unitOfMeasurementModel"
37
+ :items="unitOfMeasurementOptions"
38
+ :rules="[requiredRule]"
39
+ density="comfortable"
40
+ variant="outlined"
41
+ placeholder="Select unitOfMeasurement"
42
+ hide-details="auto"
43
+ class="mb-0"
44
+ />
45
+ </v-col>
46
+ </v-row>
47
+ </v-form>
48
+ </v-card-text>
49
+
50
+ <v-toolbar class="pa-0" density="compact">
51
+ <v-row no-gutters>
52
+ <v-col cols="6" class="pa-0">
53
+ <v-btn
54
+ block
55
+ variant="text"
56
+ class="text-none"
57
+ size="large"
58
+ @click="close"
59
+ height="48"
60
+ >
61
+ Cancel
62
+ </v-btn>
63
+ </v-col>
64
+
65
+ <v-col cols="6" class="pa-0">
66
+ <v-btn
67
+ block
68
+ variant="flat"
69
+ color="black"
70
+ class="text-none font-weight-bold rounded-0"
71
+ height="48"
72
+ :loading="submitting"
73
+ @click="submit"
74
+ >
75
+ {{ prop.mode === "edit" ? "Save" : "Create" }}
76
+ </v-btn>
77
+ </v-col>
78
+ </v-row>
79
+ </v-toolbar>
80
+ </v-card>
81
+ </v-dialog>
82
+ </template>
83
+
84
+ <script setup lang="ts">
85
+ const nameModel = defineModel("name", { type: String, default: "" });
86
+ const unitOfMeasurementModel = defineModel("unitOfMeasurement", {
87
+ type: String,
88
+ default: "",
89
+ });
90
+ const showDialog = defineModel({ type: Boolean, default: false });
91
+
92
+ const prop = defineProps({
93
+ mode: {
94
+ type: String,
95
+ default: "add",
96
+ },
97
+ supplyData: {
98
+ type: Object,
99
+ default: null,
100
+ },
101
+ });
102
+
103
+ const emit = defineEmits(["saved", "close"]);
104
+
105
+ const formRef = ref<any>(null);
106
+ const valid = ref(false);
107
+ const submitting = ref(false);
108
+
109
+ const { requiredRule } = useUtils();
110
+
111
+ const unitOfMeasurementOptions = [
112
+ "Pieces",
113
+ "Boxes",
114
+ "Bottles",
115
+ "Liters",
116
+ "Kilograms",
117
+ "Grams",
118
+ "Rolls",
119
+ "Packs",
120
+ "Sets",
121
+ "Units",
122
+ ];
123
+
124
+ watchEffect(() => {
125
+ nameModel.value = prop.supplyData?.name || "";
126
+ unitOfMeasurementModel.value = prop.supplyData?.unitOfMeasurement || "";
127
+ });
128
+
129
+ function close() {
130
+ showDialog.value = false;
131
+ emit("close");
132
+ }
133
+
134
+ async function submit() {
135
+ const form = formRef.value as any;
136
+ let ok = valid.value;
137
+ if (form && typeof form.validate === "function") {
138
+ const result = await form.validate();
139
+ ok = result.valid;
140
+ }
141
+
142
+ if (!ok) return;
143
+
144
+ submitting.value = true;
145
+ try {
146
+ await new Promise((r) => setTimeout(r, 500));
147
+
148
+ emit("saved", {
149
+ name: nameModel.value,
150
+
151
+ unitOfMeasurement: unitOfMeasurementModel.value,
152
+ });
153
+
154
+ nameModel.value = "";
155
+ unitOfMeasurementModel.value = "";
156
+
157
+ showDialog.value = false;
158
+ emit("close");
159
+ } catch (e) {
160
+ console.error("Error submitting form:", e);
161
+ } finally {
162
+ submitting.value = false;
163
+ }
164
+ }
165
+ </script>