@7365admin1/layer-common 1.11.17 → 1.11.19

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,17 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.11.19
4
+
5
+ ### Patch Changes
6
+
7
+ - e340731: Update Layer-common
8
+
9
+ ## 1.11.18
10
+
11
+ ### Patch Changes
12
+
13
+ - 5e21af9: updates and fixes
14
+
3
15
  ## 1.11.17
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,207 @@
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">Assign Pass & Keys</span>
6
+ <ButtonClose @click="emit('close')" />
7
+ </v-row>
8
+ </v-toolbar>
9
+
10
+ <v-card-text>
11
+ <v-row no-gutters class="ga-1">
12
+ <v-col cols="12">
13
+ <span class="text-subtitle-2 text-medium-emphasis">
14
+ Name: {{ prop.visitor.name }}
15
+ </span>
16
+ </v-col>
17
+
18
+ <v-col cols="12" class="mt-3">
19
+ <InputLabel title="Pass" />
20
+ <v-autocomplete
21
+ v-model="selectedPass"
22
+ v-model:search="passInput"
23
+ :hide-no-data="false"
24
+ :items="passItems"
25
+ item-title="prefixAndName"
26
+ item-value="_id"
27
+ variant="outlined"
28
+ hide-details
29
+ density="compact"
30
+ small-chips
31
+ :loading="fetchPassesPending"
32
+ >
33
+ <template v-slot:chip="{ props: chipProps, item }">
34
+ <v-chip v-if="selectedPass" v-bind="chipProps" prepend-icon="mdi-card-bulleted-outline"
35
+ :text="item.raw?.prefixAndName" />
36
+ </template>
37
+ <template v-slot:no-data>
38
+ <v-list-item density="compact">
39
+ <v-list-item-title>No available passes</v-list-item-title>
40
+ </v-list-item>
41
+ </template>
42
+ </v-autocomplete>
43
+ </v-col>
44
+
45
+ <v-col v-if="showKeys" cols="12" class="mt-3">
46
+ <InputLabel title="Keys" />
47
+ <v-autocomplete
48
+ v-model="selectedKeys"
49
+ v-model:search="keyInput"
50
+ :hide-no-data="false"
51
+ :items="keyItems"
52
+ item-title="prefixAndName"
53
+ item-value="_id"
54
+ multiple
55
+ variant="outlined"
56
+ hide-details
57
+ density="compact"
58
+ small-chips
59
+ :loading="fetchKeysPending"
60
+ >
61
+ <template v-slot:chip="{ props: chipProps, item }">
62
+ <v-chip v-if="selectedKeys.length > 0" v-bind="chipProps" prepend-icon="mdi-key"
63
+ :text="item.raw?.prefixAndName" />
64
+ </template>
65
+ <template v-slot:no-data>
66
+ <v-list-item density="compact">
67
+ <v-list-item-title>No available keys</v-list-item-title>
68
+ </v-list-item>
69
+ </template>
70
+ </v-autocomplete>
71
+ </v-col>
72
+ </v-row>
73
+
74
+ <v-row v-if="errorMessage" no-gutters class="mt-2">
75
+ <p class="text-error text-subtitle-2 w-100 text-center">{{ errorMessage }}</p>
76
+ </v-row>
77
+ </v-card-text>
78
+
79
+ <v-toolbar density="compact">
80
+ <v-row no-gutters>
81
+ <v-col cols="6">
82
+ <v-btn tile block variant="text" class="text-none" size="48" text="Cancel" @click="emit('close')" />
83
+ </v-col>
84
+ <v-col cols="6">
85
+ <v-btn
86
+ tile block variant="flat" color="black" class="text-none" size="48"
87
+ text="Assign"
88
+ :disabled="!selectedPass && selectedKeys.length === 0"
89
+ :loading="processing"
90
+ @click="handleSubmit"
91
+ />
92
+ </v-col>
93
+ </v-row>
94
+ </v-toolbar>
95
+ </v-card>
96
+ </template>
97
+
98
+ <script setup lang="ts">
99
+ import type { PropType } from 'vue'
100
+ import usePassKey from '../composables/usePassKey'
101
+ import useVisitor from '../composables/useVisitor'
102
+
103
+ const prop = defineProps({
104
+ visitor: {
105
+ type: Object as PropType<TVisitor>,
106
+ required: true,
107
+ },
108
+ site: {
109
+ type: String,
110
+ required: true,
111
+ },
112
+ type: {
113
+ type: String as PropType<TVisitorType>,
114
+ required: true,
115
+ },
116
+ contractorType: {
117
+ type: String,
118
+ default: '',
119
+ },
120
+ })
121
+
122
+ const emit = defineEmits<{
123
+ (e: 'done'): void
124
+ (e: 'close'): void
125
+ }>()
126
+
127
+ const { getPassKeysByPageSearch } = usePassKey()
128
+ const { updateVisitor } = useVisitor()
129
+
130
+ const processing = ref(false)
131
+ const errorMessage = ref('')
132
+
133
+ const selectedPass = ref<string>('')
134
+ const selectedKeys = ref<string[]>([])
135
+ const passInput = ref('')
136
+ const keyInput = ref('')
137
+ const passItems = ref<TPassKey[]>([])
138
+ const keyItems = ref<TPassKey[]>([])
139
+
140
+ const showKeys = computed(() => prop.visitor.type === 'contractor')
141
+
142
+ const passTypesComputed = computed(() => {
143
+ if (prop.type === 'contractor') {
144
+ return prop.contractorType === 'property-agent' ? ['agent-pass'] : ['contractor-pass']
145
+ }
146
+ return ['visitor-pass']
147
+ })
148
+
149
+ const { data: passesData, pending: fetchPassesPending } = await useLazyAsyncData(
150
+ `add-pass-key-visitor-passes-${prop.visitor._id}`,
151
+ () => getPassKeysByPageSearch({
152
+ search: passInput.value,
153
+ page: 1,
154
+ limit: 500,
155
+ passTypes: passTypesComputed.value,
156
+ sites: [prop.site],
157
+ statuses: ['Available'],
158
+ })
159
+ )
160
+
161
+ const { data: keysData, pending: fetchKeysPending } = await useLazyAsyncData(
162
+ `add-pass-key-visitor-keys-${prop.visitor._id}`,
163
+ () => getPassKeysByPageSearch({
164
+ search: keyInput.value,
165
+ page: 1,
166
+ limit: 500,
167
+ passTypes: ['pass-key'],
168
+ sites: [prop.site],
169
+ statuses: ['Available'],
170
+ })
171
+ )
172
+
173
+ watch(passesData, (data: any) => {
174
+ passItems.value = Array.isArray(data?.items) ? data.items : []
175
+ })
176
+
177
+ watch(keysData, (data: any) => {
178
+ keyItems.value = Array.isArray(data?.items) ? data.items : []
179
+ })
180
+
181
+ async function handleSubmit() {
182
+ if (!prop.visitor._id) return
183
+ errorMessage.value = ''
184
+ processing.value = true
185
+
186
+ try {
187
+ const payload: Partial<TVisitorPayload> = {}
188
+
189
+ if (selectedPass.value) {
190
+ payload.visitorPass = [{ keyId: selectedPass.value, status: "In Use" }]
191
+ }
192
+
193
+ if (selectedKeys.value.length > 0) {
194
+ payload.passKeys = selectedKeys.value.map(keyId => ({ keyId, status: "In Use" }))
195
+ }
196
+
197
+ await updateVisitor(prop.visitor._id, payload)
198
+ emit('done')
199
+ } catch (error: any) {
200
+ errorMessage.value = error?.data?.message || 'Failed to assign pass & keys. Please try again.'
201
+ } finally {
202
+ processing.value = false
203
+ }
204
+ }
205
+ </script>
206
+
207
+ <style scoped></style>
@@ -227,6 +227,17 @@
227
227
 
228
228
  </v-row>
229
229
  </v-col>
230
+
231
+ <v-col cols="12" class="mt-2">
232
+ <InputLabel class="text-capitalize d-block mb-1" title="Unit Documents" />
233
+ <InputFileV2
234
+ v-model="buildingUnit.buildingUnitFiles"
235
+ :multiple="true"
236
+ accept="*/*"
237
+ title="Upload documents"
238
+ :height="104"
239
+ />
240
+ </v-col>
230
241
  </v-row>
231
242
  </template>
232
243
  </v-card-text>
@@ -331,6 +342,35 @@ const buildingUnit = ref({
331
342
 
332
343
  buildingUnit.value = JSON.parse(JSON.stringify(prop.roomFacility));
333
344
 
345
+ // Normalize buildingUnitFiles: extract IDs for InputFileV2, keep names in a separate map
346
+ const buildingUnitFilesNames = ref<Record<string, string>>({});
347
+ const rawFiles = buildingUnit.value.buildingUnitFiles as any[];
348
+ if (rawFiles?.length && typeof rawFiles[0] === 'object') {
349
+ rawFiles.forEach((f: { id: string; name: string }) => {
350
+ buildingUnitFilesNames.value[f.id] = f.name ?? "";
351
+ });
352
+ buildingUnit.value.buildingUnitFiles = rawFiles.map((f: { id: string }) => f.id);
353
+ }
354
+
355
+ const { getFileById } = useFile();
356
+
357
+ watch(
358
+ () => buildingUnit.value.buildingUnitFiles as string[],
359
+ async (ids) => {
360
+ for (const id of ids) {
361
+ if (!buildingUnitFilesNames.value[id]) {
362
+ try {
363
+ const meta = await getFileById(id) as any;
364
+ buildingUnitFilesNames.value[id] = meta?.data?.name ?? meta?.name ?? "";
365
+ } catch {
366
+ buildingUnitFilesNames.value[id] = "";
367
+ }
368
+ }
369
+ }
370
+ },
371
+ { deep: true }
372
+ );
373
+
334
374
  const emit = defineEmits(["cancel", "success", "success:create-more", "delete-unit"]);
335
375
 
336
376
 
@@ -462,7 +502,7 @@ async function submit() {
462
502
  name: buildingUnit.value.name,
463
503
  level: buildingUnit.value.level,
464
504
  // category: buildingUnit.value.category,
465
- buildingUnitFiles: buildingUnit.value.buildingUnitFiles || [],
505
+ buildingUnitFiles: (buildingUnit.value.buildingUnitFiles as string[] || []).map((id) => ({ id, name: buildingUnitFilesNames.value[id] ?? "" })),
466
506
  companyName: buildingUnit.value.companyName,
467
507
  companyRegistrationNumber: buildingUnit.value.companyRegistrationNumber || "",
468
508
  leaseStart: buildingUnit.value.leaseStart,
@@ -26,7 +26,11 @@ const prop = defineProps({
26
26
  member: {
27
27
  type: Object as PropType<TMemberInfo>,
28
28
  required: true
29
- }
29
+ },
30
+ visitorPassItems: {
31
+ type: Array as PropType<TPassKey[]>,
32
+ default: () => []
33
+ }
30
34
  })
31
35
 
32
36
  const emit = defineEmits(['remove'])
@@ -77,7 +77,7 @@ const props = defineProps({
77
77
  }
78
78
  })
79
79
 
80
- const { addFile, deleteFile, getFileUrl, urlToFile } = useFile()
80
+ const { addFile, deleteFile, getFileUrl, getFileById, urlToFile } = useFile()
81
81
 
82
82
  const showImageCarousel = ref(false)
83
83
  const activeImageId = ref("")
@@ -187,8 +187,9 @@ async function downloadFile(id: string, filename: string) {
187
187
  const result: { file: File; id: string }[] = []
188
188
  for (const id of ids) {
189
189
  try {
190
- const url = await getFileUrl(id)
191
- const name = decodeURIComponent(url.split('/').pop() || `file_${id}`)
190
+ const meta = await getFileById(id) as any
191
+ const name: string = meta?.data?.name || meta?.name || `file_${id}`
192
+ const url = getFileUrl(id)
192
193
  const file = await urlToFile(url, name)
193
194
  result.push({ file, id })
194
195
  } catch (err) {
@@ -19,9 +19,9 @@
19
19
  </v-col>
20
20
 
21
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"
22
+ <v-autocomplete v-model="selectedPass" v-model:search="passInput"
23
+ :hide-no-data="false" class="mt-3" :items="passItemsFilteredFinal"
24
+ item-title="prefixAndName" item-value="_id" label="Pass (optional)" variant="outlined"
25
25
  hide-details density="compact" persistent-hint small-chips>
26
26
  <template v-slot:no-data>
27
27
  <v-list-item density="compact">
@@ -33,12 +33,19 @@
33
33
  <v-list-item v-else>No data available</v-list-item>
34
34
  </v-list-item>
35
35
  </template>
36
- </v-combobox>
36
+
37
+ <template v-slot:chip="{ props, item }">
38
+ <v-chip v-if="selectedPass" v-bind="props"
39
+ prepend-icon="mdi-card-bulleted-outline"
40
+ :text="item.raw?.prefixAndName"></v-chip>
41
+ </template>
42
+ </v-autocomplete>
37
43
  </v-col>
38
44
 
39
45
  <v-col cols="12">
40
46
  <InputLabel class="text-capitalize" title="Phone Number" required />
41
- <InputPhoneNumberV2 v-model="memberForm.contact" density="compact" hide-details/>
47
+ <InputPhoneNumberV2 v-model="memberForm.contact" density="compact" default-country="SG"
48
+ hide-details />
42
49
  </v-col>
43
50
 
44
51
  <v-row class="pt-3" justify="space-between">
@@ -56,20 +63,41 @@
56
63
  </v-card-text>
57
64
  </v-card>
58
65
  <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)" />
66
+ <v-row v-if="committedMembers.length > 0" no-gutters class="w-100 mt-5 ga-3">
67
+ <template v-for="member, index in committedMembers" :key="member.nric">
68
+ <CardMemberInfoSummary :member="membersDisplayed[index]" @remove="handleRemoveMember(index)" />
62
69
  </template>
63
70
  </v-row>
64
71
  </v-row>
65
72
  </template>
66
73
 
67
74
  <script setup lang="ts">
75
+ import type { PropType } from 'vue'
76
+ import usePassKey from '../composables/usePassKey'
77
+ import useUtils from '../composables/useUtils'
78
+
68
79
 
69
80
  const props = defineProps({
81
+ type: {
82
+ type: String,
83
+ required: true
84
+ },
85
+ contractorType: {
86
+ type: String,
87
+ required: false
88
+ },
89
+ site: {
90
+ type: String,
91
+ required: true
92
+ },
93
+ selectedVisitorPass: {
94
+ type: Array as PropType<TPassKeyPayload[]>,
95
+ default: () => []
96
+ }
70
97
  })
71
98
 
72
99
  const { requiredRule } = useUtils()
100
+ const { getPassKeysByPageSearch } = usePassKey()
73
101
 
74
102
 
75
103
  const memberForm = reactive<TMemberInfo>({
@@ -85,33 +113,83 @@ const processing = ref(false);
85
113
  const errorMessage = ref('')
86
114
 
87
115
  // pass
88
- const pass = ref()
89
116
  const passInput = ref('')
90
- const passItems = ref(['Test'])
117
+ const passItems = ref<TPassKey[]>([])
118
+
119
+ const selectedPass = ref<string>('')
120
+
121
+ const members = defineModel<TMemberInfo[]>({ required: true, default: [] })
122
+ const committedMembers = ref<TMemberInfo[]>([])
123
+
124
+ // Always keep the model in sync: committed members + current form draft
125
+ watch(() => memberForm, (newForm) => {
126
+ if (memberForm.name && memberForm.nric && memberForm.contact) {
127
+ members.value = [...committedMembers.value, { ...newForm }]
128
+ } else {
129
+ members.value = [...committedMembers.value]
130
+ }
131
+ }, { deep: true })
132
+
133
+
134
+ const passTypesComputed = computed(() => {
135
+ if (props.type === 'contractor') {
136
+ if (props.contractorType === 'property-agent') {
137
+ return ["agent-pass"]
138
+ } else {
139
+ return ["contractor-pass"]
140
+ }
141
+ } else {
142
+ return ["visitor-pass"]
143
+ }
144
+ })
91
145
 
92
- const members = defineModel<TMemberInfo[]>({required: true, default: []})
146
+ const { data: passesData, refresh: refreshPassesData, pending: fetchPassesPending } = await useLazyAsyncData('get-pass-keys', () => {
147
+ return getPassKeysByPageSearch({
148
+ search: passInput.value,
149
+ page: 1,
150
+ limit: 500,
151
+ passTypes: passTypesComputed.value,
152
+ sites: [props.site],
153
+ statuses: ["Available"]
154
+ })
155
+ })
93
156
 
157
+ watch(passesData, (data: any) => {
158
+ passItems.value = Array.isArray(data?.items) ? data.items : []
159
+ })
94
160
 
95
- function handleFocusedPass() {
161
+ const passItemsFilteredFinal = computed(() => {
162
+ return passItems.value.filter((item: TPassKey) => {
163
+ return !props.selectedVisitorPass.some((pass: TPassKeyPayload) => pass.keyId === item._id) && !committedMembers.value.some((member: TMemberInfo) => member.visitorPass === item._id)
164
+ })
165
+ })
166
+
167
+ const membersDisplayed = computed(() => {
168
+ return committedMembers.value.map((member: TMemberInfo) => {
169
+ return {
170
+ ...member,
171
+ visitorPass: passItems.value.find((item: TPassKey) => item._id === member.visitorPass)?.prefixAndName || ""
172
+ }
173
+ })
174
+ })
175
+
176
+ watch(selectedPass, (newVal) => {
177
+ memberForm.visitorPass = [{ keyId: newVal }]
178
+ })
96
179
 
97
- }
98
180
 
99
- function handleSelectPass(val: any) {
100
- memberForm.visitorPass = val
101
- }
102
181
 
103
182
  function handleClearForm() {
104
183
  formRef.value?.reset()
105
184
  }
106
185
 
107
186
  async function handleAddMember() {
108
- members.value.push({...memberForm})
187
+ committedMembers.value.push({ ...memberForm })
109
188
  handleClearForm()
110
189
  }
111
190
 
112
- function handleRemoveMember(index: number){
113
- const filtered = members.value.filter((item, qIndex) => qIndex !== index)
114
- members.value = filtered
191
+ function handleRemoveMember(index: number) {
192
+ committedMembers.value = committedMembers.value.filter((_, i) => i !== index)
115
193
  }
116
194
 
117
195
 
@@ -4,27 +4,36 @@
4
4
  <v-card-text>
5
5
  <v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize" disabled
6
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>
7
+ <v-autocomplete v-model="selectedPass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
8
+ :items="passItems" :rules="props.passRules" item-title="prefixAndName" item-value="_id" label="Pass"
9
+ variant="outlined" hide-details density="compact" persistent-hint small-chips>
10
+
11
+ <template v-slot:chip="{ props, item }">
12
+ <v-chip v-if="selectedPass" v-bind="props" prepend-icon="mdi-card-bulleted-outline"
13
+ :text="item.raw?.prefixAndName"></v-chip>
20
14
  </template>
21
- </v-combobox>
15
+ </v-autocomplete>
16
+
17
+
18
+ <template v-if="!props.hideKeys">
19
+ <v-autocomplete v-model="selectedKeys" v-model:search="keyInput" :hide-no-data="false" class="mt-3"
20
+ :items="keyItems" :rules="props.passRules" item-title="prefixAndName" item-value="_id"
21
+ label="Keys" multiple variant="outlined" hide-details density="compact" persistent-hint
22
+ small-chips>
23
+
24
+ <template v-slot:chip="{ props, item }">
25
+ <v-chip v-if="selectedKeys.length > 0" v-bind="props" prepend-icon="mdi-key"
26
+ :text="item.raw?.prefixAndName"></v-chip>
27
+ </template>
28
+ </v-autocomplete>
29
+ </template>
22
30
 
23
31
  <v-divider class="my-4 w-100" />
24
-
32
+
25
33
 
26
34
  <template v-if="selectedType">
27
- <v-number-input v-model="count" variant="outlined" :min="1" :precision="0" density="compact" :rules="countRules" />
35
+ <v-number-input v-model="count" variant="outlined" :min="1" :precision="0" density="compact"
36
+ :rules="countRules" />
28
37
  </template>
29
38
  </v-card-text>
30
39
  </v-card>
@@ -35,6 +44,7 @@
35
44
  import type { PropType } from 'vue'
36
45
  import type { ValidationRule } from 'vuetify/lib/types.mjs'
37
46
  import usePassKey from '../composables/usePassKey'
47
+ import useKey from '../composables/useKey'
38
48
 
39
49
  const props = defineProps({
40
50
  passRules: {
@@ -56,12 +66,21 @@ const props = defineProps({
56
66
  contractorType: {
57
67
  type: String,
58
68
  default: ""
69
+ },
70
+ hideKeys: {
71
+ type: Boolean,
72
+ default: false
59
73
  }
60
74
  })
61
75
 
62
- const pass = ref("")
76
+ const pass = defineModel<TPassKeyPayload[]>("pass", { default: [] })
77
+ const keys = defineModel<TPassKeyPayload[]>("keys", { default: [] })
78
+ const selectedPass = ref<string>('')
79
+ const selectedKeys = ref<string[]>([])
63
80
  const passInput = ref('')
64
- const passItems = ref([])
81
+ const keyInput = ref('')
82
+ const passItems = ref<TPassKey[]>([])
83
+ const keyItems = ref<any[]>([])
65
84
  const selectedType = ref<'qr-pass' | 'nfc-card'>()
66
85
  const count = ref(1)
67
86
 
@@ -79,15 +98,15 @@ const typeItems = [
79
98
  ]
80
99
 
81
100
  const passTypesComputed = computed(() => {
82
- if(props.type === 'contractor'){
83
- if(props.contractorType === 'property-agent'){
84
- return ["agent-pass"]
101
+ if (props.type === 'contractor') {
102
+ if (props.contractorType === 'property-agent') {
103
+ return ["agent-pass"]
104
+ } else {
105
+ return ["contractor-pass"]
106
+ }
85
107
  } else {
86
- return ["contractor-pass"]
108
+ return ["visitor-pass"]
87
109
  }
88
- } else {
89
- return ["visitor-pass"]
90
- }
91
110
  })
92
111
 
93
112
  const { data: passesData, refresh: refreshPassesData, pending: fetchPassesPending } = await useLazyAsyncData('get-pass-keys', () => {
@@ -105,22 +124,48 @@ watch(passesData, (data: any) => {
105
124
  passItems.value = data?.items || []
106
125
  })
107
126
 
127
+ const { data: keysData, refresh: refreshKeysData, pending: fetchKeysPending } = await useLazyAsyncData('get-keys', () => {
128
+ return getPassKeysByPageSearch({
129
+ search: keyInput.value,
130
+ statuses: ["Available"],
131
+ passTypes: ['pass-key'],
132
+ page: 1,
133
+ limit: 500,
134
+ sites: [props.site],
135
+ })
136
+ })
137
+
138
+ watch(keysData, (data: any) => {
139
+ keyItems.value = data?.items || []
140
+ })
108
141
 
109
- function handleFocusedPass() {
142
+ watch(selectedPass, (newVal) => {
143
+ pass.value = [{ keyId: newVal }]
144
+ })
145
+
146
+ watch(selectedKeys, (newVal) => {
147
+ keys.value = newVal.map(key => ({ keyId: key }))
148
+ })
110
149
 
111
- }
112
150
 
113
- function handleSelectPass() {
114
151
 
115
- }
116
152
 
117
153
  //prevent negative value;
118
154
  watch(count, (newCount) => {
119
- if(newCount < 1){
155
+ if (newCount < 1) {
120
156
  count.value = 1
121
157
  }
122
158
  })
123
159
 
160
+ onMounted(() => {
161
+ if (pass.value.length > 0) {
162
+ selectedPass.value = pass.value[0].keyId
163
+ }
164
+ if (keys.value.length > 0) {
165
+ selectedKeys.value = keys.value.map(k => k.keyId)
166
+ }
167
+ })
168
+
124
169
  </script>
125
170
 
126
171
  <style scoped></style>