@7365admin1/layer-common 3.0.20 → 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.
- package/CHANGELOG.md +12 -0
- package/components/DashboardMain.vue +300 -782
- package/components/EquipmentItemMain.vue +151 -47
- package/components/EquipmentManagementMain.vue +8 -1
- package/components/HidIntercomManagement.vue +1890 -0
- package/components/HygieneUpdateMoreAction.vue +7 -2
- package/components/InvitationMain.vue +32 -3
- package/components/MemberMain.vue +38 -7
- package/components/Nfc/NFCPatrolReportMain.vue +3 -25
- package/components/ScheduleTaskMain.vue +47 -12
- package/components/UnitMain.vue +8 -1
- package/components/VisitorForm.vue +2 -0
- package/components/VisitorManagement.vue +23 -0
- package/components/VisitorSocketPopUp.vue +312 -0
- package/composables/useHidAmico.ts +22 -0
- package/composables/useHidNavigation.ts +7 -0
- package/composables/useVisitor.ts +3 -0
- package/composables/useVisitorSocket.ts +25 -0
- package/package.json +2 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +24 -0
- package/types/verification.d.ts +6 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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="
|
|
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="
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
387
|
-
<v-chip
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
:
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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="
|
|
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="
|
|
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
|
-
|
|
538
|
-
|
|
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
|
-
• </template
|
|
567
|
-
>{{ item.time }}
|
|
334
|
+
• </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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
604
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
item.
|
|
857
|
-
|
|
858
|
-
|
|
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
|
-
|
|
577
|
+
}}</span>
|
|
931
578
|
<span>{{ item.person }}</span>
|
|
932
579
|
</div>
|
|
933
580
|
|
|
934
|
-
<span
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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-
|
|
974
|
-
|
|
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
|
-
|
|
629
|
+
}}</span>
|
|
1003
630
|
</div>
|
|
1004
631
|
|
|
1005
|
-
<div
|
|
1006
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
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
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
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
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
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
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
1970
|
+
title: "Active Patrol",
|
|
1971
|
+
route: "virtualPatrol",
|
|
1972
|
+
keys: ["activePatrolItems", "patrolItems"],
|
|
1973
|
+
}
|
|
2457
1974
|
: {
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
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
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
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
|
-
|
|
3423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3556
|
-
|
|
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
|
-
|
|
3655
|
-
|
|
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
|
-
|
|
3695
|
-
|
|
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
|
-
|
|
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
|
|
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;
|