@iservice365/layer-common 1.5.5 → 1.5.6
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 +6 -0
- package/components/DashboardPlaceholder.vue +355 -100
- 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/WorkOrder/Main.vue +38 -6
- 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
package/CHANGELOG.md
CHANGED
|
@@ -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
|
|
|
@@ -208,7 +210,24 @@
|
|
|
208
210
|
Feedback Tickets
|
|
209
211
|
</h3>
|
|
210
212
|
</v-col>
|
|
211
|
-
<v-col
|
|
213
|
+
<v-col
|
|
214
|
+
cols="12"
|
|
215
|
+
md="6"
|
|
216
|
+
class="d-flex flex-column flex-md-row justify-md-end gap-2"
|
|
217
|
+
>
|
|
218
|
+
<v-select
|
|
219
|
+
v-model="selectedService"
|
|
220
|
+
:items="serviceList"
|
|
221
|
+
item-title="name"
|
|
222
|
+
item-value="_id"
|
|
223
|
+
label="Select Service"
|
|
224
|
+
density="compact"
|
|
225
|
+
hide-details
|
|
226
|
+
variant="outlined"
|
|
227
|
+
style="max-width: 200px"
|
|
228
|
+
clearable
|
|
229
|
+
class="pr-4"
|
|
230
|
+
></v-select>
|
|
212
231
|
<v-select
|
|
213
232
|
v-model="feedbackPeriod"
|
|
214
233
|
:items="['This Week', 'This Month', 'This Year']"
|
|
@@ -353,8 +372,23 @@
|
|
|
353
372
|
</p>
|
|
354
373
|
</div>
|
|
355
374
|
|
|
356
|
-
<!--
|
|
357
|
-
<div class="mb-4">
|
|
375
|
+
<!-- Dropdowns -->
|
|
376
|
+
<div class="mb-4 d-flex flex-column gap-2">
|
|
377
|
+
<!-- Facility/Building selector for Bookings only -->
|
|
378
|
+
<v-select
|
|
379
|
+
v-if="pie.id === 1"
|
|
380
|
+
v-model="selectedBooking"
|
|
381
|
+
:items="bookingsList"
|
|
382
|
+
item-title="name"
|
|
383
|
+
item-value="_id"
|
|
384
|
+
label="Select Facility"
|
|
385
|
+
density="compact"
|
|
386
|
+
hide-details
|
|
387
|
+
variant="outlined"
|
|
388
|
+
clearable
|
|
389
|
+
class="pb-4"
|
|
390
|
+
></v-select>
|
|
391
|
+
<!-- Period selector -->
|
|
358
392
|
<v-select
|
|
359
393
|
:model-value="pie.period"
|
|
360
394
|
:items="['This Week', 'This Month', 'This Year']"
|
|
@@ -449,14 +483,41 @@
|
|
|
449
483
|
</template>
|
|
450
484
|
|
|
451
485
|
<script setup lang="ts">
|
|
452
|
-
import { ref, computed } from "vue";
|
|
453
486
|
import { useDisplay } from "vuetify";
|
|
454
487
|
|
|
455
488
|
const display = useDisplay();
|
|
489
|
+
const { getServiceProviderNames } = useServiceProvider();
|
|
490
|
+
const { getAll: getAllBuildings } = useBuilding();
|
|
491
|
+
const { getSiteById } = useSite();
|
|
492
|
+
const route = useRoute();
|
|
493
|
+
|
|
494
|
+
// Get siteId from route params
|
|
495
|
+
const siteId = computed(() => (route.params.site as string) || "");
|
|
496
|
+
|
|
497
|
+
// Reactive site data that updates when siteId changes
|
|
498
|
+
const siteData = ref<any>(null);
|
|
499
|
+
|
|
500
|
+
// Fetch site data whenever siteId changes
|
|
501
|
+
watchEffect(async () => {
|
|
502
|
+
if (siteId.value) {
|
|
503
|
+
try {
|
|
504
|
+
const site = await getSiteById(siteId.value);
|
|
505
|
+
siteData.value = site;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.error("Error fetching site:", error);
|
|
508
|
+
siteData.value = null;
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
siteData.value = null;
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Get current site name from fetched site data
|
|
516
|
+
const currentSiteName = computed(() => siteData.value?.name || "");
|
|
456
517
|
|
|
457
518
|
// Type definitions
|
|
458
|
-
type Period =
|
|
459
|
-
type CardType =
|
|
519
|
+
type Period = "This Week" | "This Month" | "This Year";
|
|
520
|
+
type CardType = "guest" | "pickup" | "dropoff" | "contractor" | "delivery";
|
|
460
521
|
|
|
461
522
|
interface CardPeriods {
|
|
462
523
|
guest: Period;
|
|
@@ -477,6 +538,14 @@ const bookingsPeriod = ref<Period>("This Week");
|
|
|
477
538
|
const buildingPeriod = ref<Period>("This Week");
|
|
478
539
|
const workOrdersPeriod = ref<Period>("This Week");
|
|
479
540
|
|
|
541
|
+
// Service provider states
|
|
542
|
+
const selectedService = ref<any>(null);
|
|
543
|
+
const serviceList = ref<Array<{ _id: string; name: string }>>([]);
|
|
544
|
+
|
|
545
|
+
// Booking filter - list of facilities/buildings
|
|
546
|
+
const selectedBooking = ref<any>(null);
|
|
547
|
+
const bookingsList = ref<Array<{ _id: string; name: string }>>([]);
|
|
548
|
+
|
|
480
549
|
// Individual card periods
|
|
481
550
|
const cardPeriods = ref<CardPeriods>({
|
|
482
551
|
guest: "This Week",
|
|
@@ -487,7 +556,7 @@ const cardPeriods = ref<CardPeriods>({
|
|
|
487
556
|
});
|
|
488
557
|
|
|
489
558
|
// Data sets for different periods
|
|
490
|
-
const dataByPeriod = {
|
|
559
|
+
const dataByPeriod: Record<string, any> = {
|
|
491
560
|
"This Week": {
|
|
492
561
|
guest: { value: "156", percentage: "12.5 %", chipColor: "success" },
|
|
493
562
|
pickup: { value: "42", percentage: "8.3 %", chipColor: "success" },
|
|
@@ -799,60 +868,83 @@ const dataByPeriod = {
|
|
|
799
868
|
},
|
|
800
869
|
};
|
|
801
870
|
|
|
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
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
]
|
|
871
|
+
// Computed properties for reactive data with site-based multiplier
|
|
872
|
+
const countCardList = computed(() => {
|
|
873
|
+
// Calculate multiplier based on siteId (different sites get different multipliers)
|
|
874
|
+
const siteHash = siteId.value
|
|
875
|
+
? siteId.value.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
|
876
|
+
: 0;
|
|
877
|
+
const siteMultiplier = siteId.value ? 0.6 + (siteHash % 40) / 100 : 1; // Between 0.6 and 1.0
|
|
878
|
+
|
|
879
|
+
const applyMultiplier = (value: string) => {
|
|
880
|
+
const numValue = parseInt(value.replace(/,/g, ""));
|
|
881
|
+
const newValue = Math.round(numValue * siteMultiplier);
|
|
882
|
+
return newValue >= 1000 ? newValue.toLocaleString() : newValue.toString();
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
return [
|
|
886
|
+
{
|
|
887
|
+
id: 1,
|
|
888
|
+
label: "Guest",
|
|
889
|
+
value: applyMultiplier(dataByPeriod[cardPeriods.value.guest].guest.value),
|
|
890
|
+
icon: "mdi-account-multiple",
|
|
891
|
+
color: "primary",
|
|
892
|
+
percentage: dataByPeriod[cardPeriods.value.guest].guest.percentage,
|
|
893
|
+
chipColor: dataByPeriod[cardPeriods.value.guest].guest.chipColor,
|
|
894
|
+
period: cardPeriods.value.guest,
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
id: 2,
|
|
898
|
+
label: "Pickup",
|
|
899
|
+
value: applyMultiplier(
|
|
900
|
+
dataByPeriod[cardPeriods.value.pickup].pickup.value
|
|
901
|
+
),
|
|
902
|
+
icon: "mdi-package",
|
|
903
|
+
color: "error",
|
|
904
|
+
percentage: dataByPeriod[cardPeriods.value.pickup].pickup.percentage,
|
|
905
|
+
chipColor: dataByPeriod[cardPeriods.value.pickup].pickup.chipColor,
|
|
906
|
+
period: cardPeriods.value.pickup,
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
id: 3,
|
|
910
|
+
label: "Drop-off",
|
|
911
|
+
value: applyMultiplier(
|
|
912
|
+
dataByPeriod[cardPeriods.value.dropoff].dropoff.value
|
|
913
|
+
),
|
|
914
|
+
icon: "mdi-package-down",
|
|
915
|
+
color: "warning",
|
|
916
|
+
percentage: dataByPeriod[cardPeriods.value.dropoff].dropoff.percentage,
|
|
917
|
+
chipColor: dataByPeriod[cardPeriods.value.dropoff].dropoff.chipColor,
|
|
918
|
+
period: cardPeriods.value.dropoff,
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
id: 4,
|
|
922
|
+
label: "Contractor",
|
|
923
|
+
value: applyMultiplier(
|
|
924
|
+
dataByPeriod[cardPeriods.value.contractor].contractor.value
|
|
925
|
+
),
|
|
926
|
+
icon: "mdi-hard-hat",
|
|
927
|
+
color: "success",
|
|
928
|
+
percentage:
|
|
929
|
+
dataByPeriod[cardPeriods.value.contractor].contractor.percentage,
|
|
930
|
+
chipColor:
|
|
931
|
+
dataByPeriod[cardPeriods.value.contractor].contractor.chipColor,
|
|
932
|
+
period: cardPeriods.value.contractor,
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
id: 5,
|
|
936
|
+
label: "Delivery",
|
|
937
|
+
value: applyMultiplier(
|
|
938
|
+
dataByPeriod[cardPeriods.value.delivery].delivery.value
|
|
939
|
+
),
|
|
940
|
+
icon: "mdi-truck",
|
|
941
|
+
color: "grey",
|
|
942
|
+
percentage: dataByPeriod[cardPeriods.value.delivery].delivery.percentage,
|
|
943
|
+
chipColor: dataByPeriod[cardPeriods.value.delivery].delivery.chipColor,
|
|
944
|
+
period: cardPeriods.value.delivery,
|
|
945
|
+
},
|
|
946
|
+
];
|
|
947
|
+
});
|
|
856
948
|
|
|
857
949
|
// Computed reactive data
|
|
858
950
|
const visitorPoints = computed(
|
|
@@ -868,9 +960,34 @@ const visitorLabels = [
|
|
|
868
960
|
{ x: 1100, text: "Sat" },
|
|
869
961
|
];
|
|
870
962
|
|
|
871
|
-
const feedbackBars = computed(
|
|
872
|
-
|
|
873
|
-
|
|
963
|
+
const feedbackBars = computed(() => {
|
|
964
|
+
const baseFeedback = dataByPeriod[feedbackPeriod.value].feedback;
|
|
965
|
+
|
|
966
|
+
// If no service is selected, return the base data
|
|
967
|
+
if (!selectedService.value) {
|
|
968
|
+
return baseFeedback;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// If a service is selected, multiply values by a consistent factor
|
|
972
|
+
// Use the service ID to generate a consistent multiplier for that service
|
|
973
|
+
const serviceHash = selectedService.value
|
|
974
|
+
.split("")
|
|
975
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
976
|
+
const multiplier = 0.3 + (serviceHash % 50) / 100; // Between 0.3 and 0.8
|
|
977
|
+
|
|
978
|
+
return baseFeedback.map((bar: any) => {
|
|
979
|
+
const newValue = Math.round(bar.value * multiplier);
|
|
980
|
+
const newHeight = bar.height * multiplier;
|
|
981
|
+
const newY = 290 - newHeight;
|
|
982
|
+
|
|
983
|
+
return {
|
|
984
|
+
...bar,
|
|
985
|
+
value: newValue,
|
|
986
|
+
height: newHeight,
|
|
987
|
+
y: newY,
|
|
988
|
+
};
|
|
989
|
+
});
|
|
990
|
+
});
|
|
874
991
|
|
|
875
992
|
// Function to update individual card periods
|
|
876
993
|
const updateCardPeriod = (cardType: string, period: string) => {
|
|
@@ -886,6 +1003,19 @@ const updatePiePeriod = (pieId: number, period: string) => {
|
|
|
886
1003
|
else if (pieId === 3) workOrdersPeriod.value = validPeriod;
|
|
887
1004
|
};
|
|
888
1005
|
|
|
1006
|
+
// Function to fetch bookings data based on selected facility
|
|
1007
|
+
const fetchBookingsData = async () => {
|
|
1008
|
+
if (!selectedBooking.value) {
|
|
1009
|
+
// Reset to base data when no booking selected
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
// Watch for booking selection changes
|
|
1015
|
+
watch(selectedBooking, () => {
|
|
1016
|
+
fetchBookingsData();
|
|
1017
|
+
});
|
|
1018
|
+
|
|
889
1019
|
// Pie chart data by period
|
|
890
1020
|
const pieChartDataByPeriod = {
|
|
891
1021
|
"This Week": {
|
|
@@ -1070,67 +1200,192 @@ const pieChartDataByPeriod = {
|
|
|
1070
1200
|
};
|
|
1071
1201
|
|
|
1072
1202
|
// 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
|
-
|
|
1203
|
+
const pieChartList = computed(() => {
|
|
1204
|
+
// Get base bookings data
|
|
1205
|
+
const baseBookingsData = pieChartDataByPeriod[bookingsPeriod.value].bookings;
|
|
1206
|
+
let bookingsData = { ...baseBookingsData };
|
|
1207
|
+
|
|
1208
|
+
if (selectedBooking.value) {
|
|
1209
|
+
const bookingHash = selectedBooking.value
|
|
1210
|
+
.split("")
|
|
1211
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
1212
|
+
const multiplier = 0.35 + (bookingHash % 45) / 100; // Between 0.35 and 0.8
|
|
1213
|
+
const newTotal = Math.round(baseBookingsData.total * multiplier);
|
|
1214
|
+
|
|
1215
|
+
const segmentMultipliers = [
|
|
1216
|
+
0.8 + (bookingHash % 15) / 100, // approved: 0.8-0.95
|
|
1217
|
+
0.4 + (bookingHash % 30) / 100, // rejected: 0.4-0.7
|
|
1218
|
+
0.5 + (bookingHash % 25) / 100, // pending: 0.5-0.75
|
|
1219
|
+
];
|
|
1220
|
+
|
|
1221
|
+
const newSegments = baseBookingsData.segments.map(
|
|
1222
|
+
(seg: any, idx: number) => {
|
|
1223
|
+
const segMultiplier = segmentMultipliers[idx] || multiplier;
|
|
1224
|
+
const newPercentage = seg.percentage * segMultiplier;
|
|
1225
|
+
return {
|
|
1226
|
+
...seg,
|
|
1227
|
+
percentage: newPercentage,
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
);
|
|
1231
|
+
|
|
1232
|
+
// Normalize percentages to total 100% (make full circle)
|
|
1233
|
+
const totalPercentage = newSegments.reduce(
|
|
1234
|
+
(sum: number, seg: any) => sum + seg.percentage,
|
|
1235
|
+
0
|
|
1236
|
+
);
|
|
1237
|
+
const normalizedSegments = newSegments.map((seg: any) => ({
|
|
1238
|
+
...seg,
|
|
1239
|
+
percentage: (seg.percentage / totalPercentage) * 100,
|
|
1240
|
+
}));
|
|
1241
|
+
|
|
1242
|
+
// Recalculate rotations based on normalized percentages
|
|
1243
|
+
let currentRotation = -90;
|
|
1244
|
+
const adjustedSegments = normalizedSegments.map((seg: any) => {
|
|
1245
|
+
const segmentWithRotation = {
|
|
1246
|
+
...seg,
|
|
1247
|
+
rotation: currentRotation,
|
|
1248
|
+
};
|
|
1249
|
+
currentRotation += seg.percentage * 3.6; // 360 degrees / 100 percentage
|
|
1250
|
+
return segmentWithRotation;
|
|
1251
|
+
});
|
|
1102
1252
|
|
|
1103
|
-
//
|
|
1104
|
-
const
|
|
1253
|
+
// Calculate new center value based on the largest segment
|
|
1254
|
+
const maxSegment = adjustedSegments.reduce((max: any, seg: any) =>
|
|
1255
|
+
seg.percentage > max.percentage ? seg : max
|
|
1256
|
+
);
|
|
1257
|
+
const newCenterValue = `${Math.round(maxSegment.percentage)}%`;
|
|
1258
|
+
|
|
1259
|
+
bookingsData = {
|
|
1260
|
+
total: newTotal,
|
|
1261
|
+
centerValue: newCenterValue,
|
|
1262
|
+
segments: adjustedSegments,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
return [
|
|
1267
|
+
{
|
|
1268
|
+
id: 1,
|
|
1269
|
+
title: "Bookings",
|
|
1270
|
+
total: bookingsData.total,
|
|
1271
|
+
centerValue: bookingsData.centerValue,
|
|
1272
|
+
segments: bookingsData.segments,
|
|
1273
|
+
period: bookingsPeriod.value,
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
id: 2,
|
|
1277
|
+
title: "Building Mngm.",
|
|
1278
|
+
total: pieChartDataByPeriod[buildingPeriod.value].building.total,
|
|
1279
|
+
centerValue:
|
|
1280
|
+
pieChartDataByPeriod[buildingPeriod.value].building.centerValue,
|
|
1281
|
+
segments: pieChartDataByPeriod[buildingPeriod.value].building.segments,
|
|
1282
|
+
period: buildingPeriod.value,
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
id: 3,
|
|
1286
|
+
title: "Work Orders",
|
|
1287
|
+
total: pieChartDataByPeriod[workOrdersPeriod.value].workOrders.total,
|
|
1288
|
+
centerValue:
|
|
1289
|
+
pieChartDataByPeriod[workOrdersPeriod.value].workOrders.centerValue,
|
|
1290
|
+
segments:
|
|
1291
|
+
pieChartDataByPeriod[workOrdersPeriod.value].workOrders.segments,
|
|
1292
|
+
period: workOrdersPeriod.value,
|
|
1293
|
+
},
|
|
1294
|
+
];
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
// Function to fetch feedback data based on selected service
|
|
1298
|
+
const fetchFeedbackData = async () => {
|
|
1299
|
+
if (!selectedService.value) {
|
|
1300
|
+
// Reset to base data when no service selected
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// Watch for service selection changes
|
|
1306
|
+
watch(selectedService, () => {
|
|
1307
|
+
fetchFeedbackData();
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// Fetch service providers and buildings/facilities on mount
|
|
1311
|
+
onMounted(async () => {
|
|
1312
|
+
try {
|
|
1313
|
+
// Fetch service providers for feedback dropdown
|
|
1314
|
+
const serviceResponse = await getServiceProviderNames({ limit: 100 });
|
|
1315
|
+
if (serviceResponse && serviceResponse.items) {
|
|
1316
|
+
serviceList.value = serviceResponse.items;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Fetch buildings/facilities for bookings dropdown
|
|
1320
|
+
// Using buildings as facilities - you can adjust to use actual facilities API
|
|
1321
|
+
const buildingsResponse = await getAllBuildings({
|
|
1322
|
+
limit: 100,
|
|
1323
|
+
status: "active", // Only show active buildings
|
|
1324
|
+
});
|
|
1325
|
+
if (buildingsResponse && buildingsResponse.items) {
|
|
1326
|
+
bookingsList.value = buildingsResponse.items.map((building: any) => ({
|
|
1327
|
+
_id: building._id,
|
|
1328
|
+
name: building.name || building.block || `Building ${building._id}`,
|
|
1329
|
+
}));
|
|
1330
|
+
}
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
console.error("Error fetching data:", error);
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
// Base Facility Bookings Data
|
|
1337
|
+
const baseFacilityBookings = [
|
|
1105
1338
|
{
|
|
1106
1339
|
id: 1,
|
|
1107
1340
|
label: "Approved Bookings",
|
|
1108
|
-
value:
|
|
1341
|
+
value: 34,
|
|
1109
1342
|
icon: "mdi-calendar-check",
|
|
1110
1343
|
iconColor: "success",
|
|
1111
1344
|
},
|
|
1112
1345
|
{
|
|
1113
1346
|
id: 2,
|
|
1114
1347
|
label: "Pending Bookings",
|
|
1115
|
-
value:
|
|
1348
|
+
value: 97,
|
|
1116
1349
|
icon: "mdi-calendar-clock",
|
|
1117
1350
|
iconColor: "warning",
|
|
1118
1351
|
},
|
|
1119
1352
|
{
|
|
1120
1353
|
id: 3,
|
|
1121
1354
|
label: "Cancelled Bookings",
|
|
1122
|
-
value:
|
|
1355
|
+
value: 28,
|
|
1123
1356
|
icon: "mdi-calendar-remove",
|
|
1124
1357
|
iconColor: "error",
|
|
1125
1358
|
},
|
|
1126
1359
|
{
|
|
1127
1360
|
id: 4,
|
|
1128
1361
|
label: "Rejected Bookings",
|
|
1129
|
-
value:
|
|
1362
|
+
value: 27,
|
|
1130
1363
|
icon: "mdi-calendar-remove",
|
|
1131
1364
|
iconColor: "error",
|
|
1132
1365
|
},
|
|
1133
1366
|
];
|
|
1367
|
+
|
|
1368
|
+
// Computed Facility Bookings - changes based on selected facility
|
|
1369
|
+
const facilityBookings = computed(() => {
|
|
1370
|
+
// If no facility is selected, return base data
|
|
1371
|
+
if (!selectedBooking.value) {
|
|
1372
|
+
return baseFacilityBookings.map((booking) => ({
|
|
1373
|
+
...booking,
|
|
1374
|
+
value: booking.value.toString(),
|
|
1375
|
+
}));
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// If a facility is selected, multiply values
|
|
1379
|
+
const bookingHash = selectedBooking.value
|
|
1380
|
+
.split("")
|
|
1381
|
+
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
1382
|
+
const multiplier = 0.35 + (bookingHash % 45) / 100; // Between 0.35 and 0.8
|
|
1383
|
+
|
|
1384
|
+
return baseFacilityBookings.map((booking) => ({
|
|
1385
|
+
...booking,
|
|
1386
|
+
value: Math.round(booking.value * multiplier).toString(),
|
|
1387
|
+
}));
|
|
1388
|
+
});
|
|
1134
1389
|
</script>
|
|
1135
1390
|
|
|
1136
1391
|
<style scoped>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
+
{{ prop.mode }} Document
|
|
7
|
+
</span>
|
|
8
|
+
</v-row>
|
|
9
|
+
</v-toolbar>
|
|
10
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-0">
|
|
11
|
+
<v-form v-model="validForm" :disabled="disable">
|
|
12
|
+
<v-col cols="12" class="px-6">
|
|
13
|
+
<InputLabel class="text-capitalize" title="Document Attachment" />
|
|
14
|
+
<InputFileV2
|
|
15
|
+
v-model="document.attachment"
|
|
16
|
+
:multiple="false"
|
|
17
|
+
:max-length="10"
|
|
18
|
+
title="Upload Images"
|
|
19
|
+
/>
|
|
20
|
+
</v-col>
|
|
21
|
+
|
|
22
|
+
<v-col cols="12">
|
|
23
|
+
<v-row no-gutters>
|
|
24
|
+
<v-col cols="12" class="text-center">
|
|
25
|
+
<span
|
|
26
|
+
class="text-none text-subtitle-2 font-weight-medium text-error"
|
|
27
|
+
>
|
|
28
|
+
{{ message }}
|
|
29
|
+
</span>
|
|
30
|
+
</v-col>
|
|
31
|
+
</v-row>
|
|
32
|
+
</v-col>
|
|
33
|
+
</v-form>
|
|
34
|
+
</v-card-text>
|
|
35
|
+
|
|
36
|
+
<v-toolbar density="compact">
|
|
37
|
+
<v-row no-gutters>
|
|
38
|
+
<v-col cols="6">
|
|
39
|
+
<v-btn
|
|
40
|
+
tile
|
|
41
|
+
block
|
|
42
|
+
variant="text"
|
|
43
|
+
class="text-none"
|
|
44
|
+
size="48"
|
|
45
|
+
@click="cancel"
|
|
46
|
+
:disabled="disable"
|
|
47
|
+
>
|
|
48
|
+
Cancel
|
|
49
|
+
</v-btn>
|
|
50
|
+
</v-col>
|
|
51
|
+
|
|
52
|
+
<v-col cols="6">
|
|
53
|
+
<v-btn
|
|
54
|
+
tile
|
|
55
|
+
block
|
|
56
|
+
variant="flat"
|
|
57
|
+
color="black"
|
|
58
|
+
class="text-none"
|
|
59
|
+
size="48"
|
|
60
|
+
:disabled="!validForm || disable"
|
|
61
|
+
@click="submit"
|
|
62
|
+
:loading="disable"
|
|
63
|
+
>
|
|
64
|
+
Submit
|
|
65
|
+
</v-btn>
|
|
66
|
+
</v-col>
|
|
67
|
+
</v-row>
|
|
68
|
+
</v-toolbar>
|
|
69
|
+
</v-card>
|
|
70
|
+
</template>
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
const prop = defineProps({
|
|
73
|
+
mode: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: "add",
|
|
76
|
+
},
|
|
77
|
+
document: {
|
|
78
|
+
type: Object as PropType<TDocument>,
|
|
79
|
+
default: () => ({
|
|
80
|
+
name: "",
|
|
81
|
+
attachment: "",
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const emit = defineEmits(["cancel", "success"]);
|
|
87
|
+
|
|
88
|
+
const validForm = ref(false);
|
|
89
|
+
const disable = ref(false);
|
|
90
|
+
const message = ref("");
|
|
91
|
+
|
|
92
|
+
function cancel() {
|
|
93
|
+
// createMore.value = false;
|
|
94
|
+
message.value = "";
|
|
95
|
+
emit("cancel");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function submit() {
|
|
99
|
+
disable.value = true;
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-col cols="12" class="mb-2">
|
|
4
|
+
<v-row no-gutters>
|
|
5
|
+
<v-btn
|
|
6
|
+
class="text-none"
|
|
7
|
+
rounded="pill"
|
|
8
|
+
variant="tonal"
|
|
9
|
+
size="large"
|
|
10
|
+
@click="setDocument()"
|
|
11
|
+
v-if="canCreate && canCreateDocument"
|
|
12
|
+
>
|
|
13
|
+
Add Document
|
|
14
|
+
</v-btn>
|
|
15
|
+
</v-row>
|
|
16
|
+
</v-col>
|
|
17
|
+
<v-col cols="12">
|
|
18
|
+
<v-card
|
|
19
|
+
width="100%"
|
|
20
|
+
variant="outlined"
|
|
21
|
+
border="thin"
|
|
22
|
+
rounded="lg"
|
|
23
|
+
:loading="loading"
|
|
24
|
+
>
|
|
25
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
26
|
+
<template #prepend>
|
|
27
|
+
<v-btn fab icon density="comfortable" @click="">
|
|
28
|
+
<v-icon>mdi-refresh</v-icon>
|
|
29
|
+
</v-btn>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<template #append>
|
|
33
|
+
<v-row no-gutters justify="end" align="center">
|
|
34
|
+
<span class="mr-2 text-caption text-fontgray">
|
|
35
|
+
{{ pageRange }}
|
|
36
|
+
</span>
|
|
37
|
+
<local-pagination
|
|
38
|
+
v-model="page"
|
|
39
|
+
:length="pages"
|
|
40
|
+
@update:value=""
|
|
41
|
+
/>
|
|
42
|
+
</v-row>
|
|
43
|
+
</template>
|
|
44
|
+
</v-toolbar>
|
|
45
|
+
<v-data-table
|
|
46
|
+
:headers="headers"
|
|
47
|
+
:items="items"
|
|
48
|
+
item-value="_id"
|
|
49
|
+
items-per-page="10"
|
|
50
|
+
fixed-header
|
|
51
|
+
hide-default-footer
|
|
52
|
+
hide-default-header
|
|
53
|
+
@click:row="tableRowClickHandler"
|
|
54
|
+
style="max-height: calc(100vh - (200px))"
|
|
55
|
+
></v-data-table>
|
|
56
|
+
</v-card>
|
|
57
|
+
</v-col>
|
|
58
|
+
|
|
59
|
+
<!-- Create Dialog -->
|
|
60
|
+
<v-dialog v-model="createDialog" width="450" persistent>
|
|
61
|
+
<DocumentForm @cancel="createDialog = false" @success="successCreate()" />
|
|
62
|
+
</v-dialog>
|
|
63
|
+
</v-row>
|
|
64
|
+
</template>
|
|
65
|
+
<script setup lang="ts">
|
|
66
|
+
definePageMeta({
|
|
67
|
+
middleware: ["01-auth", "02-org"],
|
|
68
|
+
memberOnly: true,
|
|
69
|
+
});
|
|
70
|
+
const props = defineProps({
|
|
71
|
+
headers: {
|
|
72
|
+
type: Array as PropType<Array<Record<string, any>>>,
|
|
73
|
+
default: () => [
|
|
74
|
+
{
|
|
75
|
+
title: "Name",
|
|
76
|
+
value: "name",
|
|
77
|
+
},
|
|
78
|
+
{ title: "Action", value: "action-table" },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
canCreate: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: true,
|
|
84
|
+
},
|
|
85
|
+
canUpdate: {
|
|
86
|
+
type: Boolean,
|
|
87
|
+
default: true,
|
|
88
|
+
},
|
|
89
|
+
canDelete: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: true,
|
|
92
|
+
},
|
|
93
|
+
canCreateDocument: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
default: true,
|
|
96
|
+
},
|
|
97
|
+
canUpdateDocument: {
|
|
98
|
+
type: Boolean,
|
|
99
|
+
default: true,
|
|
100
|
+
},
|
|
101
|
+
canDeleteDocument: {
|
|
102
|
+
type: Boolean,
|
|
103
|
+
default: true,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const { headerSearch } = useLocal();
|
|
108
|
+
const { getAll: _getAllDocuments } = useDocument();
|
|
109
|
+
|
|
110
|
+
const page = ref(1);
|
|
111
|
+
const pages = ref(0);
|
|
112
|
+
const pageRange = ref("-- - -- of --");
|
|
113
|
+
|
|
114
|
+
const message = ref("");
|
|
115
|
+
const messageSnackbar = ref(false);
|
|
116
|
+
const messageColor = ref("");
|
|
117
|
+
|
|
118
|
+
const items = ref<Array<Record<string, any>>>([]);
|
|
119
|
+
|
|
120
|
+
const {
|
|
121
|
+
data: getDocumentReq,
|
|
122
|
+
refresh: getDocuments,
|
|
123
|
+
status: getAllReqStatus,
|
|
124
|
+
} = useLazyAsyncData(
|
|
125
|
+
"get-all-documents",
|
|
126
|
+
() =>
|
|
127
|
+
_getAllDocuments({
|
|
128
|
+
page: page.value,
|
|
129
|
+
search: headerSearch.value,
|
|
130
|
+
}),
|
|
131
|
+
{
|
|
132
|
+
watch: [page, headerSearch],
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const loading = computed(() => getAllReqStatus.value === "pending");
|
|
137
|
+
|
|
138
|
+
watchEffect(() => {
|
|
139
|
+
if (getDocumentReq.value) {
|
|
140
|
+
items.value = getDocumentReq.value.items;
|
|
141
|
+
pages.value = getDocumentReq.value.pages;
|
|
142
|
+
pageRange.value = getDocumentReq.value.pageRange;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const createDialog = ref(false);
|
|
147
|
+
const editDialog = ref(false);
|
|
148
|
+
const previewDialog = ref(false);
|
|
149
|
+
const selectedDocument = ref<TDocument>({
|
|
150
|
+
_id: "",
|
|
151
|
+
name: "",
|
|
152
|
+
attachment: [],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
function tableRowClickHandler(_: any, data: any) {
|
|
156
|
+
selectedDocument.value = data.item as TDocument;
|
|
157
|
+
previewDialog.value = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function setDocument({
|
|
161
|
+
mode = "create",
|
|
162
|
+
dialog = true,
|
|
163
|
+
data = {} as TDocument,
|
|
164
|
+
} = {}) {
|
|
165
|
+
if (mode === "create") {
|
|
166
|
+
createDialog.value = dialog;
|
|
167
|
+
} else if (mode === "edit") {
|
|
168
|
+
editDialog.value = dialog;
|
|
169
|
+
selectedDocument.value = data;
|
|
170
|
+
} else if (mode === "preview") {
|
|
171
|
+
previewDialog.value = dialog;
|
|
172
|
+
selectedDocument.value = data;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function showMessage(msg: string, color: string) {
|
|
177
|
+
message.value = msg;
|
|
178
|
+
messageColor.value = color;
|
|
179
|
+
messageSnackbar.value = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function successCreate() {
|
|
183
|
+
createDialog.value = false;
|
|
184
|
+
getDocuments();
|
|
185
|
+
showMessage("Document created successfully!", "success");
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
>
|
|
37
37
|
<v-toolbar density="compact" color="grey-lighten-4">
|
|
38
38
|
<template #prepend>
|
|
39
|
-
<v-btn fab icon density="comfortable" @click="updatePage">
|
|
39
|
+
<v-btn fab icon density="comfortable" @click="updatePage(1)">
|
|
40
40
|
<v-icon>mdi-refresh</v-icon>
|
|
41
41
|
</v-btn>
|
|
42
42
|
</template>
|
|
@@ -351,6 +351,7 @@ async function updatePage(pageVal: any) {
|
|
|
351
351
|
const response = await _getFeedbacks({
|
|
352
352
|
page: page.value,
|
|
353
353
|
site: route.params.site as string,
|
|
354
|
+
category: props.category,
|
|
354
355
|
});
|
|
355
356
|
if (response) {
|
|
356
357
|
items.value = response.items;
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
<v-col cols="12" class="d-flex ga-2">
|
|
4
4
|
<v-select v-model="selectedCode" :variant="variant" :items="countries" item-title="code" item-value="code"
|
|
5
5
|
hide-details class="px-0" :density="density" style="max-width: 95px" :rules="[...props.rules]"
|
|
6
|
-
@update:model-value="handleUpdateCountry">
|
|
6
|
+
:readonly="props.readOnly" @update:model-value="handleUpdateCountry">
|
|
7
7
|
<template v-slot:item="{ props: itemProps, item }">
|
|
8
8
|
<v-list-item v-bind="itemProps" :title="item.raw.name" :subtitle="item.raw.dial_code" width="300" />
|
|
9
9
|
</template>
|
|
10
10
|
</v-select>
|
|
11
|
-
<v-mask-input v-model="
|
|
12
|
-
:variant="variant" hint="Enter a valid phone number"
|
|
11
|
+
<v-mask-input v-model="input" :mask="currentMask" :rules="[...props.rules, validatePhone]" ref="maskRef" :key="`mask-key-${maskKey}`"
|
|
12
|
+
:loading="loading" :readonly="props.readOnly" :variant="variant" hint="Enter a valid phone number"
|
|
13
|
+
hide-details persistent-hint return-masked-value :prefix="phonePrefix || '###'" persistent-placeholder
|
|
13
14
|
:density="density" :placeholder="placeholder || currentMask"></v-mask-input>
|
|
14
15
|
</v-col>
|
|
15
16
|
<span class="text-error text-caption w-100" v-if="errorMessage && !hideDetails">{{ errorMessage }}</span>
|
|
@@ -45,6 +46,10 @@ const props = defineProps({
|
|
|
45
46
|
loading: {
|
|
46
47
|
type: Boolean,
|
|
47
48
|
default: false
|
|
49
|
+
},
|
|
50
|
+
readOnly: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
48
53
|
}
|
|
49
54
|
})
|
|
50
55
|
|
|
@@ -60,9 +65,12 @@ type TPhoneMask =
|
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
const phone = defineModel({ default: '' })
|
|
68
|
+
const input = ref('')
|
|
63
69
|
const selectedCode = ref('SG')
|
|
64
70
|
const countries = phoneMasks
|
|
65
71
|
const errorMessage = ref('')
|
|
72
|
+
const maskRef = ref()
|
|
73
|
+
const maskKey = ref(0)
|
|
66
74
|
|
|
67
75
|
const currentMask = computed(() => {
|
|
68
76
|
const country = phoneMasks.find((c: TPhoneMask) => c.code === selectedCode.value)
|
|
@@ -72,7 +80,9 @@ const currentMask = computed(() => {
|
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
|
|
75
|
-
const validatePhone = (
|
|
83
|
+
const validatePhone = (): boolean | string => {
|
|
84
|
+
if(props.readOnly) return true;
|
|
85
|
+
const value = phone.value
|
|
76
86
|
if (!value) {
|
|
77
87
|
errorMessage.value = ''
|
|
78
88
|
return true;
|
|
@@ -92,23 +102,29 @@ const validatePhone = (value: string): boolean | string => {
|
|
|
92
102
|
|
|
93
103
|
|
|
94
104
|
|
|
95
|
-
|
|
96
105
|
function generateMaskFromRegex(regex: string): string {
|
|
106
|
+
let pattern = regex.replace(/^\^|\$$/g, '');
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
pattern = pattern.replace(
|
|
100
|
-
pattern = pattern.replace(/\\d/g, '#')
|
|
108
|
+
pattern = pattern.replace(/\(\?:\+?\d+\)\?/g, '');
|
|
109
|
+
pattern = pattern.replace(/\+?\d{1,4}/, '');
|
|
101
110
|
|
|
102
|
-
pattern = pattern.replace(
|
|
111
|
+
pattern = pattern.replace(/\\d\{(\d+)\}/g, (_, count) => '#'.repeat(Number(count)));
|
|
103
112
|
|
|
104
|
-
pattern = pattern.replace(
|
|
105
|
-
pattern = pattern.replace(/\s/g, ' ')
|
|
113
|
+
pattern = pattern.replace(/\\d/g, '#');
|
|
106
114
|
|
|
107
|
-
pattern = pattern.replace(
|
|
115
|
+
pattern = pattern.replace(/\\/g, '');
|
|
116
|
+
pattern = pattern.replace(/\(\?:/g, '');
|
|
117
|
+
pattern = pattern.trim();
|
|
108
118
|
|
|
109
|
-
return pattern
|
|
119
|
+
return pattern;
|
|
110
120
|
}
|
|
111
121
|
|
|
122
|
+
const phonePrefix = computed(() => {
|
|
123
|
+
const country = phoneMasks.find((c: TPhoneMask) => c.code === selectedCode.value)
|
|
124
|
+
return country?.dial_code || ''
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
|
|
112
128
|
function handleUpdateCountry() {
|
|
113
129
|
phone.value = ''
|
|
114
130
|
}
|
|
@@ -116,8 +132,32 @@ function handleUpdateCountry() {
|
|
|
116
132
|
const emit = defineEmits(['update:modelValue'])
|
|
117
133
|
|
|
118
134
|
watch(phone, (newVal) => {
|
|
119
|
-
|
|
135
|
+
emit('update:modelValue', newVal)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
watch(input, (newInput) => {
|
|
139
|
+
const prefix = phonePrefix.value
|
|
140
|
+
|
|
141
|
+
if (!newInput) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
phone.value = prefix + newInput
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
onMounted(() => {
|
|
150
|
+
if (!phone.value) return
|
|
151
|
+
|
|
152
|
+
const found = phoneMasks.find((c: any) => phone.value?.startsWith(c?.dial_code))
|
|
153
|
+
if (found) {
|
|
154
|
+
selectedCode.value = found.code
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
input.value = phone.value.replace(found?.dial_code || '', '')
|
|
158
|
+
maskKey.value++
|
|
120
159
|
})
|
|
121
160
|
|
|
122
161
|
|
|
162
|
+
|
|
123
163
|
</script>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
rounded="pill"
|
|
8
8
|
variant="tonal"
|
|
9
9
|
size="large"
|
|
10
|
-
@click="showCreateDialog = true"
|
|
10
|
+
@click="(showCreateDialog = true), (isEditMode = false)"
|
|
11
11
|
v-if="canCreateWorkOrder"
|
|
12
12
|
>
|
|
13
13
|
Create Work Order
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
>
|
|
36
36
|
<v-toolbar density="compact" color="grey-lighten-4">
|
|
37
37
|
<template #prepend>
|
|
38
|
-
<v-btn fab icon density="comfortable" @click="updatePage">
|
|
38
|
+
<v-btn fab icon density="comfortable" @click="updatePage(1)">
|
|
39
39
|
<v-icon>mdi-refresh</v-icon>
|
|
40
40
|
</v-btn>
|
|
41
41
|
</template>
|
|
@@ -258,7 +258,12 @@ const selected = ref<string[]>([]);
|
|
|
258
258
|
const route = useRoute();
|
|
259
259
|
const { customers } = useCustomer();
|
|
260
260
|
|
|
261
|
-
const {
|
|
261
|
+
const {
|
|
262
|
+
getWorkOrders: _getWorkOrders,
|
|
263
|
+
createWorkOrder,
|
|
264
|
+
getWorkOrderById,
|
|
265
|
+
updateWorkOrder,
|
|
266
|
+
} = useWorkOrder();
|
|
262
267
|
|
|
263
268
|
const page = ref(1);
|
|
264
269
|
const pages = ref(0);
|
|
@@ -294,6 +299,7 @@ async function updatePage(pageVal: any) {
|
|
|
294
299
|
const response = await _getWorkOrders({
|
|
295
300
|
page: page.value,
|
|
296
301
|
site: route.params.site as string,
|
|
302
|
+
category: props.category,
|
|
297
303
|
});
|
|
298
304
|
if (response) {
|
|
299
305
|
items.value = response.items;
|
|
@@ -412,7 +418,13 @@ async function submitWorkOrder() {
|
|
|
412
418
|
site: route.params.site as string,
|
|
413
419
|
};
|
|
414
420
|
|
|
415
|
-
|
|
421
|
+
let res: Record<string, any> = {};
|
|
422
|
+
|
|
423
|
+
if (isEditMode.value) {
|
|
424
|
+
res = await updateWorkOrder(_workOrderId.value as string, payload);
|
|
425
|
+
} else {
|
|
426
|
+
res = await createWorkOrder(payload);
|
|
427
|
+
}
|
|
416
428
|
|
|
417
429
|
showMessage(res.message, "success");
|
|
418
430
|
showCreateDialog.value = false;
|
|
@@ -437,12 +449,32 @@ function onViewWorkOrder(item: any) {
|
|
|
437
449
|
});
|
|
438
450
|
}
|
|
439
451
|
|
|
440
|
-
function editWorkOrder(item: any) {
|
|
452
|
+
async function editWorkOrder(item: any) {
|
|
453
|
+
try {
|
|
454
|
+
const _workOrders = await getWorkOrderById(item._id);
|
|
455
|
+
console.log(_workOrders);
|
|
456
|
+
_workOrder.value = {
|
|
457
|
+
attachments: (_workOrders.attachments || []) as string[],
|
|
458
|
+
category: _workOrders.category || "",
|
|
459
|
+
subject: _workOrders.subject || "",
|
|
460
|
+
description: _workOrders.description || "",
|
|
461
|
+
highPriority: _workOrders.highPriority || false,
|
|
462
|
+
unit: _workOrders.location || "",
|
|
463
|
+
};
|
|
464
|
+
_workOrderId.value = item._id;
|
|
465
|
+
isEditMode.value = true;
|
|
466
|
+
showCreateDialog.value = true;
|
|
467
|
+
dialogPreview.value = false;
|
|
468
|
+
} catch (error) {
|
|
469
|
+
showMessage("Failed to load full work order", "error");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function _updateWorkOrder() {}
|
|
441
474
|
|
|
442
475
|
function confirmDeleteWorkOrder(item: any) {}
|
|
443
476
|
|
|
444
477
|
function tableRowClickHandler(_: any, data: any) {
|
|
445
|
-
console.log(data.item);
|
|
446
478
|
selectedWorkOrder.value = data.item;
|
|
447
479
|
dialogPreview.value = true;
|
|
448
480
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default function useDocument() {
|
|
2
|
+
function getAll({
|
|
3
|
+
page = 1,
|
|
4
|
+
search = "",
|
|
5
|
+
limit = 10,
|
|
6
|
+
status = "active",
|
|
7
|
+
site = "",
|
|
8
|
+
} = {}) {
|
|
9
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
10
|
+
`/api/documents`,
|
|
11
|
+
{
|
|
12
|
+
method: "GET",
|
|
13
|
+
query: {
|
|
14
|
+
page,
|
|
15
|
+
search,
|
|
16
|
+
limit,
|
|
17
|
+
status,
|
|
18
|
+
site,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
getAll,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -36,7 +36,18 @@ export default function () {
|
|
|
36
36
|
}
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
async function updateSitebyId(
|
|
40
|
+
siteId: string,
|
|
41
|
+
payload: { field: string; value: number }
|
|
42
|
+
) {
|
|
43
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
44
|
+
`/api/sites/id/${siteId}`,
|
|
45
|
+
{
|
|
46
|
+
method: "PATCH",
|
|
47
|
+
body: payload,
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
40
51
|
async function addCamera(camera: TSiteCamera) {
|
|
41
52
|
return await useNuxtApp().$api<Record<string, any>>(`/api/site-cameras`, {
|
|
42
53
|
method: "POST",
|
|
@@ -107,5 +118,6 @@ export default function () {
|
|
|
107
118
|
setSiteGuardPosts,
|
|
108
119
|
updateSiteCamera,
|
|
109
120
|
deleteSiteCameraById,
|
|
121
|
+
updateSitebyId,
|
|
110
122
|
};
|
|
111
123
|
}
|
|
@@ -64,6 +64,13 @@ export default function useWorkOrder() {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function updateWorkOrder(id: string, payload: object) {
|
|
68
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/work-orders/${id}`, {
|
|
69
|
+
method: "PUT",
|
|
70
|
+
body: payload,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
67
74
|
return {
|
|
68
75
|
workOrders,
|
|
69
76
|
workOrder,
|
|
@@ -73,5 +80,6 @@ export default function useWorkOrder() {
|
|
|
73
80
|
createWorkOrder,
|
|
74
81
|
getWorkOrders,
|
|
75
82
|
getWorkOrderById,
|
|
83
|
+
updateWorkOrder,
|
|
76
84
|
};
|
|
77
85
|
}
|
package/package.json
CHANGED