@7365admin1/layer-common 3.0.25 → 3.0.27

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,19 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 3.0.27
4
+
5
+ ### Patch Changes
6
+
7
+ - c76ac10: Update Package Version
8
+ - 3d5034d: Update Version Package
9
+
10
+ ## 3.0.26
11
+
12
+ ### Patch Changes
13
+
14
+ - 4c4e01d: Update Package Version
15
+ - 165fd5a: Update Layer Common Version
16
+
3
17
  ## 3.0.25
4
18
 
5
19
  ### Patch Changes
@@ -30,7 +30,7 @@
30
30
  <div class="text-body-2 font-weight-medium">{{ details.type ?? "N/A" }}</div>
31
31
  </div>
32
32
 
33
- <div>
33
+ <div v-if="details.status">
34
34
  <div class="text-caption text-grey">Status</div>
35
35
  <v-chip
36
36
  :color="statusColor(details.status)"
@@ -47,16 +47,46 @@
47
47
  <div class="text-body-2 font-weight-medium">{{ details.isActivated ? "Yes" : "No" }}</div>
48
48
  </div>
49
49
 
50
- <div>
50
+ <div v-if="details.site?.name">
51
51
  <div class="text-caption text-grey">Site</div>
52
52
  <div class="text-body-2 font-weight-medium">{{ details.site?.name ?? "N/A" }}</div>
53
53
  </div>
54
54
 
55
55
  <div>
56
56
  <div class="text-caption text-grey">User</div>
57
- <div class="text-body-2 font-weight-medium">{{ details.user?.name ?? "N/A" }} ({{ details.user?.email ?? "N/A" }})</div>
57
+ <div class="text-body-2 font-weight-medium">
58
+ {{ details.userCred?.name ?? details.user?.name ?? "N/A" }}
59
+ <template v-if="details.user?.email">({{ details.user.email }})</template>
60
+ </div>
58
61
  </div>
59
62
 
63
+ <!-- Visitor Credentials -->
64
+ <template v-if="details.userCred">
65
+ <v-divider />
66
+ <div class="text-caption font-weight-bold text-grey-darken-2 text-uppercase">Visitor Information</div>
67
+
68
+ <div>
69
+ <div class="text-caption text-grey">NRIC</div>
70
+ <div class="text-body-2 font-weight-medium">{{ details.userCred.nric ?? "N/A" }}</div>
71
+ </div>
72
+
73
+ <div>
74
+ <div class="text-caption text-grey">Contact</div>
75
+ <div class="text-body-2 font-weight-medium">{{ details.userCred.contact ?? "N/A" }}</div>
76
+ </div>
77
+
78
+ <div v-if="details.userCred.company">
79
+ <div class="text-caption text-grey">Company</div>
80
+ <div class="text-body-2 font-weight-medium">{{ details.userCred.company }}</div>
81
+ </div>
82
+
83
+ <div v-if="details.userCred.plateNumber">
84
+ <div class="text-caption text-grey">Plate Number</div>
85
+ <div class="text-body-2 font-weight-medium">{{ details.userCred.plateNumber }}</div>
86
+ </div>
87
+ <v-divider />
88
+ </template>
89
+
60
90
  <div>
61
91
  <div class="text-caption text-grey">Created At</div>
62
92
  <div class="text-body-2 font-weight-medium">{{ formatDate(details.createdAt) }}</div>
@@ -71,6 +101,81 @@
71
101
  <div class="text-caption text-grey">Remarks</div>
72
102
  <div class="text-body-2 font-weight-medium">{{ details.remarks }}</div>
73
103
  </div>
104
+
105
+ <!-- History -->
106
+ <template v-if="details.history?.length > 0">
107
+ <v-divider />
108
+ <div
109
+ class="d-flex align-center justify-space-between cursor-pointer"
110
+ @click="historyOpen = !historyOpen"
111
+ >
112
+ <div class="text-caption font-weight-bold text-grey-darken-2 text-uppercase">
113
+ History ({{ details.history.length }})
114
+ </div>
115
+ <v-icon size="16" :icon="historyOpen ? 'mdi-chevron-up' : 'mdi-chevron-down'" />
116
+ </div>
117
+
118
+ <template v-if="historyOpen">
119
+ <div
120
+ v-for="(entry, idx) in sortedHistory"
121
+ :key="entry._id ?? idx"
122
+ class="d-flex ga-2"
123
+ >
124
+ <!-- Timeline spine -->
125
+ <div class="d-flex flex-column align-center" style="width: 20px; flex-shrink: 0;">
126
+ <div
127
+ class="timeline-dot rounded-circle mt-1"
128
+ :class="entry.visitorId ? 'bg-blue' : 'bg-orange'"
129
+ />
130
+ <div
131
+ v-if="idx < sortedHistory.length - 1"
132
+ class="timeline-line bg-grey-lighten-2 mt-1"
133
+ />
134
+ </div>
135
+
136
+ <!-- Entry content -->
137
+ <div class="d-flex flex-column ga-1 pb-4 flex-grow-1">
138
+ <div class="d-flex align-center justify-space-between">
139
+ <v-chip
140
+ size="x-small"
141
+ variant="tonal"
142
+ :color="entry.visitorId ? 'blue' : 'orange'"
143
+ :prepend-icon="entry.visitorId ? 'mdi-account-arrow-right' : 'mdi-arrow-u-left-top'"
144
+ >
145
+ {{ entry.visitorId ? "Assigned" : "Returned" }}
146
+ </v-chip>
147
+ <span class="text-caption text-grey">{{ formatDate(entry.createdAt) }}</span>
148
+ </div>
149
+
150
+ <!-- Assignment entry -->
151
+ <template v-if="entry.visitorId">
152
+ <div class="text-body-2 font-weight-medium">{{ entry.name ?? "N/A" }}</div>
153
+ <div class="text-caption text-grey-darken-1">
154
+ <span v-if="entry.nric">NRIC: {{ entry.nric }}</span>
155
+ <span v-if="entry.contact" class="ml-2">· {{ entry.contact }}</span>
156
+ </div>
157
+ <div class="text-caption text-grey-darken-1">
158
+ <span v-if="entry.company">{{ entry.company }}</span>
159
+ <span v-if="entry.plateNumber" class="ml-2">· {{ entry.plateNumber }}</span>
160
+ </div>
161
+ </template>
162
+
163
+ <!-- Return/checkout entry -->
164
+ <template v-else>
165
+ <div class="d-flex align-center ga-2">
166
+ <v-chip size="x-small" variant="flat" :color="statusColor(entry.status?.toLowerCase())">
167
+ {{ entry.status ?? "N/A" }}
168
+ </v-chip>
169
+ <span v-if="entry.damage" class="text-caption text-error">Damaged</span>
170
+ </div>
171
+ <div v-if="entry.remarks" class="text-caption text-grey-darken-1">
172
+ Remarks: {{ entry.remarks }}
173
+ </div>
174
+ </template>
175
+ </div>
176
+ </div>
177
+ </template>
178
+ </template>
74
179
  </div>
75
180
  </v-card-text>
76
181
  </v-card>
@@ -101,6 +206,13 @@ const { getCardDetails } = useAccessManagement();
101
206
 
102
207
  const details = ref<Record<string, any> | null>(null);
103
208
  const pending = ref(false);
209
+ const historyOpen = ref(false);
210
+
211
+ const sortedHistory = computed(() =>
212
+ [...(details.value?.history ?? [])].sort(
213
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
214
+ )
215
+ );
104
216
 
105
217
  watch(
106
218
  () => props.modelValue,
@@ -115,6 +227,7 @@ watch(
115
227
  }
116
228
  } else {
117
229
  details.value = null;
230
+ historyOpen.value = false;
118
231
  }
119
232
  }
120
233
  );
@@ -142,3 +255,16 @@ function formatDate(date: string) {
142
255
  }).format(new Date(date));
143
256
  }
144
257
  </script>
258
+
259
+ <style scoped>
260
+ .timeline-dot {
261
+ width: 10px;
262
+ height: 10px;
263
+ flex-shrink: 0;
264
+ }
265
+ .timeline-line {
266
+ width: 2px;
267
+ flex-grow: 1;
268
+ min-height: 16px;
269
+ }
270
+ </style>
@@ -116,9 +116,9 @@
116
116
  <!-- Available Physical -->
117
117
  <v-col cols="12" class="mt-3">
118
118
  <strong>Available Physical</strong>
119
- <div v-if="unit?.available?.physical?.length" class="mt-1">
119
+ <div v-if="availablePhysical.length" class="mt-1">
120
120
  <v-chip
121
- v-for="card in unit.available.physical"
121
+ v-for="card in availablePhysical"
122
122
  :key="card._id"
123
123
  size="small"
124
124
  class="mr-1 mb-1"
@@ -133,6 +133,26 @@
133
133
  <span v-else class="text-caption text-grey ml-1">None</span>
134
134
  </v-col>
135
135
 
136
+ <!-- Damage Physical -->
137
+ <v-col cols="12" class="mt-2">
138
+ <strong>Damage</strong>
139
+ <div v-if="damagedPhysical.length" class="mt-1">
140
+ <v-chip
141
+ v-for="card in damagedPhysical"
142
+ :key="card._id"
143
+ size="small"
144
+ class="mr-1 mb-1"
145
+ color="deep-orange"
146
+ :variant="selectedCardInUnit?._id === card._id ? 'flat' : 'tonal'"
147
+ style="cursor: pointer"
148
+ @click="toggleCard(card)"
149
+ >
150
+ {{ card.cardNo }}
151
+ </v-chip>
152
+ </div>
153
+ <span v-else class="text-caption text-grey ml-1">None</span>
154
+ </v-col>
155
+
136
156
  <!-- Available Non-Physical -->
137
157
  <v-col cols="12" class="mt-2">
138
158
  <strong>Available Non-Physical</strong>
@@ -424,11 +444,19 @@ function showToast(message: string, color: "success" | "error") {
424
444
  snackbar.value = true;
425
445
  }
426
446
 
447
+ const availablePhysical = computed(() =>
448
+ (props.unit?.available?.physical ?? []).filter((c: any) => !c.damage)
449
+ );
450
+
451
+ const damagedPhysical = computed(() =>
452
+ (props.unit?.available?.physical ?? []).filter((c: any) => c.damage === true)
453
+ );
454
+
427
455
  const isSelectedCardAvailable = computed(() => {
428
456
  if (!props.selectedCardInUnit?._id) return false;
429
457
  const id = props.selectedCardInUnit._id;
430
458
  return [
431
- ...(props.unit?.available?.physical ?? []),
459
+ ...availablePhysical.value,
432
460
  ...(props.unit?.available?.non_physical ?? []),
433
461
  ].some((c) => c._id === id);
434
462
  });
@@ -301,7 +301,16 @@ const loading = computed(() => getAllReqStatus.value === "pending");
301
301
 
302
302
  watchEffect(() => {
303
303
  if (getCardReq.value?.data) {
304
- items.value = getCardReq.value.data.items;
304
+ items.value = getCardReq.value.data.items.map((item: any) => ({
305
+ ...item,
306
+ cardCounts: {
307
+ ...item.cardCounts,
308
+ available: {
309
+ ...item.cardCounts?.available,
310
+ physical: (item.available?.physical ?? []).filter((c: any) => !c.damage).length,
311
+ },
312
+ },
313
+ }));
305
314
  pages.value = getCardReq.value.data.pages;
306
315
  pageRange.value = getCardReq.value.data.pageRange;
307
316
  }