@iservice365/layer-common 1.5.6 → 1.6.0
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/AccessManagement.vue +136 -0
- package/components/DashboardPlaceholder.vue +91 -130
- package/components/DocumentForm.vue +67 -3
- package/components/DocumentManagement.vue +188 -4
- package/components/Editor.vue +59 -25
- package/components/Input/InputPhoneNumberV2.vue +1 -0
- package/components/SupplyManagement.vue +1 -1
- package/composables/useDashboardData.ts +425 -0
- package/composables/useDocument.ts +33 -13
- package/composables/useFile.ts +11 -5
- package/package.json +1 -1
- package/types/document.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9cbea14: Publish Changes in Components (CKEditor)
|
|
8
|
+
|
|
9
|
+
## 1.5.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- cec19dc: Dashboard static data - follow up release
|
|
14
|
+
|
|
3
15
|
## 1.5.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-col cols="12" class="mb-2">
|
|
4
|
+
<v-row no-gutters align="center" justify="space-between">
|
|
5
|
+
<v-btn
|
|
6
|
+
class="text-none"
|
|
7
|
+
rounded="pill"
|
|
8
|
+
variant="tonal"
|
|
9
|
+
size="large"
|
|
10
|
+
v-if="canCreate && canCreateAccessCard"
|
|
11
|
+
>
|
|
12
|
+
Add Access Card
|
|
13
|
+
</v-btn>
|
|
14
|
+
|
|
15
|
+
<v-text-field
|
|
16
|
+
v-model="searchText"
|
|
17
|
+
placeholder="Search Unit..."
|
|
18
|
+
variant="outlined"
|
|
19
|
+
density="comfortable"
|
|
20
|
+
clearable
|
|
21
|
+
hide-details
|
|
22
|
+
class="ml-2"
|
|
23
|
+
style="max-width: 250px"
|
|
24
|
+
/>
|
|
25
|
+
</v-row>
|
|
26
|
+
</v-col>
|
|
27
|
+
<v-col cols="12">
|
|
28
|
+
<v-card
|
|
29
|
+
width="100%"
|
|
30
|
+
variant="outlined"
|
|
31
|
+
border="thin"
|
|
32
|
+
rounded="lg"
|
|
33
|
+
:loading="loading"
|
|
34
|
+
>
|
|
35
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
36
|
+
<template #prepend>
|
|
37
|
+
<v-btn fab icon density="comfortable">
|
|
38
|
+
<v-icon>mdi-refresh</v-icon>
|
|
39
|
+
</v-btn>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<template #append>
|
|
43
|
+
<v-row no-gutters justify="end" align="center">
|
|
44
|
+
<span class="mr-2 text-caption text-fontgray">
|
|
45
|
+
{{ pageRange }}
|
|
46
|
+
</span>
|
|
47
|
+
<local-pagination v-model="page" :length="pages" />
|
|
48
|
+
</v-row>
|
|
49
|
+
</template>
|
|
50
|
+
</v-toolbar>
|
|
51
|
+
<v-data-table
|
|
52
|
+
:headers="headers"
|
|
53
|
+
:items="items"
|
|
54
|
+
item-value="_id"
|
|
55
|
+
items-per-page="10"
|
|
56
|
+
fixed-header
|
|
57
|
+
hide-default-footer
|
|
58
|
+
hide-default-header
|
|
59
|
+
@click:row="(_: any, data: any) => $emit('row-click', data)"
|
|
60
|
+
style="max-height: calc(100vh - (200px))"
|
|
61
|
+
/>
|
|
62
|
+
</v-card>
|
|
63
|
+
</v-col>
|
|
64
|
+
|
|
65
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
66
|
+
</v-row>
|
|
67
|
+
</template>
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
definePageMeta({
|
|
70
|
+
middleware: ["01-auth", "02-org"],
|
|
71
|
+
memberOnly: true,
|
|
72
|
+
});
|
|
73
|
+
const props = defineProps({
|
|
74
|
+
headers: {
|
|
75
|
+
type: Array as PropType<Array<Record<string, any>>>,
|
|
76
|
+
default: () => [
|
|
77
|
+
{
|
|
78
|
+
title: "Card",
|
|
79
|
+
value: "card",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: "Unit",
|
|
83
|
+
value: "unit",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
title: "Assign",
|
|
87
|
+
value: "assign",
|
|
88
|
+
},
|
|
89
|
+
{ title: "Action", value: "action-table" },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
canCreate: {
|
|
93
|
+
type: Boolean,
|
|
94
|
+
default: true,
|
|
95
|
+
},
|
|
96
|
+
canUpdate: {
|
|
97
|
+
type: Boolean,
|
|
98
|
+
default: true,
|
|
99
|
+
},
|
|
100
|
+
canDelete: {
|
|
101
|
+
type: Boolean,
|
|
102
|
+
default: true,
|
|
103
|
+
},
|
|
104
|
+
canCreateAccessCard: {
|
|
105
|
+
type: Boolean,
|
|
106
|
+
default: true,
|
|
107
|
+
},
|
|
108
|
+
canUpdateAccessCard: {
|
|
109
|
+
type: Boolean,
|
|
110
|
+
default: true,
|
|
111
|
+
},
|
|
112
|
+
canDeleteAccessCard: {
|
|
113
|
+
type: Boolean,
|
|
114
|
+
default: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const { headerSearch } = useLocal();
|
|
119
|
+
const page = ref(1);
|
|
120
|
+
const pages = ref(0);
|
|
121
|
+
const pageRange = ref("-- - -- of --");
|
|
122
|
+
|
|
123
|
+
const message = ref("");
|
|
124
|
+
const messageSnackbar = ref(false);
|
|
125
|
+
const messageColor = ref("");
|
|
126
|
+
|
|
127
|
+
const items = ref<Array<Record<string, any>>>([]);
|
|
128
|
+
const createDialog = ref(false);
|
|
129
|
+
const editDialog = ref(false);
|
|
130
|
+
const previewDialog = ref(false);
|
|
131
|
+
const deleteLoading = ref(false);
|
|
132
|
+
const confirmDialog = ref(false);
|
|
133
|
+
const searchText = ref("");
|
|
134
|
+
|
|
135
|
+
const loading = ref(false);
|
|
136
|
+
</script>
|
|
@@ -40,9 +40,14 @@
|
|
|
40
40
|
</v-chip>
|
|
41
41
|
</v-col>
|
|
42
42
|
<v-col cols="4" class="text-right">
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
43
|
+
<div class="d-flex justify-end">
|
|
44
|
+
<div
|
|
45
|
+
class="avatar-custom"
|
|
46
|
+
:style="{ backgroundColor: card.color }"
|
|
47
|
+
>
|
|
48
|
+
<v-icon :icon="card.icon" color="white" size="28"></v-icon>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
46
51
|
</v-col>
|
|
47
52
|
</v-row>
|
|
48
53
|
<v-row no-gutters class="mt-4">
|
|
@@ -54,7 +59,7 @@
|
|
|
54
59
|
hide-details
|
|
55
60
|
variant="outlined"
|
|
56
61
|
@update:model-value="
|
|
57
|
-
|
|
62
|
+
updateCardPeriodDynamic(card.label, $event)
|
|
58
63
|
"
|
|
59
64
|
></v-select>
|
|
60
65
|
</v-col>
|
|
@@ -65,7 +70,7 @@
|
|
|
65
70
|
</v-row>
|
|
66
71
|
|
|
67
72
|
<!-- Line Chart -->
|
|
68
|
-
<v-row class="mb-4">
|
|
73
|
+
<!-- <v-row class="mb-4">
|
|
69
74
|
<v-col cols="12">
|
|
70
75
|
<v-card flat border elevation="0">
|
|
71
76
|
<v-card-title class="pa-4 pa-md-6">
|
|
@@ -97,7 +102,7 @@
|
|
|
97
102
|
style="min-width: 800px; width: 100%; height: 100%"
|
|
98
103
|
preserveAspectRatio="xMidYMid meet"
|
|
99
104
|
>
|
|
100
|
-
|
|
105
|
+
|
|
101
106
|
<text x="20" y="30" font-size="12" fill="#666">6</text>
|
|
102
107
|
<text x="20" y="80" font-size="12" fill="#666">5</text>
|
|
103
108
|
<text x="20" y="130" font-size="12" fill="#666">4</text>
|
|
@@ -106,7 +111,7 @@
|
|
|
106
111
|
<text x="20" y="280" font-size="12" fill="#666">1</text>
|
|
107
112
|
<text x="20" y="310" font-size="12" fill="#666">0</text>
|
|
108
113
|
|
|
109
|
-
|
|
114
|
+
|
|
110
115
|
<line
|
|
111
116
|
x1="60"
|
|
112
117
|
y1="20"
|
|
@@ -164,14 +169,14 @@
|
|
|
164
169
|
stroke-width="1"
|
|
165
170
|
/>
|
|
166
171
|
|
|
167
|
-
|
|
172
|
+
|
|
168
173
|
<polyline
|
|
169
174
|
:points="visitorPoints.map((p) => `${p.x},${p.y}`).join(' ')"
|
|
170
175
|
fill="none"
|
|
171
176
|
stroke="#2196F3"
|
|
172
177
|
stroke-width="3"
|
|
173
178
|
/>
|
|
174
|
-
|
|
179
|
+
|
|
175
180
|
<circle
|
|
176
181
|
v-for="point in visitorPoints"
|
|
177
182
|
:key="point.x"
|
|
@@ -180,7 +185,7 @@
|
|
|
180
185
|
r="5"
|
|
181
186
|
fill="#2196F3"
|
|
182
187
|
/>
|
|
183
|
-
|
|
188
|
+
|
|
184
189
|
<text
|
|
185
190
|
v-for="(label, idx) in visitorLabels"
|
|
186
191
|
:key="idx"
|
|
@@ -197,10 +202,10 @@
|
|
|
197
202
|
</v-card-text>
|
|
198
203
|
</v-card>
|
|
199
204
|
</v-col>
|
|
200
|
-
</v-row>
|
|
205
|
+
</v-row> -->
|
|
201
206
|
|
|
202
207
|
<!-- Bar Chart -->
|
|
203
|
-
<v-row class="mb-4">
|
|
208
|
+
<!-- <v-row class="mb-4">
|
|
204
209
|
<v-col cols="12">
|
|
205
210
|
<v-card flat border elevation="0">
|
|
206
211
|
<v-card-title class="pa-4 pa-md-6">
|
|
@@ -227,6 +232,7 @@
|
|
|
227
232
|
style="max-width: 200px"
|
|
228
233
|
clearable
|
|
229
234
|
class="pr-4"
|
|
235
|
+
@update:model-value="selectedService = $event"
|
|
230
236
|
></v-select>
|
|
231
237
|
<v-select
|
|
232
238
|
v-model="feedbackPeriod"
|
|
@@ -240,7 +246,6 @@
|
|
|
240
246
|
</v-row>
|
|
241
247
|
</v-card-title>
|
|
242
248
|
<v-card-text class="pa-4 pa-md-6 pt-2">
|
|
243
|
-
<!-- Legend -->
|
|
244
249
|
<div class="d-flex justify-center gap-4 mb-4">
|
|
245
250
|
<div class="d-flex align-center">
|
|
246
251
|
<div class="legend-box mr-2" style="background: #2196f3"></div>
|
|
@@ -261,7 +266,6 @@
|
|
|
261
266
|
style="min-width: 800px; width: 100%; height: 100%"
|
|
262
267
|
preserveAspectRatio="xMidYMid meet"
|
|
263
268
|
>
|
|
264
|
-
<!-- Y-axis labels -->
|
|
265
269
|
<text x="20" y="20" font-size="12" fill="#999">100</text>
|
|
266
270
|
<text x="20" y="80" font-size="12" fill="#999">80</text>
|
|
267
271
|
<text x="20" y="140" font-size="12" fill="#999">60</text>
|
|
@@ -269,7 +273,6 @@
|
|
|
269
273
|
<text x="20" y="260" font-size="12" fill="#999">20</text>
|
|
270
274
|
<text x="30" y="295" font-size="12" fill="#999">0</text>
|
|
271
275
|
|
|
272
|
-
<!-- Grid lines -->
|
|
273
276
|
<line
|
|
274
277
|
x1="60"
|
|
275
278
|
y1="20"
|
|
@@ -319,9 +322,7 @@
|
|
|
319
322
|
stroke-width="1"
|
|
320
323
|
/>
|
|
321
324
|
|
|
322
|
-
<!-- Bars with data -->
|
|
323
325
|
<g v-for="(bar, idx) in feedbackBars" :key="idx">
|
|
324
|
-
<!-- Bar -->
|
|
325
326
|
<rect
|
|
326
327
|
:x="bar.x"
|
|
327
328
|
:y="bar.y"
|
|
@@ -330,7 +331,7 @@
|
|
|
330
331
|
:fill="bar.color"
|
|
331
332
|
rx="4"
|
|
332
333
|
/>
|
|
333
|
-
|
|
334
|
+
|
|
334
335
|
<text
|
|
335
336
|
:x="bar.x + bar.width / 2"
|
|
336
337
|
:y="bar.y - 8"
|
|
@@ -341,7 +342,7 @@
|
|
|
341
342
|
>
|
|
342
343
|
{{ bar.value }}
|
|
343
344
|
</text>
|
|
344
|
-
|
|
345
|
+
|
|
345
346
|
<text
|
|
346
347
|
:x="bar.x + bar.width / 2"
|
|
347
348
|
y="310"
|
|
@@ -357,14 +358,14 @@
|
|
|
357
358
|
</v-card-text>
|
|
358
359
|
</v-card>
|
|
359
360
|
</v-col>
|
|
360
|
-
</v-row>
|
|
361
|
+
</v-row> -->
|
|
361
362
|
|
|
362
363
|
<!-- Pie Charts -->
|
|
363
|
-
<v-row class="mb-4">
|
|
364
|
+
<!-- <v-row class="mb-4">
|
|
364
365
|
<v-col v-for="pie in pieChartList" :key="pie.id" cols="12" md="6" lg="4">
|
|
365
366
|
<v-card flat border elevation="0" class="h-100">
|
|
366
367
|
<v-card-text class="pa-4 pa-md-6">
|
|
367
|
-
|
|
368
|
+
|
|
368
369
|
<div class="mb-3">
|
|
369
370
|
<h3 class="text-h6 font-weight-bold mb-1">{{ pie.title }}</h3>
|
|
370
371
|
<p class="text-body-2 text-grey-darken-1">
|
|
@@ -372,9 +373,9 @@
|
|
|
372
373
|
</p>
|
|
373
374
|
</div>
|
|
374
375
|
|
|
375
|
-
|
|
376
|
+
|
|
376
377
|
<div class="mb-4 d-flex flex-column gap-2">
|
|
377
|
-
|
|
378
|
+
|
|
378
379
|
<v-select
|
|
379
380
|
v-if="pie.id === 1"
|
|
380
381
|
v-model="selectedBooking"
|
|
@@ -388,7 +389,7 @@
|
|
|
388
389
|
clearable
|
|
389
390
|
class="pb-4"
|
|
390
391
|
></v-select>
|
|
391
|
-
|
|
392
|
+
|
|
392
393
|
<v-select
|
|
393
394
|
:model-value="pie.period"
|
|
394
395
|
:items="['This Week', 'This Month', 'This Year']"
|
|
@@ -399,7 +400,7 @@
|
|
|
399
400
|
></v-select>
|
|
400
401
|
</div>
|
|
401
402
|
|
|
402
|
-
|
|
403
|
+
|
|
403
404
|
<div class="d-flex flex-wrap gap-3 mb-4">
|
|
404
405
|
<div
|
|
405
406
|
v-for="(segment, idx) in pie.segments"
|
|
@@ -414,7 +415,7 @@
|
|
|
414
415
|
</div>
|
|
415
416
|
</div>
|
|
416
417
|
|
|
417
|
-
|
|
418
|
+
|
|
418
419
|
<div class="d-flex justify-center align-center">
|
|
419
420
|
<div class="position-relative">
|
|
420
421
|
<svg viewBox="0 0 200 200" width="220" height="220">
|
|
@@ -432,7 +433,7 @@
|
|
|
432
433
|
class="donut-segment"
|
|
433
434
|
/>
|
|
434
435
|
</svg>
|
|
435
|
-
|
|
436
|
+
|
|
436
437
|
<div class="donut-center">
|
|
437
438
|
<p class="text-h5 font-weight-bold">{{ pie.centerValue }}</p>
|
|
438
439
|
</div>
|
|
@@ -441,10 +442,10 @@
|
|
|
441
442
|
</v-card-text>
|
|
442
443
|
</v-card>
|
|
443
444
|
</v-col>
|
|
444
|
-
</v-row>
|
|
445
|
+
</v-row> -->
|
|
445
446
|
|
|
446
447
|
<!-- Facility Section -->
|
|
447
|
-
<v-row v-if="facilityBookings.length" class="mt-4">
|
|
448
|
+
<!-- <v-row v-if="facilityBookings.length" class="mt-4">
|
|
448
449
|
<v-col cols="12" class="mb-4">
|
|
449
450
|
<h2 class="text-h5 font-weight-bold">Facility</h2>
|
|
450
451
|
</v-col>
|
|
@@ -478,18 +479,16 @@
|
|
|
478
479
|
</v-card-text>
|
|
479
480
|
</v-card>
|
|
480
481
|
</v-col>
|
|
481
|
-
</v-row>
|
|
482
|
+
</v-row> -->
|
|
482
483
|
</v-container>
|
|
483
484
|
</template>
|
|
484
485
|
|
|
485
486
|
<script setup lang="ts">
|
|
486
|
-
import { useDisplay } from "vuetify";
|
|
487
|
-
|
|
488
|
-
const display = useDisplay();
|
|
489
487
|
const { getServiceProviderNames } = useServiceProvider();
|
|
490
488
|
const { getAll: getAllBuildings } = useBuilding();
|
|
491
489
|
const { getSiteById } = useSite();
|
|
492
490
|
const route = useRoute();
|
|
491
|
+
const { APP_NAME } = useRuntimeConfig().public;
|
|
493
492
|
|
|
494
493
|
// Get siteId from route params
|
|
495
494
|
const siteId = computed(() => (route.params.site as string) || "");
|
|
@@ -531,6 +530,14 @@ interface CardPeriods {
|
|
|
531
530
|
const isDashboardLoading = ref(false);
|
|
532
531
|
const isFeedbackTicketLoading = ref(false);
|
|
533
532
|
|
|
533
|
+
// Get dashboard data based on APP_NAME
|
|
534
|
+
const {
|
|
535
|
+
cards: dashboardCards,
|
|
536
|
+
feedbackItems,
|
|
537
|
+
feedbackChartDataMap,
|
|
538
|
+
periodMultipliers,
|
|
539
|
+
} = useDashboardData(APP_NAME);
|
|
540
|
+
|
|
534
541
|
// Period states for each section
|
|
535
542
|
const visitorPeriod = ref<Period>("This Week");
|
|
536
543
|
const feedbackPeriod = ref<Period>("This Week");
|
|
@@ -538,9 +545,15 @@ const bookingsPeriod = ref<Period>("This Week");
|
|
|
538
545
|
const buildingPeriod = ref<Period>("This Week");
|
|
539
546
|
const workOrdersPeriod = ref<Period>("This Week");
|
|
540
547
|
|
|
541
|
-
// Service provider states
|
|
548
|
+
// Service provider states - now using feedback items from composable
|
|
542
549
|
const selectedService = ref<any>(null);
|
|
543
|
-
const serviceList = ref<Array<{ _id: string; name: string }>>(
|
|
550
|
+
const serviceList = ref<Array<{ _id: string; name: string }>>(feedbackItems);
|
|
551
|
+
|
|
552
|
+
// Per-card periods keyed by label
|
|
553
|
+
const dynamicCardPeriods = ref<Record<string, Period>>({});
|
|
554
|
+
function updateCardPeriodDynamic(label: string, period: string) {
|
|
555
|
+
dynamicCardPeriods.value[label] = period as Period;
|
|
556
|
+
}
|
|
544
557
|
|
|
545
558
|
// Booking filter - list of facilities/buildings
|
|
546
559
|
const selectedBooking = ref<any>(null);
|
|
@@ -868,7 +881,7 @@ const dataByPeriod: Record<string, any> = {
|
|
|
868
881
|
},
|
|
869
882
|
};
|
|
870
883
|
|
|
871
|
-
// Computed properties for reactive data with site-based multiplier
|
|
884
|
+
// Computed properties for reactive data with site-based multiplier and APP_NAME-based cards
|
|
872
885
|
const countCardList = computed(() => {
|
|
873
886
|
// Calculate multiplier based on siteId (different sites get different multipliers)
|
|
874
887
|
const siteHash = siteId.value
|
|
@@ -876,74 +889,31 @@ const countCardList = computed(() => {
|
|
|
876
889
|
: 0;
|
|
877
890
|
const siteMultiplier = siteId.value ? 0.6 + (siteHash % 40) / 100 : 1; // Between 0.6 and 1.0
|
|
878
891
|
|
|
879
|
-
const applyMultiplier = (value:
|
|
880
|
-
const
|
|
881
|
-
const newValue = Math.round(numValue * siteMultiplier);
|
|
892
|
+
const applyMultiplier = (value: number) => {
|
|
893
|
+
const newValue = Math.round(value * siteMultiplier);
|
|
882
894
|
return newValue >= 1000 ? newValue.toLocaleString() : newValue.toString();
|
|
883
895
|
};
|
|
884
896
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
value
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
chipColor: dataByPeriod[cardPeriods.value.pickup].pickup.chipColor,
|
|
906
|
-
period: cardPeriods.value.pickup,
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
id: 3,
|
|
910
|
-
label: "Drop-off",
|
|
911
|
-
value: applyMultiplier(
|
|
912
|
-
dataByPeriod[cardPeriods.value.dropoff].dropoff.value
|
|
913
|
-
),
|
|
914
|
-
icon: "mdi-package-down",
|
|
915
|
-
color: "warning",
|
|
916
|
-
percentage: dataByPeriod[cardPeriods.value.dropoff].dropoff.percentage,
|
|
917
|
-
chipColor: dataByPeriod[cardPeriods.value.dropoff].dropoff.chipColor,
|
|
918
|
-
period: cardPeriods.value.dropoff,
|
|
919
|
-
},
|
|
920
|
-
{
|
|
921
|
-
id: 4,
|
|
922
|
-
label: "Contractor",
|
|
923
|
-
value: applyMultiplier(
|
|
924
|
-
dataByPeriod[cardPeriods.value.contractor].contractor.value
|
|
925
|
-
),
|
|
926
|
-
icon: "mdi-hard-hat",
|
|
927
|
-
color: "success",
|
|
928
|
-
percentage:
|
|
929
|
-
dataByPeriod[cardPeriods.value.contractor].contractor.percentage,
|
|
930
|
-
chipColor:
|
|
931
|
-
dataByPeriod[cardPeriods.value.contractor].contractor.chipColor,
|
|
932
|
-
period: cardPeriods.value.contractor,
|
|
933
|
-
},
|
|
934
|
-
{
|
|
935
|
-
id: 5,
|
|
936
|
-
label: "Delivery",
|
|
937
|
-
value: applyMultiplier(
|
|
938
|
-
dataByPeriod[cardPeriods.value.delivery].delivery.value
|
|
939
|
-
),
|
|
940
|
-
icon: "mdi-truck",
|
|
941
|
-
color: "grey",
|
|
942
|
-
percentage: dataByPeriod[cardPeriods.value.delivery].delivery.percentage,
|
|
943
|
-
chipColor: dataByPeriod[cardPeriods.value.delivery].delivery.chipColor,
|
|
944
|
-
period: cardPeriods.value.delivery,
|
|
945
|
-
},
|
|
946
|
-
];
|
|
897
|
+
// Map dashboard cards to card list format
|
|
898
|
+
return dashboardCards.map((card, idx) => {
|
|
899
|
+
const label = card.title;
|
|
900
|
+
const period =
|
|
901
|
+
dynamicCardPeriods.value[label] ||
|
|
902
|
+
(dynamicCardPeriods.value[label] = "This Week");
|
|
903
|
+
const periodFactor = periodMultipliers[period] ?? 1;
|
|
904
|
+
const multipliedValue = Math.round(card.value * periodFactor);
|
|
905
|
+
|
|
906
|
+
return {
|
|
907
|
+
id: idx + 1,
|
|
908
|
+
label,
|
|
909
|
+
value: applyMultiplier(multipliedValue),
|
|
910
|
+
icon: card.icon,
|
|
911
|
+
color: card.color,
|
|
912
|
+
percentage: `${card.percentage > 0 ? "+" : ""}${card.percentage} %`,
|
|
913
|
+
chipColor: card.isPositive ? "success" : "error",
|
|
914
|
+
period,
|
|
915
|
+
};
|
|
916
|
+
});
|
|
947
917
|
});
|
|
948
918
|
|
|
949
919
|
// Computed reactive data
|
|
@@ -961,32 +931,19 @@ const visitorLabels = [
|
|
|
961
931
|
];
|
|
962
932
|
|
|
963
933
|
const feedbackBars = computed(() => {
|
|
964
|
-
const baseFeedback = dataByPeriod[feedbackPeriod.value].feedback;
|
|
965
|
-
|
|
966
934
|
// If no service is selected, return the base data
|
|
967
935
|
if (!selectedService.value) {
|
|
968
|
-
return
|
|
936
|
+
return dataByPeriod[feedbackPeriod.value].feedback;
|
|
969
937
|
}
|
|
970
938
|
|
|
971
|
-
// If a service is selected,
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
.
|
|
975
|
-
|
|
976
|
-
const multiplier = 0.3 + (serviceHash % 50) / 100; // Between 0.3 and 0.8
|
|
977
|
-
|
|
978
|
-
return baseFeedback.map((bar: any) => {
|
|
979
|
-
const newValue = Math.round(bar.value * multiplier);
|
|
980
|
-
const newHeight = bar.height * multiplier;
|
|
981
|
-
const newY = 290 - newHeight;
|
|
939
|
+
// If a service is selected, use the feedback chart data from composable
|
|
940
|
+
const chartData = feedbackChartDataMap[selectedService.value];
|
|
941
|
+
if (chartData && chartData[feedbackPeriod.value]) {
|
|
942
|
+
return chartData[feedbackPeriod.value];
|
|
943
|
+
}
|
|
982
944
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
value: newValue,
|
|
986
|
-
height: newHeight,
|
|
987
|
-
y: newY,
|
|
988
|
-
};
|
|
989
|
-
});
|
|
945
|
+
// Fallback to base data if service data not found
|
|
946
|
+
return dataByPeriod[feedbackPeriod.value].feedback;
|
|
990
947
|
});
|
|
991
948
|
|
|
992
949
|
// Function to update individual card periods
|
|
@@ -1307,15 +1264,9 @@ watch(selectedService, () => {
|
|
|
1307
1264
|
fetchFeedbackData();
|
|
1308
1265
|
});
|
|
1309
1266
|
|
|
1310
|
-
// Fetch
|
|
1267
|
+
// Fetch buildings/facilities on mount
|
|
1311
1268
|
onMounted(async () => {
|
|
1312
1269
|
try {
|
|
1313
|
-
// Fetch service providers for feedback dropdown
|
|
1314
|
-
const serviceResponse = await getServiceProviderNames({ limit: 100 });
|
|
1315
|
-
if (serviceResponse && serviceResponse.items) {
|
|
1316
|
-
serviceList.value = serviceResponse.items;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
1270
|
// Fetch buildings/facilities for bookings dropdown
|
|
1320
1271
|
// Using buildings as facilities - you can adjust to use actual facilities API
|
|
1321
1272
|
const buildingsResponse = await getAllBuildings({
|
|
@@ -1499,6 +1450,16 @@ svg rect {
|
|
|
1499
1450
|
border-radius: 12px !important;
|
|
1500
1451
|
}
|
|
1501
1452
|
|
|
1453
|
+
.avatar-custom {
|
|
1454
|
+
width: 56px;
|
|
1455
|
+
height: 56px;
|
|
1456
|
+
border-radius: 12px;
|
|
1457
|
+
display: flex;
|
|
1458
|
+
align-items: center;
|
|
1459
|
+
justify-content: center;
|
|
1460
|
+
flex-shrink: 0;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1502
1463
|
/* Better chip styling */
|
|
1503
1464
|
.v-chip {
|
|
1504
1465
|
font-weight: 600;
|