@7365admin1/layer-common 3.0.24 → 3.0.26

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 (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/components/AccessCardAddForm.vue +2 -2
  3. package/components/AccessCardAssignToUnitForm.vue +2 -2
  4. package/components/AccessCardDetailsDialog.vue +31 -1
  5. package/components/AccessManagement.vue +16 -6
  6. package/components/AddPassKeyToVisitor.vue +346 -225
  7. package/components/BuildingUnitFormEdit.vue +1 -1
  8. package/components/DashboardMain.vue +124 -60
  9. package/components/DocumentManagement.vue +294 -1
  10. package/components/HidIntercomManagement.vue +186 -0
  11. package/components/HidReaderManagement.vue +21 -1
  12. package/components/Nfc/NFCPatrolReportMain.vue +1 -0
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +2 -1
  14. package/components/Nfc/PatrolReport/ReportFilters.vue +3 -1
  15. package/components/RolePermissionMain.vue +34 -5
  16. package/components/SiteSettings.vue +3 -1
  17. package/components/VisitorForm.vue +174 -398
  18. package/components/VisitorManagement.vue +302 -679
  19. package/components/VisitorSocketPopUp.vue +29 -1
  20. package/composables/useANPRSettings.ts +57 -8
  21. package/composables/useAccessManagement.ts +9 -1
  22. package/composables/useCleaningPermission.ts +2 -2
  23. package/composables/useCommonPermission.ts +2 -39
  24. package/composables/useDocument.ts +21 -0
  25. package/composables/useLocalAuth.ts +3 -1
  26. package/composables/useNFCPatrolLog.ts +35 -0
  27. package/composables/useNFCPatrolReport.ts +37 -4
  28. package/composables/useNFCPatrolReportFilters.ts +28 -5
  29. package/package.json +1 -1
  30. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -0
  31. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +2 -0
  32. package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +2 -0
  33. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +2 -0
  34. package/services/nfcPatrolReport.ts +5 -1
  35. package/types/nfc-patrol-report.ts +7 -1
  36. package/types/visitor.d.ts +1 -0
@@ -1,268 +1,389 @@
1
1
  <template>
2
- <v-card width="100%" :loading="processing">
3
- <v-toolbar>
4
- <v-row no-gutters class="fill-height px-6 d-flex justify-space-between align-center" align="center">
5
- <span class="font-weight-bold text-subtitle-1">{{ isEditMode ? 'Edit' : 'Assign' }} Pass &amp;
6
- Keys</span>
7
- <ButtonClose @click="emit('close')" />
8
- </v-row>
9
- </v-toolbar>
10
-
11
- <v-card-text>
12
- <v-row no-gutters class="ga-1">
13
- <v-col cols="12">
14
- <span class="text-subtitle-2 text-medium-emphasis">
15
- Name: {{ prop.visitor.name }}
16
- </span>
17
- </v-col>
18
-
19
- <!-- Pass section -->
20
- <v-col cols="12" class="mt-3">
21
- <InputLabel title="Pass" />
22
-
23
- <!-- Existing pass -->
24
- <div v-if="existingPass" class="d-flex align-center ga-2 mb-2">
25
- <v-chip prepend-icon="mdi-card-bulleted-outline" color="blue" variant="tonal" size="small"
26
- closable @click:close="existingPass = null">
27
- {{ existingPass.prefixAndName }}
28
- </v-chip>
29
- <span class="text-caption text-medium-emphasis">(current)</span>
30
- </div>
31
-
32
- <!-- New pass selector -->
33
- <v-autocomplete v-if="!existingPass" v-model="selectedPass" v-model:search="passInput"
34
- :hide-no-data="false" :items="passItems" item-title="prefixAndName" item-value="_id"
35
- variant="outlined" hide-details density="compact" small-chips :loading="fetchPassesPending"
36
- clearable>
37
- <template v-slot:chip="{ props: chipProps, item }">
38
- <v-chip v-if="selectedPass" v-bind="chipProps" prepend-icon="mdi-card-bulleted-outline"
39
- :text="item.raw?.prefixAndName" />
40
- </template>
41
- <template v-slot:no-data>
42
- <v-list-item density="compact">
43
- <v-list-item-title>No available passes</v-list-item-title>
44
- </v-list-item>
45
- </template>
46
- </v-autocomplete>
47
- </v-col>
48
-
49
- <!-- Keys section -->
50
- <v-col v-if="showKeys" cols="12" class="mt-3">
51
- <InputLabel title="Keys" />
52
-
53
- <!-- Existing keys -->
54
- <div v-if="existingKeys.length > 0" class="d-flex flex-wrap ga-1 mb-2">
55
- <v-chip v-for="key in existingKeys" :key="key.keyId" prepend-icon="mdi-key" color="orange"
56
- variant="tonal" size="small" closable @click:close="removeExistingKey(key.keyId)">
57
- {{ key.prefixAndName }}
58
- </v-chip>
59
- <span class="text-caption text-medium-emphasis align-self-center">(current)</span>
60
- </div>
61
-
62
- <!-- New keys selector -->
63
- <v-autocomplete v-model="selectedKeys" v-model:search="keyInput" :hide-no-data="false"
64
- :items="keyItems" item-title="prefixAndName" item-value="_id"
65
- :label="existingKeys.length > 0 ? 'Add more keys' : undefined" multiple variant="outlined"
66
- hide-details density="compact" small-chips :loading="fetchKeysPending">
67
- <template v-slot:chip="{ props: chipProps, item }">
68
- <v-chip v-if="selectedKeys.length > 0" v-bind="chipProps" prepend-icon="mdi-key"
69
- :text="item.raw?.prefixAndName" />
70
- </template>
71
- <template v-slot:no-data>
72
- <v-list-item density="compact">
73
- <v-list-item-title>No available keys</v-list-item-title>
74
- </v-list-item>
75
- </template>
76
- </v-autocomplete>
77
- </v-col>
78
- </v-row>
79
-
80
- <v-row v-if="errorMessage" no-gutters class="mt-2">
81
- <p class="text-error text-subtitle-2 w-100 text-center">{{ errorMessage }}</p>
82
- </v-row>
83
- </v-card-text>
84
-
85
- <v-toolbar density="compact">
86
- <v-row no-gutters>
87
- <v-col cols="6">
88
- <v-btn tile block variant="text" class="text-none" size="48" text="Cancel" @click="emit('close')" />
89
- </v-col>
90
- <v-col cols="6">
91
- <v-btn tile block variant="flat" color="black" class="text-none" size="48"
92
- :text="isEditMode ? 'Save' : 'Assign'" :disabled="!canSubmit" :loading="processing"
93
- @click="handleSubmit" />
94
- </v-col>
95
- </v-row>
96
- </v-toolbar>
97
- </v-card>
2
+ <v-card width="100%" :loading="processing">
3
+ <v-toolbar>
4
+ <v-row
5
+ no-gutters
6
+ class="fill-height px-6 d-flex justify-space-between align-center"
7
+ align="center"
8
+ >
9
+ <span class="font-weight-bold text-subtitle-1"
10
+ >{{ isEditMode ? "Edit" : "Assign" }} Pass &amp; Keys</span
11
+ >
12
+ <ButtonClose @click="emit('close')" />
13
+ </v-row>
14
+ </v-toolbar>
15
+
16
+ <v-card-text>
17
+ <v-row no-gutters class="ga-1">
18
+ <v-col cols="12">
19
+ <span class="text-subtitle-2 text-medium-emphasis">
20
+ Name: {{ prop.visitor.name }}
21
+ </span>
22
+ </v-col>
23
+
24
+ <!-- Pass section -->
25
+ <v-col cols="12" class="mt-3">
26
+ <InputLabel title="Pass" />
27
+
28
+ <!-- Existing pass -->
29
+ <div v-if="existingPass" class="d-flex align-center ga-2 mb-2">
30
+ <v-chip
31
+ prepend-icon="mdi-card-bulleted-outline"
32
+ color="blue"
33
+ variant="tonal"
34
+ size="small"
35
+ closable
36
+ @click:close="
37
+ existingPassToBeReplaced = existingPass;
38
+ existingPass = null;
39
+ "
40
+ >
41
+ {{ existingPass.prefixAndName }}
42
+ </v-chip>
43
+ <span class="text-caption text-medium-emphasis">(current)</span>
44
+ </div>
45
+
46
+ <!-- New pass selector -->
47
+ <v-autocomplete
48
+ v-if="!existingPass"
49
+ v-model="selectedPass"
50
+ v-model:search="passInput"
51
+ :hide-no-data="false"
52
+ :items="passItems"
53
+ item-title="prefixAndName"
54
+ item-value="_id"
55
+ variant="outlined"
56
+ hide-details
57
+ density="compact"
58
+ small-chips
59
+ :loading="fetchPassesPending"
60
+ clearable
61
+ >
62
+ <template v-slot:chip="{ props: chipProps, item }">
63
+ <v-chip
64
+ v-if="selectedPass"
65
+ v-bind="chipProps"
66
+ prepend-icon="mdi-card-bulleted-outline"
67
+ :text="item.raw?.prefixAndName"
68
+ />
69
+ </template>
70
+ <template v-slot:no-data>
71
+ <v-list-item density="compact">
72
+ <v-list-item-title>No available passes</v-list-item-title>
73
+ </v-list-item>
74
+ </template>
75
+ </v-autocomplete>
76
+ </v-col>
77
+
78
+ <!-- Keys section -->
79
+ <v-col v-if="showKeys" cols="12" class="mt-3">
80
+ <InputLabel title="Keys" />
81
+
82
+ <!-- Existing keys -->
83
+ <div
84
+ v-if="existingKeys.length > 0"
85
+ class="d-flex flex-wrap ga-1 mb-2"
86
+ >
87
+ <v-chip
88
+ v-for="key in existingKeys"
89
+ :key="key.keyId"
90
+ prepend-icon="mdi-key"
91
+ color="orange"
92
+ variant="tonal"
93
+ size="small"
94
+ closable
95
+ @click:close="removeExistingKey(key.keyId)"
96
+ >
97
+ {{ key.prefixAndName }}
98
+ </v-chip>
99
+ <span class="text-caption text-medium-emphasis align-self-center"
100
+ >(current)</span
101
+ >
102
+ </div>
103
+
104
+ <!-- New keys selector -->
105
+ <v-autocomplete
106
+ v-model="selectedKeys"
107
+ v-model:search="keyInput"
108
+ :hide-no-data="false"
109
+ :items="keyItems"
110
+ item-title="prefixAndName"
111
+ item-value="_id"
112
+ :label="existingKeys.length > 0 ? 'Add more keys' : undefined"
113
+ multiple
114
+ variant="outlined"
115
+ hide-details
116
+ density="compact"
117
+ small-chips
118
+ :loading="fetchKeysPending"
119
+ >
120
+ <template v-slot:chip="{ props: chipProps, item }">
121
+ <v-chip
122
+ v-if="selectedKeys.length > 0"
123
+ v-bind="chipProps"
124
+ prepend-icon="mdi-key"
125
+ :text="item.raw?.prefixAndName"
126
+ />
127
+ </template>
128
+ <template v-slot:no-data>
129
+ <v-list-item density="compact">
130
+ <v-list-item-title>No available keys</v-list-item-title>
131
+ </v-list-item>
132
+ </template>
133
+ </v-autocomplete>
134
+ </v-col>
135
+ </v-row>
136
+
137
+ <v-row v-if="errorMessage" no-gutters class="mt-2">
138
+ <p class="text-error text-subtitle-2 w-100 text-center">
139
+ {{ errorMessage }}
140
+ </p>
141
+ </v-row>
142
+ </v-card-text>
143
+
144
+ <v-toolbar density="compact">
145
+ <v-row no-gutters>
146
+ <v-col cols="6">
147
+ <v-btn
148
+ tile
149
+ block
150
+ variant="text"
151
+ class="text-none"
152
+ size="48"
153
+ text="Cancel"
154
+ @click="emit('close')"
155
+ />
156
+ </v-col>
157
+ <v-col cols="6">
158
+ <v-btn
159
+ tile
160
+ block
161
+ variant="flat"
162
+ color="black"
163
+ class="text-none"
164
+ size="48"
165
+ :text="isEditMode ? 'Save' : 'Assign'"
166
+ :disabled="!canSubmit"
167
+ :loading="processing"
168
+ @click="handleSubmit"
169
+ />
170
+ </v-col>
171
+ </v-row>
172
+ </v-toolbar>
173
+ </v-card>
98
174
  </template>
99
175
 
100
176
  <script setup lang="ts">
101
- import type { PropType } from 'vue'
102
- import usePassKey from '../composables/usePassKey'
103
- import useVisitor from '../composables/useVisitor'
177
+ import type { PropType } from "vue";
178
+ import usePassKey from "../composables/usePassKey";
179
+ import useVisitor from "../composables/useVisitor";
104
180
 
105
181
  const prop = defineProps({
106
- visitor: {
107
- type: Object as PropType<TVisitor>,
108
- required: true,
109
- },
110
- site: {
111
- type: String,
112
- required: true,
113
- },
114
- mode: {
115
- type: String as PropType<'add' | 'edit'>,
116
- default: 'add',
117
- },
118
- })
182
+ visitor: {
183
+ type: Object as PropType<TVisitor>,
184
+ required: true,
185
+ },
186
+ site: {
187
+ type: String,
188
+ required: true,
189
+ },
190
+ mode: {
191
+ type: String as PropType<"add" | "edit">,
192
+ default: "add",
193
+ },
194
+ });
119
195
 
120
196
  const emit = defineEmits<{
121
- (e: 'done'): void
122
- (e: 'close'): void
123
- }>()
197
+ (e: "done"): void;
198
+ (e: "close"): void;
199
+ }>();
124
200
 
125
- const { getPassKeysByPageSearch } = usePassKey()
126
- const { updateVisitorPassKey } = useVisitor()
201
+ const { getPassKeysByPageSearch } = usePassKey();
202
+ const { updateVisitor } = useVisitor();
127
203
 
128
- const processing = ref(false)
129
- const errorMessage = ref('')
204
+ const processing = ref(false);
205
+ const errorMessage = ref("");
130
206
 
131
207
  // New selections
132
- const selectedPass = ref<string>('')
133
- const selectedKeys = ref<string[]>([])
134
- const passInput = ref('')
135
- const keyInput = ref('')
136
- const passItems = ref<TPassKey[]>([])
137
- const keyItems = ref<TPassKey[]>([])
208
+ const selectedPass = ref<string>("");
209
+ const selectedKeys = ref<string[]>([]);
210
+ const passInput = ref("");
211
+ const keyInput = ref("");
212
+ const passItems = ref<TPassKey[]>([]);
213
+ const keyItems = ref<TPassKey[]>([]);
138
214
 
139
215
  // Existing pass/keys (editable)
140
- const existingPass = ref<{ keyId: string; prefixAndName: string } | null>(null)
141
- const existingKeys = ref<{ keyId: string; prefixAndName: string }[]>([])
216
+ const existingPass = ref<{ keyId: string; prefixAndName: string } | null>(null);
217
+ const existingPassToBeReplaced = ref<{
218
+ keyId: string;
219
+ prefixAndName: string;
220
+ } | null>(null);
221
+ const existingKeys = ref<{ keyId: string; prefixAndName: string }[]>([]);
142
222
 
143
- const isEditMode = computed(() => prop.mode === 'edit')
144
- const showKeys = computed(() => prop.visitor.type === 'contractor')
223
+ const isEditMode = computed(() => prop.mode === "edit");
224
+ const showKeys = computed(() => prop.visitor.type === "contractor");
145
225
 
146
226
  const passTypesComputed = computed(() => {
147
- const type = prop.visitor.type
148
- const contractorType = (prop.visitor as any).contractorType
149
- if (type === 'contractor') {
150
- return contractorType === 'property-agent' ? ['agent-pass'] : ['contractor-pass']
151
- }
152
- return ['visitor-pass']
153
- })
227
+ const type = prop.visitor.type;
228
+ const contractorType = (prop.visitor as any).contractorType;
229
+ if (type === "contractor") {
230
+ return contractorType === "property-agent"
231
+ ? ["agent-pass"]
232
+ : ["contractor-pass"];
233
+ }
234
+ return ["visitor-pass"];
235
+ });
154
236
 
155
237
  // Initialise existing values in edit mode
156
238
  onMounted(() => {
157
- if (isEditMode.value) {
158
- const vPass = prop.visitor.visitorPass
159
- if (Array.isArray(vPass) && vPass.length > 0) {
160
- const p = vPass[0] as any
161
- existingPass.value = { keyId: p.keyId, prefixAndName: p.prefixAndName }
162
- }
163
- const vKeys = prop.visitor.passKeys
164
- if (Array.isArray(vKeys) && vKeys.length > 0) {
165
- existingKeys.value = (vKeys as any[]).map(k => ({ keyId: k.keyId, prefixAndName: k.prefixAndName }))
166
- }
239
+ if (isEditMode.value) {
240
+ const vPass = prop.visitor.visitorPass;
241
+ if (Array.isArray(vPass) && vPass.length > 0) {
242
+ const p = vPass[0] as any;
243
+ existingPass.value = { keyId: p.keyId, prefixAndName: p.prefixAndName };
244
+ }
245
+ const vKeys = prop.visitor.passKeys;
246
+ if (Array.isArray(vKeys) && vKeys.length > 0) {
247
+ existingKeys.value = (vKeys as any[]).map((k) => ({
248
+ keyId: k.keyId,
249
+ prefixAndName: k.prefixAndName,
250
+ }));
167
251
  }
168
- })
252
+ }
253
+ });
169
254
 
170
255
  function removeExistingKey(keyId: string) {
171
- existingKeys.value = existingKeys.value.filter(k => k.keyId !== keyId)
256
+ existingKeys.value = existingKeys.value.filter((k) => k.keyId !== keyId);
172
257
  }
173
258
 
174
259
  // Exclude already-held pass/keys from selectable lists
175
- const existingPassId = computed(() => existingPass.value?.keyId)
176
- const existingKeyIds = computed(() => existingKeys.value.map(k => k.keyId))
177
-
178
- const { data: passesData, refresh: refreshPasses, pending: fetchPassesPending } = await useLazyAsyncData(
179
- `add-pass-key-visitor-passes-${prop.visitor._id}`,
180
- () => getPassKeysByPageSearch({
181
- search: passInput.value,
182
- page: 1,
183
- limit: 500,
184
- passTypes: passTypesComputed.value,
185
- sites: [prop.site],
186
- statuses: ['Available'],
260
+ const existingPassId = computed(() => existingPass.value?.keyId);
261
+ const existingKeyIds = computed(() => existingKeys.value.map((k) => k.keyId));
262
+
263
+ const {
264
+ data: passesData,
265
+ refresh: refreshPasses,
266
+ pending: fetchPassesPending,
267
+ } = await useLazyAsyncData(
268
+ `add-pass-key-visitor-passes-${prop.visitor._id}`,
269
+ () =>
270
+ getPassKeysByPageSearch({
271
+ search: passInput.value,
272
+ page: 1,
273
+ limit: 500,
274
+ passTypes: passTypesComputed.value,
275
+ sites: [prop.site],
276
+ statuses: ["Available"],
187
277
  })
188
- )
189
-
190
- const { data: keysData, refresh: refreshKeys, pending: fetchKeysPending } = await useLazyAsyncData(
191
- `add-pass-key-visitor-keys-${prop.visitor._id}`,
192
- () => getPassKeysByPageSearch({
193
- search: keyInput.value,
194
- page: 1,
195
- limit: 500,
196
- passTypes: ['pass-key'],
197
- sites: [prop.site],
198
- statuses: ['Available'],
278
+ );
279
+
280
+ const {
281
+ data: keysData,
282
+ refresh: refreshKeys,
283
+ pending: fetchKeysPending,
284
+ } = await useLazyAsyncData(
285
+ `add-pass-key-visitor-keys-${prop.visitor._id}`,
286
+ () =>
287
+ getPassKeysByPageSearch({
288
+ search: keyInput.value,
289
+ page: 1,
290
+ limit: 500,
291
+ passTypes: ["pass-key"],
292
+ sites: [prop.site],
293
+ statuses: ["Available"],
199
294
  })
200
- )
295
+ );
201
296
 
202
297
  watch(passesData, (data: any) => {
203
- const all: TPassKey[] = Array.isArray(data?.items) ? data.items : []
204
- passItems.value = all.filter(p => p._id !== existingPassId.value)
205
- })
298
+ const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
299
+ passItems.value = all.filter((p) => p._id !== existingPassId.value);
300
+ });
206
301
 
207
302
  watch(keysData, (data: any) => {
208
- const all: TPassKey[] = Array.isArray(data?.items) ? data.items : []
209
- keyItems.value = all.filter(k => !existingKeyIds.value.includes(k._id!))
210
- })
303
+ const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
304
+ keyItems.value = all.filter((k) => !existingKeyIds.value.includes(k._id!));
305
+ });
211
306
 
212
307
  // Re-filter when existing items change
213
- watch([existingPassId, existingKeyIds], () => {
214
- const rawPasses = (passesData.value as any)?.items ?? []
215
- passItems.value = rawPasses.filter((p: TPassKey) => p._id !== existingPassId.value)
216
- const rawKeys = (keysData.value as any)?.items ?? []
217
- keyItems.value = rawKeys.filter((k: TPassKey) => !existingKeyIds.value.includes(k._id!))
218
- }, { deep: true })
308
+ watch(
309
+ [existingPassId, existingKeyIds],
310
+ () => {
311
+ const rawPasses = (passesData.value as any)?.items ?? [];
312
+ passItems.value = rawPasses.filter(
313
+ (p: TPassKey) => p._id !== existingPassId.value
314
+ );
315
+ const rawKeys = (keysData.value as any)?.items ?? [];
316
+ keyItems.value = rawKeys.filter(
317
+ (k: TPassKey) => !existingKeyIds.value.includes(k._id!)
318
+ );
319
+ },
320
+ { deep: true }
321
+ );
219
322
 
220
323
  const canSubmit = computed(() => {
221
- if (isEditMode.value) {
222
- // allow save if anything changed: existing items modified OR new items selected
223
- return existingPass.value !== null || selectedPass.value ||
224
- existingKeys.value.length > 0 || selectedKeys.value.length > 0
225
- }
226
- return !!selectedPass.value || selectedKeys.value.length > 0
227
- })
324
+ if (isEditMode.value) {
325
+ // allow save if anything changed: existing items modified OR new items selected
326
+ return (
327
+ existingPass.value !== null ||
328
+ selectedPass.value ||
329
+ existingKeys.value.length > 0 ||
330
+ selectedKeys.value.length > 0
331
+ );
332
+ }
333
+ return !!selectedPass.value || selectedKeys.value.length > 0;
334
+ });
228
335
 
229
336
  async function handleSubmit() {
230
- if (!prop.visitor._id) return
231
- errorMessage.value = ''
232
- processing.value = true
233
-
234
- try {
235
- const payload: any = {}
236
-
237
- // Build final pass list: kept existing + newly selected
238
- const finalPasses: { keyId: string }[] = []
239
- if (existingPass.value) {
240
- finalPasses.push({ keyId: existingPass.value.keyId })
241
- }
242
- if (selectedPass.value) {
243
- finalPasses.push({ keyId: selectedPass.value})
244
- }
245
- payload.visitorPass = finalPasses
246
-
247
- // Build final keys list: kept existing + newly selected
248
- if (showKeys.value) {
249
- const finalKeys: { keyId: string}[] = [
250
- ...existingKeys.value.map(k => ({ keyId: k.keyId })),
251
- ...selectedKeys.value.map(keyId => ({ keyId })),
252
- ]
253
- payload.passKeys = finalKeys
254
- }
255
-
256
- await updateVisitorPassKey(prop.visitor._id, payload)
257
- emit('done')
258
- emit('close')
259
- } catch (error: any) {
260
- errorMessage.value = error?.data?.message || 'Failed to save pass & keys. Please try again.'
261
- } finally {
262
- processing.value = false
337
+ if (!prop.visitor._id) return;
338
+ errorMessage.value = "";
339
+ processing.value = true;
340
+
341
+ try {
342
+ let payload: any = {};
343
+
344
+ // Build final pass list: kept existing + newly selected
345
+ const finalPasses: { keyId: string; status?: string; add?: boolean }[] = [];
346
+ if (existingPass.value) {
347
+ finalPasses.push({ keyId: existingPass.value.keyId, status: "Removed" });
263
348
  }
349
+ if (existingPassToBeReplaced.value) {
350
+ finalPasses.push({
351
+ keyId: existingPassToBeReplaced.value.keyId,
352
+ status: "Removed",
353
+ });
354
+ }
355
+ if (selectedPass.value) {
356
+ finalPasses.push({
357
+ keyId: selectedPass.value,
358
+ status: "Not Returned",
359
+ add: true,
360
+ });
361
+ }
362
+ payload.visitorPass = finalPasses;
363
+
364
+ // Build final keys list: kept existing + newly selected
365
+ if (showKeys.value) {
366
+ const finalKeys: { keyId: string; status?: string; add?: boolean }[] = [
367
+ ...existingKeys.value.map((k) => ({ keyId: k.keyId })),
368
+ ...selectedKeys.value.map((keyId) => ({
369
+ keyId,
370
+ status: "Not Returned",
371
+ add: true,
372
+ })),
373
+ ];
374
+ payload.passKeys = finalKeys;
375
+ }
376
+
377
+ await updateVisitor(prop.visitor._id, payload);
378
+ emit("done");
379
+ emit("close");
380
+ } catch (error: any) {
381
+ errorMessage.value =
382
+ error?.data?.message || "Failed to save pass & keys. Please try again.";
383
+ } finally {
384
+ processing.value = false;
385
+ }
264
386
  }
265
387
  </script>
266
388
 
267
389
  <style scoped></style>
268
-
@@ -427,7 +427,7 @@ watchEffect(() => {
427
427
 
428
428
  const { data: getUnitPeople, refresh: getUnitPeopleRefresh, pending: getUnitPeoplePending } = useLazyAsyncData(
429
429
  "get-unit-people",
430
- async () => await getPeopleByUnit(buildingUnit.value._id, { limit: 10, site: prop.site })
430
+ async () => await getPeopleByUnit(buildingUnit.value._id, { limit: 10, site: prop.site, type: "resident,tenant" })
431
431
  );
432
432
 
433
433
  watch(getUnitPeople, (newData: TPeople[]) => {