@7365admin1/layer-common 1.10.7 → 1.10.8

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 (32) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/{AddSupplyForm.vue → AddEqupmentForm.vue} +5 -5
  3. package/components/BuildingManagement/units.vue +2 -2
  4. package/components/BuildingUnitFormAdd.vue +4 -4
  5. package/components/BuildingUnitFormEdit.vue +114 -68
  6. package/components/EntryPassInformation.vue +251 -23
  7. package/components/{CheckoutItemMain.vue → EquipmentItemMain.vue} +88 -85
  8. package/components/{SupplyManagement.vue → EquipmentManagement.vue} +3 -3
  9. package/components/Input/DateTimePicker.vue +17 -11
  10. package/components/ManageChecklistMain.vue +379 -41
  11. package/components/TableHygiene.vue +42 -452
  12. package/components/UnitPersonCard.vue +74 -14
  13. package/components/VisitorForm.vue +77 -21
  14. package/components/VisitorFormSelection.vue +13 -2
  15. package/components/VisitorManagement.vue +83 -55
  16. package/composables/useCleaningPermission.ts +7 -7
  17. package/composables/useDashboardData.ts +2 -2
  18. package/composables/{useSupply.ts → useEquipment.ts} +11 -11
  19. package/composables/{useCheckout.ts → useEquipmentItem.ts} +7 -7
  20. package/composables/{useCheckoutPermission.ts → useEquipmentItemPermission.ts} +13 -13
  21. package/composables/useEquipmentManagementPermission.ts +96 -0
  22. package/composables/{useSupplyPermission.ts → useEquipmentPermission.ts} +9 -9
  23. package/composables/useVehicle.ts +21 -2
  24. package/composables/useVisitor.ts +3 -3
  25. package/composables/useWorkOrder.ts +25 -3
  26. package/package.json +1 -1
  27. package/types/building.d.ts +1 -1
  28. package/types/{checkout-item.d.ts → equipment-item.d.ts} +3 -3
  29. package/types/{supply.d.ts → equipment.d.ts} +2 -2
  30. package/types/people.d.ts +3 -1
  31. package/types/vehicle.d.ts +2 -0
  32. package/types/visitor.d.ts +2 -1
@@ -3,22 +3,26 @@
3
3
  <v-row>
4
4
  <v-col cols="12" md="4" class="mt-2">
5
5
  <InputLabel class="font-weight-bold" title="Name" />
6
- <v-text-field :model-value="person.name" density="compact" :readonly="readOnly" :class="{'no-pointer': readOnly}"/>
6
+ <v-text-field :model-value="person.name" density="compact" :readonly="readOnly"
7
+ :class="{ 'no-pointer': readOnly }" />
7
8
  </v-col>
8
9
 
9
10
  <v-col cols="12" md="4" class="mt-2">
10
11
  <InputLabel class="font-weight-bold" title="Email Address" />
11
- <v-text-field :model-value="person.email" density="compact" :readonly="readOnly" :class="{'no-pointer': readOnly}"/>
12
+ <v-text-field :model-value="person.email" density="compact" :readonly="readOnly"
13
+ :class="{ 'no-pointer': readOnly }" />
12
14
  </v-col>
13
15
 
14
16
  <v-col cols="12" md="4" class="mt-2">
15
17
  <InputLabel class="font-weight-bold" title="NRIC" />
16
- <v-text-field :model-value="person.nric" density="compact" :readonly="readOnly" :class="{'no-pointer': readOnly}"/>
18
+ <v-text-field :model-value="person.nric" density="compact" :readonly="readOnly"
19
+ :class="{ 'no-pointer': readOnly }" />
17
20
  </v-col>
18
21
 
19
22
  <v-col cols="12" md="4" class="mt-2">
20
23
  <InputLabel class="font-weight-bold" title="Mobile Number" />
21
- <v-text-field :model-value="person.contact" density="compact" :readonly="readOnly" :class="{'no-pointer': readOnly}"/>
24
+ <v-text-field :model-value="person.contact" density="compact" :readonly="readOnly"
25
+ :class="{ 'no-pointer': readOnly }" />
22
26
  </v-col>
23
27
  </v-row>
24
28
 
@@ -26,25 +30,50 @@
26
30
  <v-col cols="12" md="4" class="mt-2">
27
31
  <InputLabel class="font-weight-bold" title="Other Documents and Files" :viewMode="readOnly" />
28
32
 
29
- <p
30
- v-if="!person.files?.length"
31
- class="text-blue-darken-2 font-weight-thin text-caption ml-2 mt-5"
32
- >
33
+ <p v-if="!person.files?.length" class="text-blue-darken-2 font-weight-thin text-caption ml-2 mt-5">
33
34
  **No documents to display**
34
35
  </p>
36
+ <v-list v-else dense nav class="mt-2">
37
+ <v-list-item v-for="file in person.files" :key="file.id" class="text-blue-darken-2"
38
+ :href="getFileUrl(file.id)" target="_blank">
39
+ <v-list-item-content>
40
+ <v-list-item-title class="d-flex align-center">
41
+ <span>
42
+ <v-icon size="18" class="mr-1">mdi-file-document-outline</v-icon>
43
+ </span>
44
+ <span> {{ file.name }}</span>
45
+ </v-list-item-title>
46
+ </v-list-item-content>
47
+ </v-list-item>
48
+ </v-list>
35
49
 
36
- <InputFileV2
37
- v-if="person.files?.length"
38
- :files="person.files"
39
- :viewMode="true"
40
- />
50
+ </v-col>
51
+
52
+ <v-col cols="0" md="4"></v-col>
53
+ <v-col v-if="prop.showOwnerOption" cols="12" md="4">
54
+ <v-checkbox :model-value="person.isOwner" readonly hide-details density="compact" @click="handleClick">
55
+ <template #label>
56
+ <span class="font-weight-bold text-subtitle-2 ml-2">Is Owner?</span>
57
+ </template>
58
+ </v-checkbox>
59
+
60
+ <span class="d-flex text-caption text-gray-5 ml-7">As owner, you will be receiving emails and updates
61
+ related to this unit.</span>
41
62
  </v-col>
42
63
  </v-row>
64
+
65
+ <v-dialog v-model="dialog.showPrompt" persistent width="540">
66
+ <DialogReusablePrompt :loading="loadingOwnerProcessing"
67
+ :prompt-title="`Are you sure want to ${person.isOwner ? 'remove' : 'add'} (${person.name}) as owner ?`"
68
+ @proceed="handleApprove(person)" @close="dialog.showPrompt = false" />
69
+ </v-dialog>
43
70
  </v-card-text>
44
71
  </template>
45
72
 
46
73
  <script setup lang="ts">
47
- defineProps({
74
+ import useFile from '../composables/useFile';
75
+
76
+ const prop = defineProps({
48
77
  person: {
49
78
  type: Object as PropType<TPeople>,
50
79
  required: true,
@@ -53,7 +82,38 @@ defineProps({
53
82
  type: Boolean,
54
83
  default: false,
55
84
  },
85
+ showOwnerOption: {
86
+ type: Boolean,
87
+ default: false,
88
+ },
89
+ loadingOwnerProcessing: {
90
+ type: Boolean,
91
+ default: false,
92
+ }
93
+ })
94
+
95
+ const { getFileUrl } = useFile()
96
+
97
+ const emit = defineEmits(['update:owner'])
98
+
99
+
100
+ const dialog = reactive({
101
+ showPrompt: false,
56
102
  })
103
+
104
+
105
+
106
+
107
+
108
+ const handleApprove = (person: TPeople) => {
109
+ dialog.showPrompt = false;
110
+ emit('update:owner', { person, isOwner: !person.isOwner })
111
+ }
112
+
113
+ const handleClick = () => {
114
+ dialog.showPrompt = true;
115
+ }
116
+
57
117
  </script>
58
118
 
59
119
  <style lang="scss" scoped>
@@ -12,7 +12,7 @@
12
12
  <div class="w-100 d-flex justify-space-between ga-2">
13
13
  <span class="text-subtitle-1 w-100 font-weight-bold mb-3">{{
14
14
  formTitle
15
- }}</span>
15
+ }}</span>
16
16
  <span v-if="prop.type === 'contractor'" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
17
17
  <span class="text-primary-button">{{ contractorStep }}</span>/3</span>
18
18
  </div>
@@ -30,7 +30,7 @@
30
30
  <v-list-item-title @click.stop="handleAddNewContractorType" class="d-flex align-center ga-1">
31
31
  <span><v-icon icon="mdi-plus" /></span> Add "<strong>{{
32
32
  contractorTypeInput
33
- }}</strong>" as custom contractor type.
33
+ }}</strong>" as custom contractor type.
34
34
  </v-list-item-title>
35
35
  </v-list-item>
36
36
  </template>
@@ -54,8 +54,9 @@
54
54
  <v-col v-if="shouldShowField('nric')" cols="12">
55
55
  <v-row>
56
56
  <v-col cols="12">
57
- <InputLabel class="text-capitalize" title="NRIC" required />
58
- <InputNRICNumber v-model="visitor.nric" density="comfortable" :rules="[requiredRule]"
57
+ <InputLabel class="text-capitalize" title="NRIC" :required="requireNRIC" />
58
+ <InputNRICNumber v-model="visitor.nric" density="comfortable"
59
+ :rules="[requireNRIC ? requiredRule : () => true]" :key="currentAutofillSource + 'nric-key'"
59
60
  @update:model-value="handleUpdateNRIC" :loading="fetchPersonByNRICPending" />
60
61
  </v-col>
61
62
  </v-row>
@@ -63,8 +64,9 @@
63
64
 
64
65
  <v-col v-if="shouldShowField('contact')" cols="12">
65
66
  <InputLabel class="text-capitalize" title="Phone Number" required />
66
- <InputPhoneNumberV2 v-model="visitor.contact" :rules="[requiredRule]" density="comfortable" :key="currentAutofillSource + 'phone-key'"
67
- :loading="fetchPersonByContactPending" @update:model-value="handleUpdateContact" />
67
+ <InputPhoneNumberV2 v-model="visitor.contact" :rules="[requiredRule]" density="comfortable"
68
+ :key="currentAutofillSource + 'phone-key'" :loading="fetchPersonByContactPending"
69
+ @update:model-value="handleUpdateContact" />
68
70
  </v-col>
69
71
 
70
72
  <v-col v-if="shouldShowField('deliveryType')" cols="12">
@@ -132,6 +134,7 @@
132
134
  :loading="levelsStatus === 'pending'" @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
133
135
  </v-col>
134
136
 
137
+
135
138
  <v-col v-if="shouldShowField('unit')" cols="12">
136
139
  <InputLabel class="text-capitalize" title="Unit" required />
137
140
  <v-select v-model="visitor.unit" :items="unitsArray" density="comfortable" item-title="title"
@@ -139,6 +142,11 @@
139
142
  @update:model-value="handleUpdateUnit" />
140
143
  </v-col>
141
144
 
145
+ <v-col v-if="shouldShowField('unit')" cols="12">
146
+ <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" />
148
+ </v-col>
149
+
142
150
  <v-col v-if="shouldShowField('remarks')" cols="12">
143
151
  <InputLabel class="text-capitalize" title="Remarks" />
144
152
  <v-textarea v-model="visitor.remarks" density="comfortable" :rows="3" no-resize />
@@ -146,7 +154,10 @@
146
154
 
147
155
  <v-col v-if="prop.type === 'contractor' && contractorStep === 2" cols="12">
148
156
  <PassInformation />
149
- <EntryPassInformation v-if="entryPassSettings?.data?.settings?.nfcPass" v-model="passType" v-model:quantity="passQuantity" v-model:cards="passCards" :settings="entryPassSettings" :loading="entryPassSettingsPending" @scan:barcode="$emit('scan:barcode')" @scan:camera="$emit('scan:camera')" />
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')" />
150
161
  </v-col>
151
162
 
152
163
  <v-col v-if="prop.type === 'contractor' && contractorStep === 3" cols="12">
@@ -179,9 +190,9 @@
179
190
  prop.type === 'contractor' &&
180
191
  contractorStep > 1
181
192
  " tile block variant="text" class="text-none" size="48" @click="handleGoToPreviousPage" text="Back" />
182
- <v-btn v-else-if="prop.mode === 'add'" tile block variant="text" class="text-none" size="48"
193
+ <v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none" size="48"
183
194
  @click="backToSelection" text="Back to Selection" />
184
- <v-btn v-else tile block variant="text" class="text-none" size="48" @click="close" text="Close" />
195
+ <v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
185
196
  </v-col>
186
197
  <v-col cols="6">
187
198
  <v-btn v-if="prop.type === 'contractor'" tile block variant="flat" color="primary-button" class="text-none"
@@ -192,14 +203,18 @@
192
203
  </v-row>
193
204
  </v-toolbar>
194
205
 
195
- <v-dialog v-model="dialog.vehicleNumberUsersList" v-if="vehicleNumberUserItems.length > 0" persistent max-width="600">
196
- <SearchVehicleNumberUser :vehicle-number="visitor.plateNumber ?? ''" :vehicle-number-users-list="vehicleNumberUserItems"
197
- @close="dialog.vehicleNumberUsersList = false" @update:people="handleAutofillDataViaVehicleNumber" />
206
+ <v-dialog v-model="dialog.vehicleNumberUsersList" v-if="vehicleNumberUserItems.length > 0" persistent
207
+ max-width="600">
208
+ <SearchVehicleNumberUser :vehicle-number="visitor.plateNumber ?? ''"
209
+ :vehicle-number-users-list="vehicleNumberUserItems" @close="dialog.vehicleNumberUsersList = false"
210
+ @update:people="handleAutofillDataViaVehicleNumber" />
198
211
  </v-dialog>
199
212
  </v-card>
200
213
  </template>
201
214
 
202
215
  <script setup lang="ts">
216
+ import useBuildingUnit from '../composables/useBuildingUnit';
217
+
203
218
 
204
219
  const prop = defineProps({
205
220
  type: {
@@ -215,7 +230,7 @@ const prop = defineProps({
215
230
  required: true,
216
231
  },
217
232
  mode: {
218
- type: String as PropType<"add" | "edit">,
233
+ type: String as PropType<"add" | "edit" | "register">,
219
234
  default: "add",
220
235
  },
221
236
  visitorData: {
@@ -233,6 +248,7 @@ const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
233
248
  const { createVisitor, typeFieldMap, contractorTypes } = useVisitor();
234
249
  const { getBySiteId: getEntryPassSettingsBySiteId } = useSiteEntryPassSettings();
235
250
  const { findPersonByNRIC, findPersonByContact, searchCompanyList, findUsersByPlateNumber } = usePeople()
251
+ const { getById: getUnitDataById} = useBuildingUnit()
236
252
 
237
253
  const emit = defineEmits([
238
254
  "back",
@@ -265,6 +281,8 @@ const passType = ref("");
265
281
  const passQuantity = ref<number | null>(null);
266
282
  const passCards = ref<string[]>([]);
267
283
 
284
+ const registeredUnitCompanyName = ref('N/A')
285
+
268
286
  const dialog = reactive({
269
287
  vehicleNumberUsersList: false,
270
288
  });
@@ -299,6 +317,10 @@ const shouldShowField = (fieldKey: keyof TVisitorPayload) => {
299
317
  }
300
318
  };
301
319
 
320
+ const requireNRIC = computed(() => {
321
+ return prop.type !== 'walk-in' && prop.type !== 'guest';
322
+ })
323
+
302
324
  const companyNames = ref<string[]>([])
303
325
  const companyAutofillDataArray = ref<{ companyName: string[], unit: string, block: number, level: string, unitName: string }[]>([])
304
326
 
@@ -417,7 +439,7 @@ watch(fetchPersonByContactReq, (obj) => {
417
439
  visitor.company = companyNames.value?.[0]
418
440
  }
419
441
  visitor.block = visitor.block ?? obj.block ?? ""
420
- visitor.level = visitor.level ?? obj.level ?? ""
442
+ visitor.level = visitor.level ?? obj.level ?? ""
421
443
  visitor.unit = visitor.unit ?? obj.unit ?? ""
422
444
  visitor.nric = visitor.nric ?? obj.nric ?? ""
423
445
 
@@ -481,15 +503,15 @@ const debounceSearchVehicleUsers = debounce(() => {
481
503
  return;
482
504
  }
483
505
  fetchVehicleNumberUserRefresh();
484
- }, 300);
506
+ }, 300);
485
507
 
486
508
  watch(() => visitor.plateNumber, (newVal) => {
487
- if(currentAutofillSource.value && currentAutofillSource.value !== "vehicleNumber") return;
509
+ if (currentAutofillSource.value && currentAutofillSource.value !== "vehicleNumber") return;
488
510
  debounceSearchVehicleUsers();
489
511
  });
490
512
 
491
513
 
492
- function handleAutofillDataViaVehicleNumber(item: TPeople){
514
+ function handleAutofillDataViaVehicleNumber(item: TPeople) {
493
515
 
494
516
  currentAutofillSource.value = "vehicleNumber";
495
517
 
@@ -620,6 +642,25 @@ watch(
620
642
  )
621
643
 
622
644
 
645
+ const { data: buildingUnitData, refresh: refreshBuildingUnitData, pending: buildingUnitDataPending } = useLazyAsyncData(`building-unit-data-${visitor.unit}`, () => {
646
+ if (!visitor.unit) return Promise.resolve(null);
647
+ return getUnitDataById(visitor.unit);
648
+ })
649
+
650
+ // trigger refresh of building unit data when unit changes, to get the latest unit name in case there are changes in the backend
651
+ watch(() => visitor.unit, () => {
652
+ if (visitor.unit) {
653
+ refreshBuildingUnitData();
654
+ } else {
655
+ registeredUnitCompanyName.value = 'N/A';
656
+ }
657
+ })
658
+
659
+ watch(buildingUnitData, (newVal: any) => {
660
+ registeredUnitCompanyName.value = newVal?.data?.buildingUnit?.companyName || 'N/A';
661
+ })
662
+
663
+
623
664
  function handleChangeBlock(value: any) {
624
665
  visitor.level = "";
625
666
  visitor.unit = "";
@@ -666,7 +707,7 @@ function close() {
666
707
  function formatVisitorType(type: TVisitorType): string {
667
708
  switch (type) {
668
709
  case "guest":
669
- return "Guest";
710
+ return "Drive-In";
670
711
  case "contractor":
671
712
  return "Contractor";
672
713
  case "delivery":
@@ -715,16 +756,22 @@ async function submit() {
715
756
  };
716
757
  }
717
758
  }
759
+ } else if (prop.mode === 'register') {
760
+ payload = {
761
+ ...payload,
762
+ status: "registered",
763
+ }
764
+ }
765
+
718
766
 
719
- if (prop.type === "contractor") {
767
+
768
+ if (prop.type === "contractor") {
720
769
  // contractor type logic payload
721
770
  payload = {
722
771
  ...payload,
723
772
  members: visitor.members,
724
773
  };
725
774
  }
726
- } else if (prop.mode === "edit") {
727
- }
728
775
  try {
729
776
  const res = await createVisitor(payload);
730
777
  if (res) {
@@ -753,10 +800,19 @@ watch(
753
800
  onMounted(() => {
754
801
  contractorStep.value = 1;
755
802
  currentAutofillSource.value = null;
803
+
804
+ if(prop.mode === 'register' && prop.visitorData) {
805
+ console.log('Register mode, prefill visitor data', prop.visitorData?.plateNumber )
806
+ visitor.plateNumber = prop.visitorData?.plateNumber || ""
807
+ }
756
808
  });
757
809
  </script>
758
810
  <style scoped>
759
811
  .button-outline-class {
760
812
  border: 1px solid rgba(var(--v-theme-primary));
761
813
  }
814
+
815
+ .no-pointer {
816
+ pointer-events: none;
817
+ }
762
818
  </style>
@@ -3,7 +3,7 @@
3
3
  <v-toolbar>
4
4
  <v-row no-gutters class="fill-height px-6" align="center">
5
5
  <span class="font-weight-bold text-h5 text-capitalize">
6
- Add Visitor
6
+ {{ mode }} Visitor
7
7
  </span>
8
8
  </v-row>
9
9
  </v-toolbar>
@@ -11,7 +11,7 @@
11
11
  <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5">
12
12
  <span class="text-subtitle-2 w-100 font-weight-medium d-flex justify-center mb-3">Please Select Visitor Type</span>
13
13
  <template v-for="item in selection" :key="item.value">
14
- <v-btn color="primary-button" block variant="flat" rounded="md" size="48" :text="item.label" @click="select(item.value)" class="my-2 text-capitalize text-subtitle-2" />
14
+ <v-btn v-if="showSelection(item)" color="primary-button" block variant="flat" rounded="md" size="48" :text="item.label" @click="select(item.value)" class="my-2 text-capitalize text-subtitle-2" />
15
15
  </template>
16
16
  </v-card-text>
17
17
 
@@ -36,6 +36,10 @@
36
36
 
37
37
  <script setup lang="ts">
38
38
  const prop = defineProps({
39
+ mode: {
40
+ type: String as PropType<"add" | "edit" | "register">,
41
+ default: "add",
42
+ }
39
43
  });
40
44
 
41
45
  const emit = defineEmits(['cancel', 'select']);
@@ -50,4 +54,11 @@ function cancel() {
50
54
  function select(value: string) {
51
55
  emit("select", value);
52
56
  }
57
+
58
+ function showSelection(item: any) {
59
+ if(item.value === 'walk-in' && prop.mode === 'register') {
60
+ return false ;
61
+ } return true;
62
+
63
+ }
53
64
  </script>