@7365admin1/layer-common 3.0.22 → 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.
@@ -12,12 +12,21 @@
12
12
  <!-- KPI Cards (Statistics) -->
13
13
  <div class="print-section-title">Overview Statistics</div>
14
14
  <div class="print-kpi-grid">
15
- <div v-for="card in visibleKpiCards" :key="card.key" class="print-kpi-card">
15
+ <div
16
+ v-for="card in visibleKpiCards"
17
+ :key="card.key"
18
+ class="print-kpi-card"
19
+ >
16
20
  <span class="print-kpi-title">{{ card.title }}</span>
17
- <template v-if="card.key === 'supplyAlert' && Array.isArray(card.raw?.items)">
21
+ <template
22
+ v-if="card.key === 'supplyAlert' && Array.isArray(card.raw?.items)"
23
+ >
18
24
  <div class="d-flex flex-column ga-2 mt-2">
19
- <div v-for="item in card.raw.items" :key="item._id || item.name"
20
- class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium">
25
+ <div
26
+ v-for="item in card.raw.items"
27
+ :key="item._id || item.name"
28
+ class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium"
29
+ >
21
30
  <span>{{ item.name }}</span>
22
31
  <span class="text-blue-grey">Balance: {{ item.qty }}</span>
23
32
  </div>
@@ -27,8 +36,10 @@
27
36
  <div class="print-kpi-value">{{ card.value }}</div>
28
37
  <div class="print-kpi-meta" v-if="card.meta || card.percentage">
29
38
  {{ card.meta || "" }}
30
- <span v-if="card.percentage">({{ card.percentage >= 0 ? "+" : ""
31
- }}{{ card.percentage }}%)</span>
39
+ <span v-if="card.percentage"
40
+ >({{ card.percentage >= 0 ? "+" : ""
41
+ }}{{ card.percentage }}%)</span
42
+ >
32
43
  </div>
33
44
  </template>
34
45
  </div>
@@ -39,52 +50,100 @@
39
50
  <div class="print-section-title">{{ activityTitle }}</div>
40
51
  <div class="print-chart-container">
41
52
  <svg viewBox="0 0 920 260" class="d-block w-100" style="height: 260px">
42
- <line v-for="tick in chartTicks" :key="tick" x1="48" :y1="tick" x2="900" :y2="tick"
43
- style="stroke: #e9eef2; stroke-width: 1" />
44
- <text v-for="label in chartYLabels" :key="label.text" x="40" :y="label.y" text-anchor="end"
45
- alignment-baseline="middle" style="fill: #7e8b96; font-size: 11px; font-weight: 500">
53
+ <line
54
+ v-for="tick in chartTicks"
55
+ :key="tick"
56
+ x1="48"
57
+ :y1="tick"
58
+ x2="900"
59
+ :y2="tick"
60
+ style="stroke: #e9eef2; stroke-width: 1"
61
+ />
62
+ <text
63
+ v-for="label in chartYLabels"
64
+ :key="label.text"
65
+ x="40"
66
+ :y="label.y"
67
+ text-anchor="end"
68
+ alignment-baseline="middle"
69
+ style="fill: #7e8b96; font-size: 11px; font-weight: 500"
70
+ >
46
71
  {{ label.text }}
47
72
  </text>
48
- <path :d="primaryPath" style="
73
+ <path
74
+ :d="primaryPath"
75
+ style="
49
76
  fill: none;
50
77
  stroke: #ff9b9b;
51
78
  stroke-width: 3;
52
79
  stroke-linecap: round;
53
80
  stroke-linejoin: round;
54
- " />
55
- <path :d="secondaryPath" style="
81
+ "
82
+ />
83
+ <path
84
+ :d="secondaryPath"
85
+ style="
56
86
  fill: none;
57
87
  stroke: #e53935;
58
88
  stroke-width: 3;
59
89
  stroke-linecap: round;
60
90
  stroke-linejoin: round;
61
- " />
62
- <circle v-for="point in primaryDots" :key="`primary-${point.x}-${point.y}`" :cx="point.x" :cy="point.y" r="4"
63
- style="fill: #ff9b9b; stroke: #fff; stroke-width: 2" />
64
- <circle v-for="point in secondaryDots" :key="`secondary-${point.x}-${point.y}`" :cx="point.x" :cy="point.y"
65
- r="4" style="fill: #e53935; stroke: #fff; stroke-width: 2" />
66
- <text v-for="label in chartAxisLabels" :key="`${label.text}-${label.x}`" :x="label.x" y="246"
67
- text-anchor="middle" style="fill: #7e8b96; font-size: 12px; font-weight: 600">
91
+ "
92
+ />
93
+ <circle
94
+ v-for="point in primaryDots"
95
+ :key="`primary-${point.x}-${point.y}`"
96
+ :cx="point.x"
97
+ :cy="point.y"
98
+ r="4"
99
+ style="fill: #ff9b9b; stroke: #fff; stroke-width: 2"
100
+ />
101
+ <circle
102
+ v-for="point in secondaryDots"
103
+ :key="`secondary-${point.x}-${point.y}`"
104
+ :cx="point.x"
105
+ :cy="point.y"
106
+ r="4"
107
+ style="fill: #e53935; stroke: #fff; stroke-width: 2"
108
+ />
109
+ <text
110
+ v-for="label in chartAxisLabels"
111
+ :key="`${label.text}-${label.x}`"
112
+ :x="label.x"
113
+ y="246"
114
+ text-anchor="middle"
115
+ style="fill: #7e8b96; font-size: 12px; font-weight: 600"
116
+ >
68
117
  {{ label.text }}
69
118
  </text>
70
119
  </svg>
71
120
  <div class="d-flex align-center justify-center ga-5 mt-2">
72
- <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
73
- <span class="rounded-circle" style="
121
+ <span
122
+ class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
123
+ >
124
+ <span
125
+ class="rounded-circle"
126
+ style="
74
127
  width: 12px;
75
128
  height: 12px;
76
129
  display: inline-block;
77
130
  background-color: #ff9b9b;
78
- " />
131
+ "
132
+ />
79
133
  {{ primaryLegendLabel }}
80
134
  </span>
81
- <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
82
- <span class="rounded-circle" style="
135
+ <span
136
+ class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
137
+ >
138
+ <span
139
+ class="rounded-circle"
140
+ style="
83
141
  width: 12px;
84
142
  height: 12px;
85
143
  display: inline-block;
86
144
  background-color: #e53935;
87
- " />
145
+ "
146
+ />
88
147
  {{ secondaryLegendLabel }}
89
148
  </span>
90
149
  </div>
@@ -92,30 +151,43 @@
92
151
  </div>
93
152
 
94
153
  <!-- Tables for List Widgets (Other data) -->
95
- <div v-for="section in printTableSections" :key="section.key" class="print-table-section mt-6">
154
+ <div
155
+ v-for="section in printTableSections"
156
+ :key="section.key"
157
+ class="print-table-section mt-6"
158
+ >
96
159
  <div class="print-section-title">{{ section.title }}</div>
97
160
  <table class="print-table">
98
161
  <thead>
99
162
  <tr>
100
- <th v-for="column in section.columns" :key="column.key" :style="{ width: column.width }">
163
+ <th
164
+ v-for="column in section.columns"
165
+ :key="column.key"
166
+ :style="{ width: column.width }"
167
+ >
101
168
  {{ column.label }}
102
169
  </th>
103
170
  </tr>
104
171
  </thead>
105
172
  <tbody>
106
173
  <tr v-for="row in section.rows" :key="row.id">
107
- <td v-for="column in section.columns" :key="`${row.id}-${column.key}`">
174
+ <td
175
+ v-for="column in section.columns"
176
+ :key="`${row.id}-${column.key}`"
177
+ >
108
178
  <template v-if="column.status">
109
- <span :class="[
110
- 'print-status-badge',
111
- `badge-${getStatusTone(row[column.key])}`,
112
- ]">
179
+ <span
180
+ :class="[
181
+ 'print-status-badge',
182
+ `badge-${getStatusTone(row[column.key])}`,
183
+ ]"
184
+ >
113
185
  {{ row[column.key] ?? column.fallback ?? "-" }}
114
186
  </span>
115
187
  </template>
116
188
  <strong v-else-if="column.strong">{{
117
189
  row[column.key] ?? "-"
118
- }}</strong>
190
+ }}</strong>
119
191
  <template v-else>{{ row[column.key] ?? "-" }}</template>
120
192
  </td>
121
193
  </tr>
@@ -131,7 +203,9 @@
131
203
 
132
204
  <section v-else class="unified-dashboard pb-8">
133
205
  <!-- ── Header ──────────────────────────────────────────────────────────── -->
134
- <div class="d-flex flex-column flex-md-row align-md-start justify-space-between ga-5 mb-6">
206
+ <div
207
+ class="d-flex flex-column flex-md-row align-md-start justify-space-between ga-5 mb-6"
208
+ >
135
209
  <div>
136
210
  <h1 class="text-h5 font-weight-black text-blue-grey-darken-4 mb-0">
137
211
  {{ dashboardTitle }}
@@ -142,16 +216,31 @@
142
216
  </div>
143
217
  </div>
144
218
 
145
- <div v-if="showHeaderActions" class="d-flex flex-wrap align-center justify-md-end ga-3">
146
- <v-btn variant="outlined" color="blue-grey-darken-1" class="text-none font-weight-bold"
147
- prepend-icon="mdi-tune-variant" @click="openCustomizeDialog">
219
+ <div
220
+ v-if="showHeaderActions"
221
+ class="d-flex flex-wrap align-center justify-md-end ga-3"
222
+ >
223
+ <v-btn
224
+ variant="outlined"
225
+ color="blue-grey-darken-1"
226
+ class="text-none font-weight-bold"
227
+ prepend-icon="mdi-tune-variant"
228
+ @click="openCustomizeDialog"
229
+ >
148
230
  Customize Dashboard
149
231
  </v-btn>
150
232
 
151
233
  <v-menu>
152
234
  <template #activator="{ props: menuProps }">
153
- <v-btn v-bind="menuProps" variant="outlined" color="blue-grey-darken-1" class="text-none font-weight-bold"
154
- append-icon="mdi-chevron-down" :loading="isExporting" :disabled="isExporting">
235
+ <v-btn
236
+ v-bind="menuProps"
237
+ variant="outlined"
238
+ color="blue-grey-darken-1"
239
+ class="text-none font-weight-bold"
240
+ append-icon="mdi-chevron-down"
241
+ :loading="isExporting"
242
+ :disabled="isExporting"
243
+ >
155
244
  Export
156
245
  </v-btn>
157
246
  </template>
@@ -162,65 +251,133 @@
162
251
  </v-list>
163
252
  </v-menu>
164
253
 
165
- <v-btn-toggle v-model="selectedRange" mandatory density="comfortable" divided variant="outlined"
166
- class="bg-white" style="border-radius: 8px; border-color: #d9e1e8">
167
- <v-btn v-for="range in rangeOptions" :key="range.value" :value="range.value" class="text-none" size="small"
168
- :style="selectedRange === range.value
254
+ <v-btn-toggle
255
+ v-model="selectedRange"
256
+ mandatory
257
+ density="comfortable"
258
+ divided
259
+ variant="outlined"
260
+ class="bg-white"
261
+ style="border-radius: 8px; border-color: #d9e1e8"
262
+ >
263
+ <v-btn
264
+ v-for="range in rangeOptions"
265
+ :key="range.value"
266
+ :value="range.value"
267
+ class="text-none"
268
+ size="small"
269
+ :style="
270
+ selectedRange === range.value
169
271
  ? {
170
- backgroundColor: '#e2e8f0',
171
- color: '#0b2f47',
172
- fontWeight: '600',
173
- }
272
+ backgroundColor: '#e2e8f0',
273
+ color: '#0b2f47',
274
+ fontWeight: '600',
275
+ }
174
276
  : { backgroundColor: 'transparent', color: '#64748b' }
175
- ">
277
+ "
278
+ >
176
279
  {{ range.label }}
177
280
  </v-btn>
178
281
  </v-btn-toggle>
179
282
  </div>
180
283
  </div>
181
284
 
182
- <v-progress-linear v-if="finalLoading" indeterminate color="primary" class="mb-4" />
285
+ <v-progress-linear
286
+ v-if="finalLoading"
287
+ indeterminate
288
+ color="primary"
289
+ class="mb-4"
290
+ />
183
291
 
184
292
  <!-- ── KPI Cards ───────────────────────────────────────────────────────── -->
185
293
  <v-row dense class="mb-1 align-stretch">
186
- <v-col v-for="card in visibleKpiCards" :key="card.key" cols="12" sm="6" md="4" lg="3" class="d-flex">
187
- <v-card flat border rounded="lg" class="pa-4 flex-grow-1" style="min-height: 158px">
294
+ <v-col
295
+ v-for="card in visibleKpiCards"
296
+ :key="card.key"
297
+ cols="12"
298
+ sm="6"
299
+ md="4"
300
+ lg="3"
301
+ class="d-flex"
302
+ >
303
+ <v-card
304
+ flat
305
+ border
306
+ rounded="lg"
307
+ class="pa-4 flex-grow-1"
308
+ style="min-height: 158px"
309
+ >
188
310
  <div class="d-flex align-start justify-space-between">
189
- <div class="d-inline-flex align-center justify-center rounded-lg"
190
- style="height: 28px; width: 28px; flex-shrink: 0" :style="{ backgroundColor: card.tint }">
311
+ <div
312
+ class="d-inline-flex align-center justify-center rounded-lg"
313
+ style="height: 28px; width: 28px; flex-shrink: 0"
314
+ :style="{ backgroundColor: card.tint }"
315
+ >
191
316
  <v-icon :icon="card.icon" :color="card.color" size="15" />
192
317
  </div>
193
318
 
194
319
  <v-menu v-if="card.filter" v-model="filterMenus[card.key]">
195
320
  <template #activator="{ props: menuProps }">
196
- <v-btn v-bind="menuProps" variant="outlined" density="compact" class="text-none text-caption px-2"
197
- append-icon="mdi-chevron-down" height="26" rounded="pill">
321
+ <v-btn
322
+ v-bind="menuProps"
323
+ variant="outlined"
324
+ density="compact"
325
+ class="text-none text-caption px-2"
326
+ append-icon="mdi-chevron-down"
327
+ height="26"
328
+ rounded="pill"
329
+ >
198
330
  {{ card.filter.model }}
199
331
  </v-btn>
200
332
  </template>
201
333
 
202
334
  <v-list density="compact" border rounded="lg" elevation="3">
203
- <v-list-item v-for="option in card.filter.options" :key="option" :active="card.filter.model === option"
204
- :title="option" @click="card.filter.set(option)">
335
+ <v-list-item
336
+ v-for="option in card.filter.options"
337
+ :key="option"
338
+ :active="card.filter.model === option"
339
+ :title="option"
340
+ @click="card.filter.set(option)"
341
+ >
205
342
  <template #prepend>
206
- <v-icon :icon="card.filter.model === option ? 'mdi-check' : 'mdi-blank'
207
- " size="16" />
343
+ <v-icon
344
+ :icon="
345
+ card.filter.model === option ? 'mdi-check' : 'mdi-blank'
346
+ "
347
+ size="16"
348
+ />
208
349
  </template>
209
350
  </v-list-item>
210
351
  </v-list>
211
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>
212
362
  </div>
213
363
 
214
364
  <div class="mt-6 w-100">
215
- <p class="text-caption font-weight-black text-uppercase text-blue-grey-darken-2 mb-3">
365
+ <p
366
+ class="text-caption font-weight-black text-uppercase text-blue-grey-darken-2 mb-3"
367
+ >
216
368
  {{ card.title }}
217
369
  </p>
218
- <template v-if="
219
- card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
220
- ">
370
+ <template
371
+ v-if="
372
+ card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
373
+ "
374
+ >
221
375
  <div class="d-flex flex-column ga-2 mt-2">
222
- <div v-for="item in card.raw.items" :key="item._id || item.name"
223
- class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium">
376
+ <div
377
+ v-for="item in card.raw.items"
378
+ :key="item._id || item.name"
379
+ class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium"
380
+ >
224
381
  <span>{{ item.name }}</span>
225
382
  <span class="text-blue-grey">Balance: {{ item.qty }}</span>
226
383
  </div>
@@ -235,13 +392,22 @@
235
392
  <div class="d-flex align-center flex-wrap ga-2">
236
393
  <span v-if="card.meta" class="text-caption text-blue-grey">{{
237
394
  card.meta
238
- }}</span>
239
- <v-chip :color="card.percentage >= 0 ? 'success' : 'error'" size="x-small" variant="flat"
240
- class="font-weight-bold">
241
- <v-icon :icon="card.percentage >= 0
242
- ? 'mdi-arrow-up-right'
243
- : 'mdi-arrow-down-right'
244
- " size="13" start />
395
+ }}</span>
396
+ <v-chip
397
+ :color="card.percentage >= 0 ? 'success' : 'error'"
398
+ size="x-small"
399
+ variant="flat"
400
+ class="font-weight-bold"
401
+ >
402
+ <v-icon
403
+ :icon="
404
+ card.percentage >= 0
405
+ ? 'mdi-arrow-up-right'
406
+ : 'mdi-arrow-down-right'
407
+ "
408
+ size="13"
409
+ start
410
+ />
245
411
  {{ Math.abs(card.percentage) }}%
246
412
  </v-chip>
247
413
  </div>
@@ -252,7 +418,13 @@
252
418
  </v-row>
253
419
 
254
420
  <!-- ── Activity Chart (all modes) ────────────────────────────────────── -->
255
- <v-card v-if="isWidgetVisible('activity')" flat border rounded="lg" class="px-5 pt-4 pb-3 mt-3">
421
+ <v-card
422
+ v-if="isWidgetVisible('activity')"
423
+ flat
424
+ border
425
+ rounded="lg"
426
+ class="px-5 pt-4 pb-3 mt-3"
427
+ >
256
428
  <div class="d-flex align-center justify-space-between">
257
429
  <h2 class="text-body-1 font-weight-black text-blue-grey-darken-4 mb-0">
258
430
  {{ activityTitle }}
@@ -260,54 +432,106 @@
260
432
  </div>
261
433
 
262
434
  <div v-if="hasChartData" class="mt-4" ref="chartContainerRef">
263
- <svg :viewBox="`0 0 ${chartWidth} 260`" class="d-block w-100" style="height: auto; max-height: 260px">
264
- <line v-for="tick in chartTicks" :key="tick" x1="48" :y1="tick" :x2="chartWidth - 20" :y2="tick"
265
- style="stroke: #e9eef2; stroke-width: 1" />
266
- <text v-for="label in chartYLabels" :key="label.text" x="40" :y="label.y" text-anchor="end"
267
- alignment-baseline="middle" style="fill: #7e8b96; font-size: 11px; font-weight: 500">
435
+ <svg
436
+ :viewBox="`0 0 ${chartWidth} 260`"
437
+ class="d-block w-100"
438
+ style="height: auto; max-height: 260px"
439
+ >
440
+ <line
441
+ v-for="tick in chartTicks"
442
+ :key="tick"
443
+ x1="48"
444
+ :y1="tick"
445
+ :x2="chartWidth - 20"
446
+ :y2="tick"
447
+ style="stroke: #e9eef2; stroke-width: 1"
448
+ />
449
+ <text
450
+ v-for="label in chartYLabels"
451
+ :key="label.text"
452
+ x="40"
453
+ :y="label.y"
454
+ text-anchor="end"
455
+ alignment-baseline="middle"
456
+ style="fill: #7e8b96; font-size: 11px; font-weight: 500"
457
+ >
268
458
  {{ label.text }}
269
459
  </text>
270
- <path :d="primaryPath" style="
460
+ <path
461
+ :d="primaryPath"
462
+ style="
271
463
  fill: none;
272
464
  stroke: #ff9b9b;
273
465
  stroke-width: 3;
274
466
  stroke-linecap: round;
275
467
  stroke-linejoin: round;
276
- " />
277
- <path :d="secondaryPath" style="
468
+ "
469
+ />
470
+ <path
471
+ :d="secondaryPath"
472
+ style="
278
473
  fill: none;
279
474
  stroke: #e53935;
280
475
  stroke-width: 3;
281
476
  stroke-linecap: round;
282
477
  stroke-linejoin: round;
283
- " />
284
- <circle v-for="point in primaryDots" :key="`primary-${point.x}-${point.y}`" :cx="point.x" :cy="point.y" r="4"
285
- style="fill: #ff9b9b; stroke: #fff; stroke-width: 2" />
286
- <circle v-for="point in secondaryDots" :key="`secondary-${point.x}-${point.y}`" :cx="point.x" :cy="point.y"
287
- r="4" style="fill: #e53935; stroke: #fff; stroke-width: 2" />
288
- <text v-for="label in chartAxisLabels" :key="`${label.text}-${label.x}`" :x="label.x" y="246"
289
- text-anchor="middle" style="fill: #7e8b96; font-size: 12px; font-weight: 600">
478
+ "
479
+ />
480
+ <circle
481
+ v-for="point in primaryDots"
482
+ :key="`primary-${point.x}-${point.y}`"
483
+ :cx="point.x"
484
+ :cy="point.y"
485
+ r="4"
486
+ style="fill: #ff9b9b; stroke: #fff; stroke-width: 2"
487
+ />
488
+ <circle
489
+ v-for="point in secondaryDots"
490
+ :key="`secondary-${point.x}-${point.y}`"
491
+ :cx="point.x"
492
+ :cy="point.y"
493
+ r="4"
494
+ style="fill: #e53935; stroke: #fff; stroke-width: 2"
495
+ />
496
+ <text
497
+ v-for="label in chartAxisLabels"
498
+ :key="`${label.text}-${label.x}`"
499
+ :x="label.x"
500
+ y="246"
501
+ text-anchor="middle"
502
+ style="fill: #7e8b96; font-size: 12px; font-weight: 600"
503
+ >
290
504
  {{ label.text }}
291
505
  </text>
292
506
  </svg>
293
507
 
294
508
  <div class="d-flex align-center justify-center ga-5 mt-2">
295
- <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
296
- <span class="rounded-circle" style="
509
+ <span
510
+ class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
511
+ >
512
+ <span
513
+ class="rounded-circle"
514
+ style="
297
515
  width: 12px;
298
516
  height: 12px;
299
517
  display: inline-block;
300
518
  background-color: #ff9b9b;
301
- " />
519
+ "
520
+ />
302
521
  {{ primaryLegendLabel }}
303
522
  </span>
304
- <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
305
- <span class="rounded-circle" style="
523
+ <span
524
+ class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
525
+ >
526
+ <span
527
+ class="rounded-circle"
528
+ style="
306
529
  width: 12px;
307
530
  height: 12px;
308
531
  display: inline-block;
309
532
  background-color: #e53935;
310
- " />
533
+ "
534
+ />
311
535
  {{ secondaryLegendLabel }}
312
536
  </span>
313
537
  </div>
@@ -318,33 +542,63 @@
318
542
 
319
543
  <!-- ── Hygiene / M&E / Landscape / Pest / Pool — task + attendance ────── -->
320
544
  <v-row v-if="isHygieneMode" dense class="mt-3 align-stretch">
321
- <v-col v-if="isWidgetVisible('taskSchedule')" cols="12" lg="7" class="d-flex">
322
- <DashboardPanel :title="primaryListConfig.title" action-label="View All"
323
- @action="goToDashboardRoute(primaryListConfig.route)">
545
+ <v-col
546
+ v-if="isWidgetVisible('taskSchedule')"
547
+ cols="12"
548
+ lg="7"
549
+ class="d-flex"
550
+ >
551
+ <DashboardPanel
552
+ :title="primaryListConfig.title"
553
+ action-label="View All"
554
+ @action="goToDashboardRoute(primaryListConfig.route)"
555
+ >
324
556
  <div v-if="taskScheduleItems.length" class="d-flex flex-column ga-3">
325
- <v-card v-for="item in taskScheduleItems" :key="item.id" flat border rounded="lg" class="pa-3">
557
+ <v-card
558
+ v-for="item in taskScheduleItems"
559
+ :key="item.id"
560
+ flat
561
+ border
562
+ rounded="lg"
563
+ class="pa-3"
564
+ >
326
565
  <div class="d-flex align-center flex-wrap ga-3">
327
566
  <div class="flex-grow-1" style="min-width: 0">
328
- <div class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate">
567
+ <div
568
+ class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
569
+ >
329
570
  {{ item.title }}
330
571
  </div>
331
572
  <div class="text-caption text-blue-grey mt-1 text-truncate">
332
573
  {{ item.subtitle
333
574
  }}<template v-if="item.subtitle && item.time">
334
- &bull; </template>{{ item.time }}
575
+ &bull; </template
576
+ >{{ item.time }}
335
577
  </div>
336
578
  </div>
337
- <div class="d-flex align-center ga-2 text-caption text-blue-grey">
579
+ <div
580
+ class="d-flex align-center ga-2 text-caption text-blue-grey"
581
+ >
338
582
  <AvatarMain :name="item.person" :size="30" />
339
583
  <span class="text-truncate" style="max-width: 100px">{{
340
584
  item.person
341
- }}</span>
585
+ }}</span>
342
586
  </div>
343
- <v-chip size="small" :color="getStatusTone(item.status)" variant="tonal" label>
587
+ <v-chip
588
+ size="small"
589
+ :color="getStatusTone(item.status)"
590
+ variant="tonal"
591
+ label
592
+ >
344
593
  {{ item.status }}
345
594
  </v-chip>
346
- <v-btn variant="outlined" color="primary" size="small" class="text-none font-weight-bold"
347
- @click="goToListItem(item, primaryListConfig.route)">
595
+ <v-btn
596
+ variant="outlined"
597
+ color="primary"
598
+ size="small"
599
+ class="text-none font-weight-bold"
600
+ @click="goToListItem(item, primaryListConfig.route)"
601
+ >
348
602
  View
349
603
  </v-btn>
350
604
  </div>
@@ -354,23 +608,45 @@
354
608
  </DashboardPanel>
355
609
  </v-col>
356
610
 
357
- <v-col v-if="isWidgetVisible('staffStatus')" cols="12" lg="5" class="d-flex">
358
- <DashboardPanel title="Staff's Total Attendance" action-label="View All"
359
- @action="goToDashboardRoute('attendance')">
611
+ <v-col
612
+ v-if="isWidgetVisible('staffStatus')"
613
+ cols="12"
614
+ lg="5"
615
+ class="d-flex"
616
+ >
617
+ <DashboardPanel
618
+ title="Staff's Total Attendance"
619
+ action-label="View All"
620
+ @action="goToDashboardRoute('attendance')"
621
+ >
360
622
  <div v-if="staffStatusItems.length" class="d-flex flex-column ga-3">
361
- <v-card v-for="item in staffStatusItems" :key="item.id" flat border rounded="lg" class="pa-3">
623
+ <v-card
624
+ v-for="item in staffStatusItems"
625
+ :key="item.id"
626
+ flat
627
+ border
628
+ rounded="lg"
629
+ class="pa-3"
630
+ >
362
631
  <div class="d-flex align-start ga-3">
363
632
  <AvatarMain :name="item.name" :size="32" />
364
633
  <div class="flex-grow-1" style="min-width: 0">
365
- <div class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate">
634
+ <div
635
+ class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
636
+ >
366
637
  {{ item.name }}
367
638
  </div>
368
639
  <div class="text-caption text-blue-grey mt-1 text-truncate">
369
640
  {{ item.role }}
370
641
  </div>
371
642
  </div>
372
- <v-chip size="small" color="blue-grey-darken-1" variant="tonal" label
373
- class="ml-auto flex-shrink-0 font-weight-bold">
643
+ <v-chip
644
+ size="small"
645
+ color="blue-grey-darken-1"
646
+ variant="tonal"
647
+ label
648
+ class="ml-auto flex-shrink-0 font-weight-bold"
649
+ >
374
650
  {{ item.totalHours || 0 }} hrs
375
651
  </v-chip>
376
652
  </div>
@@ -381,55 +657,80 @@
381
657
  </v-col>
382
658
 
383
659
  <v-col v-if="isWidgetVisible('feedbacks')" cols="12" class="d-flex">
384
- <DashboardPanel title="Feedbacks" action-label="View All" @action="goToDashboardRoute('feedbacks')">
660
+ <DashboardPanel
661
+ title="Feedbacks"
662
+ action-label="View All"
663
+ @action="goToDashboardRoute('feedbacks')"
664
+ >
385
665
  <div v-if="feedbackItems.length" class="figma-list mt-2">
386
- <div v-for="item in feedbackItems" :key="item.id" class="d-flex align-center justify-space-between" style="
666
+ <div
667
+ v-for="item in feedbackItems"
668
+ :key="item.id"
669
+ class="d-flex align-center justify-space-between"
670
+ style="
387
671
  padding: 16px 20px;
388
672
  border: 1px solid #e2e8f0;
389
673
  border-radius: 12px;
390
674
  margin-bottom: 12px;
391
675
  background-color: #ffffff;
392
676
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
393
- ">
677
+ "
678
+ >
394
679
  <div style="flex: 1; min-width: 140px">
395
- <strong style="
680
+ <strong
681
+ style="
396
682
  display: block;
397
683
  font-size: 15px;
398
684
  font-weight: 700;
399
685
  color: #0b2f47;
400
686
  margin-bottom: 4px;
401
- ">{{ item.title }}</strong>
687
+ "
688
+ >{{ item.title }}</strong
689
+ >
402
690
  <span style="font-size: 13px; color: #94a3b8">{{
403
691
  item.subtitle
404
- }}</span>
692
+ }}</span>
405
693
  </div>
406
694
 
407
- <div style="flex: 1; display: flex; align-items: center; gap: 12px">
695
+ <div
696
+ style="flex: 1; display: flex; align-items: center; gap: 12px"
697
+ >
408
698
  <AvatarMain :name="item.person" :size="36" />
409
699
  <span style="font-size: 14px; color: #64748b">{{
410
700
  item.person
411
- }}</span>
701
+ }}</span>
412
702
  </div>
413
703
 
414
704
  <div style="flex: 1; display: flex; justify-content: center">
415
- <div :style="getStatusPillStyle(item.status)" style="
705
+ <div
706
+ :style="getStatusPillStyle(item.status)"
707
+ style="
416
708
  padding: 6px 16px;
417
709
  border-radius: 20px;
418
710
  font-size: 13px;
419
711
  font-weight: 500;
420
712
  text-transform: capitalize;
421
- ">
713
+ "
714
+ >
422
715
  {{ item.status || "To-Do" }}
423
716
  </div>
424
717
  </div>
425
718
 
426
719
  <div style="flex: 0 0 auto; text-align: right">
427
- <v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="
720
+ <v-btn
721
+ variant="outlined"
722
+ class="text-none font-weight-medium"
723
+ size="small"
724
+ color="#3b82f6"
725
+ style="
428
726
  border-color: #e2e8f0;
429
727
  border-radius: 8px;
430
728
  padding: 0 16px;
431
729
  height: 32px;
432
- " @click="goToListItem(item, 'feedbacks')">View</v-btn>
730
+ "
731
+ @click="goToListItem(item, 'feedbacks')"
732
+ >View</v-btn
733
+ >
433
734
  </div>
434
735
  </div>
435
736
  </div>
@@ -444,22 +745,36 @@
444
745
  <div class="dashboard-card dashboard-panel figma-panel">
445
746
  <div class="figma-panel-header">
446
747
  <h2>Upcoming Events</h2>
447
- <button class="figma-panel-action" type="button" @click="goToDashboardRoute('event-mgmt')">
748
+ <button
749
+ class="figma-panel-action"
750
+ type="button"
751
+ @click="goToDashboardRoute('event-mgmt')"
752
+ >
448
753
  View All
449
754
  </button>
450
755
  </div>
451
756
  <div class="figma-panel-body">
452
757
  <div v-if="upcomingEvents.length" class="figma-list">
453
- <div v-for="item in upcomingEvents" :key="item.id" class="figma-list-row">
454
- <span :class="[
455
- 'figma-event-dot',
456
- `figma-event-dot-${getListTone(item)}`,
457
- ]" />
758
+ <div
759
+ v-for="item in upcomingEvents"
760
+ :key="item.id"
761
+ class="figma-list-row"
762
+ >
763
+ <span
764
+ :class="[
765
+ 'figma-event-dot',
766
+ `figma-event-dot-${getListTone(item)}`,
767
+ ]"
768
+ />
458
769
  <div class="figma-list-main">
459
770
  <strong>{{ item.title }}</strong>
460
771
  <p v-if="item.subtitle">{{ item.subtitle }}</p>
461
772
  </div>
462
- <button class="figma-row-action" type="button" @click="navigateTo(dashboardPath('event-mgmt'))">
773
+ <button
774
+ class="figma-row-action"
775
+ type="button"
776
+ @click="navigateTo(dashboardPath('event-mgmt'))"
777
+ >
463
778
  {{ item.actionLabel || "View" }}
464
779
  </button>
465
780
  </div>
@@ -473,24 +788,38 @@
473
788
  <div class="dashboard-card dashboard-panel figma-panel">
474
789
  <div class="figma-panel-header">
475
790
  <h2>Today's Reminder</h2>
476
- <button class="figma-panel-action" type="button" @click="goToDashboardRoute('calendar')">
791
+ <button
792
+ class="figma-panel-action"
793
+ type="button"
794
+ @click="goToDashboardRoute('calendar')"
795
+ >
477
796
  View All
478
797
  </button>
479
798
  </div>
480
799
  <div class="figma-panel-body">
481
800
  <div v-if="todayReminders.length" class="figma-list">
482
- <div v-for="item in todayReminders" :key="item.id" class="figma-list-row">
483
- <span :class="[
484
- 'figma-icon-badge',
485
- `figma-icon-badge-${getListTone(item)}`,
486
- ]">
801
+ <div
802
+ v-for="item in todayReminders"
803
+ :key="item.id"
804
+ class="figma-list-row"
805
+ >
806
+ <span
807
+ :class="[
808
+ 'figma-icon-badge',
809
+ `figma-icon-badge-${getListTone(item)}`,
810
+ ]"
811
+ >
487
812
  <v-icon :icon="item.icon || 'mdi-alert'" size="15" />
488
813
  </span>
489
814
  <div class="figma-list-main">
490
815
  <strong>{{ item.title }}</strong>
491
816
  <p v-if="item.subtitle">{{ item.subtitle }}</p>
492
817
  </div>
493
- <button class="figma-row-action" type="button" @click="navigateTo(dashboardPath('calendar'))">
818
+ <button
819
+ class="figma-row-action"
820
+ type="button"
821
+ @click="navigateTo(dashboardPath('calendar'))"
822
+ >
494
823
  {{ item.actionLabel || "View" }}
495
824
  </button>
496
825
  </div>
@@ -504,29 +833,42 @@
504
833
  <div class="dashboard-card dashboard-panel figma-panel">
505
834
  <div class="figma-panel-header">
506
835
  <h2>Today's Attentions</h2>
507
-
508
836
  </div>
509
837
  <div class="figma-panel-body">
510
838
  <div v-if="todayAttentions.length" class="figma-list">
511
- <div v-for="item in todayAttentions" :key="item.id" class="figma-list-row">
512
- <span :class="[
513
- 'figma-icon-badge',
514
- `figma-icon-badge-${getListTone(item)}`,
515
- ]">
516
- <v-icon :icon="item.icon || 'mdi-alert-circle-outline'" size="15" />
839
+ <div
840
+ v-for="item in todayAttentions"
841
+ :key="item.id"
842
+ class="figma-list-row"
843
+ >
844
+ <span
845
+ :class="[
846
+ 'figma-icon-badge',
847
+ `figma-icon-badge-${getListTone(item)}`,
848
+ ]"
849
+ >
850
+ <v-icon
851
+ :icon="item.icon || 'mdi-alert-circle-outline'"
852
+ size="15"
853
+ />
517
854
  </span>
518
855
  <div class="figma-list-main">
519
856
  <strong>{{ item.title }}</strong>
520
857
  <p v-if="item.subtitle">{{ item.subtitle }}</p>
521
858
  </div>
522
- <button class="figma-row-action" type="button" @click="
523
- navigateTo(
524
- item.type === 'facility-booking'
525
- ? `${dashboardPath('facility/bookings')}?booking=${item.id
526
- }`
527
- : dashboardPath(`incident-reports/${item.id}`)
528
- )
529
- ">
859
+ <button
860
+ class="figma-row-action"
861
+ type="button"
862
+ @click="
863
+ navigateTo(
864
+ item.type === 'facility-booking'
865
+ ? `${dashboardPath('facility/bookings')}?booking=${
866
+ item.id
867
+ }`
868
+ : dashboardPath(`incident-reports/${item.id}`)
869
+ )
870
+ "
871
+ >
530
872
  {{ item.actionLabel || "Review" }}
531
873
  </button>
532
874
  </div>
@@ -540,19 +882,30 @@
540
882
  <div class="dashboard-card dashboard-panel figma-panel">
541
883
  <div class="figma-panel-header">
542
884
  <h2>Work Order Status</h2>
543
- <button class="figma-panel-action" type="button" @click="goToDashboardRoute('work-orders')">
885
+ <button
886
+ class="figma-panel-action"
887
+ type="button"
888
+ @click="goToDashboardRoute('work-orders')"
889
+ >
544
890
  View All
545
891
  </button>
546
892
  </div>
547
893
  <div class="figma-panel-body">
548
894
  <div v-if="workOrderStatus.length" class="figma-status-stack">
549
- <div v-for="item in workOrderStatus" :key="item.label" class="figma-status-row">
895
+ <div
896
+ v-for="item in workOrderStatus"
897
+ :key="item.label"
898
+ class="figma-status-row"
899
+ >
550
900
  <div class="figma-status-label">
551
901
  <span>{{ item.label }}</span>
552
902
  <strong>{{ item.value }}</strong>
553
903
  </div>
554
904
  <div class="figma-progress-track">
555
- <span class="figma-progress-fill" :style="getStatusProgressStyle(item)" />
905
+ <span
906
+ class="figma-progress-fill"
907
+ :style="getStatusProgressStyle(item)"
908
+ />
556
909
  </div>
557
910
  </div>
558
911
  </div>
@@ -563,9 +916,17 @@
563
916
 
564
917
  <!-- Property feedbacks -->
565
918
  <v-col v-if="isWidgetVisible('feedbacks')" cols="12">
566
- <DashboardPanel title="Feedbacks" action-label="View All" @action="goToDashboardRoute('feedbacks')">
919
+ <DashboardPanel
920
+ title="Feedbacks"
921
+ action-label="View All"
922
+ @action="goToDashboardRoute('feedbacks')"
923
+ >
567
924
  <div v-if="feedbackItems.length" class="feedback-list">
568
- <div v-for="item in feedbackItems" :key="item.id" class="feedback-row">
925
+ <div
926
+ v-for="item in feedbackItems"
927
+ :key="item.id"
928
+ class="feedback-row"
929
+ >
569
930
  <div class="feedback-summary">
570
931
  <strong>{{ item.title }}</strong>
571
932
  <p>{{ item.subtitle }}</p>
@@ -574,18 +935,24 @@
574
935
  <div class="feedback-person">
575
936
  <span class="feedback-avatar">{{
576
937
  getInitials(item.person)
577
- }}</span>
938
+ }}</span>
578
939
  <span>{{ item.person }}</span>
579
940
  </div>
580
941
 
581
- <span :class="[
582
- 'feedback-status-pill',
583
- `feedback-status-pill-${getFeedbackTone(item.status)}`,
584
- ]">
942
+ <span
943
+ :class="[
944
+ 'feedback-status-pill',
945
+ `feedback-status-pill-${getFeedbackTone(item.status)}`,
946
+ ]"
947
+ >
585
948
  {{ item.status }}
586
949
  </span>
587
950
 
588
- <button class="feedback-view-button" type="button" @click="goToListItem(item, 'feedbacks')">
951
+ <button
952
+ class="feedback-view-button"
953
+ type="button"
954
+ @click="goToListItem(item, 'feedbacks')"
955
+ >
589
956
  View
590
957
  </button>
591
958
  </div>
@@ -601,60 +968,90 @@
601
968
  <div class="dashboard-card dashboard-panel figma-panel">
602
969
  <div class="figma-panel-header">
603
970
  <h2>ACTIVE PATROL</h2>
604
- <button class="figma-panel-action" type="button" @click="goToDashboardRoute('virtual-patrol')">
971
+ <button
972
+ class="figma-panel-action"
973
+ type="button"
974
+ @click="goToDashboardRoute('virtual-patrol')"
975
+ >
605
976
  View All
606
977
  </button>
607
978
  </div>
608
979
  <div class="figma-panel-body">
609
- <div v-if="formattedActivePatrolItems.length" class="figma-list mt-2">
610
- <div v-for="item in formattedActivePatrolItems" :key="item.id || item._id"
611
- class="d-flex align-center justify-space-between" style="
980
+ <div
981
+ v-if="formattedActivePatrolItems.length"
982
+ class="figma-list mt-2"
983
+ >
984
+ <div
985
+ v-for="item in formattedActivePatrolItems"
986
+ :key="item.id || item._id"
987
+ class="d-flex align-center justify-space-between"
988
+ style="
612
989
  padding: 16px 20px;
613
990
  border: 1px solid #e2e8f0;
614
991
  border-radius: 12px;
615
992
  margin-bottom: 12px;
616
993
  background-color: #ffffff;
617
994
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
618
- ">
995
+ "
996
+ >
619
997
  <div style="flex: 1; min-width: 140px">
620
- <strong style="
998
+ <strong
999
+ style="
621
1000
  display: block;
622
1001
  font-size: 15px;
623
1002
  font-weight: 700;
624
1003
  color: #0b2f47;
625
1004
  margin-bottom: 4px;
626
- ">{{ item.title || item.name }}</strong>
1005
+ "
1006
+ >{{ item.title || item.name }}</strong
1007
+ >
627
1008
  <span style="font-size: 13px; color: #94a3b8">{{
628
1009
  item.subtitle || item.location || ""
629
- }}</span>
1010
+ }}</span>
630
1011
  </div>
631
1012
 
632
- <div style="flex: 1; display: flex; align-items: center; gap: 12px">
633
- <AvatarMain :name="item.person || item.assignedTo" :size="36" />
1013
+ <div
1014
+ style="flex: 1; display: flex; align-items: center; gap: 12px"
1015
+ >
1016
+ <AvatarMain
1017
+ :name="item.person || item.assignedTo"
1018
+ :size="36"
1019
+ />
634
1020
  <span style="font-size: 14px; color: #64748b">{{
635
1021
  item.person || item.assignedTo
636
- }}</span>
1022
+ }}</span>
637
1023
  </div>
638
1024
 
639
1025
  <div style="flex: 1; display: flex; justify-content: center">
640
- <div :style="getPatrolStatusStyle(item.status)" style="
1026
+ <div
1027
+ :style="getPatrolStatusStyle(item.status)"
1028
+ style="
641
1029
  padding: 6px 16px;
642
1030
  border-radius: 20px;
643
1031
  font-size: 13px;
644
1032
  font-weight: 500;
645
1033
  text-transform: capitalize;
646
- ">
1034
+ "
1035
+ >
647
1036
  {{ item.status }}
648
1037
  </div>
649
1038
  </div>
650
1039
 
651
1040
  <div style="flex: 0 0 auto; text-align: right">
652
- <v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="
1041
+ <v-btn
1042
+ variant="outlined"
1043
+ class="text-none font-weight-medium"
1044
+ size="small"
1045
+ color="#3b82f6"
1046
+ style="
653
1047
  border-color: #e2e8f0;
654
1048
  border-radius: 8px;
655
1049
  padding: 0 16px;
656
1050
  height: 32px;
657
- " @click="goToDashboardRoute('virtual-patrol')">View</v-btn>
1051
+ "
1052
+ @click="goToDashboardRoute('virtual-patrol')"
1053
+ >View</v-btn
1054
+ >
658
1055
  </div>
659
1056
  </div>
660
1057
  </div>
@@ -667,50 +1064,68 @@
667
1064
  <div class="dashboard-card dashboard-panel figma-panel">
668
1065
  <div class="figma-panel-header">
669
1066
  <h2>STAFF STATUS</h2>
670
- <button class="figma-panel-action" type="button" @click="goToDashboardRoute('attendance')">
1067
+ <button
1068
+ class="figma-panel-action"
1069
+ type="button"
1070
+ @click="goToDashboardRoute('attendance')"
1071
+ >
671
1072
  View All
672
1073
  </button>
673
1074
  </div>
674
1075
  <div class="figma-panel-body">
675
1076
  <div v-if="securityStaffItems.length" class="figma-list mt-2">
676
- <div v-for="item in securityStaffItems" :key="item._id || item.id"
677
- class="d-flex align-center justify-space-between" style="
1077
+ <div
1078
+ v-for="item in securityStaffItems"
1079
+ :key="item._id || item.id"
1080
+ class="d-flex align-center justify-space-between"
1081
+ style="
678
1082
  padding: 16px 20px;
679
1083
  border: 1px solid #e2e8f0;
680
1084
  border-radius: 12px;
681
1085
  margin-bottom: 12px;
682
1086
  background-color: #ffffff;
683
1087
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
684
- ">
1088
+ "
1089
+ >
685
1090
  <div style="display: flex; align-items: center; gap: 16px">
686
- <AvatarMain :name="item.personName ||
687
- item.employeeName ||
688
- item.userName ||
689
- 'Unassigned'
690
- " :size="40" />
1091
+ <AvatarMain
1092
+ :name="
1093
+ item.personName ||
1094
+ item.employeeName ||
1095
+ item.userName ||
1096
+ 'Unassigned'
1097
+ "
1098
+ :size="40"
1099
+ />
691
1100
  <div>
692
- <strong style="
1101
+ <strong
1102
+ style="
693
1103
  display: block;
694
1104
  font-size: 15px;
695
1105
  font-weight: 700;
696
1106
  color: #0b2f47;
697
1107
  margin-bottom: 4px;
698
- ">{{
1108
+ "
1109
+ >{{
699
1110
  item.userName || item.personName || "Unassigned"
700
- }}</strong>
1111
+ }}</strong
1112
+ >
701
1113
  <span style="font-size: 13px; color: #94a3b8">{{
702
1114
  item.role || item.designation || "Officer"
703
- }}</span>
1115
+ }}</span>
704
1116
  </div>
705
1117
  </div>
706
1118
 
707
1119
  <div style="flex: 0 0 auto">
708
- <div :style="getStaffStatusPillStyle(item.status)" style="
1120
+ <div
1121
+ :style="getStaffStatusPillStyle(item.status)"
1122
+ style="
709
1123
  padding: 6px 16px;
710
1124
  border-radius: 20px;
711
1125
  font-size: 13px;
712
1126
  font-weight: 500;
713
- ">
1127
+ "
1128
+ >
714
1129
  {{ item.status || "On duty" }}
715
1130
  </div>
716
1131
  </div>
@@ -723,55 +1138,80 @@
723
1138
 
724
1139
  <!-- Security feedbacks -->
725
1140
  <v-col v-if="isWidgetVisible('feedbacks')" cols="12">
726
- <DashboardPanel title="Feedbacks" action-label="View All" @action="goToDashboardRoute('feedbacks')">
1141
+ <DashboardPanel
1142
+ title="Feedbacks"
1143
+ action-label="View All"
1144
+ @action="goToDashboardRoute('feedbacks')"
1145
+ >
727
1146
  <div v-if="feedbackItems.length" class="figma-list mt-2">
728
- <div v-for="item in feedbackItems" :key="item.id" class="d-flex align-center justify-space-between" style="
1147
+ <div
1148
+ v-for="item in feedbackItems"
1149
+ :key="item.id"
1150
+ class="d-flex align-center justify-space-between"
1151
+ style="
729
1152
  padding: 16px 20px;
730
1153
  border: 1px solid #e2e8f0;
731
1154
  border-radius: 12px;
732
1155
  margin-bottom: 12px;
733
1156
  background-color: #ffffff;
734
1157
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
735
- ">
1158
+ "
1159
+ >
736
1160
  <div style="flex: 1; min-width: 140px">
737
- <strong style="
1161
+ <strong
1162
+ style="
738
1163
  display: block;
739
1164
  font-size: 15px;
740
1165
  font-weight: 700;
741
1166
  color: #0b2f47;
742
1167
  margin-bottom: 4px;
743
- ">{{ item.title }}</strong>
1168
+ "
1169
+ >{{ item.title }}</strong
1170
+ >
744
1171
  <span style="font-size: 13px; color: #94a3b8">{{
745
1172
  item.subtitle
746
- }}</span>
1173
+ }}</span>
747
1174
  </div>
748
1175
 
749
- <div style="flex: 1; display: flex; align-items: center; gap: 12px">
1176
+ <div
1177
+ style="flex: 1; display: flex; align-items: center; gap: 12px"
1178
+ >
750
1179
  <AvatarMain :name="item.person" :size="36" />
751
1180
  <span style="font-size: 14px; color: #64748b">{{
752
1181
  item.person
753
- }}</span>
1182
+ }}</span>
754
1183
  </div>
755
1184
 
756
1185
  <div style="flex: 1; display: flex; justify-content: center">
757
- <div :style="getStatusPillStyle(item.status)" style="
1186
+ <div
1187
+ :style="getStatusPillStyle(item.status)"
1188
+ style="
758
1189
  padding: 6px 16px;
759
1190
  border-radius: 20px;
760
1191
  font-size: 13px;
761
1192
  font-weight: 500;
762
1193
  text-transform: capitalize;
763
- ">
1194
+ "
1195
+ >
764
1196
  {{ item.status || "To-Do" }}
765
1197
  </div>
766
1198
  </div>
767
1199
 
768
1200
  <div style="flex: 0 0 auto; text-align: right">
769
- <v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="
1201
+ <v-btn
1202
+ variant="outlined"
1203
+ class="text-none font-weight-medium"
1204
+ size="small"
1205
+ color="#3b82f6"
1206
+ style="
770
1207
  border-color: #e2e8f0;
771
1208
  border-radius: 8px;
772
1209
  padding: 0 16px;
773
1210
  height: 32px;
774
- " @click="goToListItem(item, 'feedbacks')">View</v-btn>
1211
+ "
1212
+ @click="goToListItem(item, 'feedbacks')"
1213
+ >View</v-btn
1214
+ >
775
1215
  </div>
776
1216
  </div>
777
1217
  </div>
@@ -783,7 +1223,9 @@
783
1223
  <!-- ── Customize Dialog ───────────────────────────────────────────────── -->
784
1224
  <v-dialog v-model="customizeDialog" max-width="720" persistent scrollable>
785
1225
  <v-card rounded="lg">
786
- <v-card-title class="d-flex align-start justify-space-between pa-6 pb-4 border-b">
1226
+ <v-card-title
1227
+ class="d-flex align-start justify-space-between pa-6 pb-4 border-b"
1228
+ >
787
1229
  <div>
788
1230
  <div class="text-h6 font-weight-black text-blue-grey-darken-4">
789
1231
  Customize Dashboard
@@ -792,54 +1234,154 @@
792
1234
  Select which widgets to display on your dashboard
793
1235
  </div>
794
1236
  </div>
795
- <v-btn icon="mdi-close" variant="text" density="comfortable" aria-label="Close customize dashboard"
796
- @click="cancelCustomize" />
1237
+ <v-btn
1238
+ icon="mdi-close"
1239
+ variant="text"
1240
+ density="comfortable"
1241
+ aria-label="Close customize dashboard"
1242
+ @click="cancelCustomize"
1243
+ />
797
1244
  </v-card-title>
798
1245
 
799
1246
  <v-card-text class="pa-6">
800
- <v-text-field v-model="widgetSearch" placeholder="Search widgets..." prepend-inner-icon="mdi-magnify"
801
- variant="outlined" density="compact" hide-details />
1247
+ <v-text-field
1248
+ v-model="widgetSearch"
1249
+ placeholder="Search widgets..."
1250
+ prepend-inner-icon="mdi-magnify"
1251
+ variant="outlined"
1252
+ density="compact"
1253
+ hide-details
1254
+ />
802
1255
 
803
1256
  <div class="d-flex justify-end mt-3 mb-1">
804
- <v-btn variant="text" color="blue-grey-darken-4" size="small" class="text-none font-weight-bold"
805
- prepend-icon="mdi-refresh" @click="resetCustomizeDraft">
1257
+ <v-btn
1258
+ variant="text"
1259
+ color="blue-grey-darken-4"
1260
+ size="small"
1261
+ class="text-none font-weight-bold"
1262
+ prepend-icon="mdi-refresh"
1263
+ @click="resetCustomizeDraft"
1264
+ >
806
1265
  Reset to default
807
1266
  </v-btn>
808
1267
  </div>
809
1268
 
810
1269
  <div class="d-flex flex-column ga-3">
811
- <v-card v-for="widget in filteredCustomizeWidgets" :key="widget.key" flat border rounded="lg" class="pa-4">
1270
+ <v-card
1271
+ v-for="widget in filteredCustomizeWidgets"
1272
+ :key="widget.key"
1273
+ flat
1274
+ border
1275
+ rounded="lg"
1276
+ class="pa-4"
1277
+ >
812
1278
  <div class="d-flex align-center justify-space-between ga-4">
813
1279
  <div>
814
- <div class="text-body-2 font-weight-black text-blue-grey-darken-4">
1280
+ <div
1281
+ class="text-body-2 font-weight-black text-blue-grey-darken-4"
1282
+ >
815
1283
  {{ widget.title }}
816
1284
  </div>
817
1285
  <div class="text-caption text-blue-grey mt-1">
818
1286
  {{ widget.description }}
819
1287
  </div>
820
1288
  </div>
821
- <v-switch v-model="draftVisibleWidgetKeys" :value="widget.key" color="blue-grey-darken-4" inset
822
- hide-details density="compact" aria-label="Toggle dashboard widget" />
1289
+ <v-switch
1290
+ v-model="draftVisibleWidgetKeys"
1291
+ :value="widget.key"
1292
+ color="blue-grey-darken-4"
1293
+ inset
1294
+ hide-details
1295
+ density="compact"
1296
+ aria-label="Toggle dashboard widget"
1297
+ />
823
1298
  </div>
824
1299
  </v-card>
825
1300
 
826
- <DashboardEmptyState v-if="!filteredCustomizeWidgets.length" compact />
1301
+ <DashboardEmptyState
1302
+ v-if="!filteredCustomizeWidgets.length"
1303
+ compact
1304
+ />
827
1305
  </div>
828
1306
  </v-card-text>
829
1307
 
830
1308
  <v-card-actions class="pa-6 pt-4 border-t justify-end ga-3">
831
- <v-btn variant="text" class="text-none font-weight-bold text-blue-grey" @click="cancelCustomize">
1309
+ <v-btn
1310
+ variant="text"
1311
+ class="text-none font-weight-bold text-blue-grey"
1312
+ @click="cancelCustomize"
1313
+ >
832
1314
  Cancel
833
1315
  </v-btn>
834
- <v-btn variant="flat" color="blue-grey-darken-4" class="text-none font-weight-bold px-6"
835
- style="min-width: 110px" @click="submitCustomize">
1316
+ <v-btn
1317
+ variant="flat"
1318
+ color="blue-grey-darken-4"
1319
+ class="text-none font-weight-bold px-6"
1320
+ style="min-width: 110px"
1321
+ @click="submitCustomize"
1322
+ >
836
1323
  Submit
837
1324
  </v-btn>
838
1325
  </v-card-actions>
839
1326
  </v-card>
840
1327
  </v-dialog>
841
1328
 
842
- <v-snackbar v-model="snackbar.visible" :timeout="2200" :color="snackbar.color || 'primary'">
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
+
1380
+ <v-snackbar
1381
+ v-model="snackbar.visible"
1382
+ :timeout="2200"
1383
+ :color="snackbar.color || 'primary'"
1384
+ >
843
1385
  {{ snackbar.text }}
844
1386
  </v-snackbar>
845
1387
  </section>
@@ -864,6 +1406,8 @@ type DashboardMetric = {
864
1406
  outOfStock?: number;
865
1407
  completed?: number;
866
1408
  total?: number;
1409
+ byStatus?: Record<string, number>;
1410
+ statusCounts?: Record<string, number>;
867
1411
  items?: { name: string; qty: number }[];
868
1412
  };
869
1413
 
@@ -1075,7 +1619,7 @@ const initialPeriod = (
1075
1619
  ) as TDashboardValues;
1076
1620
  const selectedRange = ref(periodLabelValue[initialPeriod]);
1077
1621
  const visitorType = ref("All");
1078
- const facilityType = ref("All");
1622
+ const facilityBookingDialog = ref(false);
1079
1623
  const visibleWidgetKeys = ref<string[]>([]);
1080
1624
  const draftVisibleWidgetKeys = ref<string[]>([]);
1081
1625
  const filterMenus = reactive<Record<string, boolean>>({});
@@ -1123,10 +1667,10 @@ const { data: getDashboardReq, pending: loadingMetrics } =
1123
1667
  () =>
1124
1668
  props.serviceType && isHygieneMode.value
1125
1669
  ? getDashboardHygiene(
1126
- props.site,
1127
- currentPeriod.value,
1128
- props.serviceType as TServiceType
1129
- )
1670
+ props.site,
1671
+ currentPeriod.value,
1672
+ props.serviceType as TServiceType
1673
+ )
1130
1674
  : Promise.resolve(null),
1131
1675
  {
1132
1676
  watch: [() => props.site, currentPeriod, () => props.serviceType],
@@ -1140,13 +1684,13 @@ const { data: getDashboardAltReq, pending: loadingAltMetrics } =
1140
1684
  () =>
1141
1685
  props.site && (isPropertyMode.value || isSecurityMode.value)
1142
1686
  ? getDashboard(
1143
- props.site,
1144
- props.serviceType as TServiceType,
1145
- currentPeriod.value,
1146
- currentVisitorTypeParam.value
1147
- ? { type: currentVisitorTypeParam.value }
1148
- : {}
1149
- )
1687
+ props.site,
1688
+ props.serviceType as TServiceType,
1689
+ currentPeriod.value,
1690
+ currentVisitorTypeParam.value
1691
+ ? { type: currentVisitorTypeParam.value }
1692
+ : {}
1693
+ )
1150
1694
  : Promise.resolve(null),
1151
1695
  {
1152
1696
  watch: [
@@ -1165,10 +1709,10 @@ const { data: weeklyActivityReq, pending: loadingWeeklyActivity } =
1165
1709
  () =>
1166
1710
  props.serviceType
1167
1711
  ? getDashboardWeeklyActivity(
1168
- props.site,
1169
- props.serviceType as TServiceType,
1170
- currentPeriod.value
1171
- )
1712
+ props.site,
1713
+ props.serviceType as TServiceType,
1714
+ currentPeriod.value
1715
+ )
1172
1716
  : Promise.resolve(null),
1173
1717
  {
1174
1718
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1182,10 +1726,10 @@ const { data: taskScheduleReq, pending: loadingTaskSchedule } =
1182
1726
  () =>
1183
1727
  props.serviceType && isHygieneMode.value
1184
1728
  ? getDashboardHygieneTaskSchedule(
1185
- props.site,
1186
- props.serviceType as TServiceType,
1187
- currentPeriod.value
1188
- )
1729
+ props.site,
1730
+ props.serviceType as TServiceType,
1731
+ currentPeriod.value
1732
+ )
1189
1733
  : Promise.resolve(null),
1190
1734
  {
1191
1735
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1199,10 +1743,10 @@ const { data: attendanceReq, pending: loadingAttendance } =
1199
1743
  () =>
1200
1744
  props.serviceType && !isPropertyMode.value
1201
1745
  ? getDashboardHygieneAttendance(
1202
- props.site,
1203
- props.serviceType as TServiceType,
1204
- currentPeriod.value
1205
- )
1746
+ props.site,
1747
+ props.serviceType as TServiceType,
1748
+ currentPeriod.value
1749
+ )
1206
1750
  : Promise.resolve(null),
1207
1751
  {
1208
1752
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1216,10 +1760,10 @@ const { data: feedbacksReq, pending: loadingFeedbacks } =
1216
1760
  () =>
1217
1761
  props.serviceType
1218
1762
  ? getDashboardHygieneFeedbacks(
1219
- props.site,
1220
- props.serviceType as TServiceType,
1221
- currentPeriod.value
1222
- )
1763
+ props.site,
1764
+ props.serviceType as TServiceType,
1765
+ currentPeriod.value
1766
+ )
1223
1767
  : Promise.resolve(null),
1224
1768
  {
1225
1769
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1707,7 +2251,6 @@ const visitorOptions = computed(() =>
1707
2251
  )
1708
2252
  );
1709
2253
 
1710
- // Property-specific: facility booking metric with client-side filter
1711
2254
  const facilityBookingItems = computed(() =>
1712
2255
  getDashboardArray([
1713
2256
  "facilityBookingItems",
@@ -1715,20 +2258,47 @@ const facilityBookingItems = computed(() =>
1715
2258
  "bookingsList",
1716
2259
  ])
1717
2260
  );
1718
- const defaultFacilityOptions = ["All"];
1719
- const facilityOptions = computed(() =>
1720
- buildFilterOptions(
1721
- defaultFacilityOptions,
1722
- facilityBookingItems.value,
1723
- (item: any) =>
1724
- item.facilityName ||
1725
- item.facility?.name ||
1726
- item.facilityInfo?.name ||
1727
- item.name ||
1728
- ""
1729
- )
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)
1730
2291
  );
1731
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
+
1732
2302
  function getPropertyKpiCards() {
1733
2303
  const workOrderMetric = getMetric([
1734
2304
  "openWorkOrder",
@@ -1798,53 +2368,13 @@ function getPropertyKpiCards() {
1798
2368
  percentage: toNumber(facilityMetric?.percentage),
1799
2369
  meta: getMetricMeta(facilityMetric),
1800
2370
  raw: facilityMetric,
1801
- filter: {
1802
- model: facilityType.value,
1803
- options: facilityOptions.value,
1804
- set: (v: string) => {
1805
- facilityType.value = v;
1806
- filterMenus.facilityBookings = false;
1807
- },
1808
- },
2371
+ filter: undefined,
1809
2372
  },
1810
2373
  ];
1811
2374
  }
1812
2375
 
1813
2376
  function computeFacilityMetric(): DashboardMetric {
1814
- const metric = getMetric(["facilityBooking", "facilityBookings", "bookings"]);
1815
- const selected = facilityType.value;
1816
- if (selected === "All") return metric;
1817
- const items = facilityBookingItems.value.filter((item: any) => {
1818
- const val = String(
1819
- item.facilityName ||
1820
- item.facility?.name ||
1821
- item.facilityInfo?.name ||
1822
- item.name ||
1823
- ""
1824
- ).toLowerCase();
1825
- return val === selected.toLowerCase();
1826
- });
1827
- const ongoing = items.filter((i: any) => {
1828
- const s = String(i.status || "").toLowerCase();
1829
- return (
1830
- s.includes("ongoing") ||
1831
- s.includes("approved") ||
1832
- s.includes("in-progress")
1833
- );
1834
- }).length;
1835
- const waitingApproval = items.filter((i: any) => {
1836
- const s = String(i.status || "").toLowerCase();
1837
- return (
1838
- s.includes("pending") || s.includes("review") || s.includes("approval")
1839
- );
1840
- }).length;
1841
-
1842
- return {
1843
- ...metric,
1844
- count: ongoing + waitingApproval,
1845
- ongoing,
1846
- waitingApproval,
1847
- };
2377
+ return getMetric(["facilityBooking", "facilityBookings", "bookings"]);
1848
2378
  }
1849
2379
 
1850
2380
  function getSecurityKpiCards() {
@@ -1967,21 +2497,21 @@ const dashboardTitle = computed(() => {
1967
2497
  const primaryListConfig = computed(() =>
1968
2498
  isSecurityMode.value
1969
2499
  ? {
1970
- title: "Active Patrol",
1971
- route: "virtualPatrol",
1972
- keys: ["activePatrolItems", "patrolItems"],
1973
- }
2500
+ title: "Active Patrol",
2501
+ route: "virtualPatrol",
2502
+ keys: ["activePatrolItems", "patrolItems"],
2503
+ }
1974
2504
  : {
1975
- title: "Today's Task Schedule",
1976
- route: "scheduleTask",
1977
- keys: [
1978
- "taskScheduleItems",
1979
- "scheduleTaskItems",
1980
- "scheduledTaskItems",
1981
- "cleaningScheduleItems",
1982
- "tasks",
1983
- ],
1984
- }
2505
+ title: "Today's Task Schedule",
2506
+ route: "scheduleTask",
2507
+ keys: [
2508
+ "taskScheduleItems",
2509
+ "scheduleTaskItems",
2510
+ "scheduledTaskItems",
2511
+ "cleaningScheduleItems",
2512
+ "tasks",
2513
+ ],
2514
+ }
1985
2515
  );
1986
2516
 
1987
2517
  // ─── Activity Chart ───────────────────────────────────────────────────────────
@@ -1989,7 +2519,14 @@ const primaryListConfig = computed(() =>
1989
2519
  const activityTitle = computed(() => {
1990
2520
  if (selectedRange.value === "Today") return "Today's Activity";
1991
2521
  if (selectedRange.value === "This Month") return "This Month Activity";
1992
- 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)}`;
1993
2530
  });
1994
2531
 
1995
2532
  const activityChart = computed(() =>
@@ -2026,6 +2563,50 @@ const hasChartData = computed(
2026
2563
  );
2027
2564
  const chartTicks = [28, 66.4, 104.8, 143.2, 181.6, 220];
2028
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
+
2029
2610
  const chartYLabels = computed(() => {
2030
2611
  const max = chartMax.value;
2031
2612
  return [
@@ -2362,7 +2943,8 @@ function getMetricMeta(metric: DashboardMetric) {
2362
2943
  parts.push(`${formatNumber(toNumber(metric.ongoing))} ongoing`);
2363
2944
  if (typeof metric.waitingApproval !== "undefined")
2364
2945
  parts.push(
2365
- `${formatNumber(toNumber(metric.waitingApproval))} ${isPropertyMode.value ? "for approval" : "waiting approval"
2946
+ `${formatNumber(toNumber(metric.waitingApproval))} ${
2947
+ isPropertyMode.value ? "for approval" : "waiting approval"
2366
2948
  }`
2367
2949
  );
2368
2950
  if (typeof metric.lowStock !== "undefined")
@@ -2389,9 +2971,9 @@ function getFilteredVisitorMetric(): DashboardMetric {
2389
2971
  count: toNumber(metric.count ?? items.length),
2390
2972
  currentlyOnSite: toNumber(
2391
2973
  metric.currentlyOnSite ??
2392
- items.filter(
2393
- (item: any) => !isClosedStatus(item.status) && !item.checkOut
2394
- ).length
2974
+ items.filter(
2975
+ (item: any) => !isClosedStatus(item.status) && !item.checkOut
2976
+ ).length
2395
2977
  ),
2396
2978
  };
2397
2979
  }
@@ -2487,8 +3069,9 @@ function getListIcon(context: string) {
2487
3069
 
2488
3070
  function getListTone(item: DashboardListItem) {
2489
3071
  if (item.tone) return item.tone;
2490
- const text = `${item.title} ${item.subtitle || ""} ${item.icon || ""} ${item.color || ""
2491
- }`.toLowerCase();
3072
+ const text = `${item.title} ${item.subtitle || ""} ${item.icon || ""} ${
3073
+ item.color || ""
3074
+ }`.toLowerCase();
2492
3075
  if (
2493
3076
  text.includes("alert") ||
2494
3077
  text.includes("incident") ||
@@ -2580,10 +3163,10 @@ function normalizeStaffItem(
2580
3163
  role,
2581
3164
  status: normalizeStatus(
2582
3165
  item.statusLabel ||
2583
- item.status ||
2584
- item.currentStatus ||
2585
- item.attendanceStatus ||
2586
- "Off duty"
3166
+ item.status ||
3167
+ item.currentStatus ||
3168
+ item.attendanceStatus ||
3169
+ "Off duty"
2587
3170
  ),
2588
3171
  totalHours: item.totalHours || 0,
2589
3172
  raw: item,
@@ -2934,8 +3517,8 @@ async function exportDashboard(format: "PDF" | "Word") {
2934
3517
  selectedRange.value === "Today"
2935
3518
  ? "Today"
2936
3519
  : selectedRange.value === "This Week"
2937
- ? "This Week"
2938
- : "This Month";
3520
+ ? "This Week"
3521
+ : "This Month";
2939
3522
 
2940
3523
  const reportTitle = `${props.title || dashboardTitle.value} - ${periodLabel}`;
2941
3524
 
@@ -3017,10 +3600,11 @@ async function exportDashboard(format: "PDF" | "Word") {
3017
3600
  <td>
3018
3601
  <div class="kpi-title">${card.title}</div>
3019
3602
  ${kpiValueHtml}
3020
- <div class="kpi-meta">${card.meta || ""} ${card.percentage
3603
+ <div class="kpi-meta">${card.meta || ""} ${
3604
+ card.percentage
3021
3605
  ? `(${card.percentage >= 0 ? "+" : ""}${card.percentage}%)`
3022
3606
  : ""
3023
- }</div>
3607
+ }</div>
3024
3608
  </td>
3025
3609
  `;
3026
3610
  });
@@ -3066,8 +3650,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3066
3650
  <td>${item.person || ""}</td>
3067
3651
  <td>${item.time || ""}</td>
3068
3652
  <td><span class="status-badge badge-${getStatusTone(
3069
- item.status
3070
- )}">${item.status || ""}</span></td>
3653
+ item.status
3654
+ )}">${item.status || ""}</span></td>
3071
3655
  </tr>`;
3072
3656
  });
3073
3657
  } else {
@@ -3165,8 +3749,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3165
3749
  <td>${item.subtitle || item.location || ""}</td>
3166
3750
  <td>${item.person || item.assignedTo || ""}</td>
3167
3751
  <td><span class="status-badge badge-${getStatusTone(
3168
- item.status
3169
- )}">${item.status || ""}</span></td>
3752
+ item.status
3753
+ )}">${item.status || ""}</span></td>
3170
3754
  </tr>`;
3171
3755
  });
3172
3756
  } else {
@@ -3180,7 +3764,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3180
3764
  if (securityStaffItems.value.length > 0) {
3181
3765
  securityStaffItems.value.forEach((item) => {
3182
3766
  wordHtml += `<tr>
3183
- <td><strong>${item.userName || item.personName || "Unassigned"
3767
+ <td><strong>${
3768
+ item.userName || item.personName || "Unassigned"
3184
3769
  }</strong></td>
3185
3770
  <td>${item.role || item.designation || "Officer"}</td>
3186
3771
  <td><span class="status-badge badge-${getStatusTone(
@@ -3204,8 +3789,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3204
3789
  <td>${item.subtitle || ""}</td>
3205
3790
  <td>${item.person || ""}</td>
3206
3791
  <td><span class="status-badge badge-${getStatusTone(
3207
- item.status
3208
- )}">${item.status || "To-Do"}</span></td>
3792
+ item.status
3793
+ )}">${item.status || "To-Do"}</span></td>
3209
3794
  </tr>`;
3210
3795
  });
3211
3796
  } else {
@@ -3235,8 +3820,9 @@ async function exportDashboard(format: "PDF" | "Word") {
3235
3820
  snackbar.visible = true;
3236
3821
  }
3237
3822
  } catch (err: any) {
3238
- snackbar.text = `Export failed: ${err?.message || "Unknown error"
3239
- }. Please try again.`;
3823
+ snackbar.text = `Export failed: ${
3824
+ err?.message || "Unknown error"
3825
+ }. Please try again.`;
3240
3826
  snackbar.color = "error";
3241
3827
  snackbar.visible = true;
3242
3828
  } finally {
@@ -3567,7 +4153,7 @@ function formatDashboardTime(value?: string) {
3567
4153
  margin-top: 14px;
3568
4154
  }
3569
4155
 
3570
- .dashboard-section-grid>.v-col {
4156
+ .dashboard-section-grid > .v-col {
3571
4157
  display: flex;
3572
4158
  }
3573
4159
 
@@ -3619,6 +4205,54 @@ function formatDashboardTime(value?: string) {
3619
4205
  white-space: nowrap;
3620
4206
  }
3621
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
+
3622
4256
  .figma-panel-body {
3623
4257
  flex: 1;
3624
4258
  margin-top: 20px;
@@ -3748,7 +4382,8 @@ function formatDashboardTime(value?: string) {
3748
4382
  padding: 6px 2px 0;
3749
4383
  }
3750
4384
 
3751
- .figma-status-row {}
4385
+ .figma-status-row {
4386
+ }
3752
4387
 
3753
4388
  .figma-status-label {
3754
4389
  align-items: center;