@7365admin1/layer-common 1.11.14 → 1.11.16

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.11.16
4
+
5
+ ### Patch Changes
6
+
7
+ - 59db5ff: Update Layer common register
8
+ - acbb54d: Update Visitor Management
9
+
10
+ ## 1.11.15
11
+
12
+ ### Patch Changes
13
+
14
+ - 1f2fbb4: Update Layer-commmon pacakge
15
+
3
16
  ## 1.11.14
4
17
 
5
18
  ### Patch Changes
@@ -570,7 +570,7 @@ async function handleDownloadExcel() {
570
570
  return;
571
571
  }
572
572
 
573
- const excelBuffer = await exportAreas(props.site);
573
+ const excelBuffer = await exportAreas(props.site, props.serviceType);
574
574
 
575
575
  if (!excelBuffer) {
576
576
  showMessage("Failed to download areas.", "error");
@@ -388,7 +388,7 @@ const submit = async () => {
388
388
  }
389
389
 
390
390
  if (props.serviceType) {
391
- payload[props.action].serviceType = props.serviceType;
391
+ payload.serviceType = props.serviceType;
392
392
  }
393
393
 
394
394
  emit("saved", payload);
@@ -249,6 +249,7 @@ import useEquipment from "../composables/useEquipment";
249
249
  import useStock from "../composables/useStock";
250
250
  import { useEquipmentPermission } from "../composables/useEquipmentPermission";
251
251
  import useUtils from "../composables/useUtils";
252
+ import useEquipmentManagement from "../composables/useEquipmentManagement";
252
253
 
253
254
  const props = defineProps({
254
255
  orgId: { type: String, default: "" },
@@ -497,7 +498,8 @@ async function _deleteEquipment() {
497
498
  }
498
499
  }
499
500
 
500
- const { createStock, requestItem } = useStock();
501
+ const { createStock } = useStock();
502
+ const { checkOutItemSingle } = useEquipmentManagement();
501
503
 
502
504
  const stockActionQuantity = ref<number>(0);
503
505
  const stockActionRemarks = ref("");
@@ -535,9 +537,10 @@ async function _requestItem() {
535
537
 
536
538
  const payload: TStockCreate = {
537
539
  qty: stockActionQuantity.value,
540
+ serviceType: props.serviceType,
538
541
  };
539
542
 
540
- const response = await requestItem(props.site, id, payload);
543
+ const response = await checkOutItemSingle(props.site, id, payload);
541
544
  showMessage(response?.message, "success");
542
545
  dialogStockAction.value = false;
543
546
  stockActionQuantity.value = 0;
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <v-row no-gutters class="w-100">
3
+ <v-card class="w-100">
4
+ <v-card-text>
5
+ <v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize" disabled
6
+ prepend-icon="mdi-qrcode" />
7
+ <v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
8
+ <v-row no-gutters class="pt-5 ga-2">
9
+ <v-col cols="12">
10
+ <InputLabel class="text-capitalize" title="Full Name" required />
11
+ <v-text-field v-model.trim="memberForm.name" density="compact" hide-details
12
+ :rules="[requiredRule]" />
13
+ </v-col>
14
+
15
+ <v-col cols="12">
16
+ <InputLabel class="text-capitalize" title="NRIC" required />
17
+ <InputNRICNumber v-model.trim="memberForm.nric" density="compact" hide-details
18
+ :rules="[requiredRule]" />
19
+ </v-col>
20
+
21
+ <v-col cols="12">
22
+ <v-combobox v-model="pass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
23
+ @update:focused="handleFocusedPass" :items="passItems" item-value="value"
24
+ @update:model-value="handleSelectPass" label="Pass (optional)" variant="outlined"
25
+ hide-details density="compact" persistent-hint small-chips>
26
+ <template v-slot:no-data>
27
+ <v-list-item density="compact">
28
+ <v-list-item-title v-if="passInput">
29
+ No results matching "<strong>{{ passInput }}</strong>". This value will be
30
+ added as new
31
+ option.
32
+ </v-list-item-title>
33
+ <v-list-item v-else>No data available</v-list-item>
34
+ </v-list-item>
35
+ </template>
36
+ </v-combobox>
37
+ </v-col>
38
+
39
+ <v-col cols="12">
40
+ <InputLabel class="text-capitalize" title="Phone Number" required />
41
+ <InputPhoneNumberV2 v-model="memberForm.contact" density="compact" hide-details/>
42
+ </v-col>
43
+
44
+ <v-row class="pt-3" justify="space-between">
45
+ <v-col cols="6">
46
+ <v-btn text="Add Member" :disabled="!validForm" prepend-icon="mdi-plus"
47
+ color="primary-button" class="text-capitalize" @click="handleAddMember" />
48
+ </v-col>
49
+ <v-col cols="6" align="end">
50
+ <v-btn text="Clear Form" prepend-icon="mdi-refresh" color="primary-button"
51
+ class="text-capitalize" @click.stop="handleClearForm" />
52
+ </v-col>
53
+ </v-row>
54
+ </v-row>
55
+ </v-form>
56
+ </v-card-text>
57
+ </v-card>
58
+ <v-divider class="my-3" />
59
+ <v-row v-if="members.length > 0" no-gutters class="w-100 mt-5 ga-3">
60
+ <template v-for="member, index in members" :key="member.nric">
61
+ <CardMemberInfoSummary :member="member" @remove="handleRemoveMember(index)" />
62
+ </template>
63
+ </v-row>
64
+ </v-row>
65
+ </template>
66
+
67
+ <script setup lang="ts">
68
+
69
+ const props = defineProps({
70
+ })
71
+
72
+ const { requiredRule } = useUtils()
73
+
74
+
75
+ const memberForm = reactive<TMemberInfo>({
76
+ name: "",
77
+ nric: "",
78
+ visitorPass: "",
79
+ contact: ""
80
+ })
81
+
82
+ const validForm = ref(false)
83
+ const formRef = ref<HTMLFormElement>()
84
+ const processing = ref(false);
85
+ const errorMessage = ref('')
86
+
87
+ // pass
88
+ const pass = ref()
89
+ const passInput = ref('')
90
+ const passItems = ref(['Test'])
91
+
92
+ const members = defineModel<TMemberInfo[]>({required: true, default: []})
93
+
94
+
95
+ function handleFocusedPass() {
96
+
97
+ }
98
+
99
+ function handleSelectPass(val: any) {
100
+ memberForm.visitorPass = val
101
+ }
102
+
103
+ function handleClearForm() {
104
+ formRef.value?.reset()
105
+ }
106
+
107
+ async function handleAddMember() {
108
+ members.value.push({...memberForm})
109
+ handleClearForm()
110
+ }
111
+
112
+ function handleRemoveMember(index: number){
113
+ const filtered = members.value.filter((item, qIndex) => qIndex !== index)
114
+ members.value = filtered
115
+ }
116
+
117
+
118
+
119
+ </script>
120
+
121
+ <style scoped></style>
@@ -66,6 +66,22 @@
66
66
  </template>
67
67
  </TableMain>
68
68
 
69
+ <v-dialog v-model="viewDialog" width="450" persistent>
70
+ <VehicleUpdateMoreAction title="Preview" :can-update="false" :can-delete="false"
71
+ @close="viewDialog = false">
72
+ <template v-slot:content>
73
+ <v-row no-gutters class="mb-4">
74
+ <v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
75
+ <span v-if="selectedItem?.[key] !== undefined && selectedItem?.[key] !== null && selectedItem?.[key] !== ''" class="d-flex ga-3 align-center">
76
+ <strong>{{ label }}:</strong>
77
+ {{ formatItemValue(key, selectedItem[key]) }}
78
+ </span>
79
+ </v-col>
80
+ </v-row>
81
+ </template>
82
+ </VehicleUpdateMoreAction>
83
+ </v-dialog>
84
+
69
85
  <v-dialog v-model="actionDialog.open" max-width="520" persistent>
70
86
  <v-card>
71
87
  <v-card-title class="text-subtitle-1 font-weight-bold pt-4 px-4">
@@ -109,7 +125,7 @@ const props = defineProps({
109
125
 
110
126
  });
111
127
 
112
- const emits = defineEmits(["refresh", "create", "row-click", "update:page"]);
128
+ const emits = defineEmits(["refresh", "create", "update:page"]);
113
129
 
114
130
  const searchInput = defineModel<string>("search", { default: "" });
115
131
 
@@ -124,6 +140,35 @@ const extensionHeight = ref(0)
124
140
  const offset = ref(0)
125
141
  const createLabel = "New Overnight Parking Request"
126
142
 
143
+ const viewDialog = ref(false)
144
+ const selectedItem = ref<TOvernightParkingRequest | null>(null)
145
+
146
+ const formattedFields = {
147
+ name: "Name",
148
+ email: "Email",
149
+ contact: "Contact",
150
+ isOvernightParking: "Overnight Parking",
151
+ numberOfPassengers: "No. of Passengers",
152
+ purposeOfVisit: "Purpose of Visit",
153
+ status: "Status",
154
+ remarks: "Remarks",
155
+ createdAt: "Date Requested",
156
+ } as const
157
+
158
+ function formatItemValue(key: string, value: any) {
159
+ if (value === undefined || value === null || value === '') return ''
160
+ switch (key) {
161
+ case 'createdAt':
162
+ case 'updatedAt':
163
+ return formatDateDDMMYYYYLocal(value)
164
+ case 'isOvernightParking':
165
+ return value ? 'Yes' : 'No'
166
+ case 'status':
167
+ return formatStatus(value as TOvernightParkingRequest['status']).label
168
+ }
169
+ return value
170
+ }
171
+
127
172
  const remarks = ref('')
128
173
  const actionLoading = ref(false)
129
174
  const actionDialog = reactive<{
@@ -197,7 +242,8 @@ watch(overnightParkingRequests, (data: any) => {
197
242
  // }
198
243
 
199
244
  function handleRowClick(data: { item?: TOvernightParkingRequest }) {
200
- emits("row-click", data);
245
+ selectedItem.value = data?.item ?? null
246
+ viewDialog.value = true
201
247
  }
202
248
 
203
249
  function handleUpdatePage(value: number) {
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <v-row no-gutters class="w-100">
3
+ <v-card class="w-100">
4
+ <v-card-text>
5
+ <v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize" disabled
6
+ prepend-icon="mdi-qrcode" />
7
+ <v-combobox v-model="pass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
8
+ @update:focused="handleFocusedPass" :items="passItems" :rules="props.passRules" item-title="prefixAndName" item-value="_id"
9
+ @update:model-value="handleSelectPass" label="Pass" variant="outlined" hide-details
10
+ density="compact" persistent-hint small-chips>
11
+ <template v-slot:no-data>
12
+ <v-list-item density="compact">
13
+ <v-list-item-title v-if="passInput">
14
+ No results matching "<strong>{{ passInput }}</strong>". This value will be
15
+ added as new
16
+ option.
17
+ </v-list-item-title>
18
+ <v-list-item v-else>No data available</v-list-item>
19
+ </v-list-item>
20
+ </template>
21
+ </v-combobox>
22
+
23
+ <v-divider class="my-4 w-100" />
24
+
25
+
26
+ <template v-if="selectedType">
27
+ <v-number-input v-model="count" variant="outlined" :min="1" :precision="0" density="compact" :rules="countRules" />
28
+ </template>
29
+ </v-card-text>
30
+ </v-card>
31
+ </v-row>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import type { PropType } from 'vue'
36
+ import type { ValidationRule } from 'vuetify/lib/types.mjs'
37
+ import usePassKey from '../composables/usePassKey'
38
+
39
+ const props = defineProps({
40
+ passRules: {
41
+ type: Array as PropType<ValidationRule[]>,
42
+ default: []
43
+ },
44
+ countRules: {
45
+ type: Array as PropType<ValidationRule[]>,
46
+ default: []
47
+ },
48
+ site: {
49
+ type: String,
50
+ required: true
51
+ },
52
+ type: {
53
+ type: String as PropType<TVisitorType>,
54
+ required: true
55
+ },
56
+ contractorType: {
57
+ type: String,
58
+ default: ""
59
+ }
60
+ })
61
+
62
+ const pass = ref("")
63
+ const passInput = ref('')
64
+ const passItems = ref([])
65
+ const selectedType = ref<'qr-pass' | 'nfc-card'>()
66
+ const count = ref(1)
67
+
68
+ const { getPassKeysByPageSearch } = usePassKey()
69
+
70
+ const typeItems = [
71
+ {
72
+ label: "QR Pass",
73
+ value: "qr-pass"
74
+ },
75
+ {
76
+ label: "NFC Card",
77
+ value: "nfc-card"
78
+ }
79
+ ]
80
+
81
+ const passTypesComputed = computed(() => {
82
+ if(props.type === 'contractor'){
83
+ if(props.contractorType === 'property-agent'){
84
+ return ["agent-pass"]
85
+ } else {
86
+ return ["contractor-pass"]
87
+ }
88
+ } else {
89
+ return ["visitor-pass"]
90
+ }
91
+ })
92
+
93
+ const { data: passesData, refresh: refreshPassesData, pending: fetchPassesPending } = await useLazyAsyncData('get-pass-keys', () => {
94
+ return getPassKeysByPageSearch({
95
+ search: passInput.value,
96
+ page: 1,
97
+ limit: 500,
98
+ passTypes: passTypesComputed.value,
99
+ sites: [props.site],
100
+ statuses: ["Available"]
101
+ })
102
+ })
103
+
104
+ watch(passesData, (data: any) => {
105
+ passItems.value = data?.items || []
106
+ })
107
+
108
+
109
+ function handleFocusedPass() {
110
+
111
+ }
112
+
113
+ function handleSelectPass() {
114
+
115
+ }
116
+
117
+ //prevent negative value;
118
+ watch(count, (newCount) => {
119
+ if(newCount < 1){
120
+ count.value = 1
121
+ }
122
+ })
123
+
124
+ </script>
125
+
126
+ <style scoped></style>
@@ -377,7 +377,7 @@ const submitTask = async () => {
377
377
  (props.taskData as any)?._id || (props.taskData as any)?.id;
378
378
  response = await updateScheduleTask(taskId, payload);
379
379
  } else {
380
- response = await createScheduleTask({ ...payload, site: props.site });
380
+ response = await createScheduleTask({ ...payload, site: props.site, serviceType: props.serviceType });
381
381
  }
382
382
 
383
383
  showMessage(
@@ -88,8 +88,10 @@
88
88
  :title="selectedTask?.title || 'Schedule Task Actions'"
89
89
  :canUpdate="canUpdateScheduleTask"
90
90
  @close="dialogShowMoreActions = false"
91
- :canDelete="false"
91
+ :canDelete="canDeleteScheduleTask"
92
+ :deleteButtonLabel="'Delete Schedule Task'"
92
93
  @edit="onEditFromMoreAction"
94
+ @delete="onDeleteFromMoreAction"
93
95
  :editButtonLabel="'Edit Schedule Task'"
94
96
  >
95
97
  <template #content>
@@ -198,6 +200,52 @@
198
200
  </HygieneUpdateMoreAction>
199
201
  </v-dialog>
200
202
 
203
+ <v-dialog v-model="dialogDeleteTask" max-width="450">
204
+ <v-card>
205
+ <v-toolbar>
206
+ <v-row no-gutters class="fill-height px-6" align="center">
207
+ <span class="font-weight-bold text-h6 text-capitalize">
208
+ Delete {{ selectedTask?.title || 'Schedule Task' }}
209
+ </span>
210
+ </v-row>
211
+ </v-toolbar>
212
+ <v-card-text>
213
+ <span class="text-subtitle-2">Are you sure you want to delete this schedule task?</span>
214
+ </v-card-text>
215
+
216
+ <v-toolbar class="pa-0" density="compact">
217
+ <v-row no-gutters>
218
+ <v-col cols="6" class="pa-0">
219
+ <v-btn
220
+ block
221
+ variant="text"
222
+ class="text-none"
223
+ size="large"
224
+ @click="dialogDeleteTask = false"
225
+ height="48"
226
+ >
227
+ Cancel
228
+ </v-btn>
229
+ </v-col>
230
+
231
+ <v-col cols="6" class="pa-0">
232
+ <v-btn
233
+ block
234
+ variant="flat"
235
+ color="black"
236
+ class="text-none font-weight-bold"
237
+ height="48"
238
+ :loading="submitting"
239
+ @click="_deleteScheduleTask"
240
+ >
241
+ Delete
242
+ </v-btn>
243
+ </v-col>
244
+ </v-row>
245
+ </v-toolbar>
246
+ </v-card>
247
+ </v-dialog>
248
+
201
249
  <Snackbar
202
250
  v-model="messageSnackbar"
203
251
  :text="message"
@@ -219,7 +267,7 @@ const props = defineProps({
219
267
  });
220
268
 
221
269
  const { formatDate, debounce } = useUtils();
222
- const { getScheduleTasks, getScheduleTaskById } = useScheduleTask();
270
+ const { getScheduleTasks, getScheduleTaskById, deleteScheduleTask } = useScheduleTask();
223
271
  const {
224
272
  canViewScheduleTasks,
225
273
  canCreateScheduleTask,
@@ -349,4 +397,30 @@ const onEditFromMoreAction = async () => {
349
397
  formMode.value = "edit";
350
398
  showForm.value = true;
351
399
  };
400
+
401
+ const onDeleteFromMoreAction = () => {
402
+ dialogShowMoreActions.value = false;
403
+ dialogDeleteTask.value = true;
404
+ };
405
+
406
+ async function _deleteScheduleTask() {
407
+ if (!selectedTask.value) return;
408
+
409
+ try {
410
+ submitting.value = true;
411
+ const id =
412
+ (selectedTask.value as any)._id || (selectedTask.value as any).id;
413
+ if (!id) throw new Error("Invalid schedule task id");
414
+
415
+ const response = await deleteScheduleTask(id);
416
+ dialogDeleteTask.value = false;
417
+ showMessage(response?.message, "success");
418
+
419
+ await getTaskRefresh();
420
+ } catch (error: any) {
421
+ showMessage(error?.data?.message, "error");
422
+ } finally {
423
+ submitting.value = false;
424
+ }
425
+ }
352
426
  </script>
@@ -317,7 +317,7 @@ async function handleDownloadExcel() {
317
317
  return;
318
318
  }
319
319
 
320
- const excelBuffer = await downloadUnits(props.site);
320
+ const excelBuffer = await downloadUnits(props.site, props.serviceType);
321
321
 
322
322
  if (!excelBuffer) {
323
323
  showMessage("Failed to download units.", "error");
@@ -184,7 +184,7 @@
184
184
  </v-col>
185
185
 
186
186
  <v-col v-if="prop.type === 'contractor' && contractorStep === 2" cols="12">
187
- <PassInformation />
187
+ <PassInformation :site="prop.site" :type="prop.type" :contractor-type="visitor.contractorType" />
188
188
  <EntryPassInformation
189
189
  v-if="entryPassSettings?.data?.settings?.nfcPass"
190
190
  v-model="passType"
@@ -866,7 +866,7 @@ async function submit() {
866
866
  site: prop.site,
867
867
  };
868
868
 
869
- if (prop.mode === "add") {
869
+ if (prop.mode === "add" || prop.mode === "register") {
870
870
  const allowedFields = typeFieldMap[prop.type];
871
871
  for (const key of allowedFields as (keyof TVisitorPayload)[]) {
872
872
  if (visitor[key] !== undefined) {
@@ -876,11 +876,6 @@ async function submit() {
876
876
  };
877
877
  }
878
878
  }
879
- } else if (prop.mode === 'register') {
880
- payload = {
881
- ...payload,
882
- status: "registered",
883
- }
884
879
  }
885
880
 
886
881
 
@@ -975,7 +970,7 @@ watch(
975
970
  );
976
971
 
977
972
  onMounted(() => {
978
- contractorStep.value = 1;
973
+ contractorStep.value = 2;
979
974
  currentAutofillSource.value = null;
980
975
 
981
976
  if (prop.mode === 'register' && prop.visitorData) {
@@ -22,7 +22,7 @@
22
22
  </v-checkbox>
23
23
  <InputDateTimePicker v-model:utc="dateFrom" density="compact" hide-details />
24
24
  <InputDateTimePicker v-model:utc="dateTo" density="compact" hide-details />
25
- <v-select v-if="activeTab !== 'guests'" v-model="filterTypes" label="Filter by types" item-title="label"
25
+ <v-select v-if="activeTab == 'registered'" v-model="filterTypes" label="Filter by types" item-title="label"
26
26
  item-value="value" :items="visitorSelection" density="compact" clearable multiple max-width="200"
27
27
  hide-details>
28
28
  <template v-slot:selection="{ item, index }">
@@ -50,7 +50,7 @@
50
50
  <v-icon icon="mdi-user" size="15" />
51
51
  <span v-if="item.type === 'contractor'" class="text-capitalize">{{
52
52
  formatCamelCaseToWords(item.contractorType)
53
- }}</span>
53
+ }}</span>
54
54
  <span v-else class="text-capitalize">{{ formatType(item) }}</span>
55
55
  </span>
56
56
  <span class="d-flex align-center ga-2">
@@ -88,9 +88,10 @@
88
88
  <v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
89
89
  <span class="text-capitalize">{{
90
90
  UTCToLocalTIme(item.checkIn) || "-"
91
- }}</span>
91
+ }}</span>
92
92
  <span>
93
- <v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image" @click.stop="handleViewImage(item.snapshotEntryImage)" />
93
+ <v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
94
+ @click.stop="handleViewImage(item.snapshotEntryImage)" />
94
95
  </span>
95
96
  </span>
96
97
  <span class="d-flex align-center ga-2">
@@ -98,15 +99,16 @@
98
99
  <template v-if="item.checkOut">
99
100
  <span class="text-capitalize">{{
100
101
  UTCToLocalTIme(item.checkOut) || "-"
101
- }}</span>
102
+ }}</span>
102
103
  <span>
103
- <v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image" @click.stop="handleViewImage(item.snapshotExitImage)" />
104
+ <v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
105
+ @click.stop="handleViewImage(item.snapshotExitImage)" />
104
106
  </span>
105
107
  <span v-if="item?.manualCheckout">
106
108
  <TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
107
109
  </span>
108
110
  </template>
109
- <span v-else-if="!item?.checkOut && item?.status === 'registered'">
111
+ <span v-else-if="!item?.checkOut && (item?.status === 'registered' || item?.status === 'unregistered')">
110
112
  <v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
111
113
  :loading="loading.checkingOut && item?._id === selectedVisitorId" @click.stop="handleCheckout(item._id)"
112
114
  v-if="canCheckoutVisitor" />
@@ -149,7 +151,7 @@
149
151
  </span>
150
152
 
151
153
  <span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-center"><strong>{{ label
152
- }}:</strong>
154
+ }}:</strong>
153
155
  {{ formatValues(key, selectedVisitorObject[key]) }}
154
156
  <TooltipInfo v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
155
157
  </span>
@@ -165,6 +167,18 @@
165
167
  @delete="handleProceedDeleteVisitor" />
166
168
  </v-dialog>
167
169
 
170
+ <v-dialog v-model="dialog.snapshotImage" max-width="700">
171
+ <v-card>
172
+ <v-card-title class="d-flex justify-space-between align-center">
173
+ <span>Snapshot</span>
174
+ <v-btn icon="mdi-close" variant="text" @click="dialog.snapshotImage = false" />
175
+ </v-card-title>
176
+ <v-card-text class="pa-2 d-flex justify-center">
177
+ <v-img :src="snapshotImageUrl" max-height="600" contain />
178
+ </v-card-text>
179
+ </v-card>
180
+ </v-dialog>
181
+
168
182
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
169
183
  </v-row>
170
184
  </template>
@@ -207,7 +221,7 @@ const {
207
221
  const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
208
222
  useUtils();
209
223
  const { formatLocation } = useSecurityUtils();
210
- const { getFileUrlAnpr } = useFile();
224
+ const { getFileUrlAnpr } = useFile();
211
225
  // const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
212
226
 
213
227
  const route = useRoute()
@@ -249,8 +263,11 @@ const dialog = reactive({
249
263
  showForm: false,
250
264
  viewVisitor: false,
251
265
  deleteConfirmation: false,
266
+ snapshotImage: false,
252
267
  });
253
268
 
269
+ const snapshotImageUrl = ref("");
270
+
254
271
  const headers = computed(() => [
255
272
  { title: "Name", value: "name" },
256
273
  { title: "Type/Company", value: "type-company" },
@@ -265,6 +282,7 @@ const tabOptions = [
265
282
  { name: "Registered", value: "registered" },
266
283
  { name: "Unregistered", value: "unregistered" },
267
284
  { name: "Guests", value: "guests" },
285
+ { name: "Resident Transactions", value: "resident-transactions" },
268
286
  ];
269
287
 
270
288
  const selectedVisitorDataObject = computed(() => {
@@ -333,11 +351,21 @@ const {
333
351
  checkedOut: displayNotCheckedOut.value ? false : undefined
334
352
  }
335
353
 
336
- if (activeTab.value !== "guests") {
337
- params.status = activeTab.value
338
- } else if (activeTab.value === "guests") {
354
+ // if (activeTab.value !== "guests") {
355
+ // params.status = activeTab.value
356
+ // } else if (activeTab.value === "guests") {
357
+ // params.type = "guest"
358
+ // params.status = "pending"
359
+ // }
360
+
361
+ if(activeTab.value === "guests"){
339
362
  params.type = "guest"
340
363
  params.status = "pending"
364
+ } else if (activeTab.value === "resident-transactions") {
365
+ params.status = "registered"
366
+ params.type = "resident,tenant"
367
+ } else {
368
+ params.status = activeTab.value
341
369
  }
342
370
 
343
371
  return await getVisitors(params)
@@ -447,8 +475,8 @@ function handleRegistrationUnregisteredVisitor(item: Partial<TVisitor>) {
447
475
 
448
476
  function handleViewImage(imageId: string) {
449
477
  const imageEndpoint = `${siteId}/${imageId}`;
450
- const imageUrl = getFileUrlAnpr(imageEndpoint);
451
- window.open(imageUrl, "_blank");
478
+ snapshotImageUrl.value = getFileUrlAnpr(imageEndpoint);
479
+ dialog.snapshotImage = true;
452
480
  }
453
481
 
454
482
 
@@ -74,22 +74,21 @@ export default function useAreas() {
74
74
  function uploadAreas(file: File, site: string, serviceType?: string) {
75
75
  const formData = new FormData();
76
76
  formData.append("file", file);
77
- if (serviceType) {
78
- formData.append("serviceType", serviceType);
79
- }
80
77
  return useNuxtApp().$api<Record<string, any>>(
81
78
  `/api/hygiene-areas/site/${site}/upload`,
82
79
  {
83
80
  method: "POST",
84
81
  body: formData,
82
+ query: { serviceType },
85
83
  },
86
84
  );
87
85
  }
88
86
 
89
- async function exportAreas(site: string) {
87
+ async function exportAreas(site: string, serviceType: string) {
90
88
  return useNuxtApp().$api<Blob>(`/api/hygiene-areas/site/${site}/download`, {
91
89
  method: "GET",
92
90
  responseType: "blob",
91
+ query: { serviceType },
93
92
  });
94
93
  }
95
94
 
@@ -1,5 +1,11 @@
1
1
  export default function useEquipmentManagement() {
2
- function getSupplies({ page = 1, search = "", limit = 10, site = "", serviceType = "" } = {}) {
2
+ function getSupplies({
3
+ page = 1,
4
+ search = "",
5
+ limit = 10,
6
+ site = "",
7
+ serviceType = "",
8
+ } = {}) {
3
9
  return useNuxtApp().$api<Record<string, any>>(
4
10
  `/api/hygiene-supplies/site/${site}`,
5
11
  {
@@ -53,11 +59,26 @@ export default function useEquipmentManagement() {
53
59
  );
54
60
  }
55
61
 
62
+ function checkOutItemSingle(site: string, id: string, payload: TStockCreate) {
63
+ return useNuxtApp().$api<Record<string, any>>(
64
+ `/api/hygiene-checkout-items/site/${site}/supply/${id}`,
65
+ {
66
+ method: "POST",
67
+ body: {
68
+ qty: payload.qty,
69
+ serviceType: payload.serviceType,
70
+ },
71
+ }
72
+ );
73
+ }
74
+
56
75
  return {
57
76
  getSupplies,
58
77
  getSupplyById,
59
78
  createSupply,
60
79
  updateSupply,
61
80
  deleteSupply,
81
+ checkOutItemSingle,
82
+
62
83
  };
63
84
  }
@@ -37,6 +37,7 @@ export default function useScheduleTask() {
37
37
  description?: string;
38
38
  areas: { name: string; value: string }[];
39
39
  site: string;
40
+ serviceType: string;
40
41
  }) {
41
42
  return useNuxtApp().$api<Record<string, any>>(
42
43
  `/api/hygiene-schedule-tasks/site/${payload.site}`,
@@ -48,6 +49,7 @@ export default function useScheduleTask() {
48
49
  dates: payload.dates,
49
50
  description: payload.description,
50
51
  areas: payload.areas,
52
+ serviceType: payload.serviceType,
51
53
  },
52
54
  },
53
55
  );
@@ -78,10 +80,20 @@ export default function useScheduleTask() {
78
80
  );
79
81
  }
80
82
 
83
+ function deleteScheduleTask(id: string) {
84
+ return useNuxtApp().$api<Record<string, any>>(
85
+ `/api/hygiene-schedule-tasks/id/${id}`,
86
+ {
87
+ method: "DELETE",
88
+ },
89
+ );
90
+ }
91
+
81
92
  return {
82
93
  getScheduleTasks,
83
94
  createScheduleTask,
84
95
  updateScheduleTask,
85
96
  getScheduleTaskById,
97
+ deleteScheduleTask,
86
98
  };
87
99
  }
@@ -27,21 +27,10 @@ export default function useStock() {
27
27
  );
28
28
  }
29
29
 
30
- function requestItem(site: string, id: string, payload: TStockCreate) {
31
- return useNuxtApp().$api<Record<string, any>>(
32
- `/api/hygiene-request-items/site/${site}/supply/${id}`,
33
- {
34
- method: "POST",
35
- body: {
36
- qty: payload.qty,
37
- },
38
- }
39
- );
40
- }
30
+
41
31
 
42
32
  return {
43
33
  createStock,
44
34
  getStockBySupply,
45
- requestItem,
46
35
  };
47
36
  }
@@ -57,23 +57,22 @@ export default function useUnits() {
57
57
  function uploadUnits(file: File, site: string, serviceType?: string) {
58
58
  const formData = new FormData();
59
59
  formData.append("file", file);
60
- if (serviceType) {
61
- formData.append("serviceType", serviceType);
62
- }
63
60
 
64
61
  return useNuxtApp().$api<Record<string, any>>(
65
62
  `/api/hygiene-units/site/${site}/upload`,
66
63
  {
67
64
  method: "POST",
68
65
  body: formData,
66
+ query: { serviceType },
69
67
  }
70
68
  );
71
69
  }
72
70
 
73
- function downloadUnits(site: string) {
71
+ function downloadUnits(site: string, serviceType: string) {
74
72
  return useNuxtApp().$api<Blob>(`/api/hygiene-units/site/${site}/download`, {
75
73
  method: "GET",
76
74
  responseType: "blob",
75
+ query: { serviceType },
77
76
  });
78
77
  }
79
78
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.11.14",
5
+ "version": "1.11.16",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {