@7365admin1/layer-common 3.0.21 → 3.0.23

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/DashboardMain.vue +211 -58
  3. package/components/InvitationMain.vue +32 -3
  4. package/components/MemberMain.vue +1 -1
  5. package/components/Nfc/NFCPatrolReportMain.vue +48 -541
  6. package/components/Nfc/PatrolReport/DailyReportTab.vue +59 -0
  7. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +59 -0
  8. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +81 -0
  9. package/components/Nfc/PatrolReport/PatrolReportTab.vue +73 -0
  10. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +63 -0
  11. package/components/Nfc/PatrolReport/RemarksDialog.vue +37 -0
  12. package/components/Nfc/PatrolReport/ReportEmptyState.vue +24 -0
  13. package/components/Nfc/PatrolReport/ReportFilters.vue +88 -0
  14. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +17 -0
  15. package/components/Nfc/PatrolReport/SummaryReportTable.vue +51 -0
  16. package/components/ScheduleTaskMain.vue +47 -12
  17. package/components/ServiceProviderMain.vue +182 -184
  18. package/components/UnitMain.vue +8 -1
  19. package/components/VisitorForm.vue +2 -0
  20. package/components/VisitorManagement.vue +31 -0
  21. package/components/VisitorSocketPopUp.vue +292 -0
  22. package/composables/useNFCPatrolDailyReport.ts +27 -0
  23. package/composables/useNFCPatrolMonthlyReport.ts +27 -0
  24. package/composables/useNFCPatrolReport.ts +38 -0
  25. package/composables/useNFCPatrolReportExport.ts +31 -0
  26. package/composables/useNFCPatrolReportFilters.ts +39 -0
  27. package/composables/useVisitor.ts +3 -0
  28. package/composables/useVisitorSocket.ts +25 -0
  29. package/package.json +2 -1
  30. package/services/nfcPatrolReport.ts +172 -0
  31. package/types/nfc-patrol-report.ts +73 -0
  32. package/types/verification.d.ts +6 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 3.0.23
4
+
5
+ ### Patch Changes
6
+
7
+ - e4473f5: Daily Update Layer-common Version
8
+
9
+ ## 3.0.22
10
+
11
+ ### Patch Changes
12
+
13
+ - 632b007: Update Layer Common Version
14
+
3
15
  ## 3.0.21
4
16
 
5
17
  ### Patch Changes
@@ -350,6 +350,15 @@
350
350
  </v-list-item>
351
351
  </v-list>
352
352
  </v-menu>
353
+
354
+ <button
355
+ v-if="card.key === 'facilityBookings'"
356
+ class="figma-panel-action"
357
+ type="button"
358
+ @click="openFacilityBookingDialog"
359
+ >
360
+ View All
361
+ </button>
353
362
  </div>
354
363
 
355
364
  <div class="mt-6 w-100">
@@ -824,7 +833,6 @@
824
833
  <div class="dashboard-card dashboard-panel figma-panel">
825
834
  <div class="figma-panel-header">
826
835
  <h2>Today's Attentions</h2>
827
-
828
836
  </div>
829
837
  <div class="figma-panel-body">
830
838
  <div v-if="todayAttentions.length" class="figma-list">
@@ -1318,6 +1326,57 @@
1318
1326
  </v-card>
1319
1327
  </v-dialog>
1320
1328
 
1329
+ <v-dialog
1330
+ v-model="facilityBookingDialog"
1331
+ max-width="650"
1332
+ persistent
1333
+ scrollable
1334
+ >
1335
+ <v-card rounded="lg" class="facility-booking-modal">
1336
+ <v-card-title
1337
+ class="d-flex align-center justify-space-between pa-6 pb-3"
1338
+ >
1339
+ <div class="text-body-1 font-weight-black text-blue-grey-darken-4">
1340
+ Facility Booking
1341
+ </div>
1342
+ <v-btn
1343
+ icon="mdi-close"
1344
+ variant="text"
1345
+ density="comfortable"
1346
+ aria-label="Close facility booking summary"
1347
+ @click="facilityBookingDialog = false"
1348
+ />
1349
+ </v-card-title>
1350
+
1351
+ <v-card-text class="px-6 pb-6 pt-3">
1352
+ <div class="facility-booking-summary-list">
1353
+ <div
1354
+ v-for="item in facilityBookingBreakdown"
1355
+ :key="item.status"
1356
+ class="facility-booking-summary-row"
1357
+ >
1358
+ <span class="facility-booking-summary-label">
1359
+ {{ item.status }}
1360
+ </span>
1361
+ <span class="facility-booking-summary-dots" />
1362
+ <strong class="facility-booking-summary-value">
1363
+ {{ formatNumber(item.count) }}
1364
+ </strong>
1365
+ </div>
1366
+ </div>
1367
+
1368
+ <v-divider class="my-5" />
1369
+
1370
+ <div class="facility-booking-summary-row facility-booking-total-row">
1371
+ <strong class="facility-booking-total-label">Total</strong>
1372
+ <strong class="facility-booking-summary-value">
1373
+ {{ formatNumber(facilityBookingTotal) }}
1374
+ </strong>
1375
+ </div>
1376
+ </v-card-text>
1377
+ </v-card>
1378
+ </v-dialog>
1379
+
1321
1380
  <v-snackbar
1322
1381
  v-model="snackbar.visible"
1323
1382
  :timeout="2200"
@@ -1347,6 +1406,8 @@ type DashboardMetric = {
1347
1406
  outOfStock?: number;
1348
1407
  completed?: number;
1349
1408
  total?: number;
1409
+ byStatus?: Record<string, number>;
1410
+ statusCounts?: Record<string, number>;
1350
1411
  items?: { name: string; qty: number }[];
1351
1412
  };
1352
1413
 
@@ -1558,7 +1619,7 @@ const initialPeriod = (
1558
1619
  ) as TDashboardValues;
1559
1620
  const selectedRange = ref(periodLabelValue[initialPeriod]);
1560
1621
  const visitorType = ref("All");
1561
- const facilityType = ref("All");
1622
+ const facilityBookingDialog = ref(false);
1562
1623
  const visibleWidgetKeys = ref<string[]>([]);
1563
1624
  const draftVisibleWidgetKeys = ref<string[]>([]);
1564
1625
  const filterMenus = reactive<Record<string, boolean>>({});
@@ -2190,7 +2251,6 @@ const visitorOptions = computed(() =>
2190
2251
  )
2191
2252
  );
2192
2253
 
2193
- // Property-specific: facility booking metric with client-side filter
2194
2254
  const facilityBookingItems = computed(() =>
2195
2255
  getDashboardArray([
2196
2256
  "facilityBookingItems",
@@ -2198,20 +2258,47 @@ const facilityBookingItems = computed(() =>
2198
2258
  "bookingsList",
2199
2259
  ])
2200
2260
  );
2201
- const defaultFacilityOptions = ["All"];
2202
- const facilityOptions = computed(() =>
2203
- buildFilterOptions(
2204
- defaultFacilityOptions,
2205
- facilityBookingItems.value,
2206
- (item: any) =>
2207
- item.facilityName ||
2208
- item.facility?.name ||
2209
- item.facilityInfo?.name ||
2210
- item.name ||
2211
- ""
2212
- )
2261
+
2262
+ const facilityBookingStatuses = [
2263
+ "Pending",
2264
+ "Approved",
2265
+ "Ongoing",
2266
+ "For Review",
2267
+ "With Balance",
2268
+ "With Refund",
2269
+ "Completed",
2270
+ "Rejected",
2271
+ "Cancelled",
2272
+ ];
2273
+
2274
+ const facilityBookingBreakdown = computed(() => {
2275
+ const metric = getMetric(["facilityBooking", "facilityBookings", "bookings"]);
2276
+ const sourceCounts = metric.byStatus ?? metric.statusCounts ?? {};
2277
+ const itemCounts: Record<string, number> = {};
2278
+ facilityBookingItems.value.forEach((item: any) => {
2279
+ const status = normalizeFacilityBookingStatus(item.status);
2280
+ if (status) itemCounts[status] = (itemCounts[status] || 0) + 1;
2281
+ });
2282
+
2283
+ return facilityBookingStatuses.map((status) => ({
2284
+ status,
2285
+ count: toNumber(sourceCounts[status] ?? itemCounts[status]),
2286
+ }));
2287
+ });
2288
+
2289
+ const facilityBookingTotal = computed(() =>
2290
+ facilityBookingBreakdown.value.reduce((total, item) => total + item.count, 0)
2213
2291
  );
2214
2292
 
2293
+ function openFacilityBookingDialog() {
2294
+ facilityBookingDialog.value = true;
2295
+ }
2296
+
2297
+ function normalizeFacilityBookingStatus(status: unknown) {
2298
+ const value = String(status || "").trim();
2299
+ return value === "For Refund" ? "With Refund" : value;
2300
+ }
2301
+
2215
2302
  function getPropertyKpiCards() {
2216
2303
  const workOrderMetric = getMetric([
2217
2304
  "openWorkOrder",
@@ -2281,53 +2368,13 @@ function getPropertyKpiCards() {
2281
2368
  percentage: toNumber(facilityMetric?.percentage),
2282
2369
  meta: getMetricMeta(facilityMetric),
2283
2370
  raw: facilityMetric,
2284
- filter: {
2285
- model: facilityType.value,
2286
- options: facilityOptions.value,
2287
- set: (v: string) => {
2288
- facilityType.value = v;
2289
- filterMenus.facilityBookings = false;
2290
- },
2291
- },
2371
+ filter: undefined,
2292
2372
  },
2293
2373
  ];
2294
2374
  }
2295
2375
 
2296
2376
  function computeFacilityMetric(): DashboardMetric {
2297
- const metric = getMetric(["facilityBooking", "facilityBookings", "bookings"]);
2298
- const selected = facilityType.value;
2299
- if (selected === "All") return metric;
2300
- const items = facilityBookingItems.value.filter((item: any) => {
2301
- const val = String(
2302
- item.facilityName ||
2303
- item.facility?.name ||
2304
- item.facilityInfo?.name ||
2305
- item.name ||
2306
- ""
2307
- ).toLowerCase();
2308
- return val === selected.toLowerCase();
2309
- });
2310
- const ongoing = items.filter((i: any) => {
2311
- const s = String(i.status || "").toLowerCase();
2312
- return (
2313
- s.includes("ongoing") ||
2314
- s.includes("approved") ||
2315
- s.includes("in-progress")
2316
- );
2317
- }).length;
2318
- const waitingApproval = items.filter((i: any) => {
2319
- const s = String(i.status || "").toLowerCase();
2320
- return (
2321
- s.includes("pending") || s.includes("review") || s.includes("approval")
2322
- );
2323
- }).length;
2324
-
2325
- return {
2326
- ...metric,
2327
- count: ongoing + waitingApproval,
2328
- ongoing,
2329
- waitingApproval,
2330
- };
2377
+ return getMetric(["facilityBooking", "facilityBookings", "bookings"]);
2331
2378
  }
2332
2379
 
2333
2380
  function getSecurityKpiCards() {
@@ -2472,7 +2519,14 @@ const primaryListConfig = computed(() =>
2472
2519
  const activityTitle = computed(() => {
2473
2520
  if (selectedRange.value === "Today") return "Today's Activity";
2474
2521
  if (selectedRange.value === "This Month") return "This Month Activity";
2475
- return "This Week Activity";
2522
+ return `This Week Activity (${currentWeekRangeLabel.value})`;
2523
+ });
2524
+
2525
+ const currentWeekRangeLabel = computed(() => {
2526
+ const today = new Date();
2527
+ const start = getMondayWeekStart(today);
2528
+ const end = addDays(start, 6);
2529
+ return `Week ${getWeekOfMonth(today)}: ${formatWeekRange(start, end)}`;
2476
2530
  });
2477
2531
 
2478
2532
  const activityChart = computed(() =>
@@ -2509,6 +2563,50 @@ const hasChartData = computed(
2509
2563
  );
2510
2564
  const chartTicks = [28, 66.4, 104.8, 143.2, 181.6, 220];
2511
2565
 
2566
+ function getMondayWeekStart(date: Date) {
2567
+ const start = new Date(date);
2568
+ start.setHours(0, 0, 0, 0);
2569
+ start.setDate(start.getDate() - ((start.getDay() + 6) % 7));
2570
+ return start;
2571
+ }
2572
+
2573
+ function addDays(date: Date, days: number) {
2574
+ const next = new Date(date);
2575
+ next.setDate(next.getDate() + days);
2576
+ return next;
2577
+ }
2578
+
2579
+ function getWeekOfMonth(date: Date) {
2580
+ const monthStart = new Date(date.getFullYear(), date.getMonth(), 1);
2581
+ const firstWeekStart = getMondayWeekStart(monthStart);
2582
+ const currentWeekStart = getMondayWeekStart(date);
2583
+ const diffMs = currentWeekStart.getTime() - firstWeekStart.getTime();
2584
+ return Math.floor(diffMs / (7 * 24 * 60 * 60 * 1000)) + 1;
2585
+ }
2586
+
2587
+ function formatWeekRange(start: Date, end: Date) {
2588
+ const sameMonth = start.getMonth() === end.getMonth();
2589
+ const sameYear = start.getFullYear() === end.getFullYear();
2590
+ const monthDayFormatter = new Intl.DateTimeFormat("en-US", {
2591
+ month: "long",
2592
+ day: "numeric",
2593
+ });
2594
+ const dayFormatter = new Intl.DateTimeFormat("en-US", { day: "numeric" });
2595
+ const fullFormatter = new Intl.DateTimeFormat("en-US", {
2596
+ month: "long",
2597
+ day: "numeric",
2598
+ year: "numeric",
2599
+ });
2600
+
2601
+ if (sameMonth && sameYear) {
2602
+ return `${monthDayFormatter.format(start)} - ${dayFormatter.format(
2603
+ end
2604
+ )}, ${end.getFullYear()}`;
2605
+ }
2606
+
2607
+ return `${fullFormatter.format(start)} - ${fullFormatter.format(end)}`;
2608
+ }
2609
+
2512
2610
  const chartYLabels = computed(() => {
2513
2611
  const max = chartMax.value;
2514
2612
  return [
@@ -4107,6 +4205,54 @@ function formatDashboardTime(value?: string) {
4107
4205
  white-space: nowrap;
4108
4206
  }
4109
4207
 
4208
+ .facility-booking-modal {
4209
+ border: 1px solid #d9e0e7;
4210
+ box-shadow: 0 18px 48px rgba(15, 35, 54, 0.18);
4211
+ }
4212
+
4213
+ .facility-booking-summary-list {
4214
+ display: grid;
4215
+ gap: 24px;
4216
+ }
4217
+
4218
+ .facility-booking-summary-row {
4219
+ align-items: center;
4220
+ display: grid;
4221
+ gap: 16px;
4222
+ grid-template-columns: minmax(112px, max-content) minmax(40px, 1fr) minmax(
4223
+ 32px,
4224
+ max-content
4225
+ );
4226
+ min-height: 22px;
4227
+ }
4228
+
4229
+ .facility-booking-summary-label {
4230
+ color: #1565c0;
4231
+ font-size: 16px;
4232
+ font-weight: 500;
4233
+ line-height: 1.25;
4234
+ }
4235
+
4236
+ .facility-booking-summary-dots {
4237
+ border-bottom: 2px dotted #9aa3ad;
4238
+ display: block;
4239
+ height: 1px;
4240
+ min-width: 32px;
4241
+ }
4242
+
4243
+ .facility-booking-summary-value,
4244
+ .facility-booking-total-label {
4245
+ color: #0b2f47;
4246
+ font-size: 16px;
4247
+ font-weight: 800;
4248
+ line-height: 1.25;
4249
+ }
4250
+
4251
+ .facility-booking-total-row {
4252
+ grid-template-columns: minmax(112px, max-content) minmax(32px, max-content);
4253
+ justify-content: space-between;
4254
+ }
4255
+
4110
4256
  .figma-panel-body {
4111
4257
  flex: 1;
4112
4258
  margin-top: 20px;
@@ -4209,18 +4355,22 @@ function formatDashboardTime(value?: string) {
4209
4355
  background: #ffe0de;
4210
4356
  color: #d72f2f;
4211
4357
  }
4358
+
4212
4359
  .figma-icon-badge-info {
4213
4360
  background: #e8e2ff;
4214
4361
  color: #6d4bd8;
4215
4362
  }
4363
+
4216
4364
  .figma-icon-badge-success {
4217
4365
  background: #ddf7e6;
4218
4366
  color: #229852;
4219
4367
  }
4368
+
4220
4369
  .figma-icon-badge-warning {
4221
4370
  background: #ffeed4;
4222
4371
  color: #c57916;
4223
4372
  }
4373
+
4224
4374
  .figma-icon-badge-neutral {
4225
4375
  background: #edf2f6;
4226
4376
  color: #607386;
@@ -4356,14 +4506,17 @@ function formatDashboardTime(value?: string) {
4356
4506
  background: #72e3a1;
4357
4507
  color: #0f5132;
4358
4508
  }
4509
+
4359
4510
  .feedback-status-pill-progress {
4360
4511
  background: #fde68a;
4361
4512
  color: #92400e;
4362
4513
  }
4514
+
4363
4515
  .feedback-status-pill-danger {
4364
4516
  background: #fecaca;
4365
4517
  color: #b91c1c;
4366
4518
  }
4519
+
4367
4520
  .feedback-status-pill-todo {
4368
4521
  background: #e2e8f0;
4369
4522
  color: #64748b;
@@ -90,7 +90,7 @@
90
90
  v-if="props.cancelInvitation"
91
91
  >
92
92
  <template v-slot:activator="{ props }">
93
- <v-icon v-bind="props">mdi-dots-horizontal</v-icon>
93
+ <v-icon v-bind="props">mdi-dots-vertical</v-icon>
94
94
  </template>
95
95
  <v-list>
96
96
  <v-list-item @click="openConfirmDialog(item._id ?? '')" v-if="item.status === 'pending'">
@@ -99,6 +99,9 @@
99
99
  </v-list>
100
100
  </v-menu>
101
101
  </template>
102
+ <template #item.status="{value}">
103
+ {{ formatStatus(value) }}
104
+ </template>
102
105
  </v-data-table>
103
106
  </v-card>
104
107
  </v-col>
@@ -198,14 +201,27 @@ authenticate();
198
201
 
199
202
  const headers = computed(() => {
200
203
  const baseHeaders = [
204
+ {
205
+ title: "E-mail",
206
+ value: "email",
207
+ },
208
+ {
209
+ title: "Roles and Permission",
210
+ value: "roleName",
211
+ },
201
212
  {
202
213
  title: "Date",
203
214
  value: "createdAt",
204
215
  },
205
216
  {
206
- title: "E-mail",
207
- value: "email",
217
+ title: "Site Invited To",
218
+ value: "metadata.siteName",
219
+ },
220
+ {
221
+ title: "Status",
222
+ value: "status",
208
223
  },
224
+
209
225
  ];
210
226
 
211
227
  if (props.status === "pending" && props.cancelInvitation) {
@@ -318,6 +334,19 @@ watchEffect(() => {
318
334
  }
319
335
  });
320
336
 
337
+ const formatStatus = (status: string) => {
338
+ switch (status) {
339
+ case "pending":
340
+ return "Pending Invite";
341
+ case "expired":
342
+ return "Invitation expired";
343
+ case "accepted":
344
+ return "Accepted";
345
+ default:
346
+ return status?.[0]?.toUpperCase() + status?.slice(1) || "";
347
+ }
348
+ };
349
+
321
350
  const dialogMember = ref(false);
322
351
 
323
352
  function inviteMember() {
@@ -90,7 +90,7 @@
90
90
  width="150"
91
91
  >
92
92
  <template v-slot:activator="{ props }">
93
- <v-icon v-bind="props">mdi-dots-horizontal</v-icon>
93
+ <v-icon v-bind="props">mdi-dots-vertical</v-icon>
94
94
  </template>
95
95
  <v-list>
96
96
  <v-list-item