@7365admin1/layer-common 1.10.4 → 1.10.6

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.
@@ -0,0 +1,300 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <TableMain :headers="headers" :items="items" :loading="loading || getVehiclesPending" :page="page" :pages="pages"
4
+ :pageRange="pageRange" :extension-height="70" :canCreate="canCreateVehicle" @refresh="getVehiclesRefresh"
5
+ createLabel="Add Vehicle" show-header @row-click="handleRowClick" @create="dialog.showSelection = true"
6
+ @update:page="handleUpdatePage">
7
+
8
+ <template #extension>
9
+ <v-row no-gutters class="px-5 py-1 d-flex align-center justify-end ga-3">
10
+ <v-text-field v-model="searchInput" density="compact" placeholder="Search" clearable max-width="300"
11
+ append-inner-icon="mdi-magnify" hide-details />
12
+ <v-select v-model="vehicleTypeFilter" density="compact" item-title="label" item-value="value"
13
+ placeholder="Filter by Type" clearable max-width="200" hide-details :items="typeOptions" />
14
+ </v-row>
15
+ </template>
16
+
17
+ <template #item.block="{ value }">
18
+ {{ value ? `Blk ${value}` : "" }}
19
+ </template>
20
+ <template #item.status="{ value }">
21
+ <v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
22
+ {{ formatVehicleStatus(value).label }}
23
+ </v-chip>
24
+
25
+
26
+ </template>
27
+
28
+ <template #item.plates="{ value, item }">
29
+ <PlateNumberDisplay :plate-numbers="value" :default-value="item.plateNumber" />
30
+ </template>
31
+ </TableMain>
32
+
33
+ <!-- <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" /> -->
34
+
35
+ <v-dialog v-model="dialog.showSelection" width="450" persistent>
36
+ <VehicleAddSelection @cancel="dialog.showSelection = false" @select="handleSelectVehicleStatus" />
37
+ </v-dialog>
38
+
39
+ <v-dialog v-model="dialog.createVehicle" v-if="vehicleType" width="450" persistent>
40
+ <VehicleForm :type="vehicleType" mode="add" @back="handleBackToSelection" @done="handleAddVehicleComplete"
41
+ :org="org" :site="props.site" @close:all="handleCloseAll" />
42
+ </v-dialog>
43
+
44
+ <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
45
+ <VehicleForm :type="vehicleType" mode="edit" :vehicle-data="selectedVehicleObject"
46
+ @back="dialog.updateVehicle = false" @close="dialog.updateVehicle = false" @done="handleUpdateVehicleComplete"
47
+ :org="org" :site="site" @close:all="handleCloseAll" />
48
+ </v-dialog>
49
+
50
+ <v-dialog v-if="canViewVehicleDetails" v-model="dialog.showMoreActions" width="450" persistent>
51
+ <DialogUpdateMoreAction title="Preview" :can-update="canUpdateVehicle" :can-delete="canDeleteVehicle"
52
+ @close="dialog.showMoreActions = false" edit-button-label="Edit Vehicle" delete-button-label="Delete Vehicle"
53
+ @delete="handleDeleteVehicleAction" @edit="handleEditVehicleAction">
54
+ <template v-slot:content>
55
+ <v-row no-gutters class="ga-1 mb-5">
56
+
57
+ <template v-for="(label, key) in formattedFields" :key="key">
58
+ <v-col v-if="key === 'plates'" class="d-flex ga-2">
59
+ <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong></span>
60
+ <PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" />
61
+ </v-col>
62
+
63
+ <v-col v-else-if="selectedVehicleObject[key]" cols="12">
64
+ <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ formatValues(key,
65
+ selectedVehicleObject[key])
66
+ }}</span>
67
+ </v-col>
68
+ </template>
69
+ </v-row>
70
+ </template>
71
+ </DialogUpdateMoreAction>
72
+ </v-dialog>
73
+ <v-dialog v-model="dialog.deleteVehicle" persistent width="540">
74
+ <DialogDeleteConfirmation :message="message" prompt-title="Are you sure want to delete this vehicle?"
75
+ @delete="submitDelete" @close="closeDeleteDialog" />
76
+ </v-dialog>
77
+ </v-row>
78
+ </template>
79
+
80
+ <script lang="ts" setup>
81
+ import useVehicle from '../composables/useVehicle';
82
+
83
+ definePageMeta({
84
+ middleware: ["01-auth", "02-org"],
85
+ memberOnly: true,
86
+ })
87
+
88
+
89
+ const props = defineProps({
90
+ canCreateVehicle: { type: Boolean, default: false },
91
+ canUpdateVehicle: { type: Boolean, default: true },
92
+ canDeleteVehicle: { type: Boolean, default: true },
93
+ canViewVehicleDetails: { type: Boolean, default: true },
94
+ site: { type: String, required: true },
95
+ org: { type: String, required: true },
96
+ });
97
+
98
+
99
+ const headers = [
100
+ { title: "Name", value: "name" },
101
+ // { title: "Building", value: "buildingName" },
102
+ { title: "Vehicle Numbers", value: "plates" },
103
+ { title: "NRIC", value: "nric" },
104
+ { title: "Block", value: "block" },
105
+ { title: "Floor", value: "level" },
106
+ { title: "Unit", value: "unit" },
107
+ { title: "Category", value: "category" },
108
+ { title: "Type", value: "type" },
109
+ { title: "Status", value: "status" },
110
+ ]
111
+
112
+
113
+
114
+ const { formatCamelCaseToWords, formatDate, debounce } = useUtils();
115
+ const { getVehicles, deleteVehicle, formatVehicleStatus } = useVehicle();
116
+
117
+ const items = ref<Array<Record<string, any>>>([]);
118
+ const page = ref(1);
119
+ const pages = ref(0);
120
+ const pageRange = ref("-- - -- of --");
121
+
122
+ const searchInput = ref("")
123
+
124
+ const loading = ref(false);
125
+ const selectedVehicleId = ref<string | null>(null)
126
+ const vehicleType = ref<TVehicleType | null>(null);
127
+ const vehicleTypeFilter = ref<TVehicleType | null>(null);
128
+
129
+ const message = ref("");
130
+ const messageColor = ref("");
131
+ const messageSnackbar = ref(false);
132
+ const bypass = ref(false);
133
+
134
+ function showMessage(msg: string, color: string) {
135
+ message.value = msg;
136
+ messageColor.value = color;
137
+ messageSnackbar.value = true;
138
+ }
139
+
140
+ const dialog = reactive({
141
+ createVehicle: false,
142
+ updateVehicle: false,
143
+ showSelection: false,
144
+ deleteVehicle: false,
145
+ showMoreActions: false,
146
+ });
147
+
148
+ const typeOptions = [
149
+ { label: "Whitelist", value: "whitelist" },
150
+ { label: "Blocklist", value: "blocklist" },
151
+ { label: "Season Pass", value: "seasonPass" },
152
+ ]
153
+
154
+ const formattedFields: Partial<Record<keyof TVehicle, string>> = {
155
+ name: "Name",
156
+ plates: "Vehicle Numbers",
157
+ phoneNumber: "Phone Number",
158
+ nric: "NRIC",
159
+ block: "Block",
160
+ level: "Level",
161
+ unit: "Unit",
162
+ start: "Start",
163
+ end: "End",
164
+ category: "Category",
165
+ type: "Type",
166
+ remarks: "Remarks",
167
+ }
168
+
169
+ function formatValues(key: string, value: any) {
170
+ if (!value) return ""
171
+ switch (key) {
172
+ case "start":
173
+ return formatDate(value)
174
+ case "end":
175
+ return formatDate(value)
176
+ case "type":
177
+ case "category":
178
+ return String(value).charAt(0).toUpperCase() + String(value).slice(1).toLowerCase();
179
+ }
180
+
181
+ return value;
182
+ }
183
+
184
+ const closeDeleteDialog = () => {
185
+ dialog.deleteVehicle = false;
186
+ };
187
+
188
+ const handleCloseAll = () => {
189
+ dialog.createVehicle = false;
190
+ dialog.updateVehicle = false;
191
+ dialog.showSelection = false;
192
+ vehicleType.value = null;
193
+ }
194
+
195
+ const selectedVehicleObject = computed(() => {
196
+ return items.value.find(item => item?._id === selectedVehicleId.value) || {}
197
+ })
198
+
199
+ const { data: getVehiclesReq, refresh: getVehiclesRefresh, pending: getVehiclesPending } =
200
+ await useLazyAsyncData(
201
+ "get-all-vehicles",
202
+ () =>
203
+ getVehicles({
204
+ page: page.value,
205
+ search: searchInput.value,
206
+ type: vehicleTypeFilter.value ?? "",
207
+ }),
208
+ {
209
+ watch: [page],
210
+ }
211
+ );
212
+
213
+
214
+ watch(getVehiclesReq, (newData: any) => {
215
+ items.value = newData?.items || [];
216
+ pages.value = newData?.pages || 0;
217
+ pageRange.value = newData?.pageRange || "-- - -- of --";
218
+ })
219
+
220
+ function handleRowClick(data: any) {
221
+ selectedVehicleId.value = data?.item?._id;
222
+ dialog.showMoreActions = true;
223
+ message.value = "";
224
+ }
225
+
226
+ function handleEditVehicleAction() {
227
+ vehicleType.value = selectedVehicleObject.value?.type || null;
228
+ dialog.showSelection = false;
229
+ dialog.updateVehicle = true;
230
+ }
231
+
232
+ function handleDeleteVehicleAction() {
233
+ dialog.showMoreActions = false;
234
+ dialog.deleteVehicle = true;
235
+ }
236
+
237
+ function handleSelectVehicleStatus(value: TVehicleType) {
238
+ vehicleType.value = value;
239
+ dialog.showSelection = false;
240
+ dialog.createVehicle = true;
241
+ }
242
+
243
+ function handleBackToSelection() {
244
+ dialog.showSelection = true;
245
+ dialog.createVehicle = false;
246
+ vehicleType.value = null;
247
+ }
248
+
249
+ function handleAddVehicleComplete() {
250
+ dialog.showSelection = false;
251
+ dialog.createVehicle = false;
252
+ vehicleType.value = null;
253
+ getVehiclesRefresh();
254
+ }
255
+
256
+ function handleUpdateVehicleComplete() {
257
+ dialog.showSelection = false;
258
+ dialog.updateVehicle = false;
259
+ dialog.showMoreActions = false;
260
+ vehicleType.value = null;
261
+ getVehiclesRefresh();
262
+ }
263
+
264
+ function handleUpdatePage(newPageNum: number) {
265
+ page.value = newPageNum;
266
+ }
267
+
268
+ const formatPlateNumbers = (value: any) => {
269
+ if (!value || value.length === 0) return "";
270
+
271
+ const firstTwo = value.slice(0, 2).map((item: any) => item.plateNumber || "");
272
+ return firstTwo.join(", ");
273
+ };
274
+
275
+ async function submitDelete() {
276
+ bypass.value = false;
277
+
278
+ try {
279
+ const res = await deleteVehicle({ site: props.site, id: selectedVehicleId.value as string, recno: selectedVehicleObject.value.recno, type: selectedVehicleObject.value.type }
280
+ );
281
+ dialog.deleteVehicle = false;
282
+ dialog.showMoreActions = false;
283
+ selectedVehicleId.value = null
284
+ showMessage(res.message, "success");
285
+ getVehiclesRefresh();
286
+ } catch (error: any) {
287
+ console.error("Error deleting vehicle:", error);
288
+ message.value = error.response._data.message;
289
+ bypass.value = true;
290
+ }
291
+ }
292
+
293
+ const debouncedSearch = debounce(getVehiclesRefresh, 500);
294
+ watch([searchInput, vehicleTypeFilter], () => {
295
+ page.value = 1;
296
+ debouncedSearch()
297
+ })
298
+
299
+
300
+ </script>
@@ -108,7 +108,7 @@
108
108
  </template>
109
109
  </TableMain>
110
110
 
111
- <v-dialog v-model="dialog.showSelection" width="450" persistent>
111
+ <v-dialog v-model="dialog.showSelection" width="450" persistent>
112
112
  <VisitorFormSelection @cancel="dialog.showSelection = false" @select="handleSelectVisitorType" />
113
113
  </v-dialog>
114
114
 
@@ -437,7 +437,8 @@ async function handleFileAdded(file: File) {
437
437
  const res = await addFile(file);
438
438
  const uploadedId = res?.id;
439
439
  if (uploadedId) {
440
- const url = `${API_DO_STORAGE_ENDPOINT}/${uploadedId}`;
440
+ // const url = `${API_DO_STORAGE_ENDPOINT}/${uploadedId}`;
441
+ const url = `${uploadedId}`;
441
442
  _workOrder.value.attachments = _workOrder.value.attachments ?? [];
442
443
  _workOrder.value.attachments.push(url);
443
444
  }
@@ -148,13 +148,30 @@ export default function useAccessManagement() {
148
148
  );
149
149
  }
150
150
 
151
- function deleteCard(payload: { cardId: string }) {
151
+ function deleteCard(payload: { cardId: string; remarks: string }) {
152
152
  return useNuxtApp().$api(`/api/access-management/delete-card`, {
153
153
  method: "PATCH",
154
154
  body: payload,
155
155
  });
156
156
  }
157
157
 
158
+ function getCardHistory(cardId: string) {
159
+ return useNuxtApp().$api<Record<string, any>[]>(
160
+ `/api/access-management/card-history/${cardId}`,
161
+ { method: "GET" }
162
+ );
163
+ }
164
+
165
+ function getCardDetails(params: { siteId: string; cardId: string }) {
166
+ return useNuxtApp().$api<{ message: string; data: Record<string, any> }>(
167
+ `/api/access-management/card-details`,
168
+ {
169
+ method: "GET",
170
+ query: { siteId: params.siteId, cardId: params.cardId },
171
+ }
172
+ );
173
+ }
174
+
158
175
  return {
159
176
  getDoorAccessLevels,
160
177
  getLiftAccessLevels,
@@ -167,5 +184,7 @@ export default function useAccessManagement() {
167
184
  assignAccessCard,
168
185
  getAvailableAccessCards,
169
186
  deleteCard,
187
+ getCardHistory,
188
+ getCardDetails,
170
189
  };
171
190
  }
@@ -29,7 +29,8 @@ export default function(){
29
29
  sort?: 'asc' | 'desc';
30
30
  site?: string;
31
31
  status?: TAnnouncementStatus;
32
- search?: string
32
+ search?: string;
33
+ recipients?: TAnnouncementRecipients;
33
34
  }
34
35
 
35
36
  async function getAll({
@@ -38,7 +39,8 @@ export default function(){
38
39
  sort = "asc",
39
40
  site,
40
41
  status,
41
- search
42
+ search,
43
+ recipients
42
44
  } : TGetAllParams) {
43
45
  return await useNuxtApp().$api<Record<string, any>>(
44
46
  "/api/bulletin-boards",
@@ -50,8 +52,8 @@ export default function(){
50
52
  sort,
51
53
  site,
52
54
  status,
53
- search
54
-
55
+ search,
56
+ recipients
55
57
  },
56
58
  }
57
59
  );
@@ -75,9 +75,9 @@ export default function useFeedback() {
75
75
  }
76
76
 
77
77
  function deleteFeedback(id: string) {
78
- return useNuxtApp().$api<Record<string, any>>(`/api/feedbacks/deleted/feedback`, {
78
+ return useNuxtApp().$api<Record<string, any>>(`/api/feedbacks/deleted/feedback/${id}`, {
79
79
  method: "PUT",
80
- query: { id },
80
+ // query: { id },
81
81
  });
82
82
  }
83
83
 
@@ -25,6 +25,15 @@ export default function () {
25
25
  });
26
26
  }
27
27
 
28
+ async function findPersonByNRICMultipleResult(
29
+ nric: string, site: string
30
+ ){
31
+ return await $fetch<Record<any, any>>(`/api/people/all-nric`, {
32
+ method: "GET",
33
+ query: { nric, site }
34
+ });
35
+ }
36
+
28
37
  async function findPersonByContact(
29
38
  contact: string
30
39
  ): Promise<null | Partial<TPeople>> {
@@ -95,6 +104,7 @@ export default function () {
95
104
  updateById,
96
105
  deleteById,
97
106
  findPersonByNRIC,
107
+ findPersonByNRICMultipleResult,
98
108
  findPersonByContact,
99
109
  getPeopleByUnit,
100
110
  searchCompanyList,
@@ -0,0 +1,114 @@
1
+ export default function useVehicle() {
2
+ async function getVehicles({
3
+ search = "",
4
+ page = 1,
5
+ limit = 10,
6
+ sort = "asc",
7
+ order = "",
8
+ type = "",
9
+ category = "",
10
+ } = {}) {
11
+ return await useNuxtApp().$api<Record<string, any>>("/api/vehicles", {
12
+ method: "GET",
13
+ query: { search, page, limit, sort, order, type, category },
14
+ });
15
+ }
16
+
17
+ async function getVehicleById(id: string) {
18
+ return await useNuxtApp().$api<Record<string, any>>(`/api/vehicles/${id}`, {
19
+ method: "GET",
20
+ });
21
+ }
22
+
23
+ async function addVehicle(payload: Partial<TVehiclePayload>) {
24
+ return await useNuxtApp().$api<Record<string, any>>("/api/vehicles", {
25
+ method: "POST",
26
+ body: payload,
27
+ });
28
+ }
29
+
30
+ async function updateVehicle(id: string, payload: Partial<TVehiclePayload>) {
31
+ return await useNuxtApp().$api<Record<string, any>>(`/api/vehicles/${id}`, {
32
+ method: "PUT",
33
+ body: payload,
34
+ });
35
+ }
36
+
37
+ async function getCustomSeasonPassTypes(site: string) {
38
+ return await useNuxtApp().$api<Record<string, any>>(
39
+ `/api/vehicles/site/${site}`,
40
+ {
41
+ method: "GET",
42
+ }
43
+ );
44
+ }
45
+
46
+ interface IDeleteVehicleParams {
47
+ site: string;
48
+ id: string;
49
+ recno: string;
50
+ type: "whitelist" | "blocklist";
51
+ }
52
+
53
+ async function deleteVehicle({ site, id, recno, type }: IDeleteVehicleParams) {
54
+ return await useNuxtApp().$api<Record<string, any>>(
55
+ `/api/vehicles/${id}`,
56
+ {
57
+ method: "PUT",
58
+ body: {
59
+ site,
60
+ recno,
61
+ type,
62
+ },
63
+ }
64
+ );
65
+ }
66
+
67
+ async function getVehicleByNRIC(search: string) {
68
+ return await useNuxtApp().$api<Record<string, any>>(
69
+ `/api/vehicles/nric`,
70
+ {
71
+ method: "GET",
72
+ query: {
73
+ search,
74
+ },
75
+ }
76
+ );
77
+ }
78
+
79
+
80
+ function formatVehicleStatus(status: string){
81
+ let label = ""
82
+ let color = ""
83
+ switch (status) {
84
+ case 'pending':
85
+ label = "Pending"
86
+ color = "orange"
87
+ break;
88
+ case 'active':
89
+ label = "Active"
90
+ color = "green"
91
+ break;
92
+ case 'rejected':
93
+ label = "Rejected"
94
+ color = "red"
95
+ break;
96
+ default:
97
+ label = status?.charAt(0).toUpperCase() + status?.slice(1) || ""
98
+ color = "grey"
99
+ }
100
+
101
+ return { label, color }
102
+ }
103
+
104
+ return {
105
+ getVehicles,
106
+ addVehicle,
107
+ getCustomSeasonPassTypes,
108
+ getVehicleById,
109
+ updateVehicle,
110
+ deleteVehicle,
111
+ getVehicleByNRIC,
112
+ formatVehicleStatus
113
+ };
114
+ }
@@ -72,9 +72,9 @@ export default function useWorkOrder() {
72
72
  }
73
73
 
74
74
  function deleteWorkOrder(id: string) {
75
- return useNuxtApp().$api<Record<string, any>>(`/api/work-orders/deleted/work-order`, {
75
+ return useNuxtApp().$api<Record<string, any>>(`/api/work-orders/deleted/work-order/${id}`, {
76
76
  method: "PUT",
77
- query: { id },
77
+ // query: { id },
78
78
  });
79
79
  }
80
80
 
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.10.4",
5
+ "version": "1.10.6",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
package/types/people.d.ts CHANGED
@@ -16,8 +16,11 @@ declare type TPeople = {
16
16
  org?: string;
17
17
  site?: string,
18
18
  type?: TPeopleType;
19
+ plates?: TPlateNumber[]
19
20
  };
20
21
 
22
+ declare type TPlateNumber = { plateNumber: string, recNo: string }
23
+
21
24
 
22
25
  declare type TPeoplePayload = Pick<TGuest, "name" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "nric" | "contact" | "remarks" | "org" | "site" | "start" | "end" | "type">
23
26
 
@@ -0,0 +1,43 @@
1
+ declare type TVehicle = {
2
+ plateNumber: string;
3
+ plates?: TPlateNumber[]; // For display purposes, the API will return an array of plate numbers if there are multiple associated with the same vehicle record
4
+ type: TVehicleType;
5
+ category: "resident" | "visitor";
6
+ direction: "entry" | "exit" | "both" | "none";
7
+ name: string;
8
+ phoneNumber?: string;
9
+ block: number | "";
10
+ level: string;
11
+ unit: string;
12
+ nric?: string | null;
13
+ remarks?: string;
14
+ seasonPassType?: string;
15
+ start?: string; // ISO date string
16
+ end?: string; // ISO date string
17
+ site?: string;
18
+ org?: string;
19
+ _id?: string;
20
+ recNo?: string;
21
+ };
22
+
23
+ declare type TVehicleType = "whitelist" | "blocklist" | "seasonpass";
24
+
25
+ declare type TVehiclePayload = Pick<
26
+ TVehicle,
27
+ | "plateNumber"
28
+ | "type"
29
+ | "category"
30
+ | "direction"
31
+ | "name"
32
+ | "phoneNumber"
33
+ | "block"
34
+ | "level"
35
+ | "unit"
36
+ | "nric"
37
+ | "remarks"
38
+ | "seasonPassType"
39
+ | "start"
40
+ | "end"
41
+ | "site"
42
+ | "org"
43
+ >;