@7365admin1/layer-common 3.0.26 → 3.0.28

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
+ ## 3.0.28
4
+
5
+ ### Patch Changes
6
+
7
+ - 0c04bbf: Update Package Version
8
+
9
+ ## 3.0.27
10
+
11
+ ### Patch Changes
12
+
13
+ - c76ac10: Update Package Version
14
+ - 3d5034d: Update Version Package
15
+
3
16
  ## 3.0.26
4
17
 
5
18
  ### 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,7 +47,7 @@
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>
@@ -101,6 +101,81 @@
101
101
  <div class="text-caption text-grey">Remarks</div>
102
102
  <div class="text-body-2 font-weight-medium">{{ details.remarks }}</div>
103
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>
104
179
  </div>
105
180
  </v-card-text>
106
181
  </v-card>
@@ -131,6 +206,13 @@ const { getCardDetails } = useAccessManagement();
131
206
 
132
207
  const details = ref<Record<string, any> | null>(null);
133
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
+ );
134
216
 
135
217
  watch(
136
218
  () => props.modelValue,
@@ -145,6 +227,7 @@ watch(
145
227
  }
146
228
  } else {
147
229
  details.value = null;
230
+ historyOpen.value = false;
148
231
  }
149
232
  }
150
233
  );
@@ -172,3 +255,16 @@ function formatDate(date: string) {
172
255
  }).format(new Date(date));
173
256
  }
174
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>
@@ -41,7 +41,7 @@
41
41
  <div class="text-caption font-weight-bold mb-1 text-uppercase text-grey-darken-1">
42
42
  Person <span class="text-error">*</span>
43
43
  </div>
44
- <v-autocomplete
44
+ <v-select
45
45
  v-model="assignPerson"
46
46
  :items="filteredPeopleItems"
47
47
  density="compact"
@@ -63,7 +63,7 @@
63
63
  {{ personTypeLabel(item.raw) }}
64
64
  </v-chip>
65
65
  </template>
66
- </v-autocomplete>
66
+ </v-select>
67
67
  <p v-if="assignError" class="text-error text-caption mt-2">{{ assignError }}</p>
68
68
  </v-card-text>
69
69
 
@@ -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
  });
@@ -517,7 +545,10 @@ async function confirmAssign() {
517
545
  cancelAssign();
518
546
  emit("refresh");
519
547
  } catch (e: any) {
520
- const msg = e?.data?.message ?? "Failed to assign. Please try again.";
548
+ const rawMsg = e?.data?.message ?? "";
549
+ const msg = rawMsg.includes("500")
550
+ ? "EntryPass server is unavailable. Please contact your administrator."
551
+ : rawMsg || "Failed to assign. Please try again.";
521
552
  assignError.value = msg;
522
553
  showToast(msg, "error");
523
554
  } finally {
@@ -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
  }