@7365admin1/layer-common 1.11.31 → 1.11.33

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.33
4
+
5
+ ### Patch Changes
6
+
7
+ - ed63329: Update Layer-common version
8
+
9
+ ## 1.11.32
10
+
11
+ ### Patch Changes
12
+
13
+ - fe5ced9: Update Unregistered Registration
14
+
3
15
  ## 1.11.31
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,228 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+
4
+ <!-- MAIN TABLE -->
5
+ <v-col cols="12">
6
+ <TableMain :headers="currentHeaders" :items="currentItems" :loading="loadingState" :page="currentPage"
7
+ :pages="currentPages" :pageRange="pageRange" :extension-height="120" :offset="300" show-header
8
+ @refresh="handleRefresh" @update:page="handleUpdatePage">
9
+
10
+ <!-- EXTENSION -->
11
+ <template #extension>
12
+ <v-row no-gutters class="w-100 d-flex flex-column ga-2 pt-2">
13
+
14
+ <v-tabs v-model="tab" class="w-100" height="32" @update:model-value="onTabChange">
15
+ <v-tab value="active">Active</v-tab>
16
+ <v-tab value="suspended">Suspended</v-tab>
17
+ <v-tab value="pending">Pending Invitation</v-tab>
18
+ </v-tabs>
19
+
20
+ <div class="px-3 py-2 w-100">
21
+ <v-text-field v-model="search" density="compact" placeholder="Search" clearable max-width="300"
22
+ append-inner-icon="mdi-magnify" hide-details @update:model-value="handleSearch" />
23
+ </div>
24
+
25
+ </v-row>
26
+ </template>
27
+
28
+ <!-- ACTIONS (ONLY ONE INVITE BUTTON HERE) -->
29
+ <template #actions>
30
+ <v-btn class="text-none" rounded="pill" variant="tonal" size="large" @click="openInviteDialog">
31
+ Invite Client
32
+ </v-btn>
33
+ </template>
34
+
35
+ <!-- CUSTOM CELL -->
36
+ <template #item.index="{ index }">
37
+ {{ (currentPage - 1) * 10 + index + 1 }}
38
+ </template>
39
+
40
+ <template #item.status="{ value }">
41
+ <v-chip size="small" variant="tonal">
42
+ {{ value }}
43
+ </v-chip>
44
+ </template>
45
+ <template #item.action="{ item }">
46
+ <v-btn v-if="tab === 'pending'" size="small" color="error" variant="tonal"
47
+ :loading="cancelLoadingId === item._id" :disabled="cancelLoadingId === item._id"
48
+ @click="cancelInvite(item)">
49
+ Cancel Invite
50
+ </v-btn>
51
+ </template>
52
+ </TableMain>
53
+ </v-col>
54
+
55
+ <!-- DIALOG -->
56
+ <v-dialog v-model="dialog" max-width="500">
57
+ <InvitationForm title="Invite Client" app="organization" @success="handleSuccess" @cancel="dialog = false" />
58
+ </v-dialog>
59
+
60
+ </v-row>
61
+ </template>
62
+ <script setup lang="ts">
63
+ import useOrg from '@7365admin1/layer-common/composables/useOrg'
64
+ import useRole from '@7365admin1/layer-common/composables/useRole'
65
+ import useVerification from '@7365admin1/layer-common/composables/useVerification'
66
+ import { computed, onMounted, ref, watch } from 'vue'
67
+
68
+ const tab = ref<'active' | 'suspended' | 'pending'>('active')
69
+ const dialog = ref(false)
70
+
71
+ const { getAll } = useOrg()
72
+ const { getVerifications, cancelUserInvitation } = useVerification()
73
+ const { getRoleById } = useRole()
74
+ const roleCache = ref<Record<string, string>>({})
75
+
76
+ /* ================= STATE ================= */
77
+ const items = ref<any[]>([])
78
+ const loading = ref(false)
79
+
80
+ const page = ref(1)
81
+ const pages = ref(1)
82
+ const search = ref("")
83
+ async function resolveRoleName(id: string) {
84
+ if (!id) return "-"
85
+
86
+ if (roleCache.value[id]) return roleCache.value[id]
87
+
88
+ const res = await getRoleById(id)
89
+
90
+ const roleName = res?.name ?? "-"
91
+
92
+ roleCache.value[id] = roleName
93
+
94
+ return roleName
95
+ }
96
+ /* ================= HEADERS ================= */
97
+ const orgHeaders = [
98
+ { title: "Organization Name", key: "name" },
99
+ { title: "Site", key: "sites" },
100
+ { title: "Plan Type", key: "plan" },
101
+ { title: "Billing Cycle", key: "bill" },
102
+ { title: "Subscription Start", key: "start" },
103
+ { title: "Subscription End", key: "end" },
104
+ { title: "Status", key: "status" },
105
+ { title: "Action", key: "actions" },
106
+ ]
107
+
108
+ const inviteHeaders = [
109
+ { title: "Email", key: "email" },
110
+ { title: "App", key: "app" },
111
+ { title: "Role", key: "role" },
112
+ { title: "Status", key: "status" },
113
+ { title: "Action", key: "action" },
114
+ ]
115
+
116
+ /* ================= COMPUTED ================= */
117
+ const currentHeaders = computed(() =>
118
+ tab.value === 'pending' ? inviteHeaders : orgHeaders
119
+ )
120
+
121
+ const currentItems = computed(() => items.value)
122
+ const currentPage = computed(() => page.value)
123
+ const currentPages = computed(() => pages.value)
124
+
125
+ const loadingState = computed(() => loading.value)
126
+ const totalItems = ref(0)
127
+ /* ================= API ================= */
128
+ async function fetchData() {
129
+ loading.value = true
130
+
131
+ try {
132
+ if (tab.value === 'pending') {
133
+ const res = await getVerifications({
134
+ status: "pending",
135
+ type: "user-invite",
136
+ page: page.value,
137
+ search: search.value,
138
+ app: "organization",
139
+ })
140
+
141
+ items.value = await Promise.all(
142
+ (res?.items || []).map(async (i: any) => ({
143
+ ...i,
144
+ app: i.metadata?.app ?? "-",
145
+ role: await resolveRoleName(i.metadata?.role),
146
+ }))
147
+ )
148
+
149
+ pages.value = res?.pages || 1
150
+ totalItems.value = res?.total || items.value.length
151
+ } else {
152
+ const res = await getAll({
153
+ page: page.value,
154
+ search: search.value,
155
+ nature: tab.value === "active" ? "" : "suspended",
156
+ })
157
+
158
+ items.value = res?.data?.items || res?.items || []
159
+
160
+ pages.value = res?.data?.totalPages || res?.totalPages || 1
161
+ totalItems.value =
162
+ res?.data?.totalItems ||
163
+ res?.totalItems ||
164
+ items.value.length
165
+ }
166
+ } finally {
167
+ loading.value = false
168
+ }
169
+ }
170
+ const pageRange = computed(() => {
171
+ if (!totalItems.value) return "0-0 of 0"
172
+
173
+ const pageSize = 10
174
+
175
+ const start = (page.value - 1) * pageSize + 1
176
+ const end = Math.min(page.value * pageSize, totalItems.value)
177
+
178
+ return `${start}-${end} of ${totalItems.value}`
179
+ })
180
+ /* ================= EVENTS ================= */
181
+ function onTabChange() {
182
+ page.value = 1
183
+ fetchData()
184
+ }
185
+
186
+ function handleUpdatePage(p: number) {
187
+ page.value = p
188
+ fetchData()
189
+ }
190
+
191
+ function handleRefresh() {
192
+ fetchData()
193
+ }
194
+
195
+ function handleSearch() {
196
+ page.value = 1
197
+ fetchData()
198
+ }
199
+
200
+ /* ================= DIALOG ================= */
201
+ function openInviteDialog() {
202
+ dialog.value = true
203
+ }
204
+
205
+ function handleSuccess() {
206
+ dialog.value = false
207
+ fetchData()
208
+ }
209
+
210
+ /* ================= INIT ================= */
211
+ onMounted(() => {
212
+ fetchData()
213
+ })
214
+
215
+ const cancelLoadingId = ref<string | null>(null)
216
+
217
+ async function cancelInvite(item: any) {
218
+ try {
219
+ cancelLoadingId.value = item._id
220
+
221
+ await cancelUserInvitation(item._id)
222
+
223
+ await fetchData()
224
+ } finally {
225
+ cancelLoadingId.value = null
226
+ }
227
+ }
228
+ </script>
@@ -3,7 +3,7 @@
3
3
  <v-list>
4
4
  <v-list-item>
5
5
  <v-list-item-title class="text-h6 text-white">
6
- {{ APP_NAME }}
6
+ {{ props.title || APP_NAME }}
7
7
  </v-list-item-title>
8
8
  </v-list-item>
9
9
  <slot name="action" />
@@ -36,6 +36,7 @@
36
36
  <script setup lang="ts">
37
37
  const props = defineProps({
38
38
  navigationItems: { type: Array<TNavigationItem>, required: true },
39
+ title: { type: String, default: "" }
39
40
  });
40
41
 
41
42
  const { drawer } = useLocal();
@@ -924,7 +924,14 @@ async function submit() {
924
924
  }
925
925
 
926
926
  try {
927
- const res = await createVisitor(payload);
927
+ let res;
928
+ if (prop.mode === "add") {
929
+ res = await createVisitor(payload as TVisitorPayload);
930
+ } else if ((prop.mode === "edit" || prop.mode === "register") && prop.visitorData?._id) {
931
+ const { unitName, ...rest } = payload
932
+ res = await updateVisitor(prop.visitorData._id, rest as TVisitorPayload);
933
+ }
934
+
928
935
  if (res) {
929
936
  if (prop.type === "contractor" && passType.value) {
930
937
  const visitorId = res as unknown as string;
@@ -209,34 +209,21 @@
209
209
  <v-row no-gutters class="d-flex flex-column py-2">
210
210
  <span class="d-flex align-center ga-2">
211
211
  <v-icon
212
- icon="mdi-clock-time-eight-outline"
213
- color="success"
212
+ icon="mdi-clock-time-four-outline"
213
+ color="green"
214
214
  size="20"
215
215
  />
216
- <template v-if="!item.checkIn && canUpdateVisitor">
217
- <v-btn
218
- size="x-small"
219
- class="text-capitalize"
220
- color="success"
221
- text="Checkin"
222
- :disabled="isVisitorDataFromScannedQRCodeCheckingInOut"
223
- @click.stop="handleCheckin(item)"
224
- :loading="isVisitorDataFromScannedQRCodeCheckingInOut"
216
+ <span class="text-capitalize">{{
217
+ UTCToLocalTIme(item.checkIn) || "-"
218
+ }}</span>
219
+ <span>
220
+ <v-icon
221
+ v-if="item?.snapshotEntryImage"
222
+ size="17"
223
+ icon="mdi-image"
224
+ @click.stop="handleViewImage(item.snapshotEntryImage)"
225
225
  />
226
- </template>
227
- <template v-else>
228
- <span class="text-capitalize">{{
229
- UTCToLocalTIme(item.checkIn)
230
- }}</span>
231
- <span>
232
- <v-icon
233
- v-if="item?.snapshotEntryImage"
234
- size="17"
235
- icon="mdi-image"
236
- @click.stop="handleViewImage(item.snapshotEntryImage)"
237
- />
238
- </span>
239
- </template>
226
+ </span>
240
227
  </span>
241
228
 
242
229
  <span
@@ -379,6 +366,23 @@
379
366
  <v-icon icon="mdi-clock-time-four-outline" size="20" />
380
367
  {{ item.arrivalTime ?? "N/A" }}
381
368
  </span>
369
+
370
+ <span v-if="!item.checkIn && canUpdateVisitor">
371
+ <span class="d-flex align-center ga-2 cursor-pointer">
372
+ <v-icon
373
+ icon="mdi-clock-time-eight-outline"
374
+ color="success"
375
+ size="20"
376
+ />
377
+ <v-btn
378
+ size="x-small"
379
+ class="text-capitalize"
380
+ color="success"
381
+ text="Checkin"
382
+ @click.stop="handleCheckin(item)"
383
+ />
384
+ </span>
385
+ </span>
382
386
  </v-row>
383
387
  </template>
384
388
 
@@ -1327,6 +1331,11 @@ const {
1327
1331
  params.status = activeTab.value;
1328
1332
  }
1329
1333
 
1334
+ if (activeTab.value !== "registered") {
1335
+ delete params.type;
1336
+ delete params.checkedOut;
1337
+ }
1338
+
1330
1339
  return await getVisitors(params);
1331
1340
  },
1332
1341
  {
@@ -1761,6 +1770,11 @@ function buildReportParams(): any {
1761
1770
  params.status = activeTab.value;
1762
1771
  }
1763
1772
 
1773
+ if (activeTab.value !== "registered") {
1774
+ delete params.type;
1775
+ delete params.checkedOut;
1776
+ }
1777
+
1764
1778
  return params;
1765
1779
  }
1766
1780
 
@@ -1798,6 +1812,7 @@ async function handleDownloadCSVReport() {
1798
1812
  showMessage("Generating CSV report...", "info");
1799
1813
 
1800
1814
  const params = buildReportParams();
1815
+
1801
1816
  const reportData = await getVisitors(params);
1802
1817
  const csvData = generateCSVData(reportData.items ?? []);
1803
1818
  downloadCSVFile(csvData);
@@ -1951,11 +1966,14 @@ onMounted(() => {
1951
1966
  if (route.query.dateTo) {
1952
1967
  dateTo.value = normalizeDateOnly(route.query.dateTo as string);
1953
1968
  }
1954
- filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(
1955
- Boolean
1956
- ) as TVisitorType[];
1957
- displayNotCheckedOut.value =
1958
- (route.query.checkedOut as string) == "true" || false;
1969
+ if (activeTab.value === "registered") {
1970
+ filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(
1971
+ Boolean
1972
+ ) as TVisitorType[];
1973
+
1974
+ displayNotCheckedOut.value =
1975
+ (route.query.checkedOut as string) == "true" || false;
1976
+ }
1959
1977
  });
1960
1978
  </script>
1961
1979
 
@@ -10,7 +10,7 @@
10
10
  <span style="font-size: clamp(11px, 1.1vw, 13px); word-break: break-word;">Date Range: {{
11
11
  formatDateDDMMYYYY(dateFrom) }} - {{ formatDateDDMMYYYY(dateTo) ?? "" }}</span>
12
12
  <span class="text-subtitle-1" style="word-break: break-word;">Site Name: {{ activeSiteName
13
- }}</span>
13
+ }}</span>
14
14
  </div>
15
15
 
16
16
  </v-row>
@@ -26,12 +26,12 @@
26
26
  formatCamelCaseToWords(item.contractorType)
27
27
  }}</span>
28
28
  <span v-else class="text-capitalize" style="word-break: break-word;">{{ formatType(item)
29
- }}</span>
29
+ }}</span>
30
30
  </span>
31
31
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
32
32
  <v-icon icon="mdi-domain" size="15" />
33
33
  <span class="text-capitalize" style="word-break: break-word;">{{ item?.company || "N/A"
34
- }}</span>
34
+ }}</span>
35
35
  </span>
36
36
  </template>
37
37
 
@@ -52,7 +52,7 @@
52
52
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
53
53
  <v-icon icon="mdi-phone" size="15" />
54
54
  <span class="text-capitalize" style="word-break: break-word;">{{ item?.contact || "N/A"
55
- }}</span>
55
+ }}</span>
56
56
  </span>
57
57
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
58
58
  <v-icon icon="mdi-car-back" size="15" />
@@ -64,7 +64,8 @@
64
64
  <template v-slot:item.checkInOut="{ item }">
65
65
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
66
66
  <v-icon icon="mdi-login" size="15" />
67
- <span class="text-capitalize" style="word-break: break-word;">{{ formatDate(item.checkIn) }}</span>
67
+ <span class="text-capitalize" style="word-break: break-word;">{{
68
+ formatDate(item.checkIn) }}</span>
68
69
  </span>
69
70
  <span class="d-flex align-center ga-2" style="flex-wrap: wrap;">
70
71
  <v-icon icon="mdi-logout" size="15" />
@@ -238,6 +239,11 @@ const {
238
239
  params.status = activeTab.value;
239
240
  }
240
241
 
242
+ if (activeTab.value !== 'registered') {
243
+ delete params.type
244
+ delete params.checkedOut
245
+ }
246
+
241
247
  return await getVisitors(params);
242
248
  },
243
249
  {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.11.31",
5
+ "version": "1.11.33",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {