@7365admin1/layer-common 3.0.17 → 3.0.19

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
+ ## 3.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - 96c813f: Update Layer Common version
8
+
9
+ ## 3.0.18
10
+
11
+ ### Patch Changes
12
+
13
+ - 8cf0029: Update Package Version release
14
+
3
15
  ## 3.0.17
4
16
 
5
17
  ### Patch Changes
@@ -15,8 +15,8 @@
15
15
  <v-btn fab icon density="comfortable" @click="getBuildings()">
16
16
  <v-icon>mdi-refresh</v-icon>
17
17
  </v-btn>
18
- <v-text-field v-model="searchInput" autocomplete="off" placeholder="Search" density="compact" hide-details dense clearable
19
- class="mx-2" style="width: 200px;" />
18
+ <v-text-field v-model="searchInput" autocomplete="off" placeholder="Search" density="compact" hide-details
19
+ dense clearable class="mx-2" style="width: 200px;" />
20
20
  </template>
21
21
 
22
22
  <template #append>
@@ -45,7 +45,7 @@
45
45
  <v-dialog v-model="createDialog" width="450" persistent>
46
46
  <BuildingForm :site="site" mode="add" :blocks="items" @cancel="createDialog = false" @success="successCreate()"
47
47
  @success:create-more="getBuildings()" />
48
- </v-dialog>
48
+ </v-dialog>
49
49
 
50
50
  <!-- Edit Dialog -->
51
51
  <v-dialog v-model="editDialog" width="450" persistent>
@@ -222,7 +222,10 @@ const site = (useRoute().params.site as string) ?? "";
222
222
  const status = (useRoute().params.status as string) ?? "active";
223
223
 
224
224
  const { debounce } = useUtils();
225
- const { getAll: _getBuildings, deleteById } = useBuilding();
225
+ const {
226
+ getAll: _getBuildings,
227
+ deleteById,
228
+ } = useBuilding();
226
229
 
227
230
  const searchInput = ref("");
228
231
  const headerSearch = ref("");
@@ -6,6 +6,13 @@
6
6
  v-if="canCreate && canCreateUnit">
7
7
  Add Unit
8
8
  </v-btn>
9
+ <v-btn class="text-none ml-2" rounded="pill" variant="tonal" @click="openBulkUploadFilePicker()" size="large"
10
+ :loading="bulkUploadLoading" v-if="canCreate && canCreateUnit">
11
+ <v-icon start>mdi-upload</v-icon>
12
+ Bulk Upload
13
+ </v-btn>
14
+ <input ref="bulkUploadFileInput" type="file" accept=".xlsx,.xls,.csv" class="d-none"
15
+ @change="handleBulkUploadFileChange" />
9
16
  </v-row>
10
17
  </v-col>
11
18
  <v-col cols="12">
@@ -30,8 +37,7 @@
30
37
  </v-toolbar>
31
38
 
32
39
  <v-data-table :headers="props.headers" :items="items" item-value="_id" items-per-page="20" fixed-header
33
- hide-default-footer @click:row="tableRowClickHandler"
34
- style="max-height: calc(100vh - (200px))">
40
+ hide-default-footer @click:row="tableRowClickHandler" style="max-height: calc(100vh - (200px))">
35
41
  <template #item.block="{ value, item }">
36
42
  {{ value ? `Block ${value}` : "" }}
37
43
  </template>
@@ -44,10 +50,10 @@
44
50
  <BuildingUnitFormAdd :site="site" @cancel="createDialog = false" @success="successCreate()"
45
51
  @success:create-more="getBuildingUnit()" />
46
52
  </v-dialog>
47
-
48
- <v-dialog v-if="canViewUnitDetails" v-model="dialogPreview":width="smAndUp ? '70svw' : '100%'" persistent>
49
- <BuildingUnitFormEdit v-if="selectedBuildingUnit" @cancel="dialogPreview = false" @success="successUpdate()" @delete-unit="openDeleteDialog()"
50
- :roomFacility="selectedBuildingUnit" />
53
+
54
+ <v-dialog v-if="canViewUnitDetails" v-model="dialogPreview" :width="smAndUp ? '70svw' : '100%'" persistent>
55
+ <BuildingUnitFormEdit v-if="selectedBuildingUnit" @cancel="dialogPreview = false" @success="successUpdate()"
56
+ @delete-unit="openDeleteDialog()" :roomFacility="selectedBuildingUnit" />
51
57
  </v-dialog>
52
58
 
53
59
  <v-dialog v-model="dialogDelete" :loading="deleteLoading" width="450" persistent>
@@ -88,6 +94,67 @@
88
94
  </v-card>
89
95
  </v-dialog>
90
96
 
97
+ <v-dialog v-model="bulkUploadSkippedDialog" :width="smAndUp ? '720' : '100%'" persistent>
98
+ <v-card width="100%">
99
+ <v-toolbar density="compact" class="pl-4">
100
+ <span class="font-weight-medium text-h5">Skipped Rows</span>
101
+ </v-toolbar>
102
+
103
+ <v-card-text style="max-height: 70vh; overflow-y: auto">
104
+ <p class="text-subtitle-2 mb-4">
105
+ Some rows were not imported. Review the details below and update the sheet if needed.
106
+ </p>
107
+
108
+ <v-alert v-if="bulkUploadSkippedSummary.length" type="warning" variant="tonal" density="compact"
109
+ class="mb-4">
110
+ {{ bulkUploadSkippedSummary.length }} row(s) skipped during bulk upload.
111
+ </v-alert>
112
+
113
+ <!-- <v-list v-if="bulkUploadSkippedSummary.length" density="compact" class="pa-0 mb-4">
114
+ <v-list-item v-for="(item, index) in bulkUploadSkippedSummary" :key="`summary-${index}`"
115
+ class="px-0">
116
+ <v-list-item-title class="text-subtitle-2">
117
+ Row {{ item.rowNumber ?? item.row ?? "N/A" }}
118
+ </v-list-item-title>
119
+ <v-list-item-subtitle class="text-wrap">
120
+ {{ formatSkippedSummaryReason(item) }}
121
+ </v-list-item-subtitle>
122
+ </v-list-item>
123
+ </v-list> -->
124
+
125
+ <template v-if="bulkUploadSkippedErrors.length">
126
+ <!-- <v-divider class="mb-4" /> -->
127
+ <p class="text-subtitle-2 mb-2">Details</p>
128
+ <v-table density="compact">
129
+ <thead>
130
+ <tr>
131
+ <th class="text-left">Row</th>
132
+ <th class="text-left">Block</th>
133
+ <th class="text-left">Building</th>
134
+ <th class="text-left">Reason</th>
135
+ </tr>
136
+ </thead>
137
+ <tbody>
138
+ <tr v-for="(item, index) in bulkUploadSkippedErrors" :key="`error-${index}`">
139
+ <td>{{ item.row ?? "N/A" }}</td>
140
+ <td>{{ item.block ?? "N/A" }}</td>
141
+ <td>{{ item.name ?? "N/A" }}</td>
142
+ <td>{{ item.reason ?? "Skipped" }}</td>
143
+ </tr>
144
+ </tbody>
145
+ </v-table>
146
+ </template>
147
+ </v-card-text>
148
+
149
+ <v-toolbar density="compact">
150
+ <v-btn tile block size="48" color="black" variant="flat" class="text-none"
151
+ @click="bulkUploadSkippedDialog = false">
152
+ Close
153
+ </v-btn>
154
+ </v-toolbar>
155
+ </v-card>
156
+ </v-dialog>
157
+
91
158
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
92
159
  </v-row>
93
160
  </template>
@@ -103,7 +170,7 @@ const props = defineProps({
103
170
  title: "Unit Name",
104
171
  value: "name",
105
172
  },
106
- {
173
+ {
107
174
  title: "Level",
108
175
  value: "level.name",
109
176
  },
@@ -115,7 +182,7 @@ const props = defineProps({
115
182
  title: "Block Name",
116
183
  value: "buildingName",
117
184
  },
118
-
185
+
119
186
  // {
120
187
  // title: "Type",
121
188
  // value: "type",
@@ -162,6 +229,8 @@ const { debounce, formatDateDDMMYYYYLocal, toOrdinal } = useUtils();
162
229
 
163
230
  const { getAll, deleteById } = useBuildingUnit();
164
231
 
232
+ const { uploadSpreadsheetBuilding } = useBuilding();
233
+
165
234
  const searchInput = ref("");
166
235
  const headerSearch = ref("");
167
236
  const page = ref(1);
@@ -243,6 +312,12 @@ function openEditDialog() {
243
312
 
244
313
  const dialogDelete = ref(false);
245
314
  const deleteLoading = ref(false);
315
+ const bulkUploadFile = ref<File | null>(null);
316
+ const bulkUploadFileInput = ref<HTMLInputElement | null>(null);
317
+ const bulkUploadLoading = ref(false);
318
+ const bulkUploadSkippedDialog = ref(false);
319
+ const bulkUploadSkippedErrors = ref<Array<Record<string, any>>>([]);
320
+ const bulkUploadSkippedSummary = ref<Array<Record<string, any>>>([]);
246
321
 
247
322
  function openDeleteDialog() {
248
323
  dialogDelete.value = true;
@@ -272,6 +347,109 @@ async function handleDeleteBuildingUnit() {
272
347
  }
273
348
  }
274
349
 
350
+ function openBulkUploadFilePicker() {
351
+ if (bulkUploadLoading.value) {
352
+ return;
353
+ }
354
+
355
+ bulkUploadFile.value = null;
356
+ bulkUploadSkippedErrors.value = [];
357
+ bulkUploadSkippedSummary.value = [];
358
+ bulkUploadSkippedDialog.value = false;
359
+ if (bulkUploadFileInput.value) {
360
+ bulkUploadFileInput.value.value = "";
361
+ bulkUploadFileInput.value.click();
362
+ }
363
+ }
364
+
365
+ function formatSkippedSummaryReason(item: Record<string, any>) {
366
+ if (Array.isArray(item.errors) && item.errors.length) {
367
+ return item.errors.join(", ");
368
+ }
369
+
370
+ return item.reason ?? "Skipped";
371
+ }
372
+
373
+ function getSkippedRowsCount(result: Record<string, any> | null | undefined) {
374
+ if (typeof result?.skippedRows === "number") {
375
+ return result.skippedRows;
376
+ }
377
+
378
+ if (Array.isArray(result?.skippedRows)) {
379
+ return result.skippedRows.length;
380
+ }
381
+
382
+ return 0;
383
+ }
384
+
385
+ function getBulkUploadSkippedErrors(result: Record<string, any> | null | undefined) {
386
+ if (Array.isArray(result?.skippedErrors)) {
387
+ return result.skippedErrors;
388
+ }
389
+
390
+ if (Array.isArray(result?.skippedRows)) {
391
+ return result.skippedRows;
392
+ }
393
+
394
+ return [];
395
+ }
396
+
397
+ function getBulkUploadSkippedSummary(result: Record<string, any> | null | undefined) {
398
+ if (Array.isArray(result?.skippedItemsSummary)) {
399
+ return result.skippedItemsSummary;
400
+ }
401
+
402
+ if (Array.isArray(result?.skippedRows)) {
403
+ return result.skippedRows.map((item: Record<string, any>) => ({
404
+ rowNumber: item.row,
405
+ reason: item.reason,
406
+ block: item.block,
407
+ name: item.name,
408
+ data: item,
409
+ }));
410
+ }
411
+
412
+ return [];
413
+ }
414
+
415
+ async function handleBulkUploadFileChange(event: Event) {
416
+ const input = event.target as HTMLInputElement;
417
+ const file = input.files?.[0] ?? null;
418
+
419
+ if (!file) {
420
+ return;
421
+ }
422
+
423
+ bulkUploadFile.value = file;
424
+ bulkUploadLoading.value = true;
425
+
426
+ try {
427
+ const result = await uploadSpreadsheetBuilding({
428
+ site,
429
+ file,
430
+ });
431
+ await getBuildingUnit();
432
+ bulkUploadSkippedErrors.value = getBulkUploadSkippedErrors(result);
433
+ bulkUploadSkippedSummary.value = getBulkUploadSkippedSummary(result);
434
+ bulkUploadSkippedDialog.value =
435
+ bulkUploadSkippedErrors.value.length > 0 ||
436
+ bulkUploadSkippedSummary.value.length > 0;
437
+ showMessage(
438
+ `Bulk upload completed. Inserted ${result?.insertedUnits ?? 0} unit(s). Skipped ${getSkippedRowsCount(result)} row(s).`,
439
+ "success"
440
+ );
441
+ } catch (error: any) {
442
+ showMessage(
443
+ error?.response?._data?.message || "Failed to upload spreadsheet.",
444
+ "error"
445
+ );
446
+ } finally {
447
+ bulkUploadLoading.value = false;
448
+ bulkUploadFile.value = null;
449
+ input.value = "";
450
+ }
451
+ }
452
+
275
453
  function setBuilding({
276
454
  mode = "create",
277
455
  dialog = true,
@@ -290,7 +468,7 @@ function setBuilding({
290
468
  </script>
291
469
 
292
470
  <style scoped>
293
- .v-data-table :deep(thead th) {
471
+ .v-data-table :deep(thead th) {
294
472
  font-weight: bold !important;
295
473
  }
296
474
  </style>
@@ -3547,10 +3547,10 @@ async function exportDashboard(format: "PDF" | "Word") {
3547
3547
  }
3548
3548
 
3549
3549
  if (isHygieneMode.value) {
3550
+ wordHtml += `<div class="section-title">${primaryListConfig.value.title}</div>`;
3551
+ wordHtml += `<table class="data-table">`;
3552
+ wordHtml += `<thead><tr><th>Task Title</th><th>Description / Subtitle</th><th>Assigned Staff</th><th>Time</th><th>Status</th></tr></thead><tbody>`;
3550
3553
  if (taskScheduleItems.value.length > 0) {
3551
- wordHtml += `<div class="section-title">${primaryListConfig.value.title}</div>`;
3552
- wordHtml += `<table class="data-table">`;
3553
- wordHtml += `<thead><tr><th>Task Title</th><th>Description / Subtitle</th><th>Assigned Staff</th><th>Time</th><th>Status</th></tr></thead><tbody>`;
3554
3554
  taskScheduleItems.value.forEach((item) => {
3555
3555
  wordHtml += `<tr>
3556
3556
  <td><strong>${item.title}</strong></td>
@@ -3562,13 +3562,15 @@ async function exportDashboard(format: "PDF" | "Word") {
3562
3562
  )}">${item.status || ""}</span></td>
3563
3563
  </tr>`;
3564
3564
  });
3565
- wordHtml += `</tbody></table>`;
3565
+ } else {
3566
+ wordHtml += `<tr><td colspan="5" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3566
3567
  }
3568
+ wordHtml += `</tbody></table>`;
3567
3569
 
3570
+ wordHtml += `<div class="section-title">Staff's Total Attendance</div>`;
3571
+ wordHtml += `<table class="data-table">`;
3572
+ wordHtml += `<thead><tr><th>Staff Name</th><th>Role</th><th>Total Hours</th></tr></thead><tbody>`;
3568
3573
  if (staffStatusItems.value.length > 0) {
3569
- wordHtml += `<div class="section-title">Staff's Total Attendance</div>`;
3570
- wordHtml += `<table class="data-table">`;
3571
- wordHtml += `<thead><tr><th>Staff Name</th><th>Role</th><th>Total Hours</th></tr></thead><tbody>`;
3572
3574
  staffStatusItems.value.forEach((item) => {
3573
3575
  wordHtml += `<tr>
3574
3576
  <td><strong>${item.name}</strong></td>
@@ -3576,69 +3578,79 @@ async function exportDashboard(format: "PDF" | "Word") {
3576
3578
  <td>${item.totalHours || 0} hrs</td>
3577
3579
  </tr>`;
3578
3580
  });
3579
- wordHtml += `</tbody></table>`;
3581
+ } else {
3582
+ wordHtml += `<tr><td colspan="3" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3580
3583
  }
3584
+ wordHtml += `</tbody></table>`;
3581
3585
  }
3582
3586
 
3583
3587
  if (isPropertyMode.value) {
3588
+ wordHtml += `<div class="section-title">Upcoming Events</div>`;
3589
+ wordHtml += `<table class="data-table">`;
3590
+ wordHtml += `<thead><tr><th>Event Title</th><th>Description / Subtitle</th></tr></thead><tbody>`;
3584
3591
  if (upcomingEvents.value.length > 0) {
3585
- wordHtml += `<div class="section-title">Upcoming Events</div>`;
3586
- wordHtml += `<table class="data-table">`;
3587
- wordHtml += `<thead><tr><th>Event Title</th><th>Description / Subtitle</th></tr></thead><tbody>`;
3588
3592
  upcomingEvents.value.forEach((item) => {
3589
3593
  wordHtml += `<tr>
3590
3594
  <td><strong>${item.title}</strong></td>
3591
3595
  <td>${item.subtitle || ""}</td>
3592
3596
  </tr>`;
3593
3597
  });
3594
- wordHtml += `</tbody></table>`;
3598
+ } else {
3599
+ wordHtml += `<tr><td colspan="2" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3595
3600
  }
3601
+ wordHtml += `</tbody></table>`;
3596
3602
 
3603
+ wordHtml += `<div class="section-title">Today's Reminder</div>`;
3604
+ wordHtml += `<table class="data-table">`;
3605
+ wordHtml += `<thead><tr><th>Reminder</th><th>Details / Subtitle</th></tr></thead><tbody>`;
3597
3606
  if (todayReminders.value.length > 0) {
3598
- wordHtml += `<div class="section-title">Today's Reminder</div>`;
3599
- wordHtml += `<table class="data-table">`;
3600
- wordHtml += `<thead><tr><th>Reminder</th><th>Details / Subtitle</th></tr></thead><tbody>`;
3601
3607
  todayReminders.value.forEach((item) => {
3602
3608
  wordHtml += `<tr>
3603
3609
  <td><strong>${item.title}</strong></td>
3604
3610
  <td>${item.subtitle || ""}</td>
3605
3611
  </tr>`;
3606
3612
  });
3607
- wordHtml += `</tbody></table>`;
3613
+ } else {
3614
+ wordHtml += `<tr><td colspan="2" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3608
3615
  }
3616
+ wordHtml += `</tbody></table>`;
3609
3617
 
3618
+ wordHtml += `<div class="section-title">Today's Attentions</div>`;
3619
+ wordHtml += `<table class="data-table">`;
3620
+ wordHtml += `<thead><tr><th>Attention</th><th>Details / Subtitle</th></tr></thead><tbody>`;
3610
3621
  if (todayAttentions.value.length > 0) {
3611
- wordHtml += `<div class="section-title">Today's Attentions</div>`;
3612
- wordHtml += `<table class="data-table">`;
3613
- wordHtml += `<thead><tr><th>Attention</th><th>Details / Subtitle</th></tr></thead><tbody>`;
3614
3622
  todayAttentions.value.forEach((item) => {
3615
3623
  wordHtml += `<tr>
3616
3624
  <td><strong>${item.title}</strong></td>
3617
3625
  <td>${item.subtitle || ""}</td>
3618
3626
  </tr>`;
3619
3627
  });
3620
- wordHtml += `</tbody></table>`;
3628
+ } else {
3629
+ wordHtml += `<tr><td colspan="2" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3621
3630
  }
3631
+ wordHtml += `</tbody></table>`;
3622
3632
 
3633
+ wordHtml += `<div class="section-title">Work Order Status</div>`;
3634
+ wordHtml += `<table class="data-table">`;
3635
+ wordHtml += `<thead><tr><th>Status</th><th>Count / Value</th></tr></thead><tbody>`;
3623
3636
  if (workOrderStatus.value.length > 0) {
3624
- wordHtml += `<div class="section-title">Work Order Status</div>`;
3625
- wordHtml += `<table class="data-table">`;
3626
- wordHtml += `<thead><tr><th>Status</th><th>Count / Value</th></tr></thead><tbody>`;
3627
3637
  workOrderStatus.value.forEach((item) => {
3628
3638
  wordHtml += `<tr>
3629
3639
  <td><strong>${item.label}</strong></td>
3630
3640
  <td>${item.value || 0}</td>
3631
3641
  </tr>`;
3632
3642
  });
3633
- wordHtml += `</tbody></table>`;
3643
+ } else {
3644
+ wordHtml += `<tr><td colspan="2" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3634
3645
  }
3646
+ wordHtml += `</tbody></table>`;
3635
3647
  }
3636
3648
 
3637
3649
  if (isSecurityMode.value) {
3650
+ wordHtml += `<div class="section-title">Active Patrol</div>`;
3651
+ wordHtml += `<table class="data-table">`;
3652
+ wordHtml += `<thead><tr><th>Patrol Route</th><th>Location</th><th>Assigned Officer</th><th>Status</th></tr></thead><tbody>`;
3638
3653
  if (activePatrolItems.value.length > 0) {
3639
- wordHtml += `<div class="section-title">Active Patrol</div>`;
3640
- wordHtml += `<table class="data-table">`;
3641
- wordHtml += `<thead><tr><th>Patrol Route</th><th>Location</th><th>Assigned Officer</th><th>Status</th></tr></thead><tbody>`;
3642
3654
  activePatrolItems.value.forEach((item) => {
3643
3655
  wordHtml += `<tr>
3644
3656
  <td><strong>${item.title || item.name}</strong></td>
@@ -3649,13 +3661,15 @@ async function exportDashboard(format: "PDF" | "Word") {
3649
3661
  )}">${item.status || ""}</span></td>
3650
3662
  </tr>`;
3651
3663
  });
3652
- wordHtml += `</tbody></table>`;
3664
+ } else {
3665
+ wordHtml += `<tr><td colspan="4" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3653
3666
  }
3667
+ wordHtml += `</tbody></table>`;
3654
3668
 
3669
+ wordHtml += `<div class="section-title">Staff Status</div>`;
3670
+ wordHtml += `<table class="data-table">`;
3671
+ wordHtml += `<thead><tr><th>Officer Name</th><th>Role / Designation</th><th>Status</th></tr></thead><tbody>`;
3655
3672
  if (securityStaffItems.value.length > 0) {
3656
- wordHtml += `<div class="section-title">Staff Status</div>`;
3657
- wordHtml += `<table class="data-table">`;
3658
- wordHtml += `<thead><tr><th>Officer Name</th><th>Role / Designation</th><th>Status</th></tr></thead><tbody>`;
3659
3673
  securityStaffItems.value.forEach((item) => {
3660
3674
  wordHtml += `<tr>
3661
3675
  <td><strong>${
@@ -3667,14 +3681,16 @@ async function exportDashboard(format: "PDF" | "Word") {
3667
3681
  )}">${item.status || "On duty"}</span></td>
3668
3682
  </tr>`;
3669
3683
  });
3670
- wordHtml += `</tbody></table>`;
3684
+ } else {
3685
+ wordHtml += `<tr><td colspan="3" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3671
3686
  }
3687
+ wordHtml += `</tbody></table>`;
3672
3688
  }
3673
3689
 
3690
+ wordHtml += `<div class="section-title">Feedbacks</div>`;
3691
+ wordHtml += `<table class="data-table">`;
3692
+ wordHtml += `<thead><tr><th>Title</th><th>Description / Category</th><th>Reporter / Person</th><th>Status</th></tr></thead><tbody>`;
3674
3693
  if (feedbackItems.value.length > 0) {
3675
- wordHtml += `<div class="section-title">Feedbacks</div>`;
3676
- wordHtml += `<table class="data-table">`;
3677
- wordHtml += `<thead><tr><th>Title</th><th>Description / Category</th><th>Reporter / Person</th><th>Status</th></tr></thead><tbody>`;
3678
3694
  feedbackItems.value.forEach((item) => {
3679
3695
  wordHtml += `<tr>
3680
3696
  <td><strong>${item.title}</strong></td>
@@ -3685,8 +3701,10 @@ async function exportDashboard(format: "PDF" | "Word") {
3685
3701
  )}">${item.status || "To-Do"}</span></td>
3686
3702
  </tr>`;
3687
3703
  });
3688
- wordHtml += `</tbody></table>`;
3704
+ } else {
3705
+ wordHtml += `<tr><td colspan="4" style="padding: 10px; text-align: center; color: #64748b;">No data available</td></tr>`;
3689
3706
  }
3707
+ wordHtml += `</tbody></table>`;
3690
3708
 
3691
3709
  wordHtml += `
3692
3710
  </body>