@7365admin1/layer-common 3.0.31-staging.27 → 3.1.1

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.
@@ -121,6 +121,10 @@
121
121
  </v-row>
122
122
  </v-col>
123
123
 
124
+
125
+
126
+
127
+
124
128
  <v-col v-if="shouldShowField('deliveryType')" cols="12">
125
129
  <v-row>
126
130
  <v-col cols="12">
@@ -159,6 +163,7 @@
159
163
  </v-col>
160
164
  </template>
161
165
 
166
+
162
167
  <template v-if="shouldShowField('delivery-company')">
163
168
  <v-col cols="12">
164
169
  <InputLabel class="text-capitalize" title="Delivery Company" />
@@ -167,7 +172,7 @@
167
172
  :loading="siteDataPending" variant="outlined" density="comfortable" persistent-hint small-chips>
168
173
  <template v-slot:no-data>
169
174
  <v-list-item>
170
- <v-list-item-title v-if="siteDataPending">
175
+ <v-list-item-title v-if="fetchCompanyListPending">
171
176
  <v-progress-circular indeterminate size="20" class="mr-3" />
172
177
  Searching companies…
173
178
  </v-list-item-title>
@@ -175,7 +180,7 @@
175
180
  class="d-flex align-center ga-1">
176
181
  <span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
177
182
  companyNameInput
178
- }}</strong>" as new company.
183
+ }}</strong>" as new company.
179
184
  </v-list-item-title>
180
185
  <v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
181
186
  Start typing to search for companies.
@@ -190,7 +195,6 @@
190
195
  </template>
191
196
 
192
197
 
193
-
194
198
  <v-col v-if="shouldShowField('block')" cols="12">
195
199
  <InputLabel class="text-capitalize" title="Block" required />
196
200
  <v-autocomplete v-model="visitor.block" :items="blocksArray" item-value="value" autocomplete="off"
@@ -307,7 +311,7 @@
307
311
  <span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
308
312
  </div>
309
313
 
310
- <template v-if="selectedNricContactOptions.length >= 1">
314
+ <template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
311
315
  <h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
312
316
  <v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
313
317
  <v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
@@ -315,7 +319,7 @@
315
319
  </v-radio-group>
316
320
  </template>
317
321
 
318
- <template v-if="selectedNricPlateOptions.length >= 1">
322
+ <template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
319
323
  <h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
320
324
  <v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
321
325
  <v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
@@ -683,10 +687,6 @@ const requireNRIC = computed(() => {
683
687
  return prop.type !== "walk-in" && prop.type !== "guest";
684
688
  });
685
689
 
686
- const isVehicleRequired = computed(() => {
687
- return ["guest", "drop-off", "pick-up"].includes(prop.type);
688
- });
689
-
690
690
  const hidQrCodePassConfig = computed(() => {
691
691
  return (siteData.value as any)?.metadata?.hidQrCodePass || {};
692
692
  });
@@ -1084,9 +1084,21 @@ function confirmNricAutofillOptions() {
1084
1084
  closeNricAutofillOptions();
1085
1085
  }
1086
1086
 
1087
+ const {
1088
+ data: fetchCompanyListReq,
1089
+ refresh: fetchCompanyListRefresh,
1090
+ pending: fetchCompanyListPending,
1091
+ } = useLazyAsyncData(`fetch-company-list`, () => {
1092
+ if (!companyNameInput.value) return Promise.resolve(null);
1093
+ return searchCompanyList(companyNameInput.value);
1094
+ });
1087
1095
 
1088
-
1089
-
1096
+ watch(fetchCompanyListReq, (arr) => {
1097
+ if (Array.isArray(arr)) {
1098
+ companyAutofillDataArray.value = arr;
1099
+ companyNames.value = arr?.flatMap((x) => x?.companyName);
1100
+ }
1101
+ });
1090
1102
  function getVisitorSearchItems(res: any): TVisitor[] {
1091
1103
  const items =
1092
1104
  res?.items ||
@@ -1198,6 +1210,18 @@ async function handleCheckout(visitorId: string) {
1198
1210
  }
1199
1211
  }
1200
1212
 
1213
+ const debounceFetchCompany = debounce(
1214
+ async () => fetchCompanyListRefresh(),
1215
+ 200
1216
+ );
1217
+
1218
+ watch(companyNameInput, async (val) => {
1219
+ if (!val) {
1220
+ companyNames.value = [];
1221
+ return;
1222
+ }
1223
+ await debounceFetchCompany();
1224
+ });
1201
1225
 
1202
1226
  function handleAutofillDataViaVehicleNumber(item: TPeople) {
1203
1227
  currentAutofillSource.value = "vehicleNumber";
@@ -1206,7 +1230,9 @@ function handleAutofillDataViaVehicleNumber(item: TPeople) {
1206
1230
  visitor.nric = item.nric;
1207
1231
  visitor.contact = item.contact;
1208
1232
  companyNames.value = item.companyName ?? [];
1209
-
1233
+ if (!visitor.company) {
1234
+ visitor.company = companyNames.value?.[0];
1235
+ }
1210
1236
  visitor.block = item.block ?? "";
1211
1237
  visitor.level = item.level ?? "";
1212
1238
  visitor.unit = item.unit ?? "";
@@ -315,9 +315,7 @@
315
315
  style="cursor: pointer"
316
316
  >
317
317
  <v-chip
318
- v-for="pass in item.visitorPass.filter(
319
- (i) => i.status != 'Removed'
320
- )"
318
+ v-for="pass in item.visitorPass"
321
319
  :key="(pass as any)._id ?? (pass as any).keyId"
322
320
  prepend-icon="mdi-card-bulleted-outline"
323
321
  size="x-small"
@@ -327,9 +325,7 @@
327
325
  {{ (pass as any)?.prefixAndName }}
328
326
  </v-chip>
329
327
  <v-chip
330
- v-for="key in item.passKeys.filter(
331
- (i) => i.status != 'Removed'
332
- )"
328
+ v-for="key in item.passKeys"
333
329
  :key="(key as any)._id ?? (key as any).keyId"
334
330
  prepend-icon="mdi-key"
335
331
  size="x-small"
@@ -408,16 +404,9 @@
408
404
  </v-menu>
409
405
 
410
406
  <!-- QRCODE identifier -->
411
- <div
412
- v-if="
413
- item.accessCards?.type === 'QRCODE' && item.accessCards?.cardNo
414
- "
415
- class="d-flex align-center ga-1 mt-1"
416
- >
407
+ <div v-if="item.accessCards?.type === 'QRCODE' && item.accessCards?.cardNo" class="d-flex align-center ga-1 mt-1">
417
408
  <v-icon size="15" icon="mdi-qrcode" color="teal" />
418
- <v-chip size="x-small" variant="tonal" color="teal"
419
- >QR · ({{ item.cards?.flat().length ?? 1 }})</v-chip
420
- >
409
+ <v-chip size="x-small" variant="tonal" color="teal">QR · ({{ item.cards?.flat().length ?? 1 }})</v-chip>
421
410
  </div>
422
411
  </v-row>
423
412
  </template>
@@ -825,9 +814,7 @@
825
814
  item-title="label"
826
815
  item-value="value"
827
816
  v-model="pass.status"
828
- :disabled="
829
- selectedVisitorObject.checkOut || pass.status == 'Removed'
830
- "
817
+ :disabled="selectedVisitorObject.checkOut"
831
818
  ></v-select>
832
819
  <v-textarea
833
820
  v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
@@ -864,9 +851,7 @@
864
851
  item-title="label"
865
852
  item-value="value"
866
853
  v-model="key.status"
867
- :disabled="
868
- selectedVisitorObject.checkOut || key.status == 'Removed'
869
- "
854
+ :disabled="selectedVisitorObject.checkOut"
870
855
  ></v-select>
871
856
  <v-textarea
872
857
  v-if="key.status === 'Lost' || key.status === 'Damaged'"
@@ -926,87 +911,37 @@
926
911
  <v-dialog v-model="dialog.returnNfcCard" max-width="450" persistent>
927
912
  <v-card>
928
913
  <v-toolbar density="compact" color="">
929
- <v-row
930
- no-gutters
931
- class="d-flex fill-height justify-space-between align-center px-4"
932
- >
914
+ <v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
933
915
  <span class="font-weight-bold">Return NFC Card</span>
934
- <v-btn
935
- icon="mdi-close"
936
- variant="text"
937
- @click="dialog.returnNfcCard = false"
938
- />
916
+ <v-btn icon="mdi-close" variant="text" @click="dialog.returnNfcCard = false" />
939
917
  </v-row>
940
918
  </v-toolbar>
941
919
  <v-card-text class="px-4 pb-2">
942
- <p class="text-body-2 mb-3">
943
- Please indicate the status of the NFC card before checking out.
944
- </p>
920
+ <p class="text-body-2 mb-3">Please indicate the status of the NFC card before checking out.</p>
945
921
  <div>
946
922
  <InputLabel class="text-capitalize" title="Full Name" />
947
- <v-text-field
948
- v-model="selectedVisitorObject.name"
949
- density="comfortable"
950
- readonly
951
- />
923
+ <v-text-field v-model="selectedVisitorObject.name" density="comfortable" readonly />
952
924
  </div>
953
925
  <div>
954
926
  <InputLabel class="text-capitalize" title="Location" />
955
- <v-text-field
956
- v-model="selectedVisitorObject.location"
957
- density="comfortable"
958
- readonly
959
- />
927
+ <v-text-field v-model="selectedVisitorObject.location" density="comfortable" readonly />
960
928
  </div>
961
- <div
962
- v-for="(card, idx) in nfcCardReturnStatuses"
963
- :key="card.cardId"
964
- class="mb-4"
965
- >
929
+ <div v-for="(card, idx) in nfcCardReturnStatuses" :key="card.cardId" class="mb-4">
966
930
  <div class="d-flex align-center ga-2 mb-2">
967
931
  <v-icon size="18" icon="mdi-credit-card-outline" color="purple" />
968
- <v-chip size="small" variant="tonal" color="purple">{{
969
- card.cardNo
970
- }}</v-chip>
932
+ <v-chip size="small" variant="tonal" color="purple">{{ card.cardNo }}</v-chip>
971
933
  </div>
972
- <v-select
973
- v-model="nfcCardReturnStatuses[idx].status"
974
- :items="nfcPassStatusOptions"
975
- item-title="label"
976
- item-value="value"
977
- density="comfortable"
978
- hide-details
979
- />
980
- <v-textarea
981
- v-if="card.status === 'Lost' || card.status === 'Damage'"
982
- v-model="nfcCardReturnStatuses[idx].remarks"
983
- label="Remarks (required)"
984
- no-resize
985
- rows="3"
986
- class="mt-2"
987
- density="compact"
988
- />
934
+ <v-select v-model="nfcCardReturnStatuses[idx].status" :items="nfcPassStatusOptions" item-title="label" item-value="value" density="comfortable" hide-details />
935
+ <v-textarea v-if="card.status === 'Lost' || card.status === 'Damage'" v-model="nfcCardReturnStatuses[idx].remarks" label="Remarks (required)" no-resize rows="3" class="mt-2" density="compact" />
989
936
  </div>
990
937
  </v-card-text>
991
938
  <v-toolbar class="pa-0" density="compact">
992
939
  <v-row no-gutters>
993
940
  <v-col cols="6">
994
- <v-btn variant="text" block @click="dialog.returnNfcCard = false"
995
- >Close</v-btn
996
- >
941
+ <v-btn variant="text" block @click="dialog.returnNfcCard = false">Close</v-btn>
997
942
  </v-col>
998
943
  <v-col cols="6">
999
- <v-btn
1000
- color="red"
1001
- variant="flat"
1002
- height="48"
1003
- rounded="0"
1004
- block
1005
- :loading="loading.checkingOut"
1006
- :disabled="!canConfirmNfcCheckout"
1007
- @click="handleNfcCheckout"
1008
- >Confirm Checkout</v-btn
1009
- >
944
+ <v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut" :disabled="!canConfirmNfcCheckout" @click="handleNfcCheckout">Confirm Checkout</v-btn>
1010
945
  </v-col>
1011
946
  </v-row>
1012
947
  </v-toolbar>
@@ -1036,7 +971,7 @@
1036
971
  @close="dialog.editPassKey = false"
1037
972
  @done="
1038
973
  () => {
1039
- // dialog.editPassKey = false;
974
+ dialog.editPassKey = false;
1040
975
  getVisitorRefresh();
1041
976
  }
1042
977
  "
@@ -1891,8 +1826,7 @@ const nfcCardReturnStatuses = ref<
1891
1826
  const canConfirmCheckout = computed(() => {
1892
1827
  const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
1893
1828
  return allEntries.every((entry) => {
1894
- if (!entry.status || ["In Use", "Not Returned"].includes(entry.status))
1895
- return false;
1829
+ if (!entry.status || ["In Use","Not Returned"].includes(entry.status)) return false;
1896
1830
  if (
1897
1831
  (entry.status === "Lost" || entry.status === "Damaged") &&
1898
1832
  !entry.remarks.trim()
@@ -1906,8 +1840,7 @@ const canConfirmNfcCheckout = computed(() => {
1906
1840
  if (nfcCardReturnStatuses.value.length === 0) return false;
1907
1841
  return nfcCardReturnStatuses.value.every(({ status, remarks }) => {
1908
1842
  if (!status || status === "In Use") return false;
1909
- if ((status === "Lost" || status === "Damage") && !remarks.trim())
1910
- return false;
1843
+ if ((status === "Lost" || status === "Damage") && !remarks.trim()) return false;
1911
1844
  return true;
1912
1845
  });
1913
1846
  });
@@ -1950,28 +1883,17 @@ function handleCheckout(userId: string) {
1950
1883
  return;
1951
1884
  }
1952
1885
 
1953
- const hasNfc =
1954
- visitor?.accessCards?.type === "NFC" && !!visitor?.accessCards?.cardNo;
1886
+ const hasNfc = visitor?.accessCards?.type === "NFC" && !!visitor?.accessCards?.cardNo;
1955
1887
  if (hasNfc) {
1956
1888
  const allCards: any[] = visitor.cards?.flat() ?? [];
1957
1889
  nfcCardReturnStatuses.value = allCards.map((card: any) => ({
1958
1890
  cardId: card._id,
1959
- cardNo:
1960
- card._id === visitor.accessCards._id
1961
- ? visitor.accessCards.cardNo
1962
- : card._id,
1891
+ cardNo: card._id === visitor.accessCards._id ? visitor.accessCards.cardNo : card._id,
1963
1892
  status: "In Use",
1964
1893
  remarks: "",
1965
1894
  }));
1966
1895
  if (nfcCardReturnStatuses.value.length === 0) {
1967
- nfcCardReturnStatuses.value = [
1968
- {
1969
- cardId: visitor.accessCards._id,
1970
- cardNo: visitor.accessCards.cardNo,
1971
- status: "In Use",
1972
- remarks: "",
1973
- },
1974
- ];
1896
+ nfcCardReturnStatuses.value = [{ cardId: visitor.accessCards._id, cardNo: visitor.accessCards.cardNo, status: "In Use", remarks: "" }];
1975
1897
  }
1976
1898
  dialog.returnNfcCard = true;
1977
1899
  return;
@@ -2039,9 +1961,7 @@ async function handleNfcCheckout() {
2039
1961
  })),
2040
1962
  });
2041
1963
  if (res) {
2042
- await updateVisitor(userId as string, {
2043
- checkOut: new Date().toISOString(),
2044
- });
1964
+ await updateVisitor(userId as string, { checkOut: new Date().toISOString() });
2045
1965
  showMessage("Visitor successfully checked-out!", "info");
2046
1966
  await getVisitorRefresh();
2047
1967
  dialog.returnNfcCard = false;
@@ -2049,10 +1969,7 @@ async function handleNfcCheckout() {
2049
1969
  }
2050
1970
  } catch (error: any) {
2051
1971
  const errorMessage = error?.response?._data?.message;
2052
- showMessage(
2053
- errorMessage || "Something went wrong. Please try again later.",
2054
- "error"
2055
- );
1972
+ showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
2056
1973
  } finally {
2057
1974
  loading.checkingOut = false;
2058
1975
  }
@@ -2324,7 +2241,11 @@ const syncVisitorFiltersFromRoute = () => {
2324
2241
 
2325
2242
  onMounted(syncVisitorFiltersFromRoute);
2326
2243
 
2327
- watch(() => route.query, syncVisitorFiltersFromRoute, { deep: true });
2244
+ watch(
2245
+ () => route.query,
2246
+ syncVisitorFiltersFromRoute,
2247
+ { deep: true }
2248
+ );
2328
2249
 
2329
2250
  const { socketUnregisteredVisitorTrigger, visitorSocketData } =
2330
2251
  useVisitorSocket();
@@ -1,23 +1,58 @@
1
1
  export function useCleaningSchedulePermission() {
2
+ const { hasPermission } = usePermission();
3
+ const { permissions } = useCleaningPermission();
4
+
2
5
  const { userAppRole } = useLocalSetup();
3
6
 
4
- const can = (action: string) =>
5
- computed(() => {
6
- const permissions = userAppRole.value?.permissions;
7
- if (!permissions) return false;
8
- if (permissions.includes("*")) return true;
9
- return permissions.some((permission) =>
10
- permission.endsWith(`-schedule-mgmt:${action}`),
11
- );
12
- });
7
+ const canViewSchedules = computed(() => {
8
+ if (!userAppRole.value) return false;
9
+ if (userAppRole.value.permissions.includes("*")) return true;
10
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "see-all-schedules");
11
+ });
12
+
13
+ const canViewScheduleDetails = computed(() => {
14
+ if (!userAppRole.value) return false;
15
+ if (userAppRole.value.permissions.includes("*")) return true;
16
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "see-schedule-details");
17
+ });
18
+
19
+ const canDownloadSchedule = computed(() => {
20
+ if (!userAppRole.value) return false;
21
+ if (userAppRole.value.permissions.includes("*")) return true;
22
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "download-schedule");
23
+ });
24
+
25
+ const canManageScheduleTasks = computed(() => {
26
+ if (!userAppRole.value) return false;
27
+ if (userAppRole.value.permissions.includes("*")) return true;
28
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "manage-schedule-tasks");
29
+ });
30
+
31
+ const canGenerateChecklist = computed(() => {
32
+ if (!userAppRole.value) return false;
33
+ if (userAppRole.value.permissions.includes("*")) return true;
34
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "generate-checklist");
35
+ });
36
+
37
+ const canViewHistory = computed(() => {
38
+ if (!userAppRole.value) return false;
39
+ if (userAppRole.value.permissions.includes("*")) return true;
40
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "view-history");
41
+ });
42
+
43
+ const canAddRemarks = computed(() => {
44
+ if (!userAppRole.value) return false;
45
+ if (userAppRole.value.permissions.includes("*")) return true;
46
+ return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "add-remarks");
47
+ });
13
48
 
14
49
  return {
15
- canViewSchedules: can("see-all-schedules"),
16
- canViewScheduleDetails: can("see-schedule-details"),
17
- canDownloadSchedule: can("download-schedule"),
18
- canManageScheduleTasks: can("manage-schedule-tasks"),
19
- canGenerateChecklist: can("generate-checklist"),
20
- canViewHistory: can("view-history"),
21
- canAddRemarks: can("add-remarks"),
50
+ canViewSchedules,
51
+ canViewScheduleDetails,
52
+ canDownloadSchedule,
53
+ canManageScheduleTasks,
54
+ canGenerateChecklist,
55
+ canViewHistory,
56
+ canAddRemarks,
22
57
  };
23
58
  }
@@ -27,7 +27,6 @@ export default function useEquipment() {
27
27
  name: payload.name,
28
28
  unitOfMeasurement: payload.unitOfMeasurement,
29
29
  serviceType,
30
- ...(payload.attachment ? { attachment: payload.attachment } : {}),
31
30
  },
32
31
  }
33
32
  );
@@ -41,7 +40,6 @@ export default function useEquipment() {
41
40
  body: {
42
41
  name: payload.name,
43
42
  unitOfMeasurement: payload.unitOfMeasurement,
44
- ...(payload.attachment ? { attachment: payload.attachment } : {}),
45
43
  },
46
44
  }
47
45
  );
@@ -183,4 +183,4 @@ export default function useLocalAuth() {
183
183
  updateVerificationStatus,
184
184
  signUp,
185
185
  };
186
- }
186
+ }
@@ -63,12 +63,15 @@ export function useNFCPatrolDailyReport(
63
63
 
64
64
  try {
65
65
  const [logsRes, siteInfo] = await Promise.all([
66
- getPatrolLogs({
66
+ getPatrolLogs<NFCPatrolLogListResponse>({
67
67
  page: 1,
68
68
  limit: 100,
69
69
  site,
70
70
  date: filters.date,
71
71
  routeId: filters.route,
72
+ routeStartTime: filters.timeRange
73
+ ? filters.timeRange.split(" - ")[0]
74
+ : undefined,
72
75
  }),
73
76
  getSiteById(site),
74
77
  ]);
@@ -2,8 +2,6 @@ import type {
2
2
  NFCPatrolReportFilters,
3
3
  NFCPatrolSummaryReport,
4
4
  NFCPatrolReportSummaryRow,
5
- NFCPatrolMonthlyReportRow,
6
- NFCPatrolMonthlyReport,
7
5
  } from "../types/nfc-patrol-report";
8
6
  import useNFCPatrolLog from "./useNFCPatrolLog";
9
7
  import useSite from "./useSite";
@@ -14,55 +12,6 @@ interface NFCPatrolLogListResponse {
14
12
  pageRange?: string;
15
13
  }
16
14
 
17
- const MONTHS = [
18
- "January",
19
- "February",
20
- "March",
21
- "April",
22
- "May",
23
- "June",
24
- "July",
25
- "August",
26
- "September",
27
- "October",
28
- "November",
29
- "December",
30
- ];
31
- function mapLogsToMonthlyRows(
32
- logs: Record<string, any>[],
33
- ): NFCPatrolMonthlyReportRow[] {
34
- const rows = MONTHS.map((month) => ({
35
- month,
36
- checked: 0,
37
- checkedPercentage: 0,
38
- missed: 0,
39
- missedPercentage: 0,
40
- }));
41
-
42
- logs.forEach((log) => {
43
- const monthIndex = new Date(log.date).getMonth();
44
-
45
- for (const checkpoint of log.checkPoints ?? []) {
46
- if (checkpoint.status === "Completed") {
47
- rows[monthIndex].checked++;
48
- } else if (checkpoint.status === "Skipped") {
49
- rows[monthIndex].missed++;
50
- }
51
- }
52
- });
53
-
54
- rows.forEach((row) => {
55
- const total = row.checked + row.missed;
56
-
57
- if (total > 0) {
58
- row.checkedPercentage = Math.round((row.checked / total) * 100);
59
- row.missedPercentage = Math.round((row.missed / total) * 100);
60
- }
61
- });
62
-
63
- return rows;
64
- }
65
-
66
15
  function buildAddress(address?: Record<string, string>) {
67
16
  if (!address) return undefined;
68
17
 
@@ -78,7 +27,23 @@ function buildAddress(address?: Record<string, string>) {
78
27
  return parts.length ? parts.join(", ") : undefined;
79
28
  }
80
29
 
30
+ function mapLogToSummaryRow(
31
+ log: Record<string, any>,
32
+ index: number,
33
+ ): NFCPatrolReportSummaryRow {
34
+ const checkpoints: Record<string, any>[] = log.checkPoints ?? [];
35
+ const checked = checkpoints.filter((cp) => cp.status === "Completed").length;
36
+ const missed = checkpoints.filter((cp) => cp.status === "Skipped").length;
81
37
 
38
+ return {
39
+ id: log._id,
40
+ routeName: `${index + 1}. ${log.route?.name ?? "-"}`,
41
+ securityCheckSchedule: log.route?.startTime ?? "-",
42
+ checked,
43
+ missed,
44
+ status: missed === 0 ? "Completed" : "Incomplete",
45
+ };
46
+ }
82
47
 
83
48
  export function useNFCPatrolMonthlyReport(
84
49
  filters: NFCPatrolReportFilters,
@@ -87,12 +52,12 @@ export function useNFCPatrolMonthlyReport(
87
52
  const { getAll: getPatrolLogs } = useNFCPatrolLog();
88
53
  const { getSiteById } = useSite();
89
54
 
90
- const report = ref<NFCPatrolMonthlyReport | null>(null);
55
+ const report = ref<NFCPatrolSummaryReport | null>(null);
91
56
  const loading = ref(false);
92
57
  const notFound = ref(false);
93
58
 
94
59
  async function fetchReport() {
95
- if (!site || !filters.route) {
60
+ if (!site || !filters.date || !filters.route) {
96
61
  report.value = null;
97
62
  notFound.value = false;
98
63
  return;
@@ -100,16 +65,19 @@ export function useNFCPatrolMonthlyReport(
100
65
 
101
66
  loading.value = true;
102
67
  notFound.value = false;
103
- const currentYear = new Date().getFullYear().toString();
68
+
104
69
  try {
105
70
  const [logsRes, siteInfo] = await Promise.all([
106
- getPatrolLogs({
107
- page: 1,
108
- limit: 100,
109
- site,
110
- routeId: filters.route,
111
- date: currentYear,
112
- type: "month",
71
+ getPatrolLogs<NFCPatrolLogListResponse>({
72
+ page: 1,
73
+ limit: 100,
74
+ site,
75
+ date: filters.date,
76
+ routeId: filters.route,
77
+ routeStartTime: filters.timeRange
78
+ ? filters.timeRange.split(" - ")[0]
79
+ : undefined,
80
+ type: "month",
113
81
  }),
114
82
  getSiteById(site),
115
83
  ]);
@@ -125,7 +93,7 @@ export function useNFCPatrolMonthlyReport(
125
93
  address: buildAddress(siteInfo.address),
126
94
  },
127
95
  title: `Patrol Route Summary: ${routeName}`,
128
- rows: mapLogsToMonthlyRows(logs),
96
+ rows: logs.map(mapLogToSummaryRow),
129
97
  };
130
98
  } else {
131
99
  report.value = null;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.0.31-staging.27",
5
+ "version": "3.1.1",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -41,5 +41,4 @@ type TSiteCategory =
41
41
  | "shopping_mall"
42
42
  | "mix_development"
43
43
  | "industrial"
44
- | "co_working"
45
- | "co_living";
44
+ | "co_working_co_living";
@@ -6,10 +6,6 @@ declare type TEquipment = {
6
6
  unitOfMeasurement: string;
7
7
  createdAt: string;
8
8
  updatedAt: string;
9
- attachment?: string;
10
9
  };
11
10
 
12
- declare type TEquipmentCreate = Pick<
13
- TEquipment,
14
- "name" | "unitOfMeasurement" | "attachment"
15
- >;
11
+ declare type TEquipmentCreate = Pick<TEquipment, "name" | "unitOfMeasurement">;