@iservice365/layer-common 1.5.5 → 1.5.7
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/DashboardPlaceholder.vue +346 -130
- package/components/DocumentForm.vue +101 -0
- package/components/DocumentManagement.vue +187 -0
- package/components/FeedbackMain.vue +2 -1
- package/components/Input/InputPhoneNumberV2.vue +54 -14
- package/components/SupplyManagement.vue +1 -1
- package/components/WorkOrder/Main.vue +38 -6
- package/composables/useDashboardData.ts +425 -0
- package/composables/useDocument.ts +27 -0
- package/composables/useSiteSettings.ts +13 -1
- package/composables/useWorkOrder.ts +8 -0
- package/package.json +1 -1
- package/types/document.d.ts +5 -0
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
<!-- Header -->
|
|
4
4
|
<v-row no-gutters class="mb-6">
|
|
5
5
|
<v-col cols="12">
|
|
6
|
-
<h1 class="text-h4 text-md-h3 font-weight-bold">
|
|
6
|
+
<h1 class="text-h4 text-md-h3 font-weight-bold">
|
|
7
|
+
Dashboard{{ currentSiteName ? ` - ${currentSiteName}` : "" }}
|
|
8
|
+
</h1>
|
|
7
9
|
</v-col>
|
|
8
10
|
</v-row>
|
|
9
11
|
|
|
@@ -38,9 +40,14 @@
|
|
|
38
40
|
</v-chip>
|
|
39
41
|
</v-col>
|
|
40
42
|
<v-col cols="4" class="text-right">
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
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>
|
|
44
51
|
</v-col>
|
|
45
52
|
</v-row>
|
|
46
53
|
<v-row no-gutters class="mt-4">
|
|
@@ -52,7 +59,7 @@
|
|
|
52
59
|
hide-details
|
|
53
60
|
variant="outlined"
|
|
54
61
|
@update:model-value="
|
|
55
|
-
|
|
62
|
+
updateCardPeriodDynamic(card.label, $event)
|
|
56
63
|
"
|
|
57
64
|
></v-select>
|
|
58
65
|
</v-col>
|
|
@@ -63,7 +70,7 @@
|
|
|
63
70
|
</v-row>
|
|
64
71
|
|
|
65
72
|
<!-- Line Chart -->
|
|
66
|
-
<v-row class="mb-4">
|
|
73
|
+
<!-- <v-row class="mb-4">
|
|
67
74
|
<v-col cols="12">
|
|
68
75
|
<v-card flat border elevation="0">
|
|
69
76
|
<v-card-title class="pa-4 pa-md-6">
|
|
@@ -95,7 +102,7 @@
|
|
|
95
102
|
style="min-width: 800px; width: 100%; height: 100%"
|
|
96
103
|
preserveAspectRatio="xMidYMid meet"
|
|
97
104
|
>
|
|
98
|
-
|
|
105
|
+
|
|
99
106
|
<text x="20" y="30" font-size="12" fill="#666">6</text>
|
|
100
107
|
<text x="20" y="80" font-size="12" fill="#666">5</text>
|
|
101
108
|
<text x="20" y="130" font-size="12" fill="#666">4</text>
|
|
@@ -104,7 +111,7 @@
|
|
|
104
111
|
<text x="20" y="280" font-size="12" fill="#666">1</text>
|
|
105
112
|
<text x="20" y="310" font-size="12" fill="#666">0</text>
|
|
106
113
|
|
|
107
|
-
|
|
114
|
+
|
|
108
115
|
<line
|
|
109
116
|
x1="60"
|
|
110
117
|
y1="20"
|
|
@@ -162,14 +169,14 @@
|
|
|
162
169
|
stroke-width="1"
|
|
163
170
|
/>
|
|
164
171
|
|
|
165
|
-
|
|
172
|
+
|
|
166
173
|
<polyline
|
|
167
174
|
:points="visitorPoints.map((p) => `${p.x},${p.y}`).join(' ')"
|
|
168
175
|
fill="none"
|
|
169
176
|
stroke="#2196F3"
|
|
170
177
|
stroke-width="3"
|
|
171
178
|
/>
|
|
172
|
-
|
|
179
|
+
|
|
173
180
|
<circle
|
|
174
181
|
v-for="point in visitorPoints"
|
|
175
182
|
:key="point.x"
|
|
@@ -178,7 +185,7 @@
|
|
|
178
185
|
r="5"
|
|
179
186
|
fill="#2196F3"
|
|
180
187
|
/>
|
|
181
|
-
|
|
188
|
+
|
|
182
189
|
<text
|
|
183
190
|
v-for="(label, idx) in visitorLabels"
|
|
184
191
|
:key="idx"
|
|
@@ -195,10 +202,10 @@
|
|
|
195
202
|
</v-card-text>
|
|
196
203
|
</v-card>
|
|
197
204
|
</v-col>
|
|
198
|
-
</v-row>
|
|
205
|
+
</v-row> -->
|
|
199
206
|
|
|
200
207
|
<!-- Bar Chart -->
|
|
201
|
-
<v-row class="mb-4">
|
|
208
|
+
<!-- <v-row class="mb-4">
|
|
202
209
|
<v-col cols="12">
|
|
203
210
|
<v-card flat border elevation="0">
|
|
204
211
|
<v-card-title class="pa-4 pa-md-6">
|
|
@@ -208,7 +215,25 @@
|
|
|
208
215
|
Feedback Tickets
|
|
209
216
|
</h3>
|
|
210
217
|
</v-col>
|
|
211
|
-
<v-col
|
|
218
|
+
<v-col
|
|
219
|
+
cols="12"
|
|
220
|
+
md="6"
|
|
221
|
+
class="d-flex flex-column flex-md-row justify-md-end gap-2"
|
|
222
|
+
>
|
|
223
|
+
<v-select
|
|
224
|
+
v-model="selectedService"
|
|
225
|
+
:items="serviceList"
|
|
226
|
+
item-title="name"
|
|
227
|
+
item-value="_id"
|
|
228
|
+
label="Select Service"
|
|
229
|
+
density="compact"
|
|
230
|
+
hide-details
|
|
231
|
+
variant="outlined"
|
|
232
|
+
style="max-width: 200px"
|
|
233
|
+
clearable
|
|
234
|
+
class="pr-4"
|
|
235
|
+
@update:model-value="selectedService = $event"
|
|
236
|
+
></v-select>
|
|
212
237
|
<v-select
|
|
213
238
|
v-model="feedbackPeriod"
|
|
214
239
|
:items="['This Week', 'This Month', 'This Year']"
|
|
@@ -221,7 +246,6 @@
|
|
|
221
246
|
</v-row>
|
|
222
247
|
</v-card-title>
|
|
223
248
|
<v-card-text class="pa-4 pa-md-6 pt-2">
|
|
224
|
-
<!-- Legend -->
|
|
225
249
|
<div class="d-flex justify-center gap-4 mb-4">
|
|
226
250
|
<div class="d-flex align-center">
|
|
227
251
|
<div class="legend-box mr-2" style="background: #2196f3"></div>
|
|
@@ -242,7 +266,6 @@
|
|
|
242
266
|
style="min-width: 800px; width: 100%; height: 100%"
|
|
243
267
|
preserveAspectRatio="xMidYMid meet"
|
|
244
268
|
>
|
|
245
|
-
<!-- Y-axis labels -->
|
|
246
269
|
<text x="20" y="20" font-size="12" fill="#999">100</text>
|
|
247
270
|
<text x="20" y="80" font-size="12" fill="#999">80</text>
|
|
248
271
|
<text x="20" y="140" font-size="12" fill="#999">60</text>
|
|
@@ -250,7 +273,6 @@
|
|
|
250
273
|
<text x="20" y="260" font-size="12" fill="#999">20</text>
|
|
251
274
|
<text x="30" y="295" font-size="12" fill="#999">0</text>
|
|
252
275
|
|
|
253
|
-
<!-- Grid lines -->
|
|
254
276
|
<line
|
|
255
277
|
x1="60"
|
|
256
278
|
y1="20"
|
|
@@ -300,9 +322,7 @@
|
|
|
300
322
|
stroke-width="1"
|
|
301
323
|
/>
|
|
302
324
|
|
|
303
|
-
<!-- Bars with data -->
|
|
304
325
|
<g v-for="(bar, idx) in feedbackBars" :key="idx">
|
|
305
|
-
<!-- Bar -->
|
|
306
326
|
<rect
|
|
307
327
|
:x="bar.x"
|
|
308
328
|
:y="bar.y"
|
|
@@ -311,7 +331,7 @@
|
|
|
311
331
|
:fill="bar.color"
|
|
312
332
|
rx="4"
|
|
313
333
|
/>
|
|
314
|
-
|
|
334
|
+
|
|
315
335
|
<text
|
|
316
336
|
:x="bar.x + bar.width / 2"
|
|
317
337
|
:y="bar.y - 8"
|
|
@@ -322,7 +342,7 @@
|
|
|
322
342
|
>
|
|
323
343
|
{{ bar.value }}
|
|
324
344
|
</text>
|
|
325
|
-
|
|
345
|
+
|
|
326
346
|
<text
|
|
327
347
|
:x="bar.x + bar.width / 2"
|
|
328
348
|
y="310"
|
|
@@ -338,14 +358,14 @@
|
|
|
338
358
|
</v-card-text>
|
|
339
359
|
</v-card>
|
|
340
360
|
</v-col>
|
|
341
|
-
</v-row>
|
|
361
|
+
</v-row> -->
|
|
342
362
|
|
|
343
363
|
<!-- Pie Charts -->
|
|
344
|
-
<v-row class="mb-4">
|
|
364
|
+
<!-- <v-row class="mb-4">
|
|
345
365
|
<v-col v-for="pie in pieChartList" :key="pie.id" cols="12" md="6" lg="4">
|
|
346
366
|
<v-card flat border elevation="0" class="h-100">
|
|
347
367
|
<v-card-text class="pa-4 pa-md-6">
|
|
348
|
-
|
|
368
|
+
|
|
349
369
|
<div class="mb-3">
|
|
350
370
|
<h3 class="text-h6 font-weight-bold mb-1">{{ pie.title }}</h3>
|
|
351
371
|
<p class="text-body-2 text-grey-darken-1">
|
|
@@ -353,8 +373,23 @@
|
|
|
353
373
|
</p>
|
|
354
374
|
</div>
|
|
355
375
|
|
|
356
|
-
|
|
357
|
-
<div class="mb-4">
|
|
376
|
+
|
|
377
|
+
<div class="mb-4 d-flex flex-column gap-2">
|
|
378
|
+
|
|
379
|
+
<v-select
|
|
380
|
+
v-if="pie.id === 1"
|
|
381
|
+
v-model="selectedBooking"
|
|
382
|
+
:items="bookingsList"
|
|
383
|
+
item-title="name"
|
|
384
|
+
item-value="_id"
|
|
385
|
+
label="Select Facility"
|
|
386
|
+
density="compact"
|
|
387
|
+
hide-details
|
|
388
|
+
variant="outlined"
|
|
389
|
+
clearable
|
|
390
|
+
class="pb-4"
|
|
391
|
+
></v-select>
|
|
392
|
+
|
|
358
393
|
<v-select
|
|
359
394
|
:model-value="pie.period"
|
|
360
395
|
:items="['This Week', 'This Month', 'This Year']"
|
|
@@ -365,7 +400,7 @@
|
|
|
365
400
|
></v-select>
|
|
366
401
|
</div>
|
|
367
402
|
|
|
368
|
-
|
|
403
|
+
|
|
369
404
|
<div class="d-flex flex-wrap gap-3 mb-4">
|
|
370
405
|
<div
|
|
371
406
|
v-for="(segment, idx) in pie.segments"
|
|
@@ -380,7 +415,7 @@
|
|
|
380
415
|
</div>
|
|
381
416
|
</div>
|
|
382
417
|
|
|
383
|
-
|
|
418
|
+
|
|
384
419
|
<div class="d-flex justify-center align-center">
|
|
385
420
|
<div class="position-relative">
|
|
386
421
|
<svg viewBox="0 0 200 200" width="220" height="220">
|
|
@@ -398,7 +433,7 @@
|
|
|
398
433
|
class="donut-segment"
|
|
399
434
|
/>
|
|
400
435
|
</svg>
|
|
401
|
-
|
|
436
|
+
|
|
402
437
|
<div class="donut-center">
|
|
403
438
|
<p class="text-h5 font-weight-bold">{{ pie.centerValue }}</p>
|
|
404
439
|
</div>
|
|
@@ -407,10 +442,10 @@
|
|
|
407
442
|
</v-card-text>
|
|
408
443
|
</v-card>
|
|
409
444
|
</v-col>
|
|
410
|
-
</v-row>
|
|
445
|
+
</v-row> -->
|
|
411
446
|
|
|
412
447
|
<!-- Facility Section -->
|
|
413
|
-
<v-row v-if="facilityBookings.length" class="mt-4">
|
|
448
|
+
<!-- <v-row v-if="facilityBookings.length" class="mt-4">
|
|
414
449
|
<v-col cols="12" class="mb-4">
|
|
415
450
|
<h2 class="text-h5 font-weight-bold">Facility</h2>
|
|
416
451
|
</v-col>
|
|
@@ -444,19 +479,44 @@
|
|
|
444
479
|
</v-card-text>
|
|
445
480
|
</v-card>
|
|
446
481
|
</v-col>
|
|
447
|
-
</v-row>
|
|
482
|
+
</v-row> -->
|
|
448
483
|
</v-container>
|
|
449
484
|
</template>
|
|
450
485
|
|
|
451
486
|
<script setup lang="ts">
|
|
452
|
-
|
|
453
|
-
|
|
487
|
+
const { getServiceProviderNames } = useServiceProvider();
|
|
488
|
+
const { getAll: getAllBuildings } = useBuilding();
|
|
489
|
+
const { getSiteById } = useSite();
|
|
490
|
+
const route = useRoute();
|
|
491
|
+
const { APP_NAME } = useRuntimeConfig().public;
|
|
492
|
+
|
|
493
|
+
// Get siteId from route params
|
|
494
|
+
const siteId = computed(() => (route.params.site as string) || "");
|
|
495
|
+
|
|
496
|
+
// Reactive site data that updates when siteId changes
|
|
497
|
+
const siteData = ref<any>(null);
|
|
454
498
|
|
|
455
|
-
|
|
499
|
+
// Fetch site data whenever siteId changes
|
|
500
|
+
watchEffect(async () => {
|
|
501
|
+
if (siteId.value) {
|
|
502
|
+
try {
|
|
503
|
+
const site = await getSiteById(siteId.value);
|
|
504
|
+
siteData.value = site;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error("Error fetching site:", error);
|
|
507
|
+
siteData.value = null;
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
siteData.value = null;
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Get current site name from fetched site data
|
|
515
|
+
const currentSiteName = computed(() => siteData.value?.name || "");
|
|
456
516
|
|
|
457
517
|
// Type definitions
|
|
458
|
-
type Period =
|
|
459
|
-
type CardType =
|
|
518
|
+
type Period = "This Week" | "This Month" | "This Year";
|
|
519
|
+
type CardType = "guest" | "pickup" | "dropoff" | "contractor" | "delivery";
|
|
460
520
|
|
|
461
521
|
interface CardPeriods {
|
|
462
522
|
guest: Period;
|
|
@@ -470,6 +530,14 @@ interface CardPeriods {
|
|
|
470
530
|
const isDashboardLoading = ref(false);
|
|
471
531
|
const isFeedbackTicketLoading = ref(false);
|
|
472
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
|
+
|
|
473
541
|
// Period states for each section
|
|
474
542
|
const visitorPeriod = ref<Period>("This Week");
|
|
475
543
|
const feedbackPeriod = ref<Period>("This Week");
|
|
@@ -477,6 +545,20 @@ const bookingsPeriod = ref<Period>("This Week");
|
|
|
477
545
|
const buildingPeriod = ref<Period>("This Week");
|
|
478
546
|
const workOrdersPeriod = ref<Period>("This Week");
|
|
479
547
|
|
|
548
|
+
// Service provider states - now using feedback items from composable
|
|
549
|
+
const selectedService = ref<any>(null);
|
|
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
|
+
}
|
|
557
|
+
|
|
558
|
+
// Booking filter - list of facilities/buildings
|
|
559
|
+
const selectedBooking = ref<any>(null);
|
|
560
|
+
const bookingsList = ref<Array<{ _id: string; name: string }>>([]);
|
|
561
|
+
|
|
480
562
|
// Individual card periods
|
|
481
563
|
const cardPeriods = ref<CardPeriods>({
|
|
482
564
|
guest: "This Week",
|
|
@@ -487,7 +569,7 @@ const cardPeriods = ref<CardPeriods>({
|
|
|
487
569
|
});
|
|
488
570
|
|
|
489
571
|
// Data sets for different periods
|
|
490
|
-
const dataByPeriod = {
|
|
572
|
+
const dataByPeriod: Record<string, any> = {
|
|
491
573
|
"This Week": {
|
|
492
574
|
guest: { value: "156", percentage: "12.5 %", chipColor: "success" },
|
|
493
575
|
pickup: { value: "42", percentage: "8.3 %", chipColor: "success" },
|
|
@@ -799,60 +881,40 @@ const dataByPeriod = {
|
|
|
799
881
|
},
|
|
800
882
|
};
|
|
801
883
|
|
|
802
|
-
// Computed properties for reactive data
|
|
803
|
-
const countCardList = computed(() =>
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
label: "Contractor",
|
|
837
|
-
value: dataByPeriod[cardPeriods.value.contractor].contractor.value,
|
|
838
|
-
icon: "mdi-hard-hat",
|
|
839
|
-
color: "success",
|
|
840
|
-
percentage:
|
|
841
|
-
dataByPeriod[cardPeriods.value.contractor].contractor.percentage,
|
|
842
|
-
chipColor: dataByPeriod[cardPeriods.value.contractor].contractor.chipColor,
|
|
843
|
-
period: cardPeriods.value.contractor,
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
id: 5,
|
|
847
|
-
label: "Delivery",
|
|
848
|
-
value: dataByPeriod[cardPeriods.value.delivery].delivery.value,
|
|
849
|
-
icon: "mdi-truck",
|
|
850
|
-
color: "grey",
|
|
851
|
-
percentage: dataByPeriod[cardPeriods.value.delivery].delivery.percentage,
|
|
852
|
-
chipColor: dataByPeriod[cardPeriods.value.delivery].delivery.chipColor,
|
|
853
|
-
period: cardPeriods.value.delivery,
|
|
854
|
-
},
|
|
855
|
-
]);
|
|
884
|
+
// Computed properties for reactive data with site-based multiplier and APP_NAME-based cards
|
|
885
|
+
const countCardList = computed(() => {
|
|
886
|
+
// Calculate multiplier based on siteId (different sites get different multipliers)
|
|
887
|
+
const siteHash = siteId.value
|
|
888
|
+
? siteId.value.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
|
889
|
+
: 0;
|
|
890
|
+
const siteMultiplier = siteId.value ? 0.6 + (siteHash % 40) / 100 : 1; // Between 0.6 and 1.0
|
|
891
|
+
|
|
892
|
+
const applyMultiplier = (value: number) => {
|
|
893
|
+
const newValue = Math.round(value * siteMultiplier);
|
|
894
|
+
return newValue >= 1000 ? newValue.toLocaleString() : newValue.toString();
|
|
895
|
+
};
|
|
896
|
+
|
|
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
|
+
});
|
|
917
|
+
});
|
|
856
918
|
|
|
857
919
|
// Computed reactive data
|
|
858
920
|
const visitorPoints = computed(
|
|
@@ -868,9 +930,21 @@ const visitorLabels = [
|
|
|
868
930
|
{ x: 1100, text: "Sat" },
|
|
869
931
|
];
|
|
870
932
|
|
|
871
|
-
const feedbackBars = computed(
|
|
872
|
-
|
|
873
|
-
)
|
|
933
|
+
const feedbackBars = computed(() => {
|
|
934
|
+
// If no service is selected, return the base data
|
|
935
|
+
if (!selectedService.value) {
|
|
936
|
+
return dataByPeriod[feedbackPeriod.value].feedback;
|
|
937
|
+
}
|
|
938
|
+
|
|
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
|
+
}
|
|
944
|
+
|
|
945
|
+
// Fallback to base data if service data not found
|
|
946
|
+
return dataByPeriod[feedbackPeriod.value].feedback;
|
|
947
|
+
});
|
|
874
948
|
|
|
875
949
|
// Function to update individual card periods
|
|
876
950
|
const updateCardPeriod = (cardType: string, period: string) => {
|
|
@@ -886,6 +960,19 @@ const updatePiePeriod = (pieId: number, period: string) => {
|
|
|
886
960
|
else if (pieId === 3) workOrdersPeriod.value = validPeriod;
|
|
887
961
|
};
|
|
888
962
|
|
|
963
|
+
// Function to fetch bookings data based on selected facility
|
|
964
|
+
const fetchBookingsData = async () => {
|
|
965
|
+
if (!selectedBooking.value) {
|
|
966
|
+
// Reset to base data when no booking selected
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// Watch for booking selection changes
|
|
972
|
+
watch(selectedBooking, () => {
|
|
973
|
+
fetchBookingsData();
|
|
974
|
+
});
|
|
975
|
+
|
|
889
976
|
// Pie chart data by period
|
|
890
977
|
const pieChartDataByPeriod = {
|
|
891
978
|
"This Week": {
|
|
@@ -1070,67 +1157,186 @@ const pieChartDataByPeriod = {
|
|
|
1070
1157
|
};
|
|
1071
1158
|
|
|
1072
1159
|
// Computed Pie Charts Data
|
|
1073
|
-
const pieChartList = computed(() =>
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1160
|
+
const pieChartList = computed(() => {
|
|
1161
|
+
// Get base bookings data
|
|
1162
|
+
const baseBookingsData = pieChartDataByPeriod[bookingsPeriod.value].bookings;
|
|
1163
|
+
let bookingsData = { ...baseBookingsData };
|
|
1164
|
+
|
|
1165
|
+
if (selectedBooking.value) {
|
|
1166
|
+
const bookingHash = selectedBooking.value
|
|
1167
|
+
.split("")
|
|
1168
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
1169
|
+
const multiplier = 0.35 + (bookingHash % 45) / 100; // Between 0.35 and 0.8
|
|
1170
|
+
const newTotal = Math.round(baseBookingsData.total * multiplier);
|
|
1171
|
+
|
|
1172
|
+
const segmentMultipliers = [
|
|
1173
|
+
0.8 + (bookingHash % 15) / 100, // approved: 0.8-0.95
|
|
1174
|
+
0.4 + (bookingHash % 30) / 100, // rejected: 0.4-0.7
|
|
1175
|
+
0.5 + (bookingHash % 25) / 100, // pending: 0.5-0.75
|
|
1176
|
+
];
|
|
1177
|
+
|
|
1178
|
+
const newSegments = baseBookingsData.segments.map(
|
|
1179
|
+
(seg: any, idx: number) => {
|
|
1180
|
+
const segMultiplier = segmentMultipliers[idx] || multiplier;
|
|
1181
|
+
const newPercentage = seg.percentage * segMultiplier;
|
|
1182
|
+
return {
|
|
1183
|
+
...seg,
|
|
1184
|
+
percentage: newPercentage,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// Normalize percentages to total 100% (make full circle)
|
|
1190
|
+
const totalPercentage = newSegments.reduce(
|
|
1191
|
+
(sum: number, seg: any) => sum + seg.percentage,
|
|
1192
|
+
0
|
|
1193
|
+
);
|
|
1194
|
+
const normalizedSegments = newSegments.map((seg: any) => ({
|
|
1195
|
+
...seg,
|
|
1196
|
+
percentage: (seg.percentage / totalPercentage) * 100,
|
|
1197
|
+
}));
|
|
1198
|
+
|
|
1199
|
+
// Recalculate rotations based on normalized percentages
|
|
1200
|
+
let currentRotation = -90;
|
|
1201
|
+
const adjustedSegments = normalizedSegments.map((seg: any) => {
|
|
1202
|
+
const segmentWithRotation = {
|
|
1203
|
+
...seg,
|
|
1204
|
+
rotation: currentRotation,
|
|
1205
|
+
};
|
|
1206
|
+
currentRotation += seg.percentage * 3.6; // 360 degrees / 100 percentage
|
|
1207
|
+
return segmentWithRotation;
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// Calculate new center value based on the largest segment
|
|
1211
|
+
const maxSegment = adjustedSegments.reduce((max: any, seg: any) =>
|
|
1212
|
+
seg.percentage > max.percentage ? seg : max
|
|
1213
|
+
);
|
|
1214
|
+
const newCenterValue = `${Math.round(maxSegment.percentage)}%`;
|
|
1102
1215
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1216
|
+
bookingsData = {
|
|
1217
|
+
total: newTotal,
|
|
1218
|
+
centerValue: newCenterValue,
|
|
1219
|
+
segments: adjustedSegments,
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return [
|
|
1224
|
+
{
|
|
1225
|
+
id: 1,
|
|
1226
|
+
title: "Bookings",
|
|
1227
|
+
total: bookingsData.total,
|
|
1228
|
+
centerValue: bookingsData.centerValue,
|
|
1229
|
+
segments: bookingsData.segments,
|
|
1230
|
+
period: bookingsPeriod.value,
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
id: 2,
|
|
1234
|
+
title: "Building Mngm.",
|
|
1235
|
+
total: pieChartDataByPeriod[buildingPeriod.value].building.total,
|
|
1236
|
+
centerValue:
|
|
1237
|
+
pieChartDataByPeriod[buildingPeriod.value].building.centerValue,
|
|
1238
|
+
segments: pieChartDataByPeriod[buildingPeriod.value].building.segments,
|
|
1239
|
+
period: buildingPeriod.value,
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
id: 3,
|
|
1243
|
+
title: "Work Orders",
|
|
1244
|
+
total: pieChartDataByPeriod[workOrdersPeriod.value].workOrders.total,
|
|
1245
|
+
centerValue:
|
|
1246
|
+
pieChartDataByPeriod[workOrdersPeriod.value].workOrders.centerValue,
|
|
1247
|
+
segments:
|
|
1248
|
+
pieChartDataByPeriod[workOrdersPeriod.value].workOrders.segments,
|
|
1249
|
+
period: workOrdersPeriod.value,
|
|
1250
|
+
},
|
|
1251
|
+
];
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
// Function to fetch feedback data based on selected service
|
|
1255
|
+
const fetchFeedbackData = async () => {
|
|
1256
|
+
if (!selectedService.value) {
|
|
1257
|
+
// Reset to base data when no service selected
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
// Watch for service selection changes
|
|
1263
|
+
watch(selectedService, () => {
|
|
1264
|
+
fetchFeedbackData();
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
// Fetch buildings/facilities on mount
|
|
1268
|
+
onMounted(async () => {
|
|
1269
|
+
try {
|
|
1270
|
+
// Fetch buildings/facilities for bookings dropdown
|
|
1271
|
+
// Using buildings as facilities - you can adjust to use actual facilities API
|
|
1272
|
+
const buildingsResponse = await getAllBuildings({
|
|
1273
|
+
limit: 100,
|
|
1274
|
+
status: "active", // Only show active buildings
|
|
1275
|
+
});
|
|
1276
|
+
if (buildingsResponse && buildingsResponse.items) {
|
|
1277
|
+
bookingsList.value = buildingsResponse.items.map((building: any) => ({
|
|
1278
|
+
_id: building._id,
|
|
1279
|
+
name: building.name || building.block || `Building ${building._id}`,
|
|
1280
|
+
}));
|
|
1281
|
+
}
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
console.error("Error fetching data:", error);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
// Base Facility Bookings Data
|
|
1288
|
+
const baseFacilityBookings = [
|
|
1105
1289
|
{
|
|
1106
1290
|
id: 1,
|
|
1107
1291
|
label: "Approved Bookings",
|
|
1108
|
-
value:
|
|
1292
|
+
value: 34,
|
|
1109
1293
|
icon: "mdi-calendar-check",
|
|
1110
1294
|
iconColor: "success",
|
|
1111
1295
|
},
|
|
1112
1296
|
{
|
|
1113
1297
|
id: 2,
|
|
1114
1298
|
label: "Pending Bookings",
|
|
1115
|
-
value:
|
|
1299
|
+
value: 97,
|
|
1116
1300
|
icon: "mdi-calendar-clock",
|
|
1117
1301
|
iconColor: "warning",
|
|
1118
1302
|
},
|
|
1119
1303
|
{
|
|
1120
1304
|
id: 3,
|
|
1121
1305
|
label: "Cancelled Bookings",
|
|
1122
|
-
value:
|
|
1306
|
+
value: 28,
|
|
1123
1307
|
icon: "mdi-calendar-remove",
|
|
1124
1308
|
iconColor: "error",
|
|
1125
1309
|
},
|
|
1126
1310
|
{
|
|
1127
1311
|
id: 4,
|
|
1128
1312
|
label: "Rejected Bookings",
|
|
1129
|
-
value:
|
|
1313
|
+
value: 27,
|
|
1130
1314
|
icon: "mdi-calendar-remove",
|
|
1131
1315
|
iconColor: "error",
|
|
1132
1316
|
},
|
|
1133
1317
|
];
|
|
1318
|
+
|
|
1319
|
+
// Computed Facility Bookings - changes based on selected facility
|
|
1320
|
+
const facilityBookings = computed(() => {
|
|
1321
|
+
// If no facility is selected, return base data
|
|
1322
|
+
if (!selectedBooking.value) {
|
|
1323
|
+
return baseFacilityBookings.map((booking) => ({
|
|
1324
|
+
...booking,
|
|
1325
|
+
value: booking.value.toString(),
|
|
1326
|
+
}));
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// If a facility is selected, multiply values
|
|
1330
|
+
const bookingHash = selectedBooking.value
|
|
1331
|
+
.split("")
|
|
1332
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
1333
|
+
const multiplier = 0.35 + (bookingHash % 45) / 100; // Between 0.35 and 0.8
|
|
1334
|
+
|
|
1335
|
+
return baseFacilityBookings.map((booking) => ({
|
|
1336
|
+
...booking,
|
|
1337
|
+
value: Math.round(booking.value * multiplier).toString(),
|
|
1338
|
+
}));
|
|
1339
|
+
});
|
|
1134
1340
|
</script>
|
|
1135
1341
|
|
|
1136
1342
|
<style scoped>
|
|
@@ -1244,6 +1450,16 @@ svg rect {
|
|
|
1244
1450
|
border-radius: 12px !important;
|
|
1245
1451
|
}
|
|
1246
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
|
+
|
|
1247
1463
|
/* Better chip styling */
|
|
1248
1464
|
.v-chip {
|
|
1249
1465
|
font-weight: 600;
|