@7365admin1/layer-common 3.0.6 → 3.0.8
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 +13 -0
- package/components/BulletinBoardForm.vue +423 -333
- package/components/CameraMain.vue +17 -1
- package/components/DashboardMain.vue +200 -150
- package/components/MemberInformation.vue +23 -15
- package/components/OnlineFormConfigurationForm.vue +2 -2
- package/components/OnlineFormFill.vue +227 -51
- package/components/PassInformation.vue +73 -34
- package/components/SiteSettings.vue +42 -121
- package/components/VehicleManagement.vue +6 -2
- package/components/VisitorForm.vue +623 -289
- package/components/VisitorManagement.vue +652 -205
- package/composables/useCommonPermission.ts +59 -0
- package/composables/useDashboard.ts +43 -1
- package/composables/useOnlineForm.ts +25 -13
- package/composables/useOnlineFormPages.ts +2 -2
- package/composables/useSettingsPermission.ts +138 -0
- package/package.json +2 -2
- package/types/dashboard.d.ts +1 -1
- package/types/visitor.d.ts +4 -2
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
@click:row="handleRowClick"
|
|
33
33
|
style="max-height: 300px"
|
|
34
34
|
>
|
|
35
|
+
<template #item.host="{ value }">
|
|
36
|
+
{{ maskHostUrl(value) }}
|
|
37
|
+
</template>
|
|
35
38
|
</v-data-table>
|
|
36
39
|
</v-card-text>
|
|
37
40
|
|
|
@@ -103,7 +106,7 @@
|
|
|
103
106
|
<v-col cols="12" class="mb-2">
|
|
104
107
|
Host:
|
|
105
108
|
<span class="font-weight-bold">
|
|
106
|
-
{{ camera.host }}
|
|
109
|
+
{{ maskHostUrl(camera.host) }}
|
|
107
110
|
</span>
|
|
108
111
|
</v-col>
|
|
109
112
|
|
|
@@ -367,6 +370,19 @@ function handleSuccess() {
|
|
|
367
370
|
getCameraRefresh();
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
function maskHostUrl(value?: string | null) {
|
|
374
|
+
if (!value) return "";
|
|
375
|
+
|
|
376
|
+
const text = String(value);
|
|
377
|
+
const protocolMatch = text.match(/^([a-z][a-z\d+\-.]*:\/\/)(.*)$/i);
|
|
378
|
+
const prefix = protocolMatch?.[1] ?? "";
|
|
379
|
+
const rest = protocolMatch?.[2] ?? text;
|
|
380
|
+
|
|
381
|
+
if (rest.length <= 8) return `${prefix}${"*".repeat(rest.length)}`;
|
|
382
|
+
|
|
383
|
+
return `${prefix}${rest.slice(0, 3)}****${rest.slice(-3)}`;
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
const camera = ref<TSiteCamera>({
|
|
371
387
|
site: "",
|
|
372
388
|
host: "",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
density="comfortable"
|
|
53
53
|
divided
|
|
54
54
|
variant="outlined"
|
|
55
|
-
|
|
55
|
+
class="bg-white"
|
|
56
|
+
style="border-radius: 8px; border-color: #d9e1e8;"
|
|
56
57
|
>
|
|
57
58
|
<v-btn
|
|
58
59
|
v-for="range in rangeOptions"
|
|
@@ -60,6 +61,11 @@
|
|
|
60
61
|
:value="range.value"
|
|
61
62
|
class="text-none"
|
|
62
63
|
size="small"
|
|
64
|
+
:style="
|
|
65
|
+
selectedRange === range.value
|
|
66
|
+
? { backgroundColor: '#e2e8f0', color: '#0b2f47', fontWeight: '600' }
|
|
67
|
+
: { backgroundColor: 'transparent', color: '#64748b' }
|
|
68
|
+
"
|
|
63
69
|
>
|
|
64
70
|
{{ range.label }}
|
|
65
71
|
</v-btn>
|
|
@@ -74,9 +80,16 @@
|
|
|
74
80
|
class="mb-4"
|
|
75
81
|
/>
|
|
76
82
|
|
|
77
|
-
<v-row dense class="mb-1">
|
|
78
|
-
<v-col
|
|
79
|
-
|
|
83
|
+
<v-row dense class="mb-1 align-stretch">
|
|
84
|
+
<v-col
|
|
85
|
+
v-for="card in visibleKpiCards"
|
|
86
|
+
:key="card.key"
|
|
87
|
+
cols="12"
|
|
88
|
+
sm="6"
|
|
89
|
+
md="4"
|
|
90
|
+
class="d-flex"
|
|
91
|
+
>
|
|
92
|
+
<v-card flat border rounded="lg" class="pa-4 flex-grow-1" style="min-height: 158px">
|
|
80
93
|
<div class="d-flex align-start justify-space-between">
|
|
81
94
|
<div
|
|
82
95
|
class="d-inline-flex align-center justify-center rounded-lg"
|
|
@@ -121,39 +134,58 @@
|
|
|
121
134
|
</v-menu>
|
|
122
135
|
</div>
|
|
123
136
|
|
|
124
|
-
<div class="mt-6">
|
|
137
|
+
<div class="mt-6 w-100">
|
|
125
138
|
<p
|
|
126
139
|
class="text-caption font-weight-black text-uppercase text-blue-grey-darken-2 mb-3"
|
|
127
140
|
>
|
|
128
141
|
{{ card.title }}
|
|
129
142
|
</p>
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
card.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
143
|
+
<template
|
|
144
|
+
v-if="
|
|
145
|
+
card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
|
|
146
|
+
"
|
|
147
|
+
>
|
|
148
|
+
<div class="d-flex flex-column ga-2 mt-2">
|
|
149
|
+
<div
|
|
150
|
+
v-for="item in card.raw.items"
|
|
151
|
+
:key="item._id || item.name"
|
|
152
|
+
class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium"
|
|
153
|
+
>
|
|
154
|
+
<span>{{ item.name }}</span>
|
|
155
|
+
<span class="text-blue-grey">Balance: {{ item.qty }}</span>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</template>
|
|
159
|
+
<template v-else>
|
|
160
|
+
<div class="d-flex align-center mb-3" style="min-height: 31px">
|
|
161
|
+
<span
|
|
162
|
+
class="text-h5 font-weight-black text-blue-grey-darken-4"
|
|
163
|
+
>{{ card.value }}</span
|
|
164
|
+
>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="d-flex align-center flex-wrap ga-2">
|
|
167
|
+
<span v-if="card.meta" class="text-caption text-blue-grey">{{
|
|
168
|
+
card.meta
|
|
169
|
+
}}</span>
|
|
170
|
+
<v-chip
|
|
171
|
+
:color="card.percentage >= 0 ? 'success' : 'error'"
|
|
172
|
+
size="x-small"
|
|
173
|
+
variant="flat"
|
|
174
|
+
class="font-weight-bold"
|
|
175
|
+
>
|
|
176
|
+
<v-icon
|
|
177
|
+
:icon="
|
|
178
|
+
card.percentage >= 0
|
|
179
|
+
? 'mdi-arrow-up-right'
|
|
180
|
+
: 'mdi-arrow-down-right'
|
|
181
|
+
"
|
|
182
|
+
size="13"
|
|
183
|
+
start
|
|
184
|
+
/>
|
|
185
|
+
{{ Math.abs(card.percentage) }}%
|
|
186
|
+
</v-chip>
|
|
187
|
+
</div>
|
|
188
|
+
</template>
|
|
157
189
|
</div>
|
|
158
190
|
</v-card>
|
|
159
191
|
</v-col>
|
|
@@ -172,19 +204,18 @@
|
|
|
172
204
|
</h2>
|
|
173
205
|
</div>
|
|
174
206
|
|
|
175
|
-
<div v-if="hasChartData" class="mt-4">
|
|
207
|
+
<div v-if="hasChartData" class="mt-4" ref="chartContainerRef">
|
|
176
208
|
<svg
|
|
177
|
-
viewBox="0 0
|
|
178
|
-
preserveAspectRatio="none"
|
|
209
|
+
:viewBox="`0 0 ${chartWidth} 260`"
|
|
179
210
|
class="d-block w-100"
|
|
180
|
-
style="height: 260px"
|
|
211
|
+
style="height: auto; max-height: 260px"
|
|
181
212
|
>
|
|
182
213
|
<line
|
|
183
214
|
v-for="tick in chartTicks"
|
|
184
215
|
:key="tick"
|
|
185
216
|
x1="48"
|
|
186
217
|
:y1="tick"
|
|
187
|
-
x2="
|
|
218
|
+
:x2="chartWidth - 20"
|
|
188
219
|
:y2="tick"
|
|
189
220
|
style="stroke: #e9eef2; stroke-width: 1"
|
|
190
221
|
/>
|
|
@@ -320,13 +351,7 @@
|
|
|
320
351
|
<div
|
|
321
352
|
class="d-flex align-center ga-2 text-caption text-blue-grey"
|
|
322
353
|
>
|
|
323
|
-
<
|
|
324
|
-
size="30"
|
|
325
|
-
color="blue-grey-lighten-4"
|
|
326
|
-
class="text-caption font-weight-bold text-blue-grey-darken-2"
|
|
327
|
-
>
|
|
328
|
-
{{ getInitials(item.person) }}
|
|
329
|
-
</v-avatar>
|
|
354
|
+
<AvatarMain :name="item.person" :size="30" />
|
|
330
355
|
<span class="text-truncate" style="max-width: 100px">{{
|
|
331
356
|
item.person
|
|
332
357
|
}}</span>
|
|
@@ -375,14 +400,8 @@
|
|
|
375
400
|
rounded="lg"
|
|
376
401
|
class="pa-3"
|
|
377
402
|
>
|
|
378
|
-
<div class="d-flex align-
|
|
379
|
-
<
|
|
380
|
-
size="34"
|
|
381
|
-
color="blue-grey-lighten-4"
|
|
382
|
-
class="text-caption font-weight-bold text-blue-grey-darken-2 flex-shrink-0"
|
|
383
|
-
>
|
|
384
|
-
{{ getInitials(item.name) }}
|
|
385
|
-
</v-avatar>
|
|
403
|
+
<div class="d-flex align-start ga-3">
|
|
404
|
+
<AvatarMain :name="item.name" :size="32" />
|
|
386
405
|
<div class="flex-grow-1" style="min-width: 0">
|
|
387
406
|
<div
|
|
388
407
|
class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
|
|
@@ -415,59 +434,37 @@
|
|
|
415
434
|
action-label="View All"
|
|
416
435
|
@action="goToDashboardRoute('feedbacks')"
|
|
417
436
|
>
|
|
418
|
-
<div v-if="feedbackItems.length" class="
|
|
419
|
-
<v-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
</div>
|
|
434
|
-
<div class="text-caption text-blue-grey mt-1 text-truncate">
|
|
435
|
-
{{ item.subtitle }}
|
|
436
|
-
</div>
|
|
437
|
+
<div v-if="feedbackItems.length" class="figma-list mt-2">
|
|
438
|
+
<div v-for="item in feedbackItems" :key="item.id" class="d-flex align-center justify-space-between" style="padding: 16px 20px; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 12px; background-color: #ffffff; box-shadow: 0 1px 2px rgba(0,0,0,0.02);">
|
|
439
|
+
<div style="flex: 1; min-width: 140px;">
|
|
440
|
+
<strong style="display: block; font-size: 15px; font-weight: 700; color: #0b2f47; margin-bottom: 4px;">{{ item.title }}</strong>
|
|
441
|
+
<span style="font-size: 13px; color: #94a3b8;">{{ item.subtitle }}</span>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<div style="flex: 1; display: flex; align-items: center; gap: 12px;">
|
|
445
|
+
<AvatarMain :name="item.person" :size="36" />
|
|
446
|
+
<span style="font-size: 14px; color: #64748b;">{{ item.person }}</span>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
<div style="flex: 1; display: flex; justify-content: center;">
|
|
450
|
+
<div v-if="getStatusTone(item.status) === 'success'" style="background-color: #72e3a1; color: #0f5132; padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize;">
|
|
451
|
+
{{ item.status }}
|
|
437
452
|
</div>
|
|
438
|
-
<div
|
|
439
|
-
|
|
440
|
-
>
|
|
441
|
-
<v-avatar
|
|
442
|
-
size="30"
|
|
443
|
-
color="blue-grey-lighten-4"
|
|
444
|
-
class="text-caption font-weight-bold text-blue-grey-darken-2"
|
|
445
|
-
>
|
|
446
|
-
{{ getInitials(item.person) }}
|
|
447
|
-
</v-avatar>
|
|
448
|
-
<span class="text-truncate" style="max-width: 100px">{{
|
|
449
|
-
item.person
|
|
450
|
-
}}</span>
|
|
453
|
+
<div v-else-if="getStatusTone(item.status) === 'warning'" style="background-color: #fde68a; color: #92400e; padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize;">
|
|
454
|
+
{{ item.status }}
|
|
451
455
|
</div>
|
|
452
|
-
<v-
|
|
453
|
-
size="small"
|
|
454
|
-
:color="getStatusTone(item.status)"
|
|
455
|
-
variant="tonal"
|
|
456
|
-
label
|
|
457
|
-
>
|
|
456
|
+
<div v-else-if="getStatusTone(item.status) === 'error'" style="background-color: #fecaca; color: #b91c1c; padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize;">
|
|
458
457
|
{{ item.status }}
|
|
459
|
-
</
|
|
460
|
-
<v-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
size="small"
|
|
464
|
-
class="text-none font-weight-bold"
|
|
465
|
-
@click="goToListItem(item, 'feedbacks')"
|
|
466
|
-
>
|
|
467
|
-
View
|
|
468
|
-
</v-btn>
|
|
458
|
+
</div>
|
|
459
|
+
<div v-else style="background-color: #e2e8f0; color: #64748b; padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize;">
|
|
460
|
+
{{ item.status || 'To-Do' }}
|
|
461
|
+
</div>
|
|
469
462
|
</div>
|
|
470
|
-
|
|
463
|
+
|
|
464
|
+
<div style="flex: 0 0 auto; text-align: right;">
|
|
465
|
+
<v-btn variant="outlined" class="text-none font-weight-medium" size="small" color="#3b82f6" style="border-color: #e2e8f0; border-radius: 8px; padding: 0 16px; height: 32px;" @click="goToListItem(item, 'feedbacks')">View</v-btn>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
471
468
|
</div>
|
|
472
469
|
<DashboardEmptyState v-else />
|
|
473
470
|
</DashboardPanel>
|
|
@@ -598,6 +595,7 @@ type DashboardMetric = {
|
|
|
598
595
|
outOfStock?: number;
|
|
599
596
|
completed?: number;
|
|
600
597
|
total?: number;
|
|
598
|
+
items?: { name: string; qty: number }[];
|
|
601
599
|
};
|
|
602
600
|
|
|
603
601
|
type DashboardListItem = {
|
|
@@ -693,13 +691,6 @@ const defaultCardValues: TDashboardCardValue[] = [
|
|
|
693
691
|
icon: "mdi-briefcase",
|
|
694
692
|
color: "#0b2f47",
|
|
695
693
|
},
|
|
696
|
-
{
|
|
697
|
-
key: "supplyAlert",
|
|
698
|
-
periodKey: "period",
|
|
699
|
-
title: "Supply Alert",
|
|
700
|
-
icon: "mdi-alert",
|
|
701
|
-
color: "#d32f2f",
|
|
702
|
-
},
|
|
703
694
|
{
|
|
704
695
|
key: "taskCompleted",
|
|
705
696
|
periodKey: "period",
|
|
@@ -707,6 +698,13 @@ const defaultCardValues: TDashboardCardValue[] = [
|
|
|
707
698
|
icon: "mdi-clipboard-check-outline",
|
|
708
699
|
color: "#f4a340",
|
|
709
700
|
},
|
|
701
|
+
{
|
|
702
|
+
key: "supplyAlert",
|
|
703
|
+
periodKey: "period",
|
|
704
|
+
title: "Supply Alert",
|
|
705
|
+
icon: "mdi-alert",
|
|
706
|
+
color: "#d32f2f",
|
|
707
|
+
},
|
|
710
708
|
];
|
|
711
709
|
|
|
712
710
|
const landscapeCardValues: TDashboardCardValue[] = [
|
|
@@ -717,13 +715,6 @@ const landscapeCardValues: TDashboardCardValue[] = [
|
|
|
717
715
|
icon: "mdi-briefcase",
|
|
718
716
|
color: "#0b2f47",
|
|
719
717
|
},
|
|
720
|
-
{
|
|
721
|
-
key: "supplyAlert",
|
|
722
|
-
periodKey: "period",
|
|
723
|
-
title: "Supply Alert",
|
|
724
|
-
icon: "mdi-alert",
|
|
725
|
-
color: "#d32f2f",
|
|
726
|
-
},
|
|
727
718
|
{
|
|
728
719
|
key: "taskCompleted",
|
|
729
720
|
periodKey: "period",
|
|
@@ -732,11 +723,11 @@ const landscapeCardValues: TDashboardCardValue[] = [
|
|
|
732
723
|
color: "#f4a340",
|
|
733
724
|
},
|
|
734
725
|
{
|
|
735
|
-
key: "
|
|
726
|
+
key: "supplyAlert",
|
|
736
727
|
periodKey: "period",
|
|
737
|
-
title: "
|
|
738
|
-
icon: "mdi-
|
|
739
|
-
color: "#
|
|
728
|
+
title: "Supply Alert",
|
|
729
|
+
icon: "mdi-alert",
|
|
730
|
+
color: "#d32f2f",
|
|
740
731
|
},
|
|
741
732
|
];
|
|
742
733
|
|
|
@@ -799,10 +790,14 @@ const { data: weeklyActivityReq, pending: loadingWeeklyActivity } =
|
|
|
799
790
|
`get-dashboard-weekly-activity-${props.site}-${props.serviceType}`,
|
|
800
791
|
() =>
|
|
801
792
|
props.serviceType
|
|
802
|
-
? getDashboardHygieneWeeklyActivity(
|
|
793
|
+
? getDashboardHygieneWeeklyActivity(
|
|
794
|
+
props.site,
|
|
795
|
+
props.serviceType,
|
|
796
|
+
currentPeriod.value
|
|
797
|
+
)
|
|
803
798
|
: Promise.resolve(null),
|
|
804
799
|
{
|
|
805
|
-
watch: [() => props.site, () => props.serviceType],
|
|
800
|
+
watch: [() => props.site, () => props.serviceType, currentPeriod],
|
|
806
801
|
}
|
|
807
802
|
);
|
|
808
803
|
|
|
@@ -862,7 +857,13 @@ const finalDashboardData = computed(() => {
|
|
|
862
857
|
let weeklyActivity = weeklyActivityReq.value ?? [];
|
|
863
858
|
const hasNoActivity =
|
|
864
859
|
!weeklyActivity.length ||
|
|
865
|
-
weeklyActivity.every(
|
|
860
|
+
weeklyActivity.every(
|
|
861
|
+
(item: any) =>
|
|
862
|
+
!item.workOrder &&
|
|
863
|
+
!item.taskCompleted &&
|
|
864
|
+
!item.openWorkOrder &&
|
|
865
|
+
!item.closedWorkOrder
|
|
866
|
+
);
|
|
866
867
|
|
|
867
868
|
if (hasNoActivity && currentPeriod.value === "thisWeek") {
|
|
868
869
|
weeklyActivity = [
|
|
@@ -895,9 +896,23 @@ const finalDashboardData = computed(() => {
|
|
|
895
896
|
percentage: 0,
|
|
896
897
|
},
|
|
897
898
|
activityChart: {
|
|
898
|
-
labels: weeklyActivity.map((item: any) => item.day),
|
|
899
|
-
workOrders: weeklyActivity.map(
|
|
900
|
-
|
|
899
|
+
labels: weeklyActivity.map((item: any) => item.day || item.label),
|
|
900
|
+
workOrders: weeklyActivity.map(
|
|
901
|
+
(item: any) => item.workOrder ?? item.openWorkOrder
|
|
902
|
+
),
|
|
903
|
+
taskCompleted: weeklyActivity.map(
|
|
904
|
+
(item: any) => item.taskCompleted ?? item.closedWorkOrder
|
|
905
|
+
),
|
|
906
|
+
openWorkOrder: weeklyActivity.some(
|
|
907
|
+
(item: any) => item.openWorkOrder !== undefined
|
|
908
|
+
)
|
|
909
|
+
? weeklyActivity.map((item: any) => item.openWorkOrder)
|
|
910
|
+
: undefined,
|
|
911
|
+
closedWorkOrder: weeklyActivity.some(
|
|
912
|
+
(item: any) => item.closedWorkOrder !== undefined
|
|
913
|
+
)
|
|
914
|
+
? weeklyActivity.map((item: any) => item.closedWorkOrder)
|
|
915
|
+
: undefined,
|
|
901
916
|
},
|
|
902
917
|
taskScheduleItems: taskScheduleReq.value?.items ?? [],
|
|
903
918
|
attendanceItems: attendanceReq.value?.items ?? [],
|
|
@@ -923,6 +938,28 @@ const snackbar = reactive({
|
|
|
923
938
|
text: "",
|
|
924
939
|
});
|
|
925
940
|
|
|
941
|
+
const chartContainerRef = ref<HTMLElement | null>(null);
|
|
942
|
+
const chartWidth = ref(920);
|
|
943
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
944
|
+
|
|
945
|
+
watch(chartContainerRef, (el) => {
|
|
946
|
+
if (resizeObserver) {
|
|
947
|
+
resizeObserver.disconnect();
|
|
948
|
+
}
|
|
949
|
+
if (el && window.ResizeObserver) {
|
|
950
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
951
|
+
if (entries[0] && entries[0].contentRect.width > 0) {
|
|
952
|
+
chartWidth.value = entries[0].contentRect.width;
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
resizeObserver.observe(el);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
onUnmounted(() => {
|
|
960
|
+
if (resizeObserver) resizeObserver.disconnect();
|
|
961
|
+
});
|
|
962
|
+
|
|
926
963
|
const rangeOptions = [
|
|
927
964
|
{ label: "Today", value: "Today" },
|
|
928
965
|
{ label: "7d", value: "This Week" },
|
|
@@ -1078,6 +1115,7 @@ const kpiCards = computed(() =>
|
|
|
1078
1115
|
value: typeof value === "string" ? value : formatNumber(value),
|
|
1079
1116
|
percentage: toNumber(metric.percentage),
|
|
1080
1117
|
meta: getMetricMeta(metric),
|
|
1118
|
+
raw: metric,
|
|
1081
1119
|
filter:
|
|
1082
1120
|
mapping.key === "visitors" && isSecurityDashboard.value
|
|
1083
1121
|
? {
|
|
@@ -1112,8 +1150,14 @@ const chartLabels = computed<string[]>(() => {
|
|
|
1112
1150
|
|
|
1113
1151
|
if (selectedRange.value === "Today")
|
|
1114
1152
|
return ["12 AM", "4 AM", "8 AM", "12 PM", "4 PM", "8 PM"];
|
|
1115
|
-
if (selectedRange.value === "This Month")
|
|
1116
|
-
|
|
1153
|
+
if (selectedRange.value === "This Month") {
|
|
1154
|
+
const daysInMonth = new Date(
|
|
1155
|
+
new Date().getFullYear(),
|
|
1156
|
+
new Date().getMonth() + 1,
|
|
1157
|
+
0
|
|
1158
|
+
).getDate();
|
|
1159
|
+
return Array.from({ length: daysInMonth }, (_, i) => String(i + 1));
|
|
1160
|
+
}
|
|
1117
1161
|
return ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
1118
1162
|
});
|
|
1119
1163
|
const primarySeries = computed(() =>
|
|
@@ -1123,10 +1167,10 @@ const secondarySeries = computed(() =>
|
|
|
1123
1167
|
normalizeSeries(activityChart.value.secondary, chartLabels.value.length)
|
|
1124
1168
|
);
|
|
1125
1169
|
const primaryLegendLabel = computed(
|
|
1126
|
-
() =>
|
|
1170
|
+
() => "Closed Work Order"
|
|
1127
1171
|
);
|
|
1128
1172
|
const secondaryLegendLabel = computed(
|
|
1129
|
-
() =>
|
|
1173
|
+
() => "Open Work Order"
|
|
1130
1174
|
);
|
|
1131
1175
|
const chartMax = computed(() =>
|
|
1132
1176
|
Math.max(1, ...primarySeries.value, ...secondarySeries.value)
|
|
@@ -1140,11 +1184,11 @@ const chartYLabels = computed(() => {
|
|
|
1140
1184
|
const max = chartMax.value;
|
|
1141
1185
|
return [
|
|
1142
1186
|
{ text: "0", y: 220 },
|
|
1143
|
-
{ text:
|
|
1144
|
-
{ text:
|
|
1145
|
-
{ text:
|
|
1146
|
-
{ text:
|
|
1147
|
-
{ text:
|
|
1187
|
+
{ text: `${Math.round(max * 0.2)}`, y: 181.6 },
|
|
1188
|
+
{ text: `${Math.round(max * 0.4)}`, y: 143.2 },
|
|
1189
|
+
{ text: `${Math.round(max * 0.6)}`, y: 104.8 },
|
|
1190
|
+
{ text: `${Math.round(max * 0.8)}`, y: 66.4 },
|
|
1191
|
+
{ text: `${Math.round(max)}`, y: 28 },
|
|
1148
1192
|
];
|
|
1149
1193
|
});
|
|
1150
1194
|
|
|
@@ -1170,10 +1214,11 @@ const secondaryPath = computed(() => buildSmoothPath(secondaryDots.value));
|
|
|
1170
1214
|
const chartAxisLabels = computed(() => {
|
|
1171
1215
|
const labels = chartLabels.value;
|
|
1172
1216
|
const count = Math.max(1, labels.length - 1);
|
|
1217
|
+
const width = chartWidth.value;
|
|
1173
1218
|
|
|
1174
1219
|
return labels.map((label, index) => ({
|
|
1175
1220
|
text: label,
|
|
1176
|
-
x: 54 + (
|
|
1221
|
+
x: 54 + ((width - 74) / count) * index,
|
|
1177
1222
|
}));
|
|
1178
1223
|
});
|
|
1179
1224
|
|
|
@@ -1339,18 +1384,33 @@ function normalizeActivityChart(source: any) {
|
|
|
1339
1384
|
const labels = Array.isArray(source?.labels)
|
|
1340
1385
|
? source.labels.map((label: any) => String(label))
|
|
1341
1386
|
: [];
|
|
1387
|
+
|
|
1388
|
+
const secondary = extractChartSeries(source, [
|
|
1389
|
+
"closedWorkOrder",
|
|
1390
|
+
"closedWorkOrders",
|
|
1391
|
+
"closed_work_orders",
|
|
1392
|
+
]);
|
|
1393
|
+
|
|
1394
|
+
const hasClosedSeries = secondary.length > 0;
|
|
1395
|
+
|
|
1342
1396
|
const primary = extractChartSeries(source, [
|
|
1397
|
+
"openWorkOrder",
|
|
1398
|
+
"openWorkOrders",
|
|
1399
|
+
"open_work_orders",
|
|
1343
1400
|
"workOrders",
|
|
1344
1401
|
"workOrder",
|
|
1345
1402
|
"work_orders",
|
|
1346
1403
|
]);
|
|
1347
|
-
|
|
1404
|
+
|
|
1405
|
+
const secondaryCandidate = hasClosedSeries
|
|
1406
|
+
? { label: "Closed Work Order", series: secondary }
|
|
1407
|
+
: getSecondaryChartSeries(source);
|
|
1348
1408
|
|
|
1349
1409
|
return {
|
|
1350
1410
|
labels,
|
|
1351
1411
|
primary,
|
|
1352
1412
|
secondary: secondaryCandidate.series,
|
|
1353
|
-
primaryLabel: "Work Order",
|
|
1413
|
+
primaryLabel: hasClosedSeries ? "Open Work Order" : "Work Order",
|
|
1354
1414
|
secondaryLabel: secondaryCandidate.label,
|
|
1355
1415
|
};
|
|
1356
1416
|
}
|
|
@@ -1401,9 +1461,10 @@ function normalizeSeries(value: any, targetLength: number) {
|
|
|
1401
1461
|
|
|
1402
1462
|
function buildChartDots(series: number[]) {
|
|
1403
1463
|
const count = Math.max(1, series.length - 1);
|
|
1464
|
+
const width = chartWidth.value;
|
|
1404
1465
|
|
|
1405
1466
|
return series.map((value, index) => ({
|
|
1406
|
-
x: 54 + (
|
|
1467
|
+
x: 54 + ((width - 74) / count) * index,
|
|
1407
1468
|
y: 220 - (toNumber(value) / chartMax.value) * 184,
|
|
1408
1469
|
}));
|
|
1409
1470
|
}
|
|
@@ -1788,18 +1849,7 @@ function goToListItem(item: TaskItem | FeedbackItem, routeKey: string) {
|
|
|
1788
1849
|
router.push(`/${props.orgId}/${props.site}/${path}/${id}`);
|
|
1789
1850
|
}
|
|
1790
1851
|
|
|
1791
|
-
|
|
1792
|
-
const parts = String(name || "")
|
|
1793
|
-
.trim()
|
|
1794
|
-
.split(/\s+/)
|
|
1795
|
-
.filter(Boolean);
|
|
1796
|
-
if (!parts.length) return "?";
|
|
1797
|
-
return parts
|
|
1798
|
-
.slice(0, 2)
|
|
1799
|
-
.map((part) => part[0])
|
|
1800
|
-
.join("")
|
|
1801
|
-
.toUpperCase();
|
|
1802
|
-
}
|
|
1852
|
+
|
|
1803
1853
|
|
|
1804
1854
|
function formatNumber(value: number) {
|
|
1805
1855
|
return new Intl.NumberFormat("en-US").format(toNumber(value));
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<v-autocomplete v-model="selectedPass" v-model:search="passInput" :hide-no-data="false"
|
|
23
23
|
class="mt-3" :items="passItemsFilteredFinal" item-title="prefixAndName" item-value="_id"
|
|
24
24
|
label="Pass (optional)" variant="outlined" density="compact" :error-messages="errorScanPassMessage"
|
|
25
|
-
persistent-hint small-chips>
|
|
25
|
+
persistent-hint small-chips @click="fetchPasses">
|
|
26
26
|
<template v-slot:no-data>
|
|
27
27
|
<v-list-item density="compact">
|
|
28
28
|
<v-list-item-title v-if="passInput">
|
|
@@ -160,20 +160,28 @@ const passTypesComputed = computed(() => {
|
|
|
160
160
|
}
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
163
|
+
const isPassListLoading = ref(false);
|
|
164
|
+
|
|
165
|
+
async function fetchPasses() {
|
|
166
|
+
// load pass list
|
|
167
|
+
passItems.value = [];
|
|
168
|
+
isPassListLoading.value = true;
|
|
169
|
+
try {
|
|
170
|
+
const result = await getPassKeysByPageSearch({
|
|
171
|
+
page: 1,
|
|
172
|
+
sites: [props.site],
|
|
173
|
+
limit: 10000,
|
|
174
|
+
passTypes: passTypesComputed.value,
|
|
175
|
+
statuses: ["Available"],
|
|
176
|
+
sortBy: { column: "prefixAndName", order: "1" },
|
|
177
|
+
});
|
|
178
|
+
passItems.value = result?.items;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
showMessage(errorConverter(error as string), "error");
|
|
181
|
+
} finally {
|
|
182
|
+
isPassListLoading.value = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
177
185
|
|
|
178
186
|
const passItemsFilteredFinal = computed(() => {
|
|
179
187
|
return passItems.value.filter((item: TPassKey) => {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
</v-col>
|
|
61
61
|
|
|
62
62
|
<!-- Attachment -->
|
|
63
|
-
<v-col cols="12" class="px-6 pt-3 pb-4">
|
|
63
|
+
<!-- <v-col cols="12" class="px-6 pt-3 pb-4">
|
|
64
64
|
<InputLabel title="Attachment (Optional)" />
|
|
65
65
|
|
|
66
66
|
<div v-if="attachmentId" class="d-flex align-center" style="gap: 8px">
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
<span v-if="attachmentError" class="text-caption text-error d-block mt-1">
|
|
131
131
|
{{ attachmentError }}
|
|
132
132
|
</span>
|
|
133
|
-
</v-col>
|
|
133
|
+
</v-col> -->
|
|
134
134
|
|
|
135
135
|
<v-col v-if="message" cols="12" class="px-6">
|
|
136
136
|
<span class="text-caption text-error">{{ message }}</span>
|