@7365admin1/layer-common 3.0.21 → 3.0.22

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,21 +12,12 @@
12
12
  <!-- KPI Cards (Statistics) -->
13
13
  <div class="print-section-title">Overview Statistics</div>
14
14
  <div class="print-kpi-grid">
15
- <div
16
- v-for="card in visibleKpiCards"
17
- :key="card.key"
18
- class="print-kpi-card"
19
- >
15
+ <div v-for="card in visibleKpiCards" :key="card.key" class="print-kpi-card">
20
16
  <span class="print-kpi-title">{{ card.title }}</span>
21
- <template
22
- v-if="card.key === 'supplyAlert' && Array.isArray(card.raw?.items)"
23
- >
17
+ <template v-if="card.key === 'supplyAlert' && Array.isArray(card.raw?.items)">
24
18
  <div class="d-flex flex-column ga-2 mt-2">
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
- >
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">
30
21
  <span>{{ item.name }}</span>
31
22
  <span class="text-blue-grey">Balance: {{ item.qty }}</span>
32
23
  </div>
@@ -36,10 +27,8 @@
36
27
  <div class="print-kpi-value">{{ card.value }}</div>
37
28
  <div class="print-kpi-meta" v-if="card.meta || card.percentage">
38
29
  {{ card.meta || "" }}
39
- <span v-if="card.percentage"
40
- >({{ card.percentage >= 0 ? "+" : ""
41
- }}{{ card.percentage }}%)</span
42
- >
30
+ <span v-if="card.percentage">({{ card.percentage >= 0 ? "+" : ""
31
+ }}{{ card.percentage }}%)</span>
43
32
  </div>
44
33
  </template>
45
34
  </div>
@@ -50,100 +39,52 @@
50
39
  <div class="print-section-title">{{ activityTitle }}</div>
51
40
  <div class="print-chart-container">
52
41
  <svg viewBox="0 0 920 260" class="d-block w-100" style="height: 260px">
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
- >
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">
71
46
  {{ label.text }}
72
47
  </text>
73
- <path
74
- :d="primaryPath"
75
- style="
48
+ <path :d="primaryPath" style="
76
49
  fill: none;
77
50
  stroke: #ff9b9b;
78
51
  stroke-width: 3;
79
52
  stroke-linecap: round;
80
53
  stroke-linejoin: round;
81
- "
82
- />
83
- <path
84
- :d="secondaryPath"
85
- style="
54
+ " />
55
+ <path :d="secondaryPath" style="
86
56
  fill: none;
87
57
  stroke: #e53935;
88
58
  stroke-width: 3;
89
59
  stroke-linecap: round;
90
60
  stroke-linejoin: round;
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
- >
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">
117
68
  {{ label.text }}
118
69
  </text>
119
70
  </svg>
120
71
  <div class="d-flex align-center justify-center ga-5 mt-2">
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="
72
+ <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
73
+ <span class="rounded-circle" style="
127
74
  width: 12px;
128
75
  height: 12px;
129
76
  display: inline-block;
130
77
  background-color: #ff9b9b;
131
- "
132
- />
78
+ " />
133
79
  {{ primaryLegendLabel }}
134
80
  </span>
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="
81
+ <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
82
+ <span class="rounded-circle" style="
141
83
  width: 12px;
142
84
  height: 12px;
143
85
  display: inline-block;
144
86
  background-color: #e53935;
145
- "
146
- />
87
+ " />
147
88
  {{ secondaryLegendLabel }}
148
89
  </span>
149
90
  </div>
@@ -151,43 +92,30 @@
151
92
  </div>
152
93
 
153
94
  <!-- Tables for List Widgets (Other data) -->
154
- <div
155
- v-for="section in printTableSections"
156
- :key="section.key"
157
- class="print-table-section mt-6"
158
- >
95
+ <div v-for="section in printTableSections" :key="section.key" class="print-table-section mt-6">
159
96
  <div class="print-section-title">{{ section.title }}</div>
160
97
  <table class="print-table">
161
98
  <thead>
162
99
  <tr>
163
- <th
164
- v-for="column in section.columns"
165
- :key="column.key"
166
- :style="{ width: column.width }"
167
- >
100
+ <th v-for="column in section.columns" :key="column.key" :style="{ width: column.width }">
168
101
  {{ column.label }}
169
102
  </th>
170
103
  </tr>
171
104
  </thead>
172
105
  <tbody>
173
106
  <tr v-for="row in section.rows" :key="row.id">
174
- <td
175
- v-for="column in section.columns"
176
- :key="`${row.id}-${column.key}`"
177
- >
107
+ <td v-for="column in section.columns" :key="`${row.id}-${column.key}`">
178
108
  <template v-if="column.status">
179
- <span
180
- :class="[
181
- 'print-status-badge',
182
- `badge-${getStatusTone(row[column.key])}`,
183
- ]"
184
- >
109
+ <span :class="[
110
+ 'print-status-badge',
111
+ `badge-${getStatusTone(row[column.key])}`,
112
+ ]">
185
113
  {{ row[column.key] ?? column.fallback ?? "-" }}
186
114
  </span>
187
115
  </template>
188
116
  <strong v-else-if="column.strong">{{
189
117
  row[column.key] ?? "-"
190
- }}</strong>
118
+ }}</strong>
191
119
  <template v-else>{{ row[column.key] ?? "-" }}</template>
192
120
  </td>
193
121
  </tr>
@@ -203,9 +131,7 @@
203
131
 
204
132
  <section v-else class="unified-dashboard pb-8">
205
133
  <!-- ── Header ──────────────────────────────────────────────────────────── -->
206
- <div
207
- class="d-flex flex-column flex-md-row align-md-start justify-space-between ga-5 mb-6"
208
- >
134
+ <div class="d-flex flex-column flex-md-row align-md-start justify-space-between ga-5 mb-6">
209
135
  <div>
210
136
  <h1 class="text-h5 font-weight-black text-blue-grey-darken-4 mb-0">
211
137
  {{ dashboardTitle }}
@@ -216,31 +142,16 @@
216
142
  </div>
217
143
  </div>
218
144
 
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
- >
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">
230
148
  Customize Dashboard
231
149
  </v-btn>
232
150
 
233
151
  <v-menu>
234
152
  <template #activator="{ props: menuProps }">
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
- >
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">
244
155
  Export
245
156
  </v-btn>
246
157
  </template>
@@ -251,101 +162,49 @@
251
162
  </v-list>
252
163
  </v-menu>
253
164
 
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
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
271
169
  ? {
272
- backgroundColor: '#e2e8f0',
273
- color: '#0b2f47',
274
- fontWeight: '600',
275
- }
170
+ backgroundColor: '#e2e8f0',
171
+ color: '#0b2f47',
172
+ fontWeight: '600',
173
+ }
276
174
  : { backgroundColor: 'transparent', color: '#64748b' }
277
- "
278
- >
175
+ ">
279
176
  {{ range.label }}
280
177
  </v-btn>
281
178
  </v-btn-toggle>
282
179
  </div>
283
180
  </div>
284
181
 
285
- <v-progress-linear
286
- v-if="finalLoading"
287
- indeterminate
288
- color="primary"
289
- class="mb-4"
290
- />
182
+ <v-progress-linear v-if="finalLoading" indeterminate color="primary" class="mb-4" />
291
183
 
292
184
  <!-- ── KPI Cards ───────────────────────────────────────────────────────── -->
293
185
  <v-row dense class="mb-1 align-stretch">
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
- >
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">
310
188
  <div class="d-flex align-start justify-space-between">
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
- >
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 }">
316
191
  <v-icon :icon="card.icon" :color="card.color" size="15" />
317
192
  </div>
318
193
 
319
194
  <v-menu v-if="card.filter" v-model="filterMenus[card.key]">
320
195
  <template #activator="{ props: menuProps }">
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
- >
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">
330
198
  {{ card.filter.model }}
331
199
  </v-btn>
332
200
  </template>
333
201
 
334
202
  <v-list density="compact" border rounded="lg" elevation="3">
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
- >
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)">
342
205
  <template #prepend>
343
- <v-icon
344
- :icon="
345
- card.filter.model === option ? 'mdi-check' : 'mdi-blank'
346
- "
347
- size="16"
348
- />
206
+ <v-icon :icon="card.filter.model === option ? 'mdi-check' : 'mdi-blank'
207
+ " size="16" />
349
208
  </template>
350
209
  </v-list-item>
351
210
  </v-list>
@@ -353,22 +212,15 @@
353
212
  </div>
354
213
 
355
214
  <div class="mt-6 w-100">
356
- <p
357
- class="text-caption font-weight-black text-uppercase text-blue-grey-darken-2 mb-3"
358
- >
215
+ <p class="text-caption font-weight-black text-uppercase text-blue-grey-darken-2 mb-3">
359
216
  {{ card.title }}
360
217
  </p>
361
- <template
362
- v-if="
363
- card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
364
- "
365
- >
218
+ <template v-if="
219
+ card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
220
+ ">
366
221
  <div class="d-flex flex-column ga-2 mt-2">
367
- <div
368
- v-for="item in card.raw.items"
369
- :key="item._id || item.name"
370
- class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium"
371
- >
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">
372
224
  <span>{{ item.name }}</span>
373
225
  <span class="text-blue-grey">Balance: {{ item.qty }}</span>
374
226
  </div>
@@ -383,22 +235,13 @@
383
235
  <div class="d-flex align-center flex-wrap ga-2">
384
236
  <span v-if="card.meta" class="text-caption text-blue-grey">{{
385
237
  card.meta
386
- }}</span>
387
- <v-chip
388
- :color="card.percentage >= 0 ? 'success' : 'error'"
389
- size="x-small"
390
- variant="flat"
391
- class="font-weight-bold"
392
- >
393
- <v-icon
394
- :icon="
395
- card.percentage >= 0
396
- ? 'mdi-arrow-up-right'
397
- : 'mdi-arrow-down-right'
398
- "
399
- size="13"
400
- start
401
- />
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 />
402
245
  {{ Math.abs(card.percentage) }}%
403
246
  </v-chip>
404
247
  </div>
@@ -409,13 +252,7 @@
409
252
  </v-row>
410
253
 
411
254
  <!-- ── Activity Chart (all modes) ────────────────────────────────────── -->
412
- <v-card
413
- v-if="isWidgetVisible('activity')"
414
- flat
415
- border
416
- rounded="lg"
417
- class="px-5 pt-4 pb-3 mt-3"
418
- >
255
+ <v-card v-if="isWidgetVisible('activity')" flat border rounded="lg" class="px-5 pt-4 pb-3 mt-3">
419
256
  <div class="d-flex align-center justify-space-between">
420
257
  <h2 class="text-body-1 font-weight-black text-blue-grey-darken-4 mb-0">
421
258
  {{ activityTitle }}
@@ -423,106 +260,54 @@
423
260
  </div>
424
261
 
425
262
  <div v-if="hasChartData" class="mt-4" ref="chartContainerRef">
426
- <svg
427
- :viewBox="`0 0 ${chartWidth} 260`"
428
- class="d-block w-100"
429
- style="height: auto; max-height: 260px"
430
- >
431
- <line
432
- v-for="tick in chartTicks"
433
- :key="tick"
434
- x1="48"
435
- :y1="tick"
436
- :x2="chartWidth - 20"
437
- :y2="tick"
438
- style="stroke: #e9eef2; stroke-width: 1"
439
- />
440
- <text
441
- v-for="label in chartYLabels"
442
- :key="label.text"
443
- x="40"
444
- :y="label.y"
445
- text-anchor="end"
446
- alignment-baseline="middle"
447
- style="fill: #7e8b96; font-size: 11px; font-weight: 500"
448
- >
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">
449
268
  {{ label.text }}
450
269
  </text>
451
- <path
452
- :d="primaryPath"
453
- style="
270
+ <path :d="primaryPath" style="
454
271
  fill: none;
455
272
  stroke: #ff9b9b;
456
273
  stroke-width: 3;
457
274
  stroke-linecap: round;
458
275
  stroke-linejoin: round;
459
- "
460
- />
461
- <path
462
- :d="secondaryPath"
463
- style="
276
+ " />
277
+ <path :d="secondaryPath" style="
464
278
  fill: none;
465
279
  stroke: #e53935;
466
280
  stroke-width: 3;
467
281
  stroke-linecap: round;
468
282
  stroke-linejoin: round;
469
- "
470
- />
471
- <circle
472
- v-for="point in primaryDots"
473
- :key="`primary-${point.x}-${point.y}`"
474
- :cx="point.x"
475
- :cy="point.y"
476
- r="4"
477
- style="fill: #ff9b9b; stroke: #fff; stroke-width: 2"
478
- />
479
- <circle
480
- v-for="point in secondaryDots"
481
- :key="`secondary-${point.x}-${point.y}`"
482
- :cx="point.x"
483
- :cy="point.y"
484
- r="4"
485
- style="fill: #e53935; stroke: #fff; stroke-width: 2"
486
- />
487
- <text
488
- v-for="label in chartAxisLabels"
489
- :key="`${label.text}-${label.x}`"
490
- :x="label.x"
491
- y="246"
492
- text-anchor="middle"
493
- style="fill: #7e8b96; font-size: 12px; font-weight: 600"
494
- >
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">
495
290
  {{ label.text }}
496
291
  </text>
497
292
  </svg>
498
293
 
499
294
  <div class="d-flex align-center justify-center ga-5 mt-2">
500
- <span
501
- class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
502
- >
503
- <span
504
- class="rounded-circle"
505
- style="
295
+ <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
296
+ <span class="rounded-circle" style="
506
297
  width: 12px;
507
298
  height: 12px;
508
299
  display: inline-block;
509
300
  background-color: #ff9b9b;
510
- "
511
- />
301
+ " />
512
302
  {{ primaryLegendLabel }}
513
303
  </span>
514
- <span
515
- class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
516
- >
517
- <span
518
- class="rounded-circle"
519
- style="
304
+ <span class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey">
305
+ <span class="rounded-circle" style="
520
306
  width: 12px;
521
307
  height: 12px;
522
308
  display: inline-block;
523
309
  background-color: #e53935;
524
- "
525
- />
310
+ " />
526
311
  {{ secondaryLegendLabel }}
527
312
  </span>
528
313
  </div>
@@ -533,63 +318,33 @@
533
318
 
534
319
  <!-- ── Hygiene / M&E / Landscape / Pest / Pool — task + attendance ────── -->
535
320
  <v-row v-if="isHygieneMode" dense class="mt-3 align-stretch">
536
- <v-col
537
- v-if="isWidgetVisible('taskSchedule')"
538
- cols="12"
539
- lg="7"
540
- class="d-flex"
541
- >
542
- <DashboardPanel
543
- :title="primaryListConfig.title"
544
- action-label="View All"
545
- @action="goToDashboardRoute(primaryListConfig.route)"
546
- >
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)">
547
324
  <div v-if="taskScheduleItems.length" class="d-flex flex-column ga-3">
548
- <v-card
549
- v-for="item in taskScheduleItems"
550
- :key="item.id"
551
- flat
552
- border
553
- rounded="lg"
554
- class="pa-3"
555
- >
325
+ <v-card v-for="item in taskScheduleItems" :key="item.id" flat border rounded="lg" class="pa-3">
556
326
  <div class="d-flex align-center flex-wrap ga-3">
557
327
  <div class="flex-grow-1" style="min-width: 0">
558
- <div
559
- class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
560
- >
328
+ <div class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate">
561
329
  {{ item.title }}
562
330
  </div>
563
331
  <div class="text-caption text-blue-grey mt-1 text-truncate">
564
332
  {{ item.subtitle
565
333
  }}<template v-if="item.subtitle && item.time">
566
- &bull; </template
567
- >{{ item.time }}
334
+ &bull; </template>{{ item.time }}
568
335
  </div>
569
336
  </div>
570
- <div
571
- class="d-flex align-center ga-2 text-caption text-blue-grey"
572
- >
337
+ <div class="d-flex align-center ga-2 text-caption text-blue-grey">
573
338
  <AvatarMain :name="item.person" :size="30" />
574
339
  <span class="text-truncate" style="max-width: 100px">{{
575
340
  item.person
576
- }}</span>
341
+ }}</span>
577
342
  </div>
578
- <v-chip
579
- size="small"
580
- :color="getStatusTone(item.status)"
581
- variant="tonal"
582
- label
583
- >
343
+ <v-chip size="small" :color="getStatusTone(item.status)" variant="tonal" label>
584
344
  {{ item.status }}
585
345
  </v-chip>
586
- <v-btn
587
- variant="outlined"
588
- color="primary"
589
- size="small"
590
- class="text-none font-weight-bold"
591
- @click="goToListItem(item, primaryListConfig.route)"
592
- >
346
+ <v-btn variant="outlined" color="primary" size="small" class="text-none font-weight-bold"
347
+ @click="goToListItem(item, primaryListConfig.route)">
593
348
  View
594
349
  </v-btn>
595
350
  </div>
@@ -599,45 +354,23 @@
599
354
  </DashboardPanel>
600
355
  </v-col>
601
356
 
602
- <v-col
603
- v-if="isWidgetVisible('staffStatus')"
604
- cols="12"
605
- lg="5"
606
- class="d-flex"
607
- >
608
- <DashboardPanel
609
- title="Staff's Total Attendance"
610
- action-label="View All"
611
- @action="goToDashboardRoute('attendance')"
612
- >
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')">
613
360
  <div v-if="staffStatusItems.length" class="d-flex flex-column ga-3">
614
- <v-card
615
- v-for="item in staffStatusItems"
616
- :key="item.id"
617
- flat
618
- border
619
- rounded="lg"
620
- class="pa-3"
621
- >
361
+ <v-card v-for="item in staffStatusItems" :key="item.id" flat border rounded="lg" class="pa-3">
622
362
  <div class="d-flex align-start ga-3">
623
363
  <AvatarMain :name="item.name" :size="32" />
624
364
  <div class="flex-grow-1" style="min-width: 0">
625
- <div
626
- class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
627
- >
365
+ <div class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate">
628
366
  {{ item.name }}
629
367
  </div>
630
368
  <div class="text-caption text-blue-grey mt-1 text-truncate">
631
369
  {{ item.role }}
632
370
  </div>
633
371
  </div>
634
- <v-chip
635
- size="small"
636
- color="blue-grey-darken-1"
637
- variant="tonal"
638
- label
639
- class="ml-auto flex-shrink-0 font-weight-bold"
640
- >
372
+ <v-chip size="small" color="blue-grey-darken-1" variant="tonal" label
373
+ class="ml-auto flex-shrink-0 font-weight-bold">
641
374
  {{ item.totalHours || 0 }} hrs
642
375
  </v-chip>
643
376
  </div>
@@ -648,80 +381,55 @@
648
381
  </v-col>
649
382
 
650
383
  <v-col v-if="isWidgetVisible('feedbacks')" cols="12" class="d-flex">
651
- <DashboardPanel
652
- title="Feedbacks"
653
- action-label="View All"
654
- @action="goToDashboardRoute('feedbacks')"
655
- >
384
+ <DashboardPanel title="Feedbacks" action-label="View All" @action="goToDashboardRoute('feedbacks')">
656
385
  <div v-if="feedbackItems.length" class="figma-list mt-2">
657
- <div
658
- v-for="item in feedbackItems"
659
- :key="item.id"
660
- class="d-flex align-center justify-space-between"
661
- style="
386
+ <div v-for="item in feedbackItems" :key="item.id" class="d-flex align-center justify-space-between" style="
662
387
  padding: 16px 20px;
663
388
  border: 1px solid #e2e8f0;
664
389
  border-radius: 12px;
665
390
  margin-bottom: 12px;
666
391
  background-color: #ffffff;
667
392
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
668
- "
669
- >
393
+ ">
670
394
  <div style="flex: 1; min-width: 140px">
671
- <strong
672
- style="
395
+ <strong style="
673
396
  display: block;
674
397
  font-size: 15px;
675
398
  font-weight: 700;
676
399
  color: #0b2f47;
677
400
  margin-bottom: 4px;
678
- "
679
- >{{ item.title }}</strong
680
- >
401
+ ">{{ item.title }}</strong>
681
402
  <span style="font-size: 13px; color: #94a3b8">{{
682
403
  item.subtitle
683
- }}</span>
404
+ }}</span>
684
405
  </div>
685
406
 
686
- <div
687
- style="flex: 1; display: flex; align-items: center; gap: 12px"
688
- >
407
+ <div style="flex: 1; display: flex; align-items: center; gap: 12px">
689
408
  <AvatarMain :name="item.person" :size="36" />
690
409
  <span style="font-size: 14px; color: #64748b">{{
691
410
  item.person
692
- }}</span>
411
+ }}</span>
693
412
  </div>
694
413
 
695
414
  <div style="flex: 1; display: flex; justify-content: center">
696
- <div
697
- :style="getStatusPillStyle(item.status)"
698
- style="
415
+ <div :style="getStatusPillStyle(item.status)" style="
699
416
  padding: 6px 16px;
700
417
  border-radius: 20px;
701
418
  font-size: 13px;
702
419
  font-weight: 500;
703
420
  text-transform: capitalize;
704
- "
705
- >
421
+ ">
706
422
  {{ item.status || "To-Do" }}
707
423
  </div>
708
424
  </div>
709
425
 
710
426
  <div style="flex: 0 0 auto; text-align: right">
711
- <v-btn
712
- variant="outlined"
713
- class="text-none font-weight-medium"
714
- size="small"
715
- color="#3b82f6"
716
- style="
427
+ <v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="
717
428
  border-color: #e2e8f0;
718
429
  border-radius: 8px;
719
430
  padding: 0 16px;
720
431
  height: 32px;
721
- "
722
- @click="goToListItem(item, 'feedbacks')"
723
- >View</v-btn
724
- >
432
+ " @click="goToListItem(item, 'feedbacks')">View</v-btn>
725
433
  </div>
726
434
  </div>
727
435
  </div>
@@ -736,36 +444,22 @@
736
444
  <div class="dashboard-card dashboard-panel figma-panel">
737
445
  <div class="figma-panel-header">
738
446
  <h2>Upcoming Events</h2>
739
- <button
740
- class="figma-panel-action"
741
- type="button"
742
- @click="goToDashboardRoute('event-mgmt')"
743
- >
447
+ <button class="figma-panel-action" type="button" @click="goToDashboardRoute('event-mgmt')">
744
448
  View All
745
449
  </button>
746
450
  </div>
747
451
  <div class="figma-panel-body">
748
452
  <div v-if="upcomingEvents.length" class="figma-list">
749
- <div
750
- v-for="item in upcomingEvents"
751
- :key="item.id"
752
- class="figma-list-row"
753
- >
754
- <span
755
- :class="[
756
- 'figma-event-dot',
757
- `figma-event-dot-${getListTone(item)}`,
758
- ]"
759
- />
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
+ ]" />
760
458
  <div class="figma-list-main">
761
459
  <strong>{{ item.title }}</strong>
762
460
  <p v-if="item.subtitle">{{ item.subtitle }}</p>
763
461
  </div>
764
- <button
765
- class="figma-row-action"
766
- type="button"
767
- @click="navigateTo(dashboardPath('event-mgmt'))"
768
- >
462
+ <button class="figma-row-action" type="button" @click="navigateTo(dashboardPath('event-mgmt'))">
769
463
  {{ item.actionLabel || "View" }}
770
464
  </button>
771
465
  </div>
@@ -779,38 +473,24 @@
779
473
  <div class="dashboard-card dashboard-panel figma-panel">
780
474
  <div class="figma-panel-header">
781
475
  <h2>Today's Reminder</h2>
782
- <button
783
- class="figma-panel-action"
784
- type="button"
785
- @click="goToDashboardRoute('calendar')"
786
- >
476
+ <button class="figma-panel-action" type="button" @click="goToDashboardRoute('calendar')">
787
477
  View All
788
478
  </button>
789
479
  </div>
790
480
  <div class="figma-panel-body">
791
481
  <div v-if="todayReminders.length" class="figma-list">
792
- <div
793
- v-for="item in todayReminders"
794
- :key="item.id"
795
- class="figma-list-row"
796
- >
797
- <span
798
- :class="[
799
- 'figma-icon-badge',
800
- `figma-icon-badge-${getListTone(item)}`,
801
- ]"
802
- >
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
+ ]">
803
487
  <v-icon :icon="item.icon || 'mdi-alert'" size="15" />
804
488
  </span>
805
489
  <div class="figma-list-main">
806
490
  <strong>{{ item.title }}</strong>
807
491
  <p v-if="item.subtitle">{{ item.subtitle }}</p>
808
492
  </div>
809
- <button
810
- class="figma-row-action"
811
- type="button"
812
- @click="navigateTo(dashboardPath('calendar'))"
813
- >
493
+ <button class="figma-row-action" type="button" @click="navigateTo(dashboardPath('calendar'))">
814
494
  {{ item.actionLabel || "View" }}
815
495
  </button>
816
496
  </div>
@@ -824,43 +504,29 @@
824
504
  <div class="dashboard-card dashboard-panel figma-panel">
825
505
  <div class="figma-panel-header">
826
506
  <h2>Today's Attentions</h2>
827
-
507
+
828
508
  </div>
829
509
  <div class="figma-panel-body">
830
510
  <div v-if="todayAttentions.length" class="figma-list">
831
- <div
832
- v-for="item in todayAttentions"
833
- :key="item.id"
834
- class="figma-list-row"
835
- >
836
- <span
837
- :class="[
838
- 'figma-icon-badge',
839
- `figma-icon-badge-${getListTone(item)}`,
840
- ]"
841
- >
842
- <v-icon
843
- :icon="item.icon || 'mdi-alert-circle-outline'"
844
- size="15"
845
- />
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" />
846
517
  </span>
847
518
  <div class="figma-list-main">
848
519
  <strong>{{ item.title }}</strong>
849
520
  <p v-if="item.subtitle">{{ item.subtitle }}</p>
850
521
  </div>
851
- <button
852
- class="figma-row-action"
853
- type="button"
854
- @click="
855
- navigateTo(
856
- item.type === 'facility-booking'
857
- ? `${dashboardPath('facility/bookings')}?booking=${
858
- item.id
859
- }`
860
- : dashboardPath(`incident-reports/${item.id}`)
861
- )
862
- "
863
- >
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
+ ">
864
530
  {{ item.actionLabel || "Review" }}
865
531
  </button>
866
532
  </div>
@@ -874,30 +540,19 @@
874
540
  <div class="dashboard-card dashboard-panel figma-panel">
875
541
  <div class="figma-panel-header">
876
542
  <h2>Work Order Status</h2>
877
- <button
878
- class="figma-panel-action"
879
- type="button"
880
- @click="goToDashboardRoute('work-orders')"
881
- >
543
+ <button class="figma-panel-action" type="button" @click="goToDashboardRoute('work-orders')">
882
544
  View All
883
545
  </button>
884
546
  </div>
885
547
  <div class="figma-panel-body">
886
548
  <div v-if="workOrderStatus.length" class="figma-status-stack">
887
- <div
888
- v-for="item in workOrderStatus"
889
- :key="item.label"
890
- class="figma-status-row"
891
- >
549
+ <div v-for="item in workOrderStatus" :key="item.label" class="figma-status-row">
892
550
  <div class="figma-status-label">
893
551
  <span>{{ item.label }}</span>
894
552
  <strong>{{ item.value }}</strong>
895
553
  </div>
896
554
  <div class="figma-progress-track">
897
- <span
898
- class="figma-progress-fill"
899
- :style="getStatusProgressStyle(item)"
900
- />
555
+ <span class="figma-progress-fill" :style="getStatusProgressStyle(item)" />
901
556
  </div>
902
557
  </div>
903
558
  </div>
@@ -908,17 +563,9 @@
908
563
 
909
564
  <!-- Property feedbacks -->
910
565
  <v-col v-if="isWidgetVisible('feedbacks')" cols="12">
911
- <DashboardPanel
912
- title="Feedbacks"
913
- action-label="View All"
914
- @action="goToDashboardRoute('feedbacks')"
915
- >
566
+ <DashboardPanel title="Feedbacks" action-label="View All" @action="goToDashboardRoute('feedbacks')">
916
567
  <div v-if="feedbackItems.length" class="feedback-list">
917
- <div
918
- v-for="item in feedbackItems"
919
- :key="item.id"
920
- class="feedback-row"
921
- >
568
+ <div v-for="item in feedbackItems" :key="item.id" class="feedback-row">
922
569
  <div class="feedback-summary">
923
570
  <strong>{{ item.title }}</strong>
924
571
  <p>{{ item.subtitle }}</p>
@@ -927,24 +574,18 @@
927
574
  <div class="feedback-person">
928
575
  <span class="feedback-avatar">{{
929
576
  getInitials(item.person)
930
- }}</span>
577
+ }}</span>
931
578
  <span>{{ item.person }}</span>
932
579
  </div>
933
580
 
934
- <span
935
- :class="[
936
- 'feedback-status-pill',
937
- `feedback-status-pill-${getFeedbackTone(item.status)}`,
938
- ]"
939
- >
581
+ <span :class="[
582
+ 'feedback-status-pill',
583
+ `feedback-status-pill-${getFeedbackTone(item.status)}`,
584
+ ]">
940
585
  {{ item.status }}
941
586
  </span>
942
587
 
943
- <button
944
- class="feedback-view-button"
945
- type="button"
946
- @click="goToListItem(item, 'feedbacks')"
947
- >
588
+ <button class="feedback-view-button" type="button" @click="goToListItem(item, 'feedbacks')">
948
589
  View
949
590
  </button>
950
591
  </div>
@@ -960,90 +601,60 @@
960
601
  <div class="dashboard-card dashboard-panel figma-panel">
961
602
  <div class="figma-panel-header">
962
603
  <h2>ACTIVE PATROL</h2>
963
- <button
964
- class="figma-panel-action"
965
- type="button"
966
- @click="goToDashboardRoute('virtual-patrol')"
967
- >
604
+ <button class="figma-panel-action" type="button" @click="goToDashboardRoute('virtual-patrol')">
968
605
  View All
969
606
  </button>
970
607
  </div>
971
608
  <div class="figma-panel-body">
972
- <div
973
- v-if="formattedActivePatrolItems.length"
974
- class="figma-list mt-2"
975
- >
976
- <div
977
- v-for="item in formattedActivePatrolItems"
978
- :key="item.id || item._id"
979
- class="d-flex align-center justify-space-between"
980
- style="
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="
981
612
  padding: 16px 20px;
982
613
  border: 1px solid #e2e8f0;
983
614
  border-radius: 12px;
984
615
  margin-bottom: 12px;
985
616
  background-color: #ffffff;
986
617
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
987
- "
988
- >
618
+ ">
989
619
  <div style="flex: 1; min-width: 140px">
990
- <strong
991
- style="
620
+ <strong style="
992
621
  display: block;
993
622
  font-size: 15px;
994
623
  font-weight: 700;
995
624
  color: #0b2f47;
996
625
  margin-bottom: 4px;
997
- "
998
- >{{ item.title || item.name }}</strong
999
- >
626
+ ">{{ item.title || item.name }}</strong>
1000
627
  <span style="font-size: 13px; color: #94a3b8">{{
1001
628
  item.subtitle || item.location || ""
1002
- }}</span>
629
+ }}</span>
1003
630
  </div>
1004
631
 
1005
- <div
1006
- style="flex: 1; display: flex; align-items: center; gap: 12px"
1007
- >
1008
- <AvatarMain
1009
- :name="item.person || item.assignedTo"
1010
- :size="36"
1011
- />
632
+ <div style="flex: 1; display: flex; align-items: center; gap: 12px">
633
+ <AvatarMain :name="item.person || item.assignedTo" :size="36" />
1012
634
  <span style="font-size: 14px; color: #64748b">{{
1013
635
  item.person || item.assignedTo
1014
- }}</span>
636
+ }}</span>
1015
637
  </div>
1016
638
 
1017
639
  <div style="flex: 1; display: flex; justify-content: center">
1018
- <div
1019
- :style="getPatrolStatusStyle(item.status)"
1020
- style="
640
+ <div :style="getPatrolStatusStyle(item.status)" style="
1021
641
  padding: 6px 16px;
1022
642
  border-radius: 20px;
1023
643
  font-size: 13px;
1024
644
  font-weight: 500;
1025
645
  text-transform: capitalize;
1026
- "
1027
- >
646
+ ">
1028
647
  {{ item.status }}
1029
648
  </div>
1030
649
  </div>
1031
650
 
1032
651
  <div style="flex: 0 0 auto; text-align: right">
1033
- <v-btn
1034
- variant="outlined"
1035
- class="text-none font-weight-medium"
1036
- size="small"
1037
- color="#3b82f6"
1038
- style="
652
+ <v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="
1039
653
  border-color: #e2e8f0;
1040
654
  border-radius: 8px;
1041
655
  padding: 0 16px;
1042
656
  height: 32px;
1043
- "
1044
- @click="goToDashboardRoute('virtual-patrol')"
1045
- >View</v-btn
1046
- >
657
+ " @click="goToDashboardRoute('virtual-patrol')">View</v-btn>
1047
658
  </div>
1048
659
  </div>
1049
660
  </div>
@@ -1056,68 +667,50 @@
1056
667
  <div class="dashboard-card dashboard-panel figma-panel">
1057
668
  <div class="figma-panel-header">
1058
669
  <h2>STAFF STATUS</h2>
1059
- <button
1060
- class="figma-panel-action"
1061
- type="button"
1062
- @click="goToDashboardRoute('attendance')"
1063
- >
670
+ <button class="figma-panel-action" type="button" @click="goToDashboardRoute('attendance')">
1064
671
  View All
1065
672
  </button>
1066
673
  </div>
1067
674
  <div class="figma-panel-body">
1068
675
  <div v-if="securityStaffItems.length" class="figma-list mt-2">
1069
- <div
1070
- v-for="item in securityStaffItems"
1071
- :key="item._id || item.id"
1072
- class="d-flex align-center justify-space-between"
1073
- style="
676
+ <div v-for="item in securityStaffItems" :key="item._id || item.id"
677
+ class="d-flex align-center justify-space-between" style="
1074
678
  padding: 16px 20px;
1075
679
  border: 1px solid #e2e8f0;
1076
680
  border-radius: 12px;
1077
681
  margin-bottom: 12px;
1078
682
  background-color: #ffffff;
1079
683
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
1080
- "
1081
- >
684
+ ">
1082
685
  <div style="display: flex; align-items: center; gap: 16px">
1083
- <AvatarMain
1084
- :name="
1085
- item.personName ||
1086
- item.employeeName ||
1087
- item.userName ||
1088
- 'Unassigned'
1089
- "
1090
- :size="40"
1091
- />
686
+ <AvatarMain :name="item.personName ||
687
+ item.employeeName ||
688
+ item.userName ||
689
+ 'Unassigned'
690
+ " :size="40" />
1092
691
  <div>
1093
- <strong
1094
- style="
692
+ <strong style="
1095
693
  display: block;
1096
694
  font-size: 15px;
1097
695
  font-weight: 700;
1098
696
  color: #0b2f47;
1099
697
  margin-bottom: 4px;
1100
- "
1101
- >{{
698
+ ">{{
1102
699
  item.userName || item.personName || "Unassigned"
1103
- }}</strong
1104
- >
700
+ }}</strong>
1105
701
  <span style="font-size: 13px; color: #94a3b8">{{
1106
702
  item.role || item.designation || "Officer"
1107
- }}</span>
703
+ }}</span>
1108
704
  </div>
1109
705
  </div>
1110
706
 
1111
707
  <div style="flex: 0 0 auto">
1112
- <div
1113
- :style="getStaffStatusPillStyle(item.status)"
1114
- style="
708
+ <div :style="getStaffStatusPillStyle(item.status)" style="
1115
709
  padding: 6px 16px;
1116
710
  border-radius: 20px;
1117
711
  font-size: 13px;
1118
712
  font-weight: 500;
1119
- "
1120
- >
713
+ ">
1121
714
  {{ item.status || "On duty" }}
1122
715
  </div>
1123
716
  </div>
@@ -1130,80 +723,55 @@
1130
723
 
1131
724
  <!-- Security feedbacks -->
1132
725
  <v-col v-if="isWidgetVisible('feedbacks')" cols="12">
1133
- <DashboardPanel
1134
- title="Feedbacks"
1135
- action-label="View All"
1136
- @action="goToDashboardRoute('feedbacks')"
1137
- >
726
+ <DashboardPanel title="Feedbacks" action-label="View All" @action="goToDashboardRoute('feedbacks')">
1138
727
  <div v-if="feedbackItems.length" class="figma-list mt-2">
1139
- <div
1140
- v-for="item in feedbackItems"
1141
- :key="item.id"
1142
- class="d-flex align-center justify-space-between"
1143
- style="
728
+ <div v-for="item in feedbackItems" :key="item.id" class="d-flex align-center justify-space-between" style="
1144
729
  padding: 16px 20px;
1145
730
  border: 1px solid #e2e8f0;
1146
731
  border-radius: 12px;
1147
732
  margin-bottom: 12px;
1148
733
  background-color: #ffffff;
1149
734
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
1150
- "
1151
- >
735
+ ">
1152
736
  <div style="flex: 1; min-width: 140px">
1153
- <strong
1154
- style="
737
+ <strong style="
1155
738
  display: block;
1156
739
  font-size: 15px;
1157
740
  font-weight: 700;
1158
741
  color: #0b2f47;
1159
742
  margin-bottom: 4px;
1160
- "
1161
- >{{ item.title }}</strong
1162
- >
743
+ ">{{ item.title }}</strong>
1163
744
  <span style="font-size: 13px; color: #94a3b8">{{
1164
745
  item.subtitle
1165
- }}</span>
746
+ }}</span>
1166
747
  </div>
1167
748
 
1168
- <div
1169
- style="flex: 1; display: flex; align-items: center; gap: 12px"
1170
- >
749
+ <div style="flex: 1; display: flex; align-items: center; gap: 12px">
1171
750
  <AvatarMain :name="item.person" :size="36" />
1172
751
  <span style="font-size: 14px; color: #64748b">{{
1173
752
  item.person
1174
- }}</span>
753
+ }}</span>
1175
754
  </div>
1176
755
 
1177
756
  <div style="flex: 1; display: flex; justify-content: center">
1178
- <div
1179
- :style="getStatusPillStyle(item.status)"
1180
- style="
757
+ <div :style="getStatusPillStyle(item.status)" style="
1181
758
  padding: 6px 16px;
1182
759
  border-radius: 20px;
1183
760
  font-size: 13px;
1184
761
  font-weight: 500;
1185
762
  text-transform: capitalize;
1186
- "
1187
- >
763
+ ">
1188
764
  {{ item.status || "To-Do" }}
1189
765
  </div>
1190
766
  </div>
1191
767
 
1192
768
  <div style="flex: 0 0 auto; text-align: right">
1193
- <v-btn
1194
- variant="outlined"
1195
- class="text-none font-weight-medium"
1196
- size="small"
1197
- color="#3b82f6"
1198
- style="
769
+ <v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="
1199
770
  border-color: #e2e8f0;
1200
771
  border-radius: 8px;
1201
772
  padding: 0 16px;
1202
773
  height: 32px;
1203
- "
1204
- @click="goToListItem(item, 'feedbacks')"
1205
- >View</v-btn
1206
- >
774
+ " @click="goToListItem(item, 'feedbacks')">View</v-btn>
1207
775
  </div>
1208
776
  </div>
1209
777
  </div>
@@ -1215,9 +783,7 @@
1215
783
  <!-- ── Customize Dialog ───────────────────────────────────────────────── -->
1216
784
  <v-dialog v-model="customizeDialog" max-width="720" persistent scrollable>
1217
785
  <v-card rounded="lg">
1218
- <v-card-title
1219
- class="d-flex align-start justify-space-between pa-6 pb-4 border-b"
1220
- >
786
+ <v-card-title class="d-flex align-start justify-space-between pa-6 pb-4 border-b">
1221
787
  <div>
1222
788
  <div class="text-h6 font-weight-black text-blue-grey-darken-4">
1223
789
  Customize Dashboard
@@ -1226,103 +792,54 @@
1226
792
  Select which widgets to display on your dashboard
1227
793
  </div>
1228
794
  </div>
1229
- <v-btn
1230
- icon="mdi-close"
1231
- variant="text"
1232
- density="comfortable"
1233
- aria-label="Close customize dashboard"
1234
- @click="cancelCustomize"
1235
- />
795
+ <v-btn icon="mdi-close" variant="text" density="comfortable" aria-label="Close customize dashboard"
796
+ @click="cancelCustomize" />
1236
797
  </v-card-title>
1237
798
 
1238
799
  <v-card-text class="pa-6">
1239
- <v-text-field
1240
- v-model="widgetSearch"
1241
- placeholder="Search widgets..."
1242
- prepend-inner-icon="mdi-magnify"
1243
- variant="outlined"
1244
- density="compact"
1245
- hide-details
1246
- />
800
+ <v-text-field v-model="widgetSearch" placeholder="Search widgets..." prepend-inner-icon="mdi-magnify"
801
+ variant="outlined" density="compact" hide-details />
1247
802
 
1248
803
  <div class="d-flex justify-end mt-3 mb-1">
1249
- <v-btn
1250
- variant="text"
1251
- color="blue-grey-darken-4"
1252
- size="small"
1253
- class="text-none font-weight-bold"
1254
- prepend-icon="mdi-refresh"
1255
- @click="resetCustomizeDraft"
1256
- >
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
806
  Reset to default
1258
807
  </v-btn>
1259
808
  </div>
1260
809
 
1261
810
  <div class="d-flex flex-column ga-3">
1262
- <v-card
1263
- v-for="widget in filteredCustomizeWidgets"
1264
- :key="widget.key"
1265
- flat
1266
- border
1267
- rounded="lg"
1268
- class="pa-4"
1269
- >
811
+ <v-card v-for="widget in filteredCustomizeWidgets" :key="widget.key" flat border rounded="lg" class="pa-4">
1270
812
  <div class="d-flex align-center justify-space-between ga-4">
1271
813
  <div>
1272
- <div
1273
- class="text-body-2 font-weight-black text-blue-grey-darken-4"
1274
- >
814
+ <div class="text-body-2 font-weight-black text-blue-grey-darken-4">
1275
815
  {{ widget.title }}
1276
816
  </div>
1277
817
  <div class="text-caption text-blue-grey mt-1">
1278
818
  {{ widget.description }}
1279
819
  </div>
1280
820
  </div>
1281
- <v-switch
1282
- v-model="draftVisibleWidgetKeys"
1283
- :value="widget.key"
1284
- color="blue-grey-darken-4"
1285
- inset
1286
- hide-details
1287
- density="compact"
1288
- aria-label="Toggle dashboard widget"
1289
- />
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" />
1290
823
  </div>
1291
824
  </v-card>
1292
825
 
1293
- <DashboardEmptyState
1294
- v-if="!filteredCustomizeWidgets.length"
1295
- compact
1296
- />
826
+ <DashboardEmptyState v-if="!filteredCustomizeWidgets.length" compact />
1297
827
  </div>
1298
828
  </v-card-text>
1299
829
 
1300
830
  <v-card-actions class="pa-6 pt-4 border-t justify-end ga-3">
1301
- <v-btn
1302
- variant="text"
1303
- class="text-none font-weight-bold text-blue-grey"
1304
- @click="cancelCustomize"
1305
- >
831
+ <v-btn variant="text" class="text-none font-weight-bold text-blue-grey" @click="cancelCustomize">
1306
832
  Cancel
1307
833
  </v-btn>
1308
- <v-btn
1309
- variant="flat"
1310
- color="blue-grey-darken-4"
1311
- class="text-none font-weight-bold px-6"
1312
- style="min-width: 110px"
1313
- @click="submitCustomize"
1314
- >
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">
1315
836
  Submit
1316
837
  </v-btn>
1317
838
  </v-card-actions>
1318
839
  </v-card>
1319
840
  </v-dialog>
1320
841
 
1321
- <v-snackbar
1322
- v-model="snackbar.visible"
1323
- :timeout="2200"
1324
- :color="snackbar.color || 'primary'"
1325
- >
842
+ <v-snackbar v-model="snackbar.visible" :timeout="2200" :color="snackbar.color || 'primary'">
1326
843
  {{ snackbar.text }}
1327
844
  </v-snackbar>
1328
845
  </section>
@@ -1606,10 +1123,10 @@ const { data: getDashboardReq, pending: loadingMetrics } =
1606
1123
  () =>
1607
1124
  props.serviceType && isHygieneMode.value
1608
1125
  ? getDashboardHygiene(
1609
- props.site,
1610
- currentPeriod.value,
1611
- props.serviceType as TServiceType
1612
- )
1126
+ props.site,
1127
+ currentPeriod.value,
1128
+ props.serviceType as TServiceType
1129
+ )
1613
1130
  : Promise.resolve(null),
1614
1131
  {
1615
1132
  watch: [() => props.site, currentPeriod, () => props.serviceType],
@@ -1623,13 +1140,13 @@ const { data: getDashboardAltReq, pending: loadingAltMetrics } =
1623
1140
  () =>
1624
1141
  props.site && (isPropertyMode.value || isSecurityMode.value)
1625
1142
  ? getDashboard(
1626
- props.site,
1627
- props.serviceType as TServiceType,
1628
- currentPeriod.value,
1629
- currentVisitorTypeParam.value
1630
- ? { type: currentVisitorTypeParam.value }
1631
- : {}
1632
- )
1143
+ props.site,
1144
+ props.serviceType as TServiceType,
1145
+ currentPeriod.value,
1146
+ currentVisitorTypeParam.value
1147
+ ? { type: currentVisitorTypeParam.value }
1148
+ : {}
1149
+ )
1633
1150
  : Promise.resolve(null),
1634
1151
  {
1635
1152
  watch: [
@@ -1648,10 +1165,10 @@ const { data: weeklyActivityReq, pending: loadingWeeklyActivity } =
1648
1165
  () =>
1649
1166
  props.serviceType
1650
1167
  ? getDashboardWeeklyActivity(
1651
- props.site,
1652
- props.serviceType as TServiceType,
1653
- currentPeriod.value
1654
- )
1168
+ props.site,
1169
+ props.serviceType as TServiceType,
1170
+ currentPeriod.value
1171
+ )
1655
1172
  : Promise.resolve(null),
1656
1173
  {
1657
1174
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1665,10 +1182,10 @@ const { data: taskScheduleReq, pending: loadingTaskSchedule } =
1665
1182
  () =>
1666
1183
  props.serviceType && isHygieneMode.value
1667
1184
  ? getDashboardHygieneTaskSchedule(
1668
- props.site,
1669
- props.serviceType as TServiceType,
1670
- currentPeriod.value
1671
- )
1185
+ props.site,
1186
+ props.serviceType as TServiceType,
1187
+ currentPeriod.value
1188
+ )
1672
1189
  : Promise.resolve(null),
1673
1190
  {
1674
1191
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1682,10 +1199,10 @@ const { data: attendanceReq, pending: loadingAttendance } =
1682
1199
  () =>
1683
1200
  props.serviceType && !isPropertyMode.value
1684
1201
  ? getDashboardHygieneAttendance(
1685
- props.site,
1686
- props.serviceType as TServiceType,
1687
- currentPeriod.value
1688
- )
1202
+ props.site,
1203
+ props.serviceType as TServiceType,
1204
+ currentPeriod.value
1205
+ )
1689
1206
  : Promise.resolve(null),
1690
1207
  {
1691
1208
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -1699,10 +1216,10 @@ const { data: feedbacksReq, pending: loadingFeedbacks } =
1699
1216
  () =>
1700
1217
  props.serviceType
1701
1218
  ? getDashboardHygieneFeedbacks(
1702
- props.site,
1703
- props.serviceType as TServiceType,
1704
- currentPeriod.value
1705
- )
1219
+ props.site,
1220
+ props.serviceType as TServiceType,
1221
+ currentPeriod.value
1222
+ )
1706
1223
  : Promise.resolve(null),
1707
1224
  {
1708
1225
  watch: [() => props.site, () => props.serviceType, currentPeriod],
@@ -2300,10 +1817,10 @@ function computeFacilityMetric(): DashboardMetric {
2300
1817
  const items = facilityBookingItems.value.filter((item: any) => {
2301
1818
  const val = String(
2302
1819
  item.facilityName ||
2303
- item.facility?.name ||
2304
- item.facilityInfo?.name ||
2305
- item.name ||
2306
- ""
1820
+ item.facility?.name ||
1821
+ item.facilityInfo?.name ||
1822
+ item.name ||
1823
+ ""
2307
1824
  ).toLowerCase();
2308
1825
  return val === selected.toLowerCase();
2309
1826
  });
@@ -2450,21 +1967,21 @@ const dashboardTitle = computed(() => {
2450
1967
  const primaryListConfig = computed(() =>
2451
1968
  isSecurityMode.value
2452
1969
  ? {
2453
- title: "Active Patrol",
2454
- route: "virtualPatrol",
2455
- keys: ["activePatrolItems", "patrolItems"],
2456
- }
1970
+ title: "Active Patrol",
1971
+ route: "virtualPatrol",
1972
+ keys: ["activePatrolItems", "patrolItems"],
1973
+ }
2457
1974
  : {
2458
- title: "Today's Task Schedule",
2459
- route: "scheduleTask",
2460
- keys: [
2461
- "taskScheduleItems",
2462
- "scheduleTaskItems",
2463
- "scheduledTaskItems",
2464
- "cleaningScheduleItems",
2465
- "tasks",
2466
- ],
2467
- }
1975
+ title: "Today's Task Schedule",
1976
+ route: "scheduleTask",
1977
+ keys: [
1978
+ "taskScheduleItems",
1979
+ "scheduleTaskItems",
1980
+ "scheduledTaskItems",
1981
+ "cleaningScheduleItems",
1982
+ "tasks",
1983
+ ],
1984
+ }
2468
1985
  );
2469
1986
 
2470
1987
  // ─── Activity Chart ───────────────────────────────────────────────────────────
@@ -2845,8 +2362,7 @@ function getMetricMeta(metric: DashboardMetric) {
2845
2362
  parts.push(`${formatNumber(toNumber(metric.ongoing))} ongoing`);
2846
2363
  if (typeof metric.waitingApproval !== "undefined")
2847
2364
  parts.push(
2848
- `${formatNumber(toNumber(metric.waitingApproval))} ${
2849
- isPropertyMode.value ? "for approval" : "waiting approval"
2365
+ `${formatNumber(toNumber(metric.waitingApproval))} ${isPropertyMode.value ? "for approval" : "waiting approval"
2850
2366
  }`
2851
2367
  );
2852
2368
  if (typeof metric.lowStock !== "undefined")
@@ -2873,9 +2389,9 @@ function getFilteredVisitorMetric(): DashboardMetric {
2873
2389
  count: toNumber(metric.count ?? items.length),
2874
2390
  currentlyOnSite: toNumber(
2875
2391
  metric.currentlyOnSite ??
2876
- items.filter(
2877
- (item: any) => !isClosedStatus(item.status) && !item.checkOut
2878
- ).length
2392
+ items.filter(
2393
+ (item: any) => !isClosedStatus(item.status) && !item.checkOut
2394
+ ).length
2879
2395
  ),
2880
2396
  };
2881
2397
  }
@@ -2971,9 +2487,8 @@ function getListIcon(context: string) {
2971
2487
 
2972
2488
  function getListTone(item: DashboardListItem) {
2973
2489
  if (item.tone) return item.tone;
2974
- const text = `${item.title} ${item.subtitle || ""} ${item.icon || ""} ${
2975
- item.color || ""
2976
- }`.toLowerCase();
2490
+ const text = `${item.title} ${item.subtitle || ""} ${item.icon || ""} ${item.color || ""
2491
+ }`.toLowerCase();
2977
2492
  if (
2978
2493
  text.includes("alert") ||
2979
2494
  text.includes("incident") ||
@@ -3065,10 +2580,10 @@ function normalizeStaffItem(
3065
2580
  role,
3066
2581
  status: normalizeStatus(
3067
2582
  item.statusLabel ||
3068
- item.status ||
3069
- item.currentStatus ||
3070
- item.attendanceStatus ||
3071
- "Off duty"
2583
+ item.status ||
2584
+ item.currentStatus ||
2585
+ item.attendanceStatus ||
2586
+ "Off duty"
3072
2587
  ),
3073
2588
  totalHours: item.totalHours || 0,
3074
2589
  raw: item,
@@ -3419,8 +2934,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3419
2934
  selectedRange.value === "Today"
3420
2935
  ? "Today"
3421
2936
  : selectedRange.value === "This Week"
3422
- ? "This Week"
3423
- : "This Month";
2937
+ ? "This Week"
2938
+ : "This Month";
3424
2939
 
3425
2940
  const reportTitle = `${props.title || dashboardTitle.value} - ${periodLabel}`;
3426
2941
 
@@ -3502,11 +3017,10 @@ async function exportDashboard(format: "PDF" | "Word") {
3502
3017
  <td>
3503
3018
  <div class="kpi-title">${card.title}</div>
3504
3019
  ${kpiValueHtml}
3505
- <div class="kpi-meta">${card.meta || ""} ${
3506
- card.percentage
3020
+ <div class="kpi-meta">${card.meta || ""} ${card.percentage
3507
3021
  ? `(${card.percentage >= 0 ? "+" : ""}${card.percentage}%)`
3508
3022
  : ""
3509
- }</div>
3023
+ }</div>
3510
3024
  </td>
3511
3025
  `;
3512
3026
  });
@@ -3552,8 +3066,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3552
3066
  <td>${item.person || ""}</td>
3553
3067
  <td>${item.time || ""}</td>
3554
3068
  <td><span class="status-badge badge-${getStatusTone(
3555
- item.status
3556
- )}">${item.status || ""}</span></td>
3069
+ item.status
3070
+ )}">${item.status || ""}</span></td>
3557
3071
  </tr>`;
3558
3072
  });
3559
3073
  } else {
@@ -3651,8 +3165,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3651
3165
  <td>${item.subtitle || item.location || ""}</td>
3652
3166
  <td>${item.person || item.assignedTo || ""}</td>
3653
3167
  <td><span class="status-badge badge-${getStatusTone(
3654
- item.status
3655
- )}">${item.status || ""}</span></td>
3168
+ item.status
3169
+ )}">${item.status || ""}</span></td>
3656
3170
  </tr>`;
3657
3171
  });
3658
3172
  } else {
@@ -3666,8 +3180,7 @@ async function exportDashboard(format: "PDF" | "Word") {
3666
3180
  if (securityStaffItems.value.length > 0) {
3667
3181
  securityStaffItems.value.forEach((item) => {
3668
3182
  wordHtml += `<tr>
3669
- <td><strong>${
3670
- item.userName || item.personName || "Unassigned"
3183
+ <td><strong>${item.userName || item.personName || "Unassigned"
3671
3184
  }</strong></td>
3672
3185
  <td>${item.role || item.designation || "Officer"}</td>
3673
3186
  <td><span class="status-badge badge-${getStatusTone(
@@ -3691,8 +3204,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3691
3204
  <td>${item.subtitle || ""}</td>
3692
3205
  <td>${item.person || ""}</td>
3693
3206
  <td><span class="status-badge badge-${getStatusTone(
3694
- item.status
3695
- )}">${item.status || "To-Do"}</span></td>
3207
+ item.status
3208
+ )}">${item.status || "To-Do"}</span></td>
3696
3209
  </tr>`;
3697
3210
  });
3698
3211
  } else {
@@ -3722,9 +3235,8 @@ async function exportDashboard(format: "PDF" | "Word") {
3722
3235
  snackbar.visible = true;
3723
3236
  }
3724
3237
  } catch (err: any) {
3725
- snackbar.text = `Export failed: ${
3726
- err?.message || "Unknown error"
3727
- }. Please try again.`;
3238
+ snackbar.text = `Export failed: ${err?.message || "Unknown error"
3239
+ }. Please try again.`;
3728
3240
  snackbar.color = "error";
3729
3241
  snackbar.visible = true;
3730
3242
  } finally {
@@ -4055,7 +3567,7 @@ function formatDashboardTime(value?: string) {
4055
3567
  margin-top: 14px;
4056
3568
  }
4057
3569
 
4058
- .dashboard-section-grid > .v-col {
3570
+ .dashboard-section-grid>.v-col {
4059
3571
  display: flex;
4060
3572
  }
4061
3573
 
@@ -4209,18 +3721,22 @@ function formatDashboardTime(value?: string) {
4209
3721
  background: #ffe0de;
4210
3722
  color: #d72f2f;
4211
3723
  }
3724
+
4212
3725
  .figma-icon-badge-info {
4213
3726
  background: #e8e2ff;
4214
3727
  color: #6d4bd8;
4215
3728
  }
3729
+
4216
3730
  .figma-icon-badge-success {
4217
3731
  background: #ddf7e6;
4218
3732
  color: #229852;
4219
3733
  }
3734
+
4220
3735
  .figma-icon-badge-warning {
4221
3736
  background: #ffeed4;
4222
3737
  color: #c57916;
4223
3738
  }
3739
+
4224
3740
  .figma-icon-badge-neutral {
4225
3741
  background: #edf2f6;
4226
3742
  color: #607386;
@@ -4232,8 +3748,7 @@ function formatDashboardTime(value?: string) {
4232
3748
  padding: 6px 2px 0;
4233
3749
  }
4234
3750
 
4235
- .figma-status-row {
4236
- }
3751
+ .figma-status-row {}
4237
3752
 
4238
3753
  .figma-status-label {
4239
3754
  align-items: center;
@@ -4356,14 +3871,17 @@ function formatDashboardTime(value?: string) {
4356
3871
  background: #72e3a1;
4357
3872
  color: #0f5132;
4358
3873
  }
3874
+
4359
3875
  .feedback-status-pill-progress {
4360
3876
  background: #fde68a;
4361
3877
  color: #92400e;
4362
3878
  }
3879
+
4363
3880
  .feedback-status-pill-danger {
4364
3881
  background: #fecaca;
4365
3882
  color: #b91c1c;
4366
3883
  }
3884
+
4367
3885
  .feedback-status-pill-todo {
4368
3886
  background: #e2e8f0;
4369
3887
  color: #64748b;