@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,33 @@
1
+ <template>
2
+ <v-tooltip v-bind="$attrs" :text="text" @click.stop>
3
+ <template v-slot:activator="{ props: activatorProps }">
4
+ <v-btn icon="mdi-information-symbol" v-bind="activatorProps" class="p-0 m-0 d-flex align-center"
5
+ :density="density" :size="size" color="primary-button" />
6
+ </template>
7
+ </v-tooltip>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+
12
+ import type { VTooltip} from "vuetify/components"
13
+
14
+ type TTooltipDensity = VTooltip["$props"]["density"]
15
+
16
+ const props = defineProps({
17
+ text: {
18
+ type: String,
19
+ default: ""
20
+ },
21
+ size: {
22
+ type: String,
23
+ default: ""
24
+ },
25
+ density: {
26
+ type: String as PropType<TTooltipDensity>,
27
+ default: "default"
28
+ }
29
+ })
30
+
31
+ </script>
32
+
33
+ <style scoped></style>
@@ -110,6 +110,36 @@
110
110
  </v-col>
111
111
  </template>
112
112
 
113
+ <template v-if="shouldShowField('delivery-company')">
114
+ <v-col cols="12">
115
+ <InputLabel class="text-capitalize" title="Delivery Company" />
116
+ <v-combobox v-model="deliveryCompany" v-model:search="deliveryCompanyInput" ref="companyCombo"
117
+ autocomplete="off" :hide-no-data="false" :items="deliveryCompanyList" item-value="value"
118
+ :loading="siteDataPending" variant="outlined"
119
+ density="comfortable" persistent-hint small-chips>
120
+ <template v-slot:no-data>
121
+ <v-list-item>
122
+ <v-list-item-title v-if="fetchCompanyListPending">
123
+ <v-progress-circular indeterminate size="20" class="mr-3" />
124
+ Searching companies…
125
+ </v-list-item-title>
126
+ <v-list-item-title v-else-if="companyNameInput" @click.stop="handleAddNewCompany"
127
+ class="d-flex align-center ga-1">
128
+ <span><v-icon icon="mdi-plus" /></span>Add "<strong>{{ companyNameInput }}</strong>" as new
129
+ company.
130
+ </v-list-item-title>
131
+ <v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
132
+ Start typing to search for companies.
133
+ </v-list-item-title>
134
+ <v-list-item-title v-else>
135
+ No companies available. Start typing to add a new one.
136
+ </v-list-item-title>
137
+ </v-list-item>
138
+ </template>
139
+ </v-combobox>
140
+ </v-col>
141
+ </template>
142
+
113
143
 
114
144
  <v-col v-if="shouldShowField('plateNumber')" cols="12">
115
145
  <v-row>
@@ -144,7 +174,8 @@
144
174
 
145
175
  <v-col v-if="shouldShowField('unit')" cols="12">
146
176
  <InputLabel class="text-capitalize" title="Registered Unit Company Name" required />
147
- <v-text-field v-model.trim="registeredUnitCompanyName" density="comfortable" :loading="buildingUnitDataPending" readonly class="no-pointer" />
177
+ <v-text-field v-model.trim="registeredUnitCompanyName" density="comfortable"
178
+ :loading="buildingUnitDataPending" readonly class="no-pointer" />
148
179
  </v-col>
149
180
 
150
181
  <v-col v-if="shouldShowField('remarks')" cols="12">
@@ -154,10 +185,16 @@
154
185
 
155
186
  <v-col v-if="prop.type === 'contractor' && contractorStep === 2" cols="12">
156
187
  <PassInformation />
157
- <EntryPassInformation v-if="entryPassSettings?.data?.settings?.nfcPass" v-model="passType"
158
- v-model:quantity="passQuantity" v-model:cards="passCards" :settings="entryPassSettings"
159
- :loading="entryPassSettingsPending" @scan:barcode="$emit('scan:barcode')"
160
- @scan:camera="$emit('scan:camera')" />
188
+ <EntryPassInformation
189
+ v-if="entryPassSettings?.data?.settings?.nfcPass"
190
+ v-model="passType"
191
+ v-model:quantity="passQuantity"
192
+ v-model:cards="passCards"
193
+ :settings="entryPassSettings"
194
+ :loading="entryPassSettingsPending"
195
+ :site-id="prop.site"
196
+ :unit-id="visitor.unit || null"
197
+ />
161
198
  </v-col>
162
199
 
163
200
  <v-col v-if="prop.type === 'contractor' && contractorStep === 3" cols="12">
@@ -190,8 +227,8 @@
190
227
  prop.type === 'contractor' &&
191
228
  contractorStep > 1
192
229
  " tile block variant="text" class="text-none" size="48" @click="handleGoToPreviousPage" text="Back" />
193
- <v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none" size="48"
194
- @click="backToSelection" text="Back to Selection" />
230
+ <v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none"
231
+ size="48" @click="backToSelection" text="Back to Selection" />
195
232
  <v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
196
233
  </v-col>
197
234
  <v-col cols="6">
@@ -209,6 +246,44 @@
209
246
  :vehicle-number-users-list="vehicleNumberUserItems" @close="dialog.vehicleNumberUsersList = false"
210
247
  @update:people="handleAutofillDataViaVehicleNumber" />
211
248
  </v-dialog>
249
+
250
+ <v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="700" persistent>
251
+ <v-card :loading="loading.checkingOut">
252
+ <v-toolbar>
253
+ <v-toolbar-title>
254
+ <v-row no-gutters class="d-flex align-center justify-space-between">
255
+ <span class="font-weight-bold">
256
+ You have an unchecked-out vehicle for: {{ visitor.plateNumber }}
257
+ </span>
258
+ </v-row>
259
+ </v-toolbar-title>
260
+ </v-toolbar>
261
+
262
+ <v-card-text>
263
+
264
+ <v-list lines="three">
265
+ <v-list-item v-if="matchingPlateNumberNonCheckedOutArr.length > 0"
266
+ v-for="v in matchingPlateNumberNonCheckedOutArr" :key="v._id" class="cursor-pointer">
267
+ <v-list-item-title>
268
+ {{ v.plateNumber }} - {{ v.name }}
269
+ </v-list-item-title>
270
+
271
+ <v-list-item-subtitle>
272
+ Checked In at : {{ UTCToLocalTIme(v.checkIn) }}
273
+ </v-list-item-subtitle>
274
+
275
+ <template #append>
276
+ <v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
277
+ :loading="loading.checkingOut && v?._id === prop.visitorData?._id"
278
+ @click.stop="handleCheckout(v._id)" />
279
+ </template>
280
+
281
+ </v-list-item>
282
+ </v-list>
283
+
284
+ </v-card-text>
285
+ </v-card>
286
+ </v-dialog>
212
287
  </v-card>
213
288
  </template>
214
289
 
@@ -243,12 +318,12 @@ type AutofillSource = "nric" | "contact" | "vehicleNumber" | null;
243
318
  const currentAutofillSource = ref<AutofillSource>(null);
244
319
 
245
320
 
246
- const { requiredRule, debounce } = useUtils();
321
+ const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
247
322
  const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
248
- const { createVisitor, typeFieldMap, contractorTypes } = useVisitor();
323
+ const { createVisitor, typeFieldMap, contractorTypes, getVisitors, updateVisitor } = useVisitor();
249
324
  const { getBySiteId: getEntryPassSettingsBySiteId } = useSiteEntryPassSettings();
250
325
  const { findPersonByNRIC, findPersonByContact, searchCompanyList, findUsersByPlateNumber } = usePeople()
251
- const { getById: getUnitDataById} = useBuildingUnit()
326
+ const { getById: getUnitDataById } = useBuildingUnit()
252
327
 
253
328
  const emit = defineEmits([
254
329
  "back",
@@ -278,15 +353,20 @@ const visitor = reactive<Partial<TVisitorPayload>>({
278
353
  });
279
354
 
280
355
  const passType = ref("");
281
- const passQuantity = ref<number | null>(null);
356
+ const passQuantity = ref<number | null>(1);
282
357
  const passCards = ref<string[]>([]);
283
358
 
284
359
  const registeredUnitCompanyName = ref('N/A')
285
360
 
286
361
  const dialog = reactive({
287
362
  vehicleNumberUsersList: false,
363
+ showNonCheckedOutDialog: false,
288
364
  });
289
365
 
366
+ const loading = reactive({
367
+ checkingOut: false,
368
+ })
369
+
290
370
  const validForm = ref(false);
291
371
  const formRef = ref<HTMLFormElement | null>(null);
292
372
  const processing = ref(false);
@@ -306,11 +386,17 @@ const blocksArray = ref<TDefaultOptionObj[]>([]);
306
386
  const levelsArray = ref<TDefaultOptionObj[]>([]);
307
387
  const unitsArray = ref<TDefaultOptionObj[]>([]);
308
388
 
389
+ const deliveryCompany = ref("");
390
+ const deliveryCompanyInput = ref("");
391
+ const deliveryCompanyList = ref<string[]>([]);
392
+
393
+ const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([])
394
+
309
395
 
310
396
  const vehicleNumberUserItems = ref<TPeople[]>([])
311
397
 
312
398
 
313
- const shouldShowField = (fieldKey: keyof TVisitorPayload) => {
399
+ const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') => {
314
400
  if (prop.type !== "contractor" || contractorStep.value === 1) {
315
401
  const visibleFields = typeFieldMap[prop.type];
316
402
  return visibleFields?.includes(fieldKey);
@@ -466,25 +552,70 @@ watch(fetchCompanyListReq, (arr) => {
466
552
  }
467
553
  })
468
554
  const {
469
- data: fetchVehicleNumberUserReq,
470
- refresh: fetchVehicleNumberUserRefresh,
471
- pending: fetchVehicleNumberUserPending,
555
+ data: fetchVisitorListByVehicleNumberReq,
556
+ refresh: fetchVisitorListByVehicleNumberRefresh,
557
+ pending: fetchVisitorListByVehicleNumberPending,
472
558
 
473
- } = useLazyAsyncData(`fetch-vehicle-number-user-list`, () => {
559
+ } = useLazyAsyncData(`fetch-visitor-list-by-vehicle-number`, () => {
474
560
  if (!visitor.plateNumber) return Promise.resolve(null)
475
- return findUsersByPlateNumber(visitor.plateNumber)
561
+ return getVisitors({ page: 1, limit: 20, site: prop.site, plateNumber: visitor.plateNumber, checkedOut: false, status: "registered" })
476
562
  })
477
563
 
478
- watch(fetchVehicleNumberUserReq, (arr) => {
479
- const arrayData = arr?.data
480
- if (Array.isArray(arrayData)) {
481
- vehicleNumberUserItems.value = arrayData;
482
- if (arrayData.length > 0) {
483
- dialog.vehicleNumberUsersList = true
484
- }
564
+ watch(fetchVisitorListByVehicleNumberReq, (arr: any) => {
565
+ const itemsArray = arr?.items || []
566
+ const isValidArray = Array.isArray(itemsArray)
567
+ matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : []
568
+ if(matchingPlateNumberNonCheckedOutArr.value.length > 0) {
569
+ dialog.showNonCheckedOutDialog = true;
570
+ } else {
571
+ dialog.showNonCheckedOutDialog = false;
485
572
  }
486
573
  })
487
574
 
575
+
576
+ const debounceSearchVisitorsByPlateNumbers = debounce(() => {
577
+ if (!visitor.plateNumber) {
578
+ matchingPlateNumberNonCheckedOutArr.value = []
579
+ dialog.showNonCheckedOutDialog = false;
580
+ return;
581
+ }
582
+ fetchVisitorListByVehicleNumberRefresh();
583
+ }, 300);
584
+
585
+ watch(() => visitor.plateNumber, (newVal) => {
586
+ debounceSearchVisitorsByPlateNumbers();
587
+ });
588
+
589
+
590
+
591
+ async function handleCheckout(visitorId: string) {
592
+ if (!visitorId) {
593
+ errorMessage.value = "Invalid visitor ID. Cannot proceed with checkout.";
594
+ return;
595
+ }
596
+
597
+ try {
598
+ loading.checkingOut = true;
599
+ const res = await updateVisitor(visitorId as string, {
600
+ checkOut: new Date().toISOString(),
601
+ });
602
+ if (res) {
603
+ await fetchVisitorListByVehicleNumberRefresh();
604
+
605
+ }
606
+ } catch (error: any) {
607
+ const errorMessage = error?.response?._data?.message;
608
+ console.log("[ERROR]", error);
609
+ errorMessage.value =
610
+ errorMessage || "An error occurred while checking out the vehicle.";
611
+ } finally {
612
+ loading.checkingOut = false;
613
+ }
614
+ }
615
+
616
+
617
+
618
+
488
619
  const debounceFetchCompany = debounce(async () => fetchCompanyListRefresh(), 200)
489
620
 
490
621
  watch(companyNameInput, async (val) => {
@@ -496,19 +627,7 @@ watch(companyNameInput, async (val) => {
496
627
  })
497
628
 
498
629
 
499
- const debounceSearchVehicleUsers = debounce(() => {
500
- if (!visitor.plateNumber) {
501
- vehicleNumberUserItems.value = [];
502
- dialog.vehicleNumberUsersList = false;
503
- return;
504
- }
505
- fetchVehicleNumberUserRefresh();
506
- }, 300);
507
630
 
508
- watch(() => visitor.plateNumber, (newVal) => {
509
- if (currentAutofillSource.value && currentAutofillSource.value !== "vehicleNumber") return;
510
- debounceSearchVehicleUsers();
511
- });
512
631
 
513
632
 
514
633
  function handleAutofillDataViaVehicleNumber(item: TPeople) {
@@ -549,6 +668,7 @@ const {
549
668
  data: siteData,
550
669
  refresh: refreshSiteData,
551
670
  status: blockStatus,
671
+ pending: siteDataPending,
552
672
  } = useLazyAsyncData(`fetch-site-data-${prop.site}`, () =>
553
673
  getSiteById(prop.site)
554
674
  );
@@ -580,7 +700,7 @@ const {
580
700
  watch(
581
701
  siteData,
582
702
  (newVal) => {
583
- const siteDataValue = newVal as any;
703
+ const siteDataValue = newVal as TSite;
584
704
  if (siteDataValue) {
585
705
  const numberOfBlocks = siteDataValue.metadata?.block || 0;
586
706
  for (let i = 1; i <= numberOfBlocks; i++) {
@@ -589,6 +709,9 @@ watch(
589
709
  value: i,
590
710
  });
591
711
  }
712
+
713
+ deliveryCompanyList.value = Array.isArray(siteDataValue?.deliveryCompanyList) ? siteDataValue.deliveryCompanyList : [];
714
+
592
715
  } else {
593
716
  blocksArray.value = [];
594
717
  }
@@ -766,12 +889,20 @@ async function submit() {
766
889
 
767
890
 
768
891
  if (prop.type === "contractor") {
769
- // contractor type logic payload
770
- payload = {
771
- ...payload,
772
- members: visitor.members,
773
- };
892
+ // contractor type logic payload
893
+ payload = {
894
+ ...payload,
895
+ members: visitor.members,
896
+ };
897
+ }
898
+
899
+ if(prop.type === "delivery"){
900
+ payload = {
901
+ ...payload,
902
+ company: deliveryCompany.value
774
903
  }
904
+ }
905
+
775
906
  try {
776
907
  const res = await createVisitor(payload);
777
908
  if (res) {
@@ -801,9 +932,9 @@ onMounted(() => {
801
932
  contractorStep.value = 1;
802
933
  currentAutofillSource.value = null;
803
934
 
804
- if(prop.mode === 'register' && prop.visitorData) {
805
- console.log('Register mode, prefill visitor data', prop.visitorData?.plateNumber )
806
- visitor.plateNumber = prop.visitorData?.plateNumber || ""
935
+ if (prop.mode === 'register' && prop.visitorData) {
936
+ console.log('Register mode, prefill visitor data', prop.visitorData?.plateNumber)
937
+ visitor.plateNumber = prop.visitorData?.plateNumber || ""
807
938
  }
808
939
  });
809
940
  </script>
@@ -89,13 +89,19 @@
89
89
  <span class="text-capitalize">{{
90
90
  UTCToLocalTIme(item.checkIn) || "-"
91
91
  }}</span>
92
+ <span>
93
+ <v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image" @click.stop="handleViewImage(item.snapshotEntryImage)" />
94
+ </span>
92
95
  </span>
93
96
  <span class="d-flex align-center ga-2">
94
97
  <v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
95
98
  <template v-if="item.checkOut">
96
99
  <span class="text-capitalize">{{
97
- UTCToLocalTIme(item.checkOut) || "_"
100
+ UTCToLocalTIme(item.checkOut) || "-"
98
101
  }}</span>
102
+ <span>
103
+ <v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image" @click.stop="handleViewImage(item.snapshotExitImage)" />
104
+ </span>
99
105
  <span v-if="item?.manualCheckout">
100
106
  <TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
101
107
  </span>
@@ -120,8 +126,9 @@
120
126
  </v-dialog>
121
127
 
122
128
  <v-dialog v-model="dialog.showForm" v-if="activeVisitorFormType" width="450" persistent>
123
- <VisitorForm :mode="mode" :org="orgId" :site="siteId" :visitor-data="selectedVisitorDataObject" :type="activeVisitorFormType" @back="handleClickBack"
124
- @done="handleVisitorFormDone" @done:more="handleVisitorFormCreateMore" @close:all="handleCloseAll" />
129
+ <VisitorForm :mode="mode" :org="orgId" :site="siteId" :visitor-data="selectedVisitorDataObject"
130
+ :type="activeVisitorFormType" @back="handleClickBack" @done="handleVisitorFormDone"
131
+ @done:more="handleVisitorFormCreateMore" @close:all="handleCloseAll" />
125
132
  </v-dialog>
126
133
 
127
134
  <v-dialog v-model="dialog.viewVisitor" width="450" persistent>
@@ -200,6 +207,7 @@ const {
200
207
  const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
201
208
  useUtils();
202
209
  const { formatLocation } = useSecurityUtils();
210
+ const { getFileUrlAnpr } = useFile();
203
211
  // const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
204
212
 
205
213
  const route = useRoute()
@@ -276,8 +284,11 @@ const formattedFields = {
276
284
  level: "Level",
277
285
  unitName: "Unit",
278
286
  checkIn: "Check In",
287
+ snapshotEntryImage: "Entry Image",
279
288
  checkOut: "Check Out",
289
+ snapshotExitImage: "Exit Image",
280
290
  remarks: "Remarks",
291
+
281
292
  } as const;
282
293
 
283
294
  function filterTypeSelectionLabel() {
@@ -326,7 +337,7 @@ const {
326
337
  params.status = activeTab.value
327
338
  } else if (activeTab.value === "guests") {
328
339
  params.type = "guest"
329
- params.status = undefined
340
+ params.status = "pending"
330
341
  }
331
342
 
332
343
  return await getVisitors(params)
@@ -348,11 +359,11 @@ watch(getVisitorReq, (newData: any) => {
348
359
  });
349
360
 
350
361
  const selectedVisitorObject = computed(() => {
362
+
351
363
  const obj = items.value.find((x: any) => x?._id === selectedVisitorId.value);
352
364
  if (!obj) return {};
353
365
  const type = obj?.type as TVisitorType | undefined;
354
- if (!type) return {};
355
- let includedKeys: string[] = ["checkIn", "checkOut"];
366
+ let includedKeys: string[] = ["checkIn", "checkOut", "plateNumber", "snapshotEntryImage", "snapshotExitImage"];
356
367
  includedKeys.unshift(...(typeFieldMap[type] ?? []));
357
368
  return Object.fromEntries(
358
369
  Object.entries(obj).filter(([key]) => includedKeys.includes(key))
@@ -434,6 +445,12 @@ function handleRegistrationUnregisteredVisitor(item: Partial<TVisitor>) {
434
445
  mode.value = "register";
435
446
  }
436
447
 
448
+ function handleViewImage(imageId: string) {
449
+ const imageEndpoint = `${siteId}/${imageId}`;
450
+ const imageUrl = getFileUrlAnpr(imageEndpoint);
451
+ window.open(imageUrl, "_blank");
452
+ }
453
+
437
454
 
438
455
  async function handleProceedDeleteVisitor() {
439
456
  try {
@@ -55,10 +55,7 @@ export default function useAccessManagement() {
55
55
  );
56
56
  }
57
57
 
58
- function getAllAccessCardsCounts(params: {
59
- site: string;
60
- userType: string;
61
- }) {
58
+ function getAllAccessCardsCounts(params: { site: string; userType: string }) {
62
59
  return useNuxtApp().$api<Record<string, any>>(
63
60
  `/api/access-management/all-access-cards-counts`,
64
61
  {
@@ -71,14 +68,16 @@ export default function useAccessManagement() {
71
68
  );
72
69
  }
73
70
 
74
- function getUserTypeAccessCards(params: {
75
- page?: number;
76
- limit?: number;
77
- search?: string;
78
- organization?: string;
79
- userType?: string;
80
- site?: string;
81
- } = {}) {
71
+ function getUserTypeAccessCards(
72
+ params: {
73
+ page?: number;
74
+ limit?: number;
75
+ search?: string;
76
+ organization?: string;
77
+ userType?: string;
78
+ site?: string;
79
+ } = {}
80
+ ) {
82
81
  return useNuxtApp().$api<Record<string, any>>(
83
82
  `/api/access-management/user-type-access-cards`,
84
83
  {
@@ -200,12 +199,14 @@ export default function useAccessManagement() {
200
199
  );
201
200
  }
202
201
 
203
- function getVisitorAccessCards(params: {
204
- page?: number;
205
- limit?: number;
206
- site?: string;
207
- search?: string;
208
- } = {}) {
202
+ function getVisitorAccessCards(
203
+ params: {
204
+ page?: number;
205
+ limit?: number;
206
+ site?: string;
207
+ search?: string;
208
+ } = {}
209
+ ) {
209
210
  return useNuxtApp().$api<Record<string, any>>(
210
211
  `/api/access-management/visitor-access-cards`,
211
212
  {
@@ -221,6 +222,45 @@ export default function useAccessManagement() {
221
222
  );
222
223
  }
223
224
 
225
+ function getAvailableContractorCards(params: {
226
+ type: "NFC" | "QRCODE";
227
+ siteId: string;
228
+ unitId: string;
229
+ page?: number;
230
+ limit?: number;
231
+ }) {
232
+ return useNuxtApp().$api<Record<string, any>>(
233
+ `/api/access-management/available-card-contractor/${params.type}`,
234
+ {
235
+ method: "GET",
236
+ query: {
237
+ siteId: params.siteId,
238
+ unitId: params.unitId,
239
+ page: params.page ?? 1,
240
+ limit: params.limit ?? 100,
241
+ },
242
+ }
243
+ );
244
+ }
245
+
246
+ function generateQrVms(params: {
247
+ site: string;
248
+ unitId: string;
249
+ quantity: number;
250
+ }) {
251
+ return useNuxtApp().$api<Record<string, any>>(
252
+ `/api/access-management/generate-qr-vms`,
253
+ {
254
+ method: "POST",
255
+ body: {
256
+ site: params.site,
257
+ unitId: params.unitId,
258
+ quantity: params.quantity,
259
+ },
260
+ }
261
+ );
262
+ }
263
+
224
264
  return {
225
265
  getDoorAccessLevels,
226
266
  getLiftAccessLevels,
@@ -238,5 +278,7 @@ export default function useAccessManagement() {
238
278
  getVisitorAccessCards,
239
279
  saveVisitorAccessCardQrTag,
240
280
  getAllVisitorAccessCardsQrTags,
281
+ getAvailableContractorCards,
282
+ generateQrVms,
241
283
  };
242
284
  }
@@ -1,12 +1,17 @@
1
+ import type { APP_CONSTANTS } from "../constants/app";
2
+
1
3
  export default function(){
2
4
 
3
- const recipientList: { title: string, value: TAnnouncementRecipients }[] = [
4
- // resident | security_agency | cleaning_services | mechanical_electrical | property_management_agency
5
+ const recipientList: { title: string, value: typeof APP_CONSTANTS[keyof typeof APP_CONSTANTS] }[] = [
5
6
  { title: "Security Agency", value: "security_agency" },
6
7
  { title: "Cleaning Services", value: "cleaning_services" },
7
8
  { title: "Mechanical & Electrical", value: "mechanical_electrical" },
8
9
  { title: "Property Management Agency", value: "property_management_agency" },
9
- { title: "Resident", value: "resident" }
10
+ { title: "Resident", value: "resident" },
11
+ { title: "Pest Control", value: "pest_control_services" },
12
+ { title: "Landscaping", value: "landscaping_services" },
13
+ { title: "Pool Maintenance", value: "pool_maintenance_services" },
14
+
10
15
  ]
11
16
 
12
17
  async function add(payload: Partial<TCreateAnnouncementPayload>) {
@@ -0,0 +1,48 @@
1
+ import { useCommonPermissions } from "./useCommonPermission";
2
+
3
+ export function useBulletinBoardPermission() {
4
+ const { hasPermission } = usePermission();
5
+ const { bulletinBoardPermissions: permissions } = useCommonPermissions();
6
+
7
+ const { userAppRole } = useLocalSetup();
8
+
9
+ const canViewBulletinBoard = computed(() => {
10
+ if (!userAppRole.value) return true;
11
+ if (userAppRole.value.permissions.includes("*")) return true;
12
+ return hasPermission(userAppRole.value, permissions, "bulletin-board", "see-all-bulletin-boards");
13
+ });
14
+
15
+ const canCreateBulletinBoard = computed(() => {
16
+ if (!userAppRole.value) return true;
17
+ if (userAppRole.value.permissions.includes("*")) return true;
18
+ return hasPermission(userAppRole.value, permissions, "bulletin-board", "add-bulletin-board");
19
+ });
20
+
21
+ const canUpdateBulletinBoard = computed(() => {
22
+ if (!userAppRole.value) return true;
23
+ if (userAppRole.value.permissions.includes("*")) return true;
24
+ return hasPermission(userAppRole.value, permissions, "bulletin-board", "update-bulletin-board");
25
+ });
26
+
27
+ const canViewBulletinBoardDetails = computed(() => {
28
+ if (!userAppRole.value) return true;
29
+ if (userAppRole.value.permissions.includes("*")) return true;
30
+ return hasPermission(userAppRole.value, permissions, "bulletin-board", "see-bulletin-board-details");
31
+ });
32
+
33
+
34
+
35
+ const canDeleteBulletinBoard = computed(() => {
36
+ if (!userAppRole.value) return true;
37
+ if (userAppRole.value.permissions.includes("*")) return true;
38
+ return hasPermission(userAppRole.value, permissions, "bulletin-board", "delete-bulletin-board");
39
+ });
40
+
41
+ return {
42
+ canViewBulletinBoard,
43
+ canCreateBulletinBoard,
44
+ canUpdateBulletinBoard,
45
+ canViewBulletinBoardDetails,
46
+ canDeleteBulletinBoard,
47
+ };
48
+ }
@@ -5,6 +5,7 @@ export default function useCleaningPermission() {
5
5
  feedbackPermissions,
6
6
  workOrderPermissions,
7
7
  invitationPermissions,
8
+ bulletinBoardPermissions,
8
9
  } = useCommonPermissions();
9
10
  const permissions: TPermissions = {
10
11
  members: memberPermissions,
@@ -12,6 +13,7 @@ export default function useCleaningPermission() {
12
13
  work_order: workOrderPermissions,
13
14
  roles: rolePermissions,
14
15
  invitations: invitationPermissions,
16
+ "bulletin-board": bulletinBoardPermissions,
15
17
  inventory: {
16
18
  "view-inventory": {
17
19
  check: true,