@7365admin1/layer-common 1.8.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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.editorconfig +12 -0
- package/.github/workflows/main.yml +17 -0
- package/.github/workflows/publish.yml +39 -0
- package/.nuxtrc +1 -0
- package/.playground/app.vue +41 -0
- package/.playground/eslint.config.mjs +6 -0
- package/.playground/nuxt.config.ts +22 -0
- package/.playground/pages/feedback.vue +30 -0
- package/CHANGELOG.md +263 -0
- package/README.md +73 -0
- package/app.vue +3 -0
- package/components/AccessCardAddForm.vue +363 -0
- package/components/AccessManagement.vue +420 -0
- package/components/Avatar/Main.vue +68 -0
- package/components/BillingMain.vue +66 -0
- package/components/BtnUploadFile.vue +139 -0
- package/components/BuildingForm.vue +303 -0
- package/components/BuildingManagement/buildings.vue +335 -0
- package/components/BuildingManagement/units.vue +350 -0
- package/components/BuildingUnitFormAdd.vue +441 -0
- package/components/BuildingUnitFormEdit.vue +429 -0
- package/components/CameraForm.vue +264 -0
- package/components/CameraMain.vue +352 -0
- package/components/Card/DeleteConfirmation.vue +51 -0
- package/components/Card/MemberInfoSummary.vue +44 -0
- package/components/Card/Toggle.vue +25 -0
- package/components/Chat/Bubbles.vue +53 -0
- package/components/Chat/Information.vue +416 -0
- package/components/Chat/ListCard.vue +62 -0
- package/components/Chat/Message.vue +158 -0
- package/components/Chat/Navigation.vue +150 -0
- package/components/ConfirmDialog.vue +66 -0
- package/components/Container/Standard.vue +33 -0
- package/components/DashboardPlaceholder.vue +1524 -0
- package/components/Dialog/DeleteConfirmation.vue +51 -0
- package/components/Dialog/ReplaceAutofillPrompt.vue +49 -0
- package/components/Dialog/UpdateMoreAction.vue +103 -0
- package/components/DocumentForm.vue +187 -0
- package/components/DocumentManagement.vue +376 -0
- package/components/Editor.vue +95 -0
- package/components/EntryPassMain.vue +518 -0
- package/components/Feedback/Form.vue +173 -0
- package/components/FeedbackDetail.vue +599 -0
- package/components/FeedbackMain.vue +588 -0
- package/components/FormDialog.vue +65 -0
- package/components/ImageCarousel.vue +138 -0
- package/components/Input/Date.vue +177 -0
- package/components/Input/DateTimePicker.vue +131 -0
- package/components/Input/File.vue +236 -0
- package/components/Input/FileV2.vue +234 -0
- package/components/Input/InputPhoneNumberV2.vue +164 -0
- package/components/Input/ListGroupSelection.vue +96 -0
- package/components/Input/NRICNumber.vue +53 -0
- package/components/Input/NewDate.vue +123 -0
- package/components/Input/Number.vue +124 -0
- package/components/Input/Password.vue +22 -0
- package/components/Input/PhoneNumber.vue +188 -0
- package/components/Input/VehicleNumber.vue +49 -0
- package/components/InputLabel.vue +22 -0
- package/components/InvitationForm.vue +359 -0
- package/components/InvitationMain.vue +310 -0
- package/components/Layout/Header.vue +129 -0
- package/components/Layout/NavigationDrawer.vue +44 -0
- package/components/ListItem.vue +35 -0
- package/components/ListView.vue +87 -0
- package/components/LocalPagination.vue +31 -0
- package/components/MemberMain.vue +459 -0
- package/components/NFC/NFCPatrolReportMain.vue +591 -0
- package/components/NFC/NFCPatrolRouteForm.vue +596 -0
- package/components/NFC/NFCPatrolRouteMain.vue +539 -0
- package/components/NFC/NFCTagForm.vue +236 -0
- package/components/NFC/NFCTagMain.vue +337 -0
- package/components/NFC/PatrolSettings.vue +130 -0
- package/components/NavigationItem.vue +83 -0
- package/components/NumberSettingField.vue +107 -0
- package/components/OnlineFormConfigurationForm.vue +290 -0
- package/components/OnlineFormsConfiguration.vue +429 -0
- package/components/PeopleForm.vue +452 -0
- package/components/PlaceholderComponent.vue +34 -0
- package/components/RolePermissionFormCreate.vue +161 -0
- package/components/RolePermissionFormPreviewUpdate.vue +183 -0
- package/components/RolePermissionMain.vue +361 -0
- package/components/SearchVehicleNumberUser.vue +91 -0
- package/components/ServiceProviderFormCreate.vue +154 -0
- package/components/ServiceProviderMain.vue +547 -0
- package/components/SignaturePad.vue +73 -0
- package/components/Snackbar.vue +23 -0
- package/components/SpecificAttr.vue +53 -0
- package/components/SupplyManagement.vue +292 -0
- package/components/SwitchContext.vue +108 -0
- package/components/TableList.vue +150 -0
- package/components/TableListSecondary.vue +164 -0
- package/components/TableMain.vue +142 -0
- package/components/TableWithButton.vue +94 -0
- package/components/VehicleUpdateMoreAction.vue +84 -0
- package/components/VideoPlayer.vue +125 -0
- package/components/VisitorForm.vue +659 -0
- package/components/VisitorFormSelection.vue +53 -0
- package/components/VisitorManagement.vue +490 -0
- package/components/WorkOrder/Create.vue +284 -0
- package/components/WorkOrder/Detail.vue +71 -0
- package/components/WorkOrder/ListView.vue +96 -0
- package/components/WorkOrder/Main.vue +489 -0
- package/components/Workorder.vue +1 -0
- package/composables/useAddress.ts +107 -0
- package/composables/useBuilding.ts +250 -0
- package/composables/useBuildingUnit.ts +116 -0
- package/composables/useCard.ts +46 -0
- package/composables/useCommonPermission.ts +207 -0
- package/composables/useCustomer.ts +113 -0
- package/composables/useCustomerSite.ts +56 -0
- package/composables/useDashboard.ts +31 -0
- package/composables/useDashboardData.ts +425 -0
- package/composables/useDocument.ts +57 -0
- package/composables/useFacility.ts +246 -0
- package/composables/useFeedback.ts +119 -0
- package/composables/useFile.ts +55 -0
- package/composables/useInvoice.ts +18 -0
- package/composables/useLocal.ts +131 -0
- package/composables/useLocalAuth.ts +137 -0
- package/composables/useLocalSetup.ts +13 -0
- package/composables/useMember.ts +111 -0
- package/composables/useNFCPatrolRoute.ts +77 -0
- package/composables/useNFCPatrolSettings.ts +19 -0
- package/composables/useNFCPatrolTag.ts +53 -0
- package/composables/useOnlineForm.ts +67 -0
- package/composables/useOrg.ts +129 -0
- package/composables/usePDFDownload.ts +25 -0
- package/composables/usePaymentMethod.ts +101 -0
- package/composables/usePeople.ts +81 -0
- package/composables/usePermission.ts +54 -0
- package/composables/usePhoneCountries.ts +561 -0
- package/composables/usePrice.ts +15 -0
- package/composables/usePromoCode.ts +36 -0
- package/composables/useRecapPermission.ts +26 -0
- package/composables/useRole.ts +104 -0
- package/composables/useSecurityUtils.ts +18 -0
- package/composables/useServiceProvider.ts +224 -0
- package/composables/useSite.ts +109 -0
- package/composables/useSiteEntryPassSettings.ts +46 -0
- package/composables/useSiteSettings.ts +123 -0
- package/composables/useSubscription.ts +150 -0
- package/composables/useUser.ts +132 -0
- package/composables/useUtils.ts +445 -0
- package/composables/useVerification.ts +34 -0
- package/composables/useVisitor.ts +120 -0
- package/composables/useWorkOrder.ts +85 -0
- package/error.vue +41 -0
- package/layouts/plain.vue +7 -0
- package/middleware/01.auth.ts +20 -0
- package/middleware/02.org.ts +21 -0
- package/middleware/03.customer.ts +13 -0
- package/middleware/member.ts +4 -0
- package/nuxt.config.ts +54 -0
- package/package.json +39 -0
- package/pages/index.vue +3 -0
- package/pages/payment-method-linked.vue +31 -0
- package/pages/require-customer.vue +56 -0
- package/pages/require-organization-membership.vue +47 -0
- package/pages/unauthorized.vue +29 -0
- package/plugins/API.ts +21 -0
- package/plugins/iconify.client.ts +5 -0
- package/plugins/secure-member.client.ts +86 -0
- package/plugins/vuetify.ts +62 -0
- package/public/bg-camera.jpg +0 -0
- package/public/bg-city.jpg +0 -0
- package/public/bg-condo.jpg +0 -0
- package/public/images/icons/delete-icon.png +0 -0
- package/public/sprite.svg +1 -0
- package/tsconfig.json +3 -0
- package/types/address.d.ts +13 -0
- package/types/building.d.ts +27 -0
- package/types/camera.d.ts +31 -0
- package/types/card.d.ts +22 -0
- package/types/customer.d.ts +27 -0
- package/types/document.d.ts +6 -0
- package/types/feedback.d.ts +68 -0
- package/types/local.d.ts +74 -0
- package/types/member.d.ts +21 -0
- package/types/online-form.d.ts +15 -0
- package/types/org.d.ts +13 -0
- package/types/people.d.ts +24 -0
- package/types/permission.d.ts +25 -0
- package/types/phone-number.d.ts +10 -0
- package/types/price.d.ts +17 -0
- package/types/promo-code.d.ts +19 -0
- package/types/role.d.ts +11 -0
- package/types/select.d.ts +4 -0
- package/types/service-provider.d.ts +15 -0
- package/types/site.d.ts +20 -0
- package/types/subscription.d.ts +23 -0
- package/types/user.d.ts +19 -0
- package/types/verification.d.ts +20 -0
- package/types/visitor.d.ts +42 -0
- package/types/work-order.d.ts +42 -0
- package/utils/phoneMasks.ts +1703 -0
|
@@ -0,0 +1,1524 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container fluid class="">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<v-row no-gutters class="mb-6">
|
|
5
|
+
<v-col cols="12">
|
|
6
|
+
<h1 class="text-h4 text-md-h3 font-weight-bold">
|
|
7
|
+
Dashboard{{ currentSiteName ? ` - ${currentSiteName}` : "" }}
|
|
8
|
+
</h1>
|
|
9
|
+
</v-col>
|
|
10
|
+
</v-row>
|
|
11
|
+
|
|
12
|
+
<!-- Count Cards -->
|
|
13
|
+
<v-row class="mb-4">
|
|
14
|
+
<v-col
|
|
15
|
+
v-for="card in countCardList"
|
|
16
|
+
:key="card.id"
|
|
17
|
+
cols="12"
|
|
18
|
+
sm="6"
|
|
19
|
+
md="6"
|
|
20
|
+
lg="4"
|
|
21
|
+
xl="2.4"
|
|
22
|
+
>
|
|
23
|
+
<v-card flat border class="h-100 card-hover" elevation="0">
|
|
24
|
+
<v-card-text class="pa-4 pa-md-6">
|
|
25
|
+
<v-row no-gutters class="align-start">
|
|
26
|
+
<v-col cols="8">
|
|
27
|
+
<p class="text-caption text-grey-darken-1 mb-2 text-uppercase">
|
|
28
|
+
{{ card.label }}
|
|
29
|
+
</p>
|
|
30
|
+
<h2 class="text-h4 text-md-h3 font-weight-bold mb-3">
|
|
31
|
+
{{ card.value }}
|
|
32
|
+
</h2>
|
|
33
|
+
<v-chip :color="card.chipColor" size="small" variant="flat">
|
|
34
|
+
<v-icon
|
|
35
|
+
size="14"
|
|
36
|
+
icon="mdi-trending-up"
|
|
37
|
+
class="mr-1"
|
|
38
|
+
></v-icon>
|
|
39
|
+
{{ card.percentage }}
|
|
40
|
+
</v-chip>
|
|
41
|
+
</v-col>
|
|
42
|
+
<v-col cols="4" class="text-right">
|
|
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>
|
|
51
|
+
</v-col>
|
|
52
|
+
</v-row>
|
|
53
|
+
<v-row no-gutters class="mt-4">
|
|
54
|
+
<v-col cols="12">
|
|
55
|
+
<v-select
|
|
56
|
+
:model-value="card.period"
|
|
57
|
+
:items="['This Week', 'This Month', 'This Year']"
|
|
58
|
+
density="compact"
|
|
59
|
+
hide-details
|
|
60
|
+
variant="outlined"
|
|
61
|
+
@update:model-value="
|
|
62
|
+
updateCardPeriodDynamic(card.label, $event)
|
|
63
|
+
"
|
|
64
|
+
></v-select>
|
|
65
|
+
</v-col>
|
|
66
|
+
</v-row>
|
|
67
|
+
</v-card-text>
|
|
68
|
+
</v-card>
|
|
69
|
+
</v-col>
|
|
70
|
+
</v-row>
|
|
71
|
+
|
|
72
|
+
<!-- Line Chart -->
|
|
73
|
+
<!-- <v-row class="mb-4">
|
|
74
|
+
<v-col cols="12">
|
|
75
|
+
<v-card flat border elevation="0">
|
|
76
|
+
<v-card-title class="pa-4 pa-md-6">
|
|
77
|
+
<v-row no-gutters align="center">
|
|
78
|
+
<v-col cols="12" md="6" class="mb-4 mb-md-0">
|
|
79
|
+
<h3 class="text-h6 text-md-h5 font-weight-medium">
|
|
80
|
+
No. of Visitors
|
|
81
|
+
</h3>
|
|
82
|
+
</v-col>
|
|
83
|
+
<v-col cols="12" md="6" class="d-flex justify-md-end">
|
|
84
|
+
<v-select
|
|
85
|
+
v-model="visitorPeriod"
|
|
86
|
+
:items="['This Week', 'This Month', 'This Year']"
|
|
87
|
+
density="compact"
|
|
88
|
+
hide-details
|
|
89
|
+
variant="outlined"
|
|
90
|
+
style="max-width: 200px"
|
|
91
|
+
></v-select>
|
|
92
|
+
</v-col>
|
|
93
|
+
</v-row>
|
|
94
|
+
</v-card-title>
|
|
95
|
+
<v-card-text class="pa-4 pa-md-6 pt-0">
|
|
96
|
+
<div
|
|
97
|
+
class="chart-container"
|
|
98
|
+
style="min-height: 280px; overflow-x: auto"
|
|
99
|
+
>
|
|
100
|
+
<svg
|
|
101
|
+
viewBox="0 0 1200 300"
|
|
102
|
+
style="min-width: 800px; width: 100%; height: 100%"
|
|
103
|
+
preserveAspectRatio="xMidYMid meet"
|
|
104
|
+
>
|
|
105
|
+
|
|
106
|
+
<text x="20" y="30" font-size="12" fill="#666">6</text>
|
|
107
|
+
<text x="20" y="80" font-size="12" fill="#666">5</text>
|
|
108
|
+
<text x="20" y="130" font-size="12" fill="#666">4</text>
|
|
109
|
+
<text x="20" y="180" font-size="12" fill="#666">3</text>
|
|
110
|
+
<text x="20" y="230" font-size="12" fill="#666">2</text>
|
|
111
|
+
<text x="20" y="280" font-size="12" fill="#666">1</text>
|
|
112
|
+
<text x="20" y="310" font-size="12" fill="#666">0</text>
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
<line
|
|
116
|
+
x1="60"
|
|
117
|
+
y1="20"
|
|
118
|
+
x2="1180"
|
|
119
|
+
y2="20"
|
|
120
|
+
stroke="#E0E0E0"
|
|
121
|
+
stroke-width="1"
|
|
122
|
+
/>
|
|
123
|
+
<line
|
|
124
|
+
x1="60"
|
|
125
|
+
y1="70"
|
|
126
|
+
x2="1180"
|
|
127
|
+
y2="70"
|
|
128
|
+
stroke="#E0E0E0"
|
|
129
|
+
stroke-width="1"
|
|
130
|
+
/>
|
|
131
|
+
<line
|
|
132
|
+
x1="60"
|
|
133
|
+
y1="120"
|
|
134
|
+
x2="1180"
|
|
135
|
+
y2="120"
|
|
136
|
+
stroke="#E0E0E0"
|
|
137
|
+
stroke-width="1"
|
|
138
|
+
/>
|
|
139
|
+
<line
|
|
140
|
+
x1="60"
|
|
141
|
+
y1="170"
|
|
142
|
+
x2="1180"
|
|
143
|
+
y2="170"
|
|
144
|
+
stroke="#E0E0E0"
|
|
145
|
+
stroke-width="1"
|
|
146
|
+
/>
|
|
147
|
+
<line
|
|
148
|
+
x1="60"
|
|
149
|
+
y1="220"
|
|
150
|
+
x2="1180"
|
|
151
|
+
y2="220"
|
|
152
|
+
stroke="#E0E0E0"
|
|
153
|
+
stroke-width="1"
|
|
154
|
+
/>
|
|
155
|
+
<line
|
|
156
|
+
x1="60"
|
|
157
|
+
y1="270"
|
|
158
|
+
x2="1180"
|
|
159
|
+
y2="270"
|
|
160
|
+
stroke="#E0E0E0"
|
|
161
|
+
stroke-width="1"
|
|
162
|
+
/>
|
|
163
|
+
<line
|
|
164
|
+
x1="60"
|
|
165
|
+
y1="300"
|
|
166
|
+
x2="1180"
|
|
167
|
+
y2="300"
|
|
168
|
+
stroke="#E0E0E0"
|
|
169
|
+
stroke-width="1"
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
<polyline
|
|
174
|
+
:points="visitorPoints.map((p) => `${p.x},${p.y}`).join(' ')"
|
|
175
|
+
fill="none"
|
|
176
|
+
stroke="#2196F3"
|
|
177
|
+
stroke-width="3"
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
<circle
|
|
181
|
+
v-for="point in visitorPoints"
|
|
182
|
+
:key="point.x"
|
|
183
|
+
:cx="point.x"
|
|
184
|
+
:cy="point.y"
|
|
185
|
+
r="5"
|
|
186
|
+
fill="#2196F3"
|
|
187
|
+
/>
|
|
188
|
+
|
|
189
|
+
<text
|
|
190
|
+
v-for="(label, idx) in visitorLabels"
|
|
191
|
+
:key="idx"
|
|
192
|
+
:x="label.x"
|
|
193
|
+
y="320"
|
|
194
|
+
font-size="11"
|
|
195
|
+
fill="#666"
|
|
196
|
+
text-anchor="middle"
|
|
197
|
+
>
|
|
198
|
+
{{ label.text }}
|
|
199
|
+
</text>
|
|
200
|
+
</svg>
|
|
201
|
+
</div>
|
|
202
|
+
</v-card-text>
|
|
203
|
+
</v-card>
|
|
204
|
+
</v-col>
|
|
205
|
+
</v-row> -->
|
|
206
|
+
|
|
207
|
+
<!-- Bar Chart -->
|
|
208
|
+
<!-- <v-row class="mb-4">
|
|
209
|
+
<v-col cols="12">
|
|
210
|
+
<v-card flat border elevation="0">
|
|
211
|
+
<v-card-title class="pa-4 pa-md-6">
|
|
212
|
+
<v-row no-gutters align="center">
|
|
213
|
+
<v-col cols="12" md="6" class="mb-4 mb-md-0">
|
|
214
|
+
<h3 class="text-h6 text-md-h5 font-weight-medium">
|
|
215
|
+
Feedback Tickets
|
|
216
|
+
</h3>
|
|
217
|
+
</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>
|
|
237
|
+
<v-select
|
|
238
|
+
v-model="feedbackPeriod"
|
|
239
|
+
:items="['This Week', 'This Month', 'This Year']"
|
|
240
|
+
density="compact"
|
|
241
|
+
hide-details
|
|
242
|
+
variant="outlined"
|
|
243
|
+
style="max-width: 200px"
|
|
244
|
+
></v-select>
|
|
245
|
+
</v-col>
|
|
246
|
+
</v-row>
|
|
247
|
+
</v-card-title>
|
|
248
|
+
<v-card-text class="pa-4 pa-md-6 pt-2">
|
|
249
|
+
<div class="d-flex justify-center gap-4 mb-4">
|
|
250
|
+
<div class="d-flex align-center">
|
|
251
|
+
<div class="legend-box mr-2" style="background: #2196f3"></div>
|
|
252
|
+
<span class="text-caption">Resolved</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="d-flex align-center">
|
|
255
|
+
<div class="legend-box mr-2" style="background: #4caf50"></div>
|
|
256
|
+
<span class="text-caption">Unresolved</span>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div
|
|
261
|
+
class="chart-container"
|
|
262
|
+
style="min-height: 280px; overflow-x: auto"
|
|
263
|
+
>
|
|
264
|
+
<svg
|
|
265
|
+
viewBox="0 0 1200 300"
|
|
266
|
+
style="min-width: 800px; width: 100%; height: 100%"
|
|
267
|
+
preserveAspectRatio="xMidYMid meet"
|
|
268
|
+
>
|
|
269
|
+
<text x="20" y="20" font-size="12" fill="#999">100</text>
|
|
270
|
+
<text x="20" y="80" font-size="12" fill="#999">80</text>
|
|
271
|
+
<text x="20" y="140" font-size="12" fill="#999">60</text>
|
|
272
|
+
<text x="20" y="200" font-size="12" fill="#999">40</text>
|
|
273
|
+
<text x="20" y="260" font-size="12" fill="#999">20</text>
|
|
274
|
+
<text x="30" y="295" font-size="12" fill="#999">0</text>
|
|
275
|
+
|
|
276
|
+
<line
|
|
277
|
+
x1="60"
|
|
278
|
+
y1="20"
|
|
279
|
+
x2="1180"
|
|
280
|
+
y2="20"
|
|
281
|
+
stroke="#E0E0E0"
|
|
282
|
+
stroke-width="1"
|
|
283
|
+
/>
|
|
284
|
+
<line
|
|
285
|
+
x1="60"
|
|
286
|
+
y1="80"
|
|
287
|
+
x2="1180"
|
|
288
|
+
y2="80"
|
|
289
|
+
stroke="#E0E0E0"
|
|
290
|
+
stroke-width="1"
|
|
291
|
+
/>
|
|
292
|
+
<line
|
|
293
|
+
x1="60"
|
|
294
|
+
y1="140"
|
|
295
|
+
x2="1180"
|
|
296
|
+
y2="140"
|
|
297
|
+
stroke="#E0E0E0"
|
|
298
|
+
stroke-width="1"
|
|
299
|
+
/>
|
|
300
|
+
<line
|
|
301
|
+
x1="60"
|
|
302
|
+
y1="200"
|
|
303
|
+
x2="1180"
|
|
304
|
+
y2="200"
|
|
305
|
+
stroke="#E0E0E0"
|
|
306
|
+
stroke-width="1"
|
|
307
|
+
/>
|
|
308
|
+
<line
|
|
309
|
+
x1="60"
|
|
310
|
+
y1="260"
|
|
311
|
+
x2="1180"
|
|
312
|
+
y2="260"
|
|
313
|
+
stroke="#E0E0E0"
|
|
314
|
+
stroke-width="1"
|
|
315
|
+
/>
|
|
316
|
+
<line
|
|
317
|
+
x1="60"
|
|
318
|
+
y1="290"
|
|
319
|
+
x2="1180"
|
|
320
|
+
y2="290"
|
|
321
|
+
stroke="#E0E0E0"
|
|
322
|
+
stroke-width="1"
|
|
323
|
+
/>
|
|
324
|
+
|
|
325
|
+
<g v-for="(bar, idx) in feedbackBars" :key="idx">
|
|
326
|
+
<rect
|
|
327
|
+
:x="bar.x"
|
|
328
|
+
:y="bar.y"
|
|
329
|
+
:width="bar.width"
|
|
330
|
+
:height="bar.height"
|
|
331
|
+
:fill="bar.color"
|
|
332
|
+
rx="4"
|
|
333
|
+
/>
|
|
334
|
+
|
|
335
|
+
<text
|
|
336
|
+
:x="bar.x + bar.width / 2"
|
|
337
|
+
:y="bar.y - 8"
|
|
338
|
+
font-size="14"
|
|
339
|
+
font-weight="bold"
|
|
340
|
+
fill="#333"
|
|
341
|
+
text-anchor="middle"
|
|
342
|
+
>
|
|
343
|
+
{{ bar.value }}
|
|
344
|
+
</text>
|
|
345
|
+
|
|
346
|
+
<text
|
|
347
|
+
:x="bar.x + bar.width / 2"
|
|
348
|
+
y="310"
|
|
349
|
+
font-size="12"
|
|
350
|
+
fill="#666"
|
|
351
|
+
text-anchor="middle"
|
|
352
|
+
>
|
|
353
|
+
{{ bar.label }}
|
|
354
|
+
</text>
|
|
355
|
+
</g>
|
|
356
|
+
</svg>
|
|
357
|
+
</div>
|
|
358
|
+
</v-card-text>
|
|
359
|
+
</v-card>
|
|
360
|
+
</v-col>
|
|
361
|
+
</v-row> -->
|
|
362
|
+
|
|
363
|
+
<!-- Pie Charts -->
|
|
364
|
+
<!-- <v-row class="mb-4">
|
|
365
|
+
<v-col v-for="pie in pieChartList" :key="pie.id" cols="12" md="6" lg="4">
|
|
366
|
+
<v-card flat border elevation="0" class="h-100">
|
|
367
|
+
<v-card-text class="pa-4 pa-md-6">
|
|
368
|
+
|
|
369
|
+
<div class="mb-3">
|
|
370
|
+
<h3 class="text-h6 font-weight-bold mb-1">{{ pie.title }}</h3>
|
|
371
|
+
<p class="text-body-2 text-grey-darken-1">
|
|
372
|
+
Total: {{ pie.total }}
|
|
373
|
+
</p>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
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
|
+
|
|
393
|
+
<v-select
|
|
394
|
+
:model-value="pie.period"
|
|
395
|
+
:items="['This Week', 'This Month', 'This Year']"
|
|
396
|
+
density="compact"
|
|
397
|
+
hide-details
|
|
398
|
+
variant="outlined"
|
|
399
|
+
@update:model-value="updatePiePeriod(pie.id, $event)"
|
|
400
|
+
></v-select>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
<div class="d-flex flex-wrap gap-3 mb-4">
|
|
405
|
+
<div
|
|
406
|
+
v-for="(segment, idx) in pie.segments"
|
|
407
|
+
:key="idx"
|
|
408
|
+
class="d-flex align-center mr-4 mb-2"
|
|
409
|
+
>
|
|
410
|
+
<div
|
|
411
|
+
class="legend-dot mr-2"
|
|
412
|
+
:style="{ backgroundColor: segment.color }"
|
|
413
|
+
></div>
|
|
414
|
+
<span class="text-caption">{{ segment.label }}</span>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
<div class="d-flex justify-center align-center">
|
|
420
|
+
<div class="position-relative">
|
|
421
|
+
<svg viewBox="0 0 200 200" width="220" height="220">
|
|
422
|
+
<circle
|
|
423
|
+
v-for="(segment, idx) in pie.segments"
|
|
424
|
+
:key="idx"
|
|
425
|
+
cx="100"
|
|
426
|
+
cy="100"
|
|
427
|
+
r="70"
|
|
428
|
+
fill="none"
|
|
429
|
+
:stroke="segment.color"
|
|
430
|
+
stroke-width="45"
|
|
431
|
+
:stroke-dasharray="`${segment.percentage * 4.398} 439.8`"
|
|
432
|
+
:transform="`rotate(${segment.rotation} 100 100)`"
|
|
433
|
+
class="donut-segment"
|
|
434
|
+
/>
|
|
435
|
+
</svg>
|
|
436
|
+
|
|
437
|
+
<div class="donut-center">
|
|
438
|
+
<p class="text-h5 font-weight-bold">{{ pie.centerValue }}</p>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</v-card-text>
|
|
443
|
+
</v-card>
|
|
444
|
+
</v-col>
|
|
445
|
+
</v-row> -->
|
|
446
|
+
|
|
447
|
+
<!-- Facility Section -->
|
|
448
|
+
<!-- <v-row v-if="facilityBookings.length" class="mt-4">
|
|
449
|
+
<v-col cols="12" class="mb-4">
|
|
450
|
+
<h2 class="text-h5 font-weight-bold">Facility</h2>
|
|
451
|
+
</v-col>
|
|
452
|
+
<v-col
|
|
453
|
+
v-for="booking in facilityBookings"
|
|
454
|
+
:key="booking.id"
|
|
455
|
+
cols="12"
|
|
456
|
+
sm="6"
|
|
457
|
+
md="6"
|
|
458
|
+
lg="3"
|
|
459
|
+
>
|
|
460
|
+
<v-card flat border elevation="0" class="h-100 card-hover">
|
|
461
|
+
<v-card-text class="pa-6">
|
|
462
|
+
<v-row no-gutters align="start" class="mb-3">
|
|
463
|
+
<v-col>
|
|
464
|
+
<p class="text-body-2 text-grey-darken-1 mb-1">
|
|
465
|
+
{{ booking.label }}
|
|
466
|
+
</p>
|
|
467
|
+
</v-col>
|
|
468
|
+
<v-col cols="auto">
|
|
469
|
+
<v-icon
|
|
470
|
+
:icon="booking.icon"
|
|
471
|
+
:color="booking.iconColor"
|
|
472
|
+
size="28"
|
|
473
|
+
></v-icon>
|
|
474
|
+
</v-col>
|
|
475
|
+
</v-row>
|
|
476
|
+
<h3 class="text-h3 font-weight-bold">
|
|
477
|
+
{{ booking.value }}
|
|
478
|
+
</h3>
|
|
479
|
+
</v-card-text>
|
|
480
|
+
</v-card>
|
|
481
|
+
</v-col>
|
|
482
|
+
</v-row> -->
|
|
483
|
+
</v-container>
|
|
484
|
+
</template>
|
|
485
|
+
|
|
486
|
+
<script setup lang="ts">
|
|
487
|
+
import useDashboard from '../composables/useDashboard';
|
|
488
|
+
|
|
489
|
+
const { getServiceProviderNames } = useServiceProvider();
|
|
490
|
+
const { getAll: getAllBuildings } = useBuilding();
|
|
491
|
+
const { getSiteById } = useSite();
|
|
492
|
+
const route = useRoute();
|
|
493
|
+
const { APP_NAME } = useRuntimeConfig().public;
|
|
494
|
+
|
|
495
|
+
// Get siteId from route params
|
|
496
|
+
const siteId = computed(() => (route.params.site as string) || "");
|
|
497
|
+
|
|
498
|
+
// Reactive site data that updates when siteId changes
|
|
499
|
+
const siteData = ref<any>(null);
|
|
500
|
+
|
|
501
|
+
// Fetch site data whenever siteId changes
|
|
502
|
+
watchEffect(async () => {
|
|
503
|
+
if (siteId.value) {
|
|
504
|
+
try {
|
|
505
|
+
const site = await getSiteById(siteId.value);
|
|
506
|
+
siteData.value = site;
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error("Error fetching site:", error);
|
|
509
|
+
siteData.value = null;
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
siteData.value = null;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Get current site name from fetched site data
|
|
517
|
+
const currentSiteName = computed(() => siteData.value?.name || "");
|
|
518
|
+
|
|
519
|
+
// Type definitions
|
|
520
|
+
type Period = "This Week" | "This Month" | "This Year";
|
|
521
|
+
type CardType = "guest" | "pickup" | "dropoff" | "contractor" | "delivery";
|
|
522
|
+
|
|
523
|
+
interface CardPeriods {
|
|
524
|
+
guest: Period;
|
|
525
|
+
pickup: Period;
|
|
526
|
+
dropoff: Period;
|
|
527
|
+
contractor: Period;
|
|
528
|
+
delivery: Period;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Loading states
|
|
532
|
+
const isDashboardLoading = ref(false);
|
|
533
|
+
const isFeedbackTicketLoading = ref(false);
|
|
534
|
+
|
|
535
|
+
// Get dashboard data based on APP_NAME
|
|
536
|
+
const {
|
|
537
|
+
cards: dashboardCards,
|
|
538
|
+
feedbackItems,
|
|
539
|
+
feedbackChartDataMap,
|
|
540
|
+
periodMultipliers,
|
|
541
|
+
} = useDashboardData(APP_NAME);
|
|
542
|
+
|
|
543
|
+
// Period states for each section
|
|
544
|
+
const visitorPeriod = ref<Period>("This Week");
|
|
545
|
+
const feedbackPeriod = ref<Period>("This Week");
|
|
546
|
+
const bookingsPeriod = ref<Period>("This Week");
|
|
547
|
+
const buildingPeriod = ref<Period>("This Week");
|
|
548
|
+
const workOrdersPeriod = ref<Period>("This Week");
|
|
549
|
+
|
|
550
|
+
// Service provider states - now using feedback items from composable
|
|
551
|
+
const selectedService = ref<any>(null);
|
|
552
|
+
const serviceList = ref<Array<{ _id: string; name: string }>>(feedbackItems);
|
|
553
|
+
|
|
554
|
+
// Per-card periods keyed by label
|
|
555
|
+
const dynamicCardPeriods = ref<Record<string, Period>>({});
|
|
556
|
+
function updateCardPeriodDynamic(label: string, period: string) {
|
|
557
|
+
dynamicCardPeriods.value[label] = period as Period;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Booking filter - list of facilities/buildings
|
|
561
|
+
const selectedBooking = ref<any>(null);
|
|
562
|
+
const bookingsList = ref<Array<{ _id: string; name: string }>>([]);
|
|
563
|
+
|
|
564
|
+
// Individual card periods
|
|
565
|
+
const cardPeriods = ref<CardPeriods>({
|
|
566
|
+
guest: "This Week",
|
|
567
|
+
pickup: "This Week",
|
|
568
|
+
dropoff: "This Week",
|
|
569
|
+
contractor: "This Week",
|
|
570
|
+
delivery: "This Week",
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// Data sets for different periods
|
|
574
|
+
const dataByPeriod: Record<string, any> = {
|
|
575
|
+
"This Week": {
|
|
576
|
+
guest: { value: "156", percentage: "12.5 %", chipColor: "success" },
|
|
577
|
+
pickup: { value: "42", percentage: "8.3 %", chipColor: "success" },
|
|
578
|
+
dropoff: { value: "38", percentage: "5.2 %", chipColor: "success" },
|
|
579
|
+
contractor: { value: "23", percentage: "-15.4 %", chipColor: "error" },
|
|
580
|
+
delivery: { value: "67", percentage: "18.9 %", chipColor: "success" },
|
|
581
|
+
visitors: [
|
|
582
|
+
{ x: 100, y: 220 },
|
|
583
|
+
{ x: 200, y: 180 },
|
|
584
|
+
{ x: 300, y: 150 },
|
|
585
|
+
{ x: 400, y: 200 },
|
|
586
|
+
{ x: 500, y: 140 },
|
|
587
|
+
{ x: 600, y: 160 },
|
|
588
|
+
{ x: 700, y: 100 },
|
|
589
|
+
{ x: 800, y: 130 },
|
|
590
|
+
{ x: 900, y: 90 },
|
|
591
|
+
{ x: 1000, y: 120 },
|
|
592
|
+
{ x: 1100, y: 80 },
|
|
593
|
+
],
|
|
594
|
+
feedback: [
|
|
595
|
+
{
|
|
596
|
+
x: 100,
|
|
597
|
+
y: 272,
|
|
598
|
+
width: 60,
|
|
599
|
+
height: 18,
|
|
600
|
+
value: 1,
|
|
601
|
+
label: "1",
|
|
602
|
+
color: "#4CAF50",
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
x: 200,
|
|
606
|
+
y: 176,
|
|
607
|
+
width: 60,
|
|
608
|
+
height: 114,
|
|
609
|
+
value: 19,
|
|
610
|
+
label: "2",
|
|
611
|
+
color: "#4CAF50",
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
x: 300,
|
|
615
|
+
y: 230,
|
|
616
|
+
width: 60,
|
|
617
|
+
height: 60,
|
|
618
|
+
value: 10,
|
|
619
|
+
label: "3",
|
|
620
|
+
color: "#4CAF50",
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
x: 400,
|
|
624
|
+
y: 248,
|
|
625
|
+
width: 60,
|
|
626
|
+
height: 42,
|
|
627
|
+
value: 7,
|
|
628
|
+
label: "4",
|
|
629
|
+
color: "#4CAF50",
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
x: 500,
|
|
633
|
+
y: 260,
|
|
634
|
+
width: 60,
|
|
635
|
+
height: 30,
|
|
636
|
+
value: 5,
|
|
637
|
+
label: "5",
|
|
638
|
+
color: "#4CAF50",
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
x: 600,
|
|
642
|
+
y: 272,
|
|
643
|
+
width: 60,
|
|
644
|
+
height: 18,
|
|
645
|
+
value: 3,
|
|
646
|
+
label: "6",
|
|
647
|
+
color: "#4CAF50",
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
x: 700,
|
|
651
|
+
y: 194,
|
|
652
|
+
width: 60,
|
|
653
|
+
height: 96,
|
|
654
|
+
value: 16,
|
|
655
|
+
label: "7",
|
|
656
|
+
color: "#4CAF50",
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
x: 800,
|
|
660
|
+
y: 188,
|
|
661
|
+
width: 60,
|
|
662
|
+
height: 102,
|
|
663
|
+
value: 17,
|
|
664
|
+
label: "8",
|
|
665
|
+
color: "#4CAF50",
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
x: 900,
|
|
669
|
+
y: 32,
|
|
670
|
+
width: 60,
|
|
671
|
+
height: 258,
|
|
672
|
+
value: 86,
|
|
673
|
+
label: "9",
|
|
674
|
+
color: "#4CAF50",
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
},
|
|
678
|
+
"This Month": {
|
|
679
|
+
guest: { value: "624", percentage: "22.3 %", chipColor: "success" },
|
|
680
|
+
pickup: { value: "178", percentage: "15.7 %", chipColor: "success" },
|
|
681
|
+
dropoff: { value: "156", percentage: "12.1 %", chipColor: "success" },
|
|
682
|
+
contractor: { value: "89", percentage: "-8.2 %", chipColor: "error" },
|
|
683
|
+
delivery: { value: "245", percentage: "28.5 %", chipColor: "success" },
|
|
684
|
+
visitors: [
|
|
685
|
+
{ x: 100, y: 180 },
|
|
686
|
+
{ x: 200, y: 150 },
|
|
687
|
+
{ x: 300, y: 120 },
|
|
688
|
+
{ x: 400, y: 160 },
|
|
689
|
+
{ x: 500, y: 100 },
|
|
690
|
+
{ x: 600, y: 130 },
|
|
691
|
+
{ x: 700, y: 80 },
|
|
692
|
+
{ x: 800, y: 110 },
|
|
693
|
+
{ x: 900, y: 70 },
|
|
694
|
+
{ x: 1000, y: 90 },
|
|
695
|
+
{ x: 1100, y: 60 },
|
|
696
|
+
],
|
|
697
|
+
feedback: [
|
|
698
|
+
{
|
|
699
|
+
x: 100,
|
|
700
|
+
y: 254,
|
|
701
|
+
width: 60,
|
|
702
|
+
height: 36,
|
|
703
|
+
value: 6,
|
|
704
|
+
label: "1",
|
|
705
|
+
color: "#4CAF50",
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
x: 200,
|
|
709
|
+
y: 140,
|
|
710
|
+
width: 60,
|
|
711
|
+
height: 150,
|
|
712
|
+
value: 25,
|
|
713
|
+
label: "2",
|
|
714
|
+
color: "#4CAF50",
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
x: 300,
|
|
718
|
+
y: 194,
|
|
719
|
+
width: 60,
|
|
720
|
+
height: 96,
|
|
721
|
+
value: 16,
|
|
722
|
+
label: "3",
|
|
723
|
+
color: "#4CAF50",
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
x: 400,
|
|
727
|
+
y: 212,
|
|
728
|
+
width: 60,
|
|
729
|
+
height: 78,
|
|
730
|
+
value: 13,
|
|
731
|
+
label: "4",
|
|
732
|
+
color: "#4CAF50",
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
x: 500,
|
|
736
|
+
y: 230,
|
|
737
|
+
width: 60,
|
|
738
|
+
height: 60,
|
|
739
|
+
value: 10,
|
|
740
|
+
label: "5",
|
|
741
|
+
color: "#4CAF50",
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
x: 600,
|
|
745
|
+
y: 248,
|
|
746
|
+
width: 60,
|
|
747
|
+
height: 42,
|
|
748
|
+
value: 7,
|
|
749
|
+
label: "6",
|
|
750
|
+
color: "#4CAF50",
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
x: 700,
|
|
754
|
+
y: 158,
|
|
755
|
+
width: 60,
|
|
756
|
+
height: 132,
|
|
757
|
+
value: 22,
|
|
758
|
+
label: "7",
|
|
759
|
+
color: "#4CAF50",
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
x: 800,
|
|
763
|
+
y: 176,
|
|
764
|
+
width: 60,
|
|
765
|
+
height: 114,
|
|
766
|
+
value: 19,
|
|
767
|
+
label: "8",
|
|
768
|
+
color: "#4CAF50",
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
x: 900,
|
|
772
|
+
y: 50,
|
|
773
|
+
width: 60,
|
|
774
|
+
height: 240,
|
|
775
|
+
value: 80,
|
|
776
|
+
label: "9",
|
|
777
|
+
color: "#4CAF50",
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
"This Year": {
|
|
782
|
+
guest: { value: "7,842", percentage: "35.6 %", chipColor: "success" },
|
|
783
|
+
pickup: { value: "2,134", percentage: "28.4 %", chipColor: "success" },
|
|
784
|
+
dropoff: { value: "1,987", percentage: "24.9 %", chipColor: "success" },
|
|
785
|
+
contractor: { value: "1,045", percentage: "5.3 %", chipColor: "success" },
|
|
786
|
+
delivery: { value: "3,256", percentage: "42.8 %", chipColor: "success" },
|
|
787
|
+
visitors: [
|
|
788
|
+
{ x: 100, y: 240 },
|
|
789
|
+
{ x: 200, y: 210 },
|
|
790
|
+
{ x: 300, y: 190 },
|
|
791
|
+
{ x: 400, y: 220 },
|
|
792
|
+
{ x: 500, y: 170 },
|
|
793
|
+
{ x: 600, y: 200 },
|
|
794
|
+
{ x: 700, y: 140 },
|
|
795
|
+
{ x: 800, y: 180 },
|
|
796
|
+
{ x: 900, y: 130 },
|
|
797
|
+
{ x: 1000, y: 160 },
|
|
798
|
+
{ x: 1100, y: 110 },
|
|
799
|
+
],
|
|
800
|
+
feedback: [
|
|
801
|
+
{
|
|
802
|
+
x: 100,
|
|
803
|
+
y: 236,
|
|
804
|
+
width: 60,
|
|
805
|
+
height: 54,
|
|
806
|
+
value: 9,
|
|
807
|
+
label: "1",
|
|
808
|
+
color: "#4CAF50",
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
x: 200,
|
|
812
|
+
y: 104,
|
|
813
|
+
width: 60,
|
|
814
|
+
height: 186,
|
|
815
|
+
value: 31,
|
|
816
|
+
label: "2",
|
|
817
|
+
color: "#4CAF50",
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
x: 300,
|
|
821
|
+
y: 176,
|
|
822
|
+
width: 60,
|
|
823
|
+
height: 114,
|
|
824
|
+
value: 19,
|
|
825
|
+
label: "3",
|
|
826
|
+
color: "#4CAF50",
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
x: 400,
|
|
830
|
+
y: 194,
|
|
831
|
+
width: 60,
|
|
832
|
+
height: 96,
|
|
833
|
+
value: 16,
|
|
834
|
+
label: "4",
|
|
835
|
+
color: "#4CAF50",
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
x: 500,
|
|
839
|
+
y: 212,
|
|
840
|
+
width: 60,
|
|
841
|
+
height: 78,
|
|
842
|
+
value: 13,
|
|
843
|
+
label: "5",
|
|
844
|
+
color: "#4CAF50",
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
x: 600,
|
|
848
|
+
y: 230,
|
|
849
|
+
width: 60,
|
|
850
|
+
height: 60,
|
|
851
|
+
value: 10,
|
|
852
|
+
label: "6",
|
|
853
|
+
color: "#4CAF50",
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
x: 700,
|
|
857
|
+
y: 122,
|
|
858
|
+
width: 60,
|
|
859
|
+
height: 168,
|
|
860
|
+
value: 28,
|
|
861
|
+
label: "7",
|
|
862
|
+
color: "#4CAF50",
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
x: 800,
|
|
866
|
+
y: 158,
|
|
867
|
+
width: 60,
|
|
868
|
+
height: 132,
|
|
869
|
+
value: 22,
|
|
870
|
+
label: "8",
|
|
871
|
+
color: "#4CAF50",
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
x: 900,
|
|
875
|
+
y: 20,
|
|
876
|
+
width: 60,
|
|
877
|
+
height: 270,
|
|
878
|
+
value: 90,
|
|
879
|
+
label: "9",
|
|
880
|
+
color: "#4CAF50",
|
|
881
|
+
},
|
|
882
|
+
],
|
|
883
|
+
},
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
const { getDashboardData } = useDashboard();
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
const {
|
|
891
|
+
data: getDashboardDataReq,
|
|
892
|
+
refresh: getDashboardDataRefresh,
|
|
893
|
+
pending: getDashboardDataPending,
|
|
894
|
+
} = await useLazyAsyncData(
|
|
895
|
+
`get-dashboard-data-${siteId}`,
|
|
896
|
+
() => getDashboardData({}),
|
|
897
|
+
{
|
|
898
|
+
watch: [ () => route.query],
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
watch(getDashboardDataReq, (newVal) => {
|
|
903
|
+
// items.value = newVal?.items || [];
|
|
904
|
+
// pages.value = newVal?.pages || 0;
|
|
905
|
+
// pageRange.value = newVal?.pageRange || "-- - -- of --";
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// Computed properties for reactive data with site-based multiplier and APP_NAME-based cards
|
|
909
|
+
const countCardList = computed(() => {
|
|
910
|
+
// Calculate multiplier based on siteId (different sites get different multipliers)
|
|
911
|
+
const siteHash = siteId.value
|
|
912
|
+
? siteId.value.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
|
913
|
+
: 0;
|
|
914
|
+
const siteMultiplier = siteId.value ? 0.6 + (siteHash % 40) / 100 : 1; // Between 0.6 and 1.0
|
|
915
|
+
|
|
916
|
+
const applyMultiplier = (value: number) => {
|
|
917
|
+
const newValue = Math.round(value * siteMultiplier);
|
|
918
|
+
return newValue >= 1000 ? newValue.toLocaleString() : newValue.toString();
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
// Map dashboard cards to card list format
|
|
922
|
+
return dashboardCards.map((card, idx) => {
|
|
923
|
+
const label = card.title;
|
|
924
|
+
const period =
|
|
925
|
+
dynamicCardPeriods.value[label] ||
|
|
926
|
+
(dynamicCardPeriods.value[label] = "This Week");
|
|
927
|
+
const periodFactor = periodMultipliers[period] ?? 1;
|
|
928
|
+
const multipliedValue = Math.round(card.value * periodFactor);
|
|
929
|
+
|
|
930
|
+
return {
|
|
931
|
+
id: idx + 1,
|
|
932
|
+
label,
|
|
933
|
+
value: applyMultiplier(multipliedValue),
|
|
934
|
+
icon: card.icon,
|
|
935
|
+
color: card.color,
|
|
936
|
+
percentage: `${card.percentage > 0 ? "+" : ""}${card.percentage} %`,
|
|
937
|
+
chipColor: card.isPositive ? "success" : "error",
|
|
938
|
+
period,
|
|
939
|
+
};
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
// Computed reactive data
|
|
944
|
+
const visitorPoints = computed(
|
|
945
|
+
() => dataByPeriod[visitorPeriod.value].visitors
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
const visitorLabels = [
|
|
949
|
+
{ x: 100, text: "Mon" },
|
|
950
|
+
{ x: 300, text: "Tue" },
|
|
951
|
+
{ x: 500, text: "Wed" },
|
|
952
|
+
{ x: 700, text: "Thu" },
|
|
953
|
+
{ x: 900, text: "Fri" },
|
|
954
|
+
{ x: 1100, text: "Sat" },
|
|
955
|
+
];
|
|
956
|
+
|
|
957
|
+
const feedbackBars = computed(() => {
|
|
958
|
+
// If no service is selected, return the base data
|
|
959
|
+
if (!selectedService.value) {
|
|
960
|
+
return dataByPeriod[feedbackPeriod.value].feedback;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// If a service is selected, use the feedback chart data from composable
|
|
964
|
+
const chartData = feedbackChartDataMap[selectedService.value];
|
|
965
|
+
if (chartData && chartData[feedbackPeriod.value]) {
|
|
966
|
+
return chartData[feedbackPeriod.value];
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Fallback to base data if service data not found
|
|
970
|
+
return dataByPeriod[feedbackPeriod.value].feedback;
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// Function to update individual card periods
|
|
974
|
+
const updateCardPeriod = (cardType: string, period: string) => {
|
|
975
|
+
const validCardType = cardType as CardType;
|
|
976
|
+
cardPeriods.value[validCardType] = period as Period;
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
// Function to update pie chart periods
|
|
980
|
+
const updatePiePeriod = (pieId: number, period: string) => {
|
|
981
|
+
const validPeriod = period as Period;
|
|
982
|
+
if (pieId === 1) bookingsPeriod.value = validPeriod;
|
|
983
|
+
else if (pieId === 2) buildingPeriod.value = validPeriod;
|
|
984
|
+
else if (pieId === 3) workOrdersPeriod.value = validPeriod;
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
// Function to fetch bookings data based on selected facility
|
|
988
|
+
const fetchBookingsData = async () => {
|
|
989
|
+
if (!selectedBooking.value) {
|
|
990
|
+
// Reset to base data when no booking selected
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
// Watch for booking selection changes
|
|
996
|
+
watch(selectedBooking, () => {
|
|
997
|
+
fetchBookingsData();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Pie chart data by period
|
|
1001
|
+
const pieChartDataByPeriod = {
|
|
1002
|
+
"This Week": {
|
|
1003
|
+
bookings: {
|
|
1004
|
+
total: 186,
|
|
1005
|
+
centerValue: "75.4%",
|
|
1006
|
+
segments: [
|
|
1007
|
+
{
|
|
1008
|
+
label: "approved",
|
|
1009
|
+
color: "#2196F3",
|
|
1010
|
+
percentage: 14.0,
|
|
1011
|
+
rotation: -90,
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
label: "rejected",
|
|
1015
|
+
color: "#4CAF50",
|
|
1016
|
+
percentage: 10.6,
|
|
1017
|
+
rotation: -39.6,
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
label: "pending",
|
|
1021
|
+
color: "#FFC107",
|
|
1022
|
+
percentage: 75.4,
|
|
1023
|
+
rotation: -1.44,
|
|
1024
|
+
},
|
|
1025
|
+
],
|
|
1026
|
+
},
|
|
1027
|
+
building: {
|
|
1028
|
+
total: 278,
|
|
1029
|
+
centerValue: "98.2%",
|
|
1030
|
+
segments: [
|
|
1031
|
+
{ label: "occupied", color: "#2196F3", percentage: 1.8, rotation: -90 },
|
|
1032
|
+
{
|
|
1033
|
+
label: "unoccupied",
|
|
1034
|
+
color: "#4CAF50",
|
|
1035
|
+
percentage: 98.2,
|
|
1036
|
+
rotation: -83.5,
|
|
1037
|
+
},
|
|
1038
|
+
],
|
|
1039
|
+
},
|
|
1040
|
+
workOrders: {
|
|
1041
|
+
total: 127,
|
|
1042
|
+
centerValue: "33.3%",
|
|
1043
|
+
segments: [
|
|
1044
|
+
{ label: "to-do", color: "#2196F3", percentage: 33.3, rotation: -90 },
|
|
1045
|
+
{
|
|
1046
|
+
label: "in-progress",
|
|
1047
|
+
color: "#4CAF50",
|
|
1048
|
+
percentage: 33.3,
|
|
1049
|
+
rotation: 29.9,
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
label: "completed",
|
|
1053
|
+
color: "#FFC107",
|
|
1054
|
+
percentage: 33.4,
|
|
1055
|
+
rotation: 149.8,
|
|
1056
|
+
},
|
|
1057
|
+
],
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
"This Month": {
|
|
1061
|
+
bookings: {
|
|
1062
|
+
total: 742,
|
|
1063
|
+
centerValue: "68.2%",
|
|
1064
|
+
segments: [
|
|
1065
|
+
{
|
|
1066
|
+
label: "approved",
|
|
1067
|
+
color: "#2196F3",
|
|
1068
|
+
percentage: 22.5,
|
|
1069
|
+
rotation: -90,
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
label: "rejected",
|
|
1073
|
+
color: "#4CAF50",
|
|
1074
|
+
percentage: 9.3,
|
|
1075
|
+
rotation: -9.0,
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
label: "pending",
|
|
1079
|
+
color: "#FFC107",
|
|
1080
|
+
percentage: 68.2,
|
|
1081
|
+
rotation: 24.48,
|
|
1082
|
+
},
|
|
1083
|
+
],
|
|
1084
|
+
},
|
|
1085
|
+
building: {
|
|
1086
|
+
total: 278,
|
|
1087
|
+
centerValue: "95.7%",
|
|
1088
|
+
segments: [
|
|
1089
|
+
{ label: "occupied", color: "#2196F3", percentage: 4.3, rotation: -90 },
|
|
1090
|
+
{
|
|
1091
|
+
label: "unoccupied",
|
|
1092
|
+
color: "#4CAF50",
|
|
1093
|
+
percentage: 95.7,
|
|
1094
|
+
rotation: -74.5,
|
|
1095
|
+
},
|
|
1096
|
+
],
|
|
1097
|
+
},
|
|
1098
|
+
workOrders: {
|
|
1099
|
+
total: 456,
|
|
1100
|
+
centerValue: "42.1%",
|
|
1101
|
+
segments: [
|
|
1102
|
+
{ label: "to-do", color: "#2196F3", percentage: 28.5, rotation: -90 },
|
|
1103
|
+
{
|
|
1104
|
+
label: "in-progress",
|
|
1105
|
+
color: "#4CAF50",
|
|
1106
|
+
percentage: 29.4,
|
|
1107
|
+
rotation: 12.6,
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
label: "completed",
|
|
1111
|
+
color: "#FFC107",
|
|
1112
|
+
percentage: 42.1,
|
|
1113
|
+
rotation: 118.4,
|
|
1114
|
+
},
|
|
1115
|
+
],
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
"This Year": {
|
|
1119
|
+
bookings: {
|
|
1120
|
+
total: 8924,
|
|
1121
|
+
centerValue: "58.9%",
|
|
1122
|
+
segments: [
|
|
1123
|
+
{
|
|
1124
|
+
label: "approved",
|
|
1125
|
+
color: "#2196F3",
|
|
1126
|
+
percentage: 35.6,
|
|
1127
|
+
rotation: -90,
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
label: "rejected",
|
|
1131
|
+
color: "#4CAF50",
|
|
1132
|
+
percentage: 5.5,
|
|
1133
|
+
rotation: 38.16,
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
label: "pending",
|
|
1137
|
+
color: "#FFC107",
|
|
1138
|
+
percentage: 58.9,
|
|
1139
|
+
rotation: 57.96,
|
|
1140
|
+
},
|
|
1141
|
+
],
|
|
1142
|
+
},
|
|
1143
|
+
building: {
|
|
1144
|
+
total: 278,
|
|
1145
|
+
centerValue: "89.2%",
|
|
1146
|
+
segments: [
|
|
1147
|
+
{
|
|
1148
|
+
label: "occupied",
|
|
1149
|
+
color: "#2196F3",
|
|
1150
|
+
percentage: 10.8,
|
|
1151
|
+
rotation: -90,
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
label: "unoccupied",
|
|
1155
|
+
color: "#4CAF50",
|
|
1156
|
+
percentage: 89.2,
|
|
1157
|
+
rotation: -51.1,
|
|
1158
|
+
},
|
|
1159
|
+
],
|
|
1160
|
+
},
|
|
1161
|
+
workOrders: {
|
|
1162
|
+
total: 5234,
|
|
1163
|
+
centerValue: "62.8%",
|
|
1164
|
+
segments: [
|
|
1165
|
+
{ label: "to-do", color: "#2196F3", percentage: 18.4, rotation: -90 },
|
|
1166
|
+
{
|
|
1167
|
+
label: "in-progress",
|
|
1168
|
+
color: "#4CAF50",
|
|
1169
|
+
percentage: 18.8,
|
|
1170
|
+
rotation: -23.8,
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
label: "completed",
|
|
1174
|
+
color: "#FFC107",
|
|
1175
|
+
percentage: 62.8,
|
|
1176
|
+
rotation: 43.8,
|
|
1177
|
+
},
|
|
1178
|
+
],
|
|
1179
|
+
},
|
|
1180
|
+
},
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
// Computed Pie Charts Data
|
|
1184
|
+
const pieChartList = computed(() => {
|
|
1185
|
+
// Get base bookings data
|
|
1186
|
+
const baseBookingsData = pieChartDataByPeriod[bookingsPeriod.value].bookings;
|
|
1187
|
+
let bookingsData = { ...baseBookingsData };
|
|
1188
|
+
|
|
1189
|
+
if (selectedBooking.value) {
|
|
1190
|
+
const bookingHash = selectedBooking.value
|
|
1191
|
+
.split("")
|
|
1192
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
1193
|
+
const multiplier = 0.35 + (bookingHash % 45) / 100; // Between 0.35 and 0.8
|
|
1194
|
+
const newTotal = Math.round(baseBookingsData.total * multiplier);
|
|
1195
|
+
|
|
1196
|
+
const segmentMultipliers = [
|
|
1197
|
+
0.8 + (bookingHash % 15) / 100, // approved: 0.8-0.95
|
|
1198
|
+
0.4 + (bookingHash % 30) / 100, // rejected: 0.4-0.7
|
|
1199
|
+
0.5 + (bookingHash % 25) / 100, // pending: 0.5-0.75
|
|
1200
|
+
];
|
|
1201
|
+
|
|
1202
|
+
const newSegments = baseBookingsData.segments.map(
|
|
1203
|
+
(seg: any, idx: number) => {
|
|
1204
|
+
const segMultiplier = segmentMultipliers[idx] || multiplier;
|
|
1205
|
+
const newPercentage = seg.percentage * segMultiplier;
|
|
1206
|
+
return {
|
|
1207
|
+
...seg,
|
|
1208
|
+
percentage: newPercentage,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
// Normalize percentages to total 100% (make full circle)
|
|
1214
|
+
const totalPercentage = newSegments.reduce(
|
|
1215
|
+
(sum: number, seg: any) => sum + seg.percentage,
|
|
1216
|
+
0
|
|
1217
|
+
);
|
|
1218
|
+
const normalizedSegments = newSegments.map((seg: any) => ({
|
|
1219
|
+
...seg,
|
|
1220
|
+
percentage: (seg.percentage / totalPercentage) * 100,
|
|
1221
|
+
}));
|
|
1222
|
+
|
|
1223
|
+
// Recalculate rotations based on normalized percentages
|
|
1224
|
+
let currentRotation = -90;
|
|
1225
|
+
const adjustedSegments = normalizedSegments.map((seg: any) => {
|
|
1226
|
+
const segmentWithRotation = {
|
|
1227
|
+
...seg,
|
|
1228
|
+
rotation: currentRotation,
|
|
1229
|
+
};
|
|
1230
|
+
currentRotation += seg.percentage * 3.6; // 360 degrees / 100 percentage
|
|
1231
|
+
return segmentWithRotation;
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
// Calculate new center value based on the largest segment
|
|
1235
|
+
const maxSegment = adjustedSegments.reduce((max: any, seg: any) =>
|
|
1236
|
+
seg.percentage > max.percentage ? seg : max
|
|
1237
|
+
);
|
|
1238
|
+
const newCenterValue = `${Math.round(maxSegment.percentage)}%`;
|
|
1239
|
+
|
|
1240
|
+
bookingsData = {
|
|
1241
|
+
total: newTotal,
|
|
1242
|
+
centerValue: newCenterValue,
|
|
1243
|
+
segments: adjustedSegments,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return [
|
|
1248
|
+
{
|
|
1249
|
+
id: 1,
|
|
1250
|
+
title: "Bookings",
|
|
1251
|
+
total: bookingsData.total,
|
|
1252
|
+
centerValue: bookingsData.centerValue,
|
|
1253
|
+
segments: bookingsData.segments,
|
|
1254
|
+
period: bookingsPeriod.value,
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
id: 2,
|
|
1258
|
+
title: "Building Mngm.",
|
|
1259
|
+
total: pieChartDataByPeriod[buildingPeriod.value].building.total,
|
|
1260
|
+
centerValue:
|
|
1261
|
+
pieChartDataByPeriod[buildingPeriod.value].building.centerValue,
|
|
1262
|
+
segments: pieChartDataByPeriod[buildingPeriod.value].building.segments,
|
|
1263
|
+
period: buildingPeriod.value,
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
id: 3,
|
|
1267
|
+
title: "Work Orders",
|
|
1268
|
+
total: pieChartDataByPeriod[workOrdersPeriod.value].workOrders.total,
|
|
1269
|
+
centerValue:
|
|
1270
|
+
pieChartDataByPeriod[workOrdersPeriod.value].workOrders.centerValue,
|
|
1271
|
+
segments:
|
|
1272
|
+
pieChartDataByPeriod[workOrdersPeriod.value].workOrders.segments,
|
|
1273
|
+
period: workOrdersPeriod.value,
|
|
1274
|
+
},
|
|
1275
|
+
];
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// Function to fetch feedback data based on selected service
|
|
1279
|
+
const fetchFeedbackData = async () => {
|
|
1280
|
+
if (!selectedService.value) {
|
|
1281
|
+
// Reset to base data when no service selected
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
// Watch for service selection changes
|
|
1287
|
+
watch(selectedService, () => {
|
|
1288
|
+
fetchFeedbackData();
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
// Fetch buildings/facilities on mount
|
|
1292
|
+
onMounted(async () => {
|
|
1293
|
+
try {
|
|
1294
|
+
// Fetch buildings/facilities for bookings dropdown
|
|
1295
|
+
// Using buildings as facilities - you can adjust to use actual facilities API
|
|
1296
|
+
const buildingsResponse = await getAllBuildings({
|
|
1297
|
+
limit: 100,
|
|
1298
|
+
status: "active", // Only show active buildings
|
|
1299
|
+
});
|
|
1300
|
+
if (buildingsResponse && buildingsResponse.items) {
|
|
1301
|
+
bookingsList.value = buildingsResponse.items.map((building: any) => ({
|
|
1302
|
+
_id: building._id,
|
|
1303
|
+
name: building.name || building.block || `Building ${building._id}`,
|
|
1304
|
+
}));
|
|
1305
|
+
}
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
console.error("Error fetching data:", error);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// Base Facility Bookings Data
|
|
1312
|
+
const baseFacilityBookings = [
|
|
1313
|
+
{
|
|
1314
|
+
id: 1,
|
|
1315
|
+
label: "Approved Bookings",
|
|
1316
|
+
value: 34,
|
|
1317
|
+
icon: "mdi-calendar-check",
|
|
1318
|
+
iconColor: "success",
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
id: 2,
|
|
1322
|
+
label: "Pending Bookings",
|
|
1323
|
+
value: 97,
|
|
1324
|
+
icon: "mdi-calendar-clock",
|
|
1325
|
+
iconColor: "warning",
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
id: 3,
|
|
1329
|
+
label: "Cancelled Bookings",
|
|
1330
|
+
value: 28,
|
|
1331
|
+
icon: "mdi-calendar-remove",
|
|
1332
|
+
iconColor: "error",
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
id: 4,
|
|
1336
|
+
label: "Rejected Bookings",
|
|
1337
|
+
value: 27,
|
|
1338
|
+
icon: "mdi-calendar-remove",
|
|
1339
|
+
iconColor: "error",
|
|
1340
|
+
},
|
|
1341
|
+
];
|
|
1342
|
+
|
|
1343
|
+
// Computed Facility Bookings - changes based on selected facility
|
|
1344
|
+
const facilityBookings = computed(() => {
|
|
1345
|
+
// If no facility is selected, return base data
|
|
1346
|
+
if (!selectedBooking.value) {
|
|
1347
|
+
return baseFacilityBookings.map((booking) => ({
|
|
1348
|
+
...booking,
|
|
1349
|
+
value: booking.value.toString(),
|
|
1350
|
+
}));
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// If a facility is selected, multiply values
|
|
1354
|
+
const bookingHash = selectedBooking.value
|
|
1355
|
+
.split("")
|
|
1356
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
1357
|
+
const multiplier = 0.35 + (bookingHash % 45) / 100; // Between 0.35 and 0.8
|
|
1358
|
+
|
|
1359
|
+
return baseFacilityBookings.map((booking) => ({
|
|
1360
|
+
...booking,
|
|
1361
|
+
value: Math.round(booking.value * multiplier).toString(),
|
|
1362
|
+
}));
|
|
1363
|
+
});
|
|
1364
|
+
</script>
|
|
1365
|
+
|
|
1366
|
+
<style scoped>
|
|
1367
|
+
.dashboard-container {
|
|
1368
|
+
max-width: 100%;
|
|
1369
|
+
background-color: #f5f5f5;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
.card-hover {
|
|
1373
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1374
|
+
cursor: pointer;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
.card-hover:hover {
|
|
1378
|
+
transform: translateY(-4px);
|
|
1379
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1) !important;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
.chart-container {
|
|
1383
|
+
min-height: 250px;
|
|
1384
|
+
display: flex;
|
|
1385
|
+
align-items: center;
|
|
1386
|
+
justify-content: center;
|
|
1387
|
+
overflow-x: auto;
|
|
1388
|
+
-webkit-overflow-scrolling: touch;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
.h-100 {
|
|
1392
|
+
height: 100%;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
.w-100 {
|
|
1396
|
+
width: 100%;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
.position-relative {
|
|
1400
|
+
position: relative;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
.position-absolute {
|
|
1404
|
+
position: absolute;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/* Responsive text sizing */
|
|
1408
|
+
@media (max-width: 600px) {
|
|
1409
|
+
.dashboard-container {
|
|
1410
|
+
padding: 12px !important;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
.chart-container {
|
|
1414
|
+
min-height: 250px;
|
|
1415
|
+
padding-bottom: 10px;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
.chart-container svg {
|
|
1419
|
+
min-width: 700px !important;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
@media (min-width: 600px) and (max-width: 960px) {
|
|
1424
|
+
.chart-container {
|
|
1425
|
+
min-height: 260px;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
.chart-container svg {
|
|
1429
|
+
min-width: 800px !important;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
@media (min-width: 960px) {
|
|
1434
|
+
.chart-container {
|
|
1435
|
+
min-height: 300px;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
.chart-container svg {
|
|
1439
|
+
min-width: auto !important;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/* SVG Animations */
|
|
1444
|
+
svg {
|
|
1445
|
+
max-width: 100%;
|
|
1446
|
+
height: auto;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
svg circle {
|
|
1450
|
+
transition: all 0.5s ease;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
svg polyline,
|
|
1454
|
+
svg rect {
|
|
1455
|
+
animation: fadeIn 1s ease-in-out;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
@keyframes fadeIn {
|
|
1459
|
+
from {
|
|
1460
|
+
opacity: 0;
|
|
1461
|
+
}
|
|
1462
|
+
to {
|
|
1463
|
+
opacity: 0.3;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
/* Card styling improvements */
|
|
1468
|
+
.v-card {
|
|
1469
|
+
border-radius: 12px !important;
|
|
1470
|
+
overflow: hidden;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
.v-avatar {
|
|
1474
|
+
border-radius: 12px !important;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
.avatar-custom {
|
|
1478
|
+
width: 56px;
|
|
1479
|
+
height: 56px;
|
|
1480
|
+
border-radius: 12px;
|
|
1481
|
+
display: flex;
|
|
1482
|
+
align-items: center;
|
|
1483
|
+
justify-content: center;
|
|
1484
|
+
flex-shrink: 0;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/* Better chip styling */
|
|
1488
|
+
.v-chip {
|
|
1489
|
+
font-weight: 600;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/* Select dropdown styling */
|
|
1493
|
+
.v-select {
|
|
1494
|
+
border-radius: 8px;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
.legend-dot {
|
|
1498
|
+
width: 10px;
|
|
1499
|
+
height: 10px;
|
|
1500
|
+
border-radius: 50%;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.legend-box {
|
|
1504
|
+
width: 12px;
|
|
1505
|
+
height: 12px;
|
|
1506
|
+
border-radius: 2px;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.donut-center {
|
|
1510
|
+
position: absolute;
|
|
1511
|
+
top: 50%;
|
|
1512
|
+
left: 50%;
|
|
1513
|
+
transform: translate(-50%, -50%);
|
|
1514
|
+
text-align: center;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.donut-segment {
|
|
1518
|
+
transition: opacity 0.2s ease;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.donut-segment:hover {
|
|
1522
|
+
opacity: 0.8;
|
|
1523
|
+
}
|
|
1524
|
+
</style>
|