@iservice365/layer-common 1.6.0 → 1.7.0

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.
@@ -7,6 +7,7 @@
7
7
  rounded="pill"
8
8
  variant="tonal"
9
9
  size="large"
10
+ @click="setCard()"
10
11
  v-if="canCreate && canCreateAccessCard"
11
12
  >
12
13
  Add Access Card
@@ -14,7 +15,7 @@
14
15
 
15
16
  <v-text-field
16
17
  v-model="searchText"
17
- placeholder="Search Unit..."
18
+ placeholder="Search Card, Unit..."
18
19
  variant="outlined"
19
20
  density="comfortable"
20
21
  clearable
@@ -34,7 +35,7 @@
34
35
  >
35
36
  <v-toolbar density="compact" color="grey-lighten-4">
36
37
  <template #prepend>
37
- <v-btn fab icon density="comfortable">
38
+ <v-btn fab icon density="comfortable" @click="getCards">
38
39
  <v-icon>mdi-refresh</v-icon>
39
40
  </v-btn>
40
41
  </template>
@@ -44,7 +45,11 @@
44
45
  <span class="mr-2 text-caption text-fontgray">
45
46
  {{ pageRange }}
46
47
  </span>
47
- <local-pagination v-model="page" :length="pages" />
48
+ <local-pagination
49
+ v-model="page"
50
+ :length="pages"
51
+ @update:value="getCards"
52
+ />
48
53
  </v-row>
49
54
  </template>
50
55
  </v-toolbar>
@@ -55,13 +60,169 @@
55
60
  items-per-page="10"
56
61
  fixed-header
57
62
  hide-default-footer
58
- hide-default-header
59
- @click:row="(_: any, data: any) => $emit('row-click', data)"
63
+ @click:row="tableRowClickHandler"
60
64
  style="max-height: calc(100vh - (200px))"
61
65
  />
62
66
  </v-card>
63
67
  </v-col>
64
68
 
69
+ <!-- Create Dialog -->
70
+ <v-dialog v-model="createDialog" width="650" persistent>
71
+ <AccessCardAddForm
72
+ @cancel="createDialog = false"
73
+ @success="successCreate()"
74
+ />
75
+ </v-dialog>
76
+
77
+ <!-- Edit Dialog -->
78
+ <v-dialog v-model="editDialog" width="650" persistent>
79
+ <AccessCardAddForm
80
+ mode="edit"
81
+ @cancel="editDialog = false"
82
+ @success="successUpdate()"
83
+ :card="selectedCard"
84
+ />
85
+ </v-dialog>
86
+
87
+ <!-- Preview Dialog -->
88
+ <v-dialog v-model="previewDialog" width="450" persistent>
89
+ <v-card width="100%">
90
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
91
+ <v-row no-gutters class="mb-4">
92
+ <v-col cols="12">
93
+ <strong>Card:</strong> {{ selectedCard?.cardNumber ?? "N/A" }}
94
+ </v-col>
95
+ <v-col cols="12">
96
+ <strong>Access Type:</strong>
97
+ {{ selectedCard?.accessCardType ?? "N/A" }}
98
+ </v-col>
99
+ <!-- <v-col cols="12">
100
+ <strong>Visitor Type:</strong>
101
+ {{ selectedCard?.visitorType ?? "N/A" }}
102
+ </v-col> -->
103
+ <v-col cols="12">
104
+ <strong>Unit:</strong>
105
+ {{ selectedCard?.unit || "N/A" }}
106
+ </v-col>
107
+ <v-col cols="12">
108
+ <strong>Assign:</strong> {{ selectedCard?.assign || "N/A" }}
109
+ </v-col>
110
+ <v-col cols="12">
111
+ <strong>Status:</strong> {{ selectedCard?.status ?? "N/A" }}
112
+ </v-col>
113
+ </v-row></v-card-text
114
+ >
115
+ <v-toolbar class="pa-0" density="compact">
116
+ <v-row no-gutters>
117
+ <v-col cols="6" class="pa-0">
118
+ <v-btn
119
+ block
120
+ variant="text"
121
+ class="text-none"
122
+ size="large"
123
+ @click="previewDialog = false"
124
+ height="48"
125
+ >
126
+ Close
127
+ </v-btn>
128
+ </v-col>
129
+ <v-col cols="6" class="pa-0" v-if="canUpdate">
130
+ <v-menu>
131
+ <template #activator="{ props }">
132
+ <v-btn
133
+ block
134
+ variant="flat"
135
+ color="black"
136
+ class="text-none"
137
+ height="48"
138
+ v-bind="props"
139
+ tile
140
+ :disabled="!canUpdateAccessCard && !canDeleteAccessCard"
141
+ >
142
+ More actions
143
+ </v-btn>
144
+ </template>
145
+ <v-list class="pa-0">
146
+ <v-list-item @click="openEditDialog()">
147
+ <v-list-item-title class="text-subtitle-2">
148
+ Edit Card
149
+ </v-list-item-title>
150
+ </v-list-item>
151
+ <v-list-item @click="openReplaceDialog()">
152
+ <v-list-item-title class="text-subtitle-2">
153
+ Replace Card
154
+ </v-list-item-title>
155
+ </v-list-item>
156
+ <v-list-item @click="openDeleteDialog()" class="text-red">
157
+ <v-list-item-title class="text-subtitle-2">
158
+ Delete Card
159
+ </v-list-item-title>
160
+ </v-list-item>
161
+ </v-list>
162
+ </v-menu>
163
+ </v-col>
164
+ </v-row>
165
+ </v-toolbar></v-card
166
+ >
167
+ </v-dialog>
168
+
169
+ <!-- Delete Dialog -->
170
+ <v-dialog
171
+ v-model="confirmDialog"
172
+ :loading="deleteLoading"
173
+ width="450"
174
+ persistent
175
+ >
176
+ <v-card width="100%">
177
+ <v-toolbar density="compact" class="pl-4">
178
+ <span class="font-weight-medium text-h5">Delete Card</span>
179
+ </v-toolbar>
180
+ <v-card-text>
181
+ <p class="text-subtitle-2 text-center">
182
+ Are you sure you want to delete this card? This action cannot be
183
+ undone.
184
+ </p>
185
+
186
+ <v-row v-if="message" no-gutters justify="center" class="mt-4">
187
+ <span class="text-caption text-error text-center">
188
+ {{ message }}
189
+ </span>
190
+ </v-row></v-card-text
191
+ >
192
+ <v-toolbar density="compact">
193
+ <v-row no-gutters>
194
+ <v-col cols="6">
195
+ <v-btn
196
+ tile
197
+ block
198
+ size="48"
199
+ variant="text"
200
+ class="text-none"
201
+ @click="confirmDialog = false"
202
+ :disabled="deleteLoading"
203
+ >
204
+ Close
205
+ </v-btn>
206
+ </v-col>
207
+ <v-col cols="6">
208
+ <v-btn
209
+ tile
210
+ block
211
+ size="48"
212
+ color="black"
213
+ variant="flat"
214
+ class="text-none"
215
+ @click="handleDeleteCard"
216
+ :loading="deleteLoading"
217
+ >
218
+ Delete Card
219
+ </v-btn>
220
+ </v-col>
221
+ </v-row></v-toolbar
222
+ >
223
+ </v-card>
224
+ </v-dialog>
225
+
65
226
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
66
227
  </v-row>
67
228
  </template>
@@ -76,7 +237,7 @@ const props = defineProps({
76
237
  default: () => [
77
238
  {
78
239
  title: "Card",
79
- value: "card",
240
+ value: "cardNumber",
80
241
  },
81
242
  {
82
243
  title: "Unit",
@@ -115,7 +276,7 @@ const props = defineProps({
115
276
  },
116
277
  });
117
278
 
118
- const { headerSearch } = useLocal();
279
+ // const { headerSearch } = useLocal();
119
280
  const page = ref(1);
120
281
  const pages = ref(0);
121
282
  const pageRange = ref("-- - -- of --");
@@ -131,6 +292,123 @@ const previewDialog = ref(false);
131
292
  const deleteLoading = ref(false);
132
293
  const confirmDialog = ref(false);
133
294
  const searchText = ref("");
295
+ const replaceDialog = ref(false);
296
+
297
+ const selectedCard = ref<TCard>({
298
+ _id: "",
299
+ name: "",
300
+ accessCardType: "",
301
+ // visitorType: "",
302
+ type: "",
303
+ cardNumber: "",
304
+ startDate: "",
305
+ endDate: "",
306
+ door: "",
307
+ accessGroup: [],
308
+ cardType: "",
309
+ pinNo: "",
310
+ useAsLiftCard: false,
311
+ liftAccessLevel: "",
312
+ isActivate: true,
313
+ isAntiPassBack: false,
314
+ status: "",
315
+ org: "",
316
+ site: "",
317
+ unit: "",
318
+ assign: "",
319
+ });
320
+ const selectedCardId = ref<string | null>(null);
321
+
322
+ const { getAll: _getAllCards, deleteById: _deleteCard } = useCard();
323
+
324
+ const {
325
+ data: getCardReq,
326
+ refresh: getCards,
327
+ status: getAllReqStatus,
328
+ } = useLazyAsyncData(
329
+ "get-all-cards",
330
+ () =>
331
+ _getAllCards({
332
+ page: page.value,
333
+ search: searchText.value,
334
+ // search: headerSearch.value,
335
+ }),
336
+ {
337
+ watch: [page, searchText],
338
+ }
339
+ );
340
+
341
+ const loading = computed(() => getAllReqStatus.value === "pending");
342
+
343
+ watchEffect(() => {
344
+ if (getCardReq.value) {
345
+ items.value = getCardReq.value.items;
346
+ pages.value = getCardReq.value.pages;
347
+ pageRange.value = getCardReq.value.pageRange;
348
+ }
349
+ });
350
+
351
+ function setCard({ mode = "create", dialog = true, data = {} as TCard } = {}) {
352
+ if (mode === "create") {
353
+ createDialog.value = dialog;
354
+ } else if (mode === "edit") {
355
+ editDialog.value = dialog;
356
+ selectedCard.value = data;
357
+ } else if (mode === "preview") {
358
+ previewDialog.value = dialog;
359
+ selectedCard.value = data;
360
+ }
361
+ }
362
+
363
+ function successCreate() {
364
+ createDialog.value = false;
365
+ getCards();
366
+ showMessage("Card created successfully!", "success");
367
+ }
368
+
369
+ function successUpdate() {
370
+ editDialog.value = false;
371
+ previewDialog.value = false;
372
+ getCards();
373
+ showMessage("Card updated successfully!", "success");
374
+ }
375
+
376
+ function showMessage(msg: string, color: string) {
377
+ message.value = msg;
378
+ messageColor.value = color;
379
+ messageSnackbar.value = true;
380
+ }
381
+
382
+ function tableRowClickHandler(_: any, data: any) {
383
+ selectedCard.value = data.item as TCard;
384
+ previewDialog.value = true;
385
+ }
386
+
387
+ function openEditDialog() {
388
+ editDialog.value = true;
389
+ }
390
+
391
+ function openDeleteDialog() {
392
+ confirmDialog.value = true;
393
+ message.value = "";
394
+ }
395
+
396
+ function openReplaceDialog() {
397
+ replaceDialog.value = true;
398
+ }
134
399
 
135
- const loading = ref(false);
400
+ async function handleDeleteCard() {
401
+ deleteLoading.value = true;
402
+ try {
403
+ await _deleteCard(selectedCard.value._id ?? "");
404
+ await getCards();
405
+ selectedCardId.value = null;
406
+ confirmDialog.value = false;
407
+ previewDialog.value = false;
408
+ } catch (error: any) {
409
+ message.value = error?.response?._data?.message || "Failed to delete card";
410
+ } finally {
411
+ deleteLoading.value = false;
412
+ }
413
+ }
136
414
  </script>
@@ -60,6 +60,28 @@
60
60
  </v-row>
61
61
  </v-col>
62
62
 
63
+ <v-col cols="12">
64
+ <v-row>
65
+ <v-col cols="12" class="mt-2">
66
+ <v-row no-gutters>
67
+ <InputLabel
68
+ class="text-capitalize font-weight-bold"
69
+ title="Owner"
70
+ />
71
+ <v-col cols="12">
72
+ <v-autocomplete
73
+ v-model="buildingUnit.ownerName"
74
+ :items="peopleItems"
75
+ item-title="name"
76
+ return-object
77
+ density="comfortable">
78
+ </v-autocomplete>
79
+ </v-col>
80
+ </v-row>
81
+ </v-col>
82
+ </v-row>
83
+ </v-col>
84
+
63
85
  <v-col cols="12">
64
86
  <v-row>
65
87
  <v-col cols="12" class="mt-2">
@@ -258,6 +280,8 @@ const prop = defineProps({
258
280
  _id: "",
259
281
  site: "",
260
282
  name: "",
283
+ owner: "",
284
+ ownerName: "",
261
285
  building: "",
262
286
  buildingName: "",
263
287
  category: "",
@@ -277,6 +301,8 @@ const buildingUnit = ref({
277
301
  _id: "",
278
302
  site: "",
279
303
  name: "",
304
+ owner: "",
305
+ ownerName: "",
280
306
  building: "",
281
307
  buildingName: "",
282
308
  level: 0,
@@ -297,8 +323,10 @@ const emit = defineEmits(["cancel", "success", "success:create-more"]);
297
323
  const validForm = ref(false);
298
324
 
299
325
  const { getAll } = useBuilding();
326
+ const { getPeopleByUnit } = usePeople();
300
327
 
301
328
  const buildings = ref<Record<string, any>[]>([]);
329
+ const peopleItems = ref<Array<Record<string, any>>>([]);
302
330
 
303
331
  const { data: getBuildingReq } = useLazyAsyncData(
304
332
  "get-all-buildings",
@@ -315,6 +343,17 @@ watchEffect(() => {
315
343
  }
316
344
  });
317
345
 
346
+ const { data: getUnitPeople } = useLazyAsyncData(
347
+ "get-unit-people",
348
+ async () => getPeopleByUnit(prop.roomFacility._id as string)
349
+ );
350
+
351
+ watch(getUnitPeople, (newData: any) => {
352
+ if (newData) {
353
+ peopleItems.value = newData ?? [];
354
+ }
355
+ });
356
+
318
357
  buildingUnit.value.site = prop.site;
319
358
 
320
359
  const selectedBuilding = computed(() => {
@@ -355,6 +394,12 @@ const hasChanges = computed(() => {
355
394
  async function submit() {
356
395
  disable.value = true;
357
396
  try {
397
+
398
+ if (buildingUnit.value.ownerName) {
399
+ buildingUnit.value.owner = (buildingUnit.value.ownerName as any)._id
400
+ buildingUnit.value.ownerName = (buildingUnit.value.ownerName as any).name
401
+ }
402
+
358
403
  await updateById(buildingUnit.value._id ?? "", {
359
404
  name: buildingUnit.value.name,
360
405
  level: buildingUnit.value.level,
@@ -364,6 +409,8 @@ async function submit() {
364
409
  companyRegistrationNumber: buildingUnit.value.companyRegistrationNumber || "",
365
410
  leaseStart: buildingUnit.value.leaseStart,
366
411
  leaseEnd: buildingUnit.value.leaseEnd,
412
+ owner: buildingUnit.value.owner || "",
413
+ ownerName: buildingUnit.value.ownerName || ""
367
414
  });
368
415
 
369
416
  emit("success");
@@ -0,0 +1,231 @@
1
+ <template>
2
+ <v-row no-gutters class="">
3
+ <v-col cols="12" elevation="3" class="border">
4
+ <v-row no-gutters class="px-6 pt-4">
5
+ <div class="d-flex mt-7 pb-5">
6
+ <p class="mr-6 font-weight-medium flex-grow-1">
7
+ Physical / Key Pass
8
+ <span class="d-block text-caption text-grey-darken-1 mt-1">
9
+ A Physical Pass Card is a tangible identification or access card
10
+ used for entry and verification purposes at a specific site or
11
+ property. Its primary functions include granting authorized
12
+ access, enhancing security, and tracking attendance or movement.
13
+ </span>
14
+ </p>
15
+ <v-switch color="success" hide-details></v-switch>
16
+ </div>
17
+ <v-divider thickness="1"></v-divider>
18
+ <div class="d-flex mt-7 pb-5">
19
+ <p class="mr-6 font-weight-medium flex-grow-1">
20
+ NFC / QR Code Pass
21
+ <span class="d-block text-caption text-grey-darken-1 mt-1">
22
+ NFC Card Pass is a type of access card that uses Near Field
23
+ Communication (NFC) technology to enable secure and convenient
24
+ entry to a site or property. This technology facilitates wireless
25
+ communication between the card and an NFC reader when they are
26
+ placed in close proximity (typically within 4 cm).
27
+ </span>
28
+ </p>
29
+ <v-switch color="success" hide-details></v-switch>
30
+ </div>
31
+ <v-divider thickness="1"></v-divider>
32
+ <div class="d-flex w-100 mt-7 pb-10">
33
+ <p class="mr-6 font-weight-medium flex-grow-1">
34
+ Upload Access Cards Template
35
+ <span class="d-block text-caption text-grey-darken-1 mt-1">
36
+ <a
37
+ target="_blank"
38
+ class="text-truncate d-flex items-center text-decoration-none"
39
+ href=""
40
+ >
41
+ <v-icon class="mr-2" size="24px"> mdi-paperclip </v-icon>
42
+ template
43
+ </a>
44
+ </span>
45
+ </p>
46
+ <v-btn
47
+ variant="flat"
48
+ color="error"
49
+ size="small"
50
+ style="height: 40px; margin-left: auto"
51
+ prepend-icon="mdi-file-document-remove-outline"
52
+ >
53
+ Remove
54
+ </v-btn>
55
+ </div>
56
+ <v-divider thickness="1"></v-divider>
57
+ <div class="d-flex w-100 mt-7 pb-10">
58
+ <p class="mr-6 font-weight-medium flex-grow-1">
59
+ Printer Setup
60
+ <v-col cols="12">
61
+ <v-row>
62
+ <v-col cols="3">
63
+ <v-text-field
64
+ label="Vendor ID"
65
+ hide-details
66
+ clearable
67
+ ></v-text-field>
68
+ </v-col>
69
+ <v-col cols="3">
70
+ <v-text-field
71
+ label="Product ID"
72
+ hide-details
73
+ clearable
74
+ ></v-text-field>
75
+ </v-col>
76
+ <v-col cols="3">
77
+ <v-btn
78
+ color="primary"
79
+ variant="outlined"
80
+ prepend-icon="mdi-magnify"
81
+ block
82
+ >
83
+ Scan Printer
84
+ </v-btn>
85
+ </v-col>
86
+ <v-col cols="3">
87
+ <v-btn
88
+ color="success"
89
+ variant="outlined"
90
+ prepend-icon="mdi-printer-check"
91
+ block
92
+ >
93
+ Test & Print
94
+ </v-btn>
95
+ </v-col>
96
+ </v-row>
97
+ <v-row class="mt-4">
98
+ <v-col cols="12" sm="6" md="4">
99
+ <v-btn
100
+ color="primary"
101
+ variant="flat"
102
+ prepend-icon="mdi-content-save"
103
+ block
104
+ >
105
+ Save Printer Settings
106
+ </v-btn>
107
+ </v-col>
108
+ </v-row>
109
+ </v-col>
110
+ </p>
111
+ </div>
112
+
113
+ <v-divider thickness="1"></v-divider>
114
+
115
+ <div class="d-flex w-100 mt-7 pb-10">
116
+ <p class="mr-6 font-weight-medium flex-grow-1">
117
+ QR Code Template
118
+ <span class="d-block text-caption text-grey-darken-1 mt-1">
119
+ <v-col cols="12">
120
+ <v-row>
121
+ <v-col cols="12">
122
+ <v-text-field
123
+ label="Header"
124
+ placeholder="Enter qr code header eg. Welcome to Company Name"
125
+ hide-details
126
+ density="comfortable"
127
+ clearable
128
+ ></v-text-field>
129
+ </v-col>
130
+ <v-col cols="12">
131
+ <v-text-field
132
+ label="Subtext"
133
+ placeholder="Enter qr code sub header"
134
+ hide-details
135
+ density="comfortable"
136
+ clearable
137
+ class="mb-2"
138
+ ></v-text-field>
139
+ </v-col>
140
+ <v-row class="mt-3">
141
+ <v-col cols="12" md="2">
142
+ <v-btn color="primary" variant="flat" block>
143
+ Preview
144
+ </v-btn>
145
+ </v-col>
146
+ <v-col cols="12" md="2">
147
+ <v-btn color="primary" variant="flat" block> Save </v-btn>
148
+ </v-col>
149
+ </v-row>
150
+ </v-row>
151
+ </v-col>
152
+ </span>
153
+ </p>
154
+ </div>
155
+
156
+ <v-divider thickness="1"></v-divider>
157
+
158
+ <div class="d-flex w-100 mt-7 pb-10">
159
+ <p class="mr-6 font-weight-medium flex-grow-1">
160
+ Resident-app Disclaimer Message
161
+ <span class="d-block text-caption text-grey-darken-1 mt-1">
162
+ <v-col cols="12">
163
+ <v-row no-gutters>
164
+ <v-col cols="12" class="mb-5">
165
+ <v-textarea
166
+ label="Disclaimer Message"
167
+ placeholder="Enter disclaimer message for resident app"
168
+ hide-details
169
+ density="comfortable"
170
+ clearable
171
+ rows="5"
172
+ auto-grow
173
+ ></v-textarea>
174
+ </v-col>
175
+ <v-row class="mt-3">
176
+ <v-col cols="12" md="2">
177
+ <v-btn color="primary" variant="flat" block> Save </v-btn>
178
+ </v-col>
179
+ </v-row>
180
+ </v-row>
181
+ </v-col>
182
+ </span>
183
+ </p>
184
+ </div>
185
+
186
+ <v-divider thickness="1"></v-divider>
187
+
188
+ <div class="d-flex w-100 mt-7 pb-10">
189
+ <p class="mr-6 font-weight-medium flex-grow-1">
190
+ Entrypass URL
191
+ <span class="d-block text-caption text-grey-darken-1 mt-1">
192
+ <v-col>
193
+ <v-text-field
194
+ label="Enter entrypass URL"
195
+ placeholder="https://example.com"
196
+ outlined
197
+ clearable
198
+ hide-details
199
+ />
200
+ </v-col>
201
+ <v-col>
202
+ <v-btn color="primary">Save</v-btn>
203
+ </v-col>
204
+ </span>
205
+ </p>
206
+ </div>
207
+ </v-row>
208
+ </v-col></v-row
209
+ >
210
+ </template>
211
+ <script lang="ts" setup>
212
+ definePageMeta({
213
+ middleware: ["01-auth", "02-org"],
214
+ memberOnly: true,
215
+ });
216
+ const prop = defineProps({
217
+ site: {
218
+ type: String,
219
+ required: true,
220
+ },
221
+ siteName: {
222
+ type: String,
223
+ required: true,
224
+ },
225
+
226
+ orgId: {
227
+ type: String,
228
+ required: true,
229
+ },
230
+ });
231
+ </script>