@7365admin1/layer-common 1.10.4 → 1.10.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 +12 -0
- package/components/AccessCardDeleteDialog.vue +109 -0
- package/components/AccessCardDetailsDialog.vue +144 -0
- package/components/AccessCardPreviewDialog.vue +18 -2
- package/components/AccessCardQrTagging.vue +183 -0
- package/components/AccessManagement.vue +22 -57
- package/components/BulletinBoardManagement.vue +6 -1
- package/components/CleaningScheduleMain.vue +18 -7
- package/components/IncidentReport/affectedEntities.vue +29 -0
- package/components/Input/InputPhoneNumberV2.vue +3 -0
- package/components/PlateNumberDisplay.vue +53 -0
- package/components/ScheduleAreaMain.vue +3 -0
- package/components/TableHygiene.vue +238 -113
- package/components/VehicleAddSelection.vue +58 -0
- package/components/VehicleForm.vue +663 -0
- package/components/VehicleManagement.vue +300 -0
- package/components/VisitorManagement.vue +1 -1
- package/components/WorkOrder/Main.vue +2 -1
- package/composables/useAccessManagement.ts +20 -1
- package/composables/useBulletin.ts +6 -4
- package/composables/useFeedback.ts +2 -2
- package/composables/usePeople.ts +10 -0
- package/composables/useVehicle.ts +114 -0
- package/composables/useWorkOrder.ts +2 -2
- package/package.json +1 -1
- package/types/people.d.ts +3 -0
- package/types/vehicle.d.ts +43 -0
|
@@ -57,10 +57,9 @@
|
|
|
57
57
|
}}
|
|
58
58
|
</template>
|
|
59
59
|
<template #item.closeIn="{ item }">
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}}</v-chip>
|
|
60
|
+
<v-chip class="text-capitalize" variant="flat" color="primary" pill>
|
|
61
|
+
{{ remainingTime[item._id] || "No Status" }}
|
|
62
|
+
</v-chip>
|
|
64
63
|
</template>
|
|
65
64
|
<template #item.status="{ value }">
|
|
66
65
|
<v-chip
|
|
@@ -129,7 +128,8 @@ const headers = [
|
|
|
129
128
|
{ title: "Completion Date", value: "completionDate" },
|
|
130
129
|
{ title: "", value: "download" },
|
|
131
130
|
];
|
|
132
|
-
const remainingTime = ref({} as
|
|
131
|
+
const remainingTime = ref({} as Record<string, string>);
|
|
132
|
+
const remainingSeconds = ref({} as Record<string, number>);
|
|
133
133
|
const downloadingId = ref<string | null>(null);
|
|
134
134
|
|
|
135
135
|
const { getCleaningSchedules, downloadChecklistPdf } = useCleaningSchedules();
|
|
@@ -148,11 +148,22 @@ const getStatusColor = (status: unknown): string => {
|
|
|
148
148
|
case "completed":
|
|
149
149
|
case "accepted":
|
|
150
150
|
return "success";
|
|
151
|
+
case "closed":
|
|
152
|
+
case "rejected":
|
|
153
|
+
return "error";
|
|
151
154
|
default:
|
|
152
155
|
return "secondary";
|
|
153
156
|
}
|
|
154
157
|
};
|
|
155
158
|
|
|
159
|
+
const getCloseInColor = (id: string): string => {
|
|
160
|
+
const secs = remainingSeconds.value[id];
|
|
161
|
+
if (secs === undefined) return "primary";
|
|
162
|
+
if (secs <= 0) return "error";
|
|
163
|
+
if (secs < 3 * 3600) return "warning";
|
|
164
|
+
return "primary";
|
|
165
|
+
};
|
|
166
|
+
|
|
156
167
|
const {
|
|
157
168
|
canDownloadSchedule,
|
|
158
169
|
canViewSchedules,
|
|
@@ -205,11 +216,11 @@ const formatTime = (seconds: number) => {
|
|
|
205
216
|
};
|
|
206
217
|
|
|
207
218
|
const updateRemainingTime = () => {
|
|
208
|
-
console.log(items.value);
|
|
209
219
|
items.value.forEach((item) => {
|
|
210
220
|
const itemId = item._id as string;
|
|
211
221
|
const _time = calculateRemainingTime(item.date as string);
|
|
212
|
-
|
|
222
|
+
remainingSeconds.value[itemId] = _time;
|
|
223
|
+
remainingTime.value[itemId] = _time <= 0 ? "00h 00m" : formatTime(_time);
|
|
213
224
|
});
|
|
214
225
|
};
|
|
215
226
|
|
|
@@ -76,6 +76,26 @@
|
|
|
76
76
|
style="max-height: calc(100vh - (200px))"
|
|
77
77
|
class="border mt-5"
|
|
78
78
|
>
|
|
79
|
+
<template #header.nric>
|
|
80
|
+
<div class="d-flex align-center">
|
|
81
|
+
NRIC
|
|
82
|
+
<v-icon
|
|
83
|
+
class="cursor-pointer ml-1"
|
|
84
|
+
size="18"
|
|
85
|
+
color="blue"
|
|
86
|
+
@click="isShowNRIC = !isShowNRIC"
|
|
87
|
+
>
|
|
88
|
+
{{ isShowNRIC ? "mdi-eye-off-outline" : "mdi-eye-outline" }}
|
|
89
|
+
</v-icon>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
<template #item.nric="{ value }">
|
|
93
|
+
<tr>
|
|
94
|
+
{{
|
|
95
|
+
maskedNric(value)
|
|
96
|
+
}}
|
|
97
|
+
</tr>
|
|
98
|
+
</template>
|
|
79
99
|
</v-data-table>
|
|
80
100
|
</v-col>
|
|
81
101
|
|
|
@@ -142,6 +162,7 @@ const injuredTableHeader = [
|
|
|
142
162
|
value: "contact",
|
|
143
163
|
},
|
|
144
164
|
];
|
|
165
|
+
|
|
145
166
|
const damagePropertyTableHeader = [
|
|
146
167
|
{
|
|
147
168
|
title: "Description",
|
|
@@ -164,4 +185,12 @@ const damagePropertyTableHeader = [
|
|
|
164
185
|
value: "action",
|
|
165
186
|
},
|
|
166
187
|
];
|
|
188
|
+
|
|
189
|
+
const isShowNRIC = ref(false);
|
|
190
|
+
|
|
191
|
+
const maskedNric = (nric: string) => {
|
|
192
|
+
if (!nric) return "";
|
|
193
|
+
if (isShowNRIC.value) return nric;
|
|
194
|
+
return nric[0] + "*".repeat(nric.length - 5) + nric.slice(-4);
|
|
195
|
+
};
|
|
167
196
|
</script>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
style="max-width: 95px"
|
|
14
14
|
:rules="[...props.rules]"
|
|
15
15
|
:readonly="props.readOnly"
|
|
16
|
+
:disabled="props.disabled"
|
|
16
17
|
@update:model-value="handleUpdateCountry"
|
|
17
18
|
>
|
|
18
19
|
<template v-slot:item="{ props: itemProps, item }">
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
:prefix="phonePrefix || ''"
|
|
41
42
|
persistent-placeholder
|
|
42
43
|
:density="density"
|
|
44
|
+
:disabled="props.disabled"
|
|
43
45
|
:placeholder="placeholder || currentMask"
|
|
44
46
|
/>
|
|
45
47
|
</v-col>
|
|
@@ -64,6 +66,7 @@ const props = defineProps({
|
|
|
64
66
|
hideDetails: { type: Boolean, default: false },
|
|
65
67
|
loading: { type: Boolean, default: false },
|
|
66
68
|
readOnly: { type: Boolean, default: false },
|
|
69
|
+
disabled: { type: Boolean, default: false },
|
|
67
70
|
})
|
|
68
71
|
|
|
69
72
|
const emit = defineEmits(['update:modelValue'])
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-flex align-center ga-2 flex-wrap">
|
|
3
|
+
<span v-if="formatted">
|
|
4
|
+
{{ formatted }}
|
|
5
|
+
</span>
|
|
6
|
+
|
|
7
|
+
<v-chip
|
|
8
|
+
v-if="extraCount > 0 && !showAll"
|
|
9
|
+
density="comfortable"
|
|
10
|
+
size="small"
|
|
11
|
+
>
|
|
12
|
+
+ {{ extraCount }}
|
|
13
|
+
</v-chip>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
plateNumbers: {
|
|
20
|
+
type: Array as PropType<string[]>,
|
|
21
|
+
default: () => []
|
|
22
|
+
},
|
|
23
|
+
defaultValue: {
|
|
24
|
+
type: String,
|
|
25
|
+
default: ""
|
|
26
|
+
},
|
|
27
|
+
showAll: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const formatted = computed(() => {
|
|
34
|
+
if (!props.plateNumbers?.length) return props.defaultValue || ""
|
|
35
|
+
|
|
36
|
+
if (props.showAll) {
|
|
37
|
+
return props.plateNumbers.map((v: any) => v?.plateNumber || "").join(", ")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const firstTwo = props.plateNumbers
|
|
41
|
+
.slice(0, 2)
|
|
42
|
+
.map((v: any) => v?.plateNumber || "")
|
|
43
|
+
|
|
44
|
+
return firstTwo.join(", ")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const extraCount = computed(() => {
|
|
48
|
+
if (!props.plateNumbers) return 0
|
|
49
|
+
return props.plateNumbers.length > 2
|
|
50
|
+
? props.plateNumbers.length - 2
|
|
51
|
+
: 0
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
|
-
<!-- Top Actions -->
|
|
4
3
|
<v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
|
|
5
4
|
<v-row no-gutters>
|
|
6
5
|
<slot name="actions">
|
|
@@ -18,7 +17,6 @@
|
|
|
18
17
|
</v-row>
|
|
19
18
|
</v-col>
|
|
20
19
|
|
|
21
|
-
<!-- List Card -->
|
|
22
20
|
<v-col cols="12">
|
|
23
21
|
<v-card
|
|
24
22
|
width="100%"
|
|
@@ -27,7 +25,6 @@
|
|
|
27
25
|
rounded="lg"
|
|
28
26
|
:loading="loading"
|
|
29
27
|
>
|
|
30
|
-
<!-- Toolbar -->
|
|
31
28
|
<v-toolbar
|
|
32
29
|
density="compact"
|
|
33
30
|
color="grey-lighten-4"
|
|
@@ -40,7 +37,10 @@
|
|
|
40
37
|
<slot name="prepend-additional" />
|
|
41
38
|
</template>
|
|
42
39
|
|
|
43
|
-
<v-toolbar-title
|
|
40
|
+
<v-toolbar-title
|
|
41
|
+
v-if="title"
|
|
42
|
+
class="text-subtitle-1 font-weight-medium"
|
|
43
|
+
>
|
|
44
44
|
{{ title }}
|
|
45
45
|
</v-toolbar-title>
|
|
46
46
|
|
|
@@ -64,126 +64,224 @@
|
|
|
64
64
|
|
|
65
65
|
<v-divider />
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
<v-sheet
|
|
68
|
+
:max-height="`calc(100vh - (${offset}px))`"
|
|
69
|
+
class="overflow-y-auto"
|
|
70
70
|
>
|
|
71
|
-
<v-
|
|
71
|
+
<v-row
|
|
72
72
|
v-if="groupedItems.length === 0"
|
|
73
|
-
|
|
73
|
+
no-gutters
|
|
74
|
+
justify="center"
|
|
75
|
+
class="py-10"
|
|
74
76
|
>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
<v-col
|
|
78
|
+
cols="auto"
|
|
79
|
+
class="text-center text-medium-emphasis text-body-2"
|
|
80
|
+
>
|
|
81
|
+
{{ noDataText }}
|
|
82
|
+
</v-col>
|
|
83
|
+
</v-row>
|
|
77
84
|
|
|
78
85
|
<template
|
|
79
86
|
v-for="(group, groupIndex) in groupedItems"
|
|
80
87
|
:key="`group-${groupIndex}`"
|
|
81
88
|
>
|
|
82
|
-
<
|
|
83
|
-
v-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
89
|
+
<v-sheet color="grey-lighten-4" border="b t">
|
|
90
|
+
<v-row no-gutters align="center" class="px-4 py-2">
|
|
91
|
+
<v-col cols="auto" class="d-flex align-center ga-2">
|
|
92
|
+
<span
|
|
93
|
+
class="text-caption font-weight-bold text-medium-emphasis text-uppercase"
|
|
94
|
+
>
|
|
95
|
+
Set {{ group.set }}
|
|
96
|
+
</span>
|
|
97
|
+
<v-chip
|
|
98
|
+
v-if="group.completedByName && isGroupComplete(group)"
|
|
99
|
+
size="x-small"
|
|
100
|
+
color="success"
|
|
101
|
+
variant="tonal"
|
|
102
|
+
prepend-icon="mdi-check-circle-outline"
|
|
103
|
+
class="text-none"
|
|
104
|
+
>
|
|
105
|
+
Completed · {{ group.completedByName }}
|
|
106
|
+
</v-chip>
|
|
107
|
+
<v-chip
|
|
108
|
+
v-else-if="
|
|
109
|
+
group.completedByName && isGroupInProgress(group)
|
|
110
|
+
"
|
|
111
|
+
size="x-small"
|
|
112
|
+
color="warning"
|
|
113
|
+
variant="tonal"
|
|
114
|
+
prepend-icon="mdi-progress-clock"
|
|
115
|
+
class="text-none"
|
|
116
|
+
>
|
|
117
|
+
Ongoing · {{ group.completedByName }}
|
|
118
|
+
</v-chip>
|
|
119
|
+
</v-col>
|
|
120
|
+
<v-spacer />
|
|
121
|
+
<v-col cols="auto">
|
|
122
|
+
<v-btn
|
|
123
|
+
v-if="group.attachments && group.attachments.length > 0"
|
|
124
|
+
size="x-small"
|
|
125
|
+
variant="tonal"
|
|
126
|
+
color="primary"
|
|
127
|
+
class="text-none"
|
|
128
|
+
prepend-icon="mdi-paperclip"
|
|
129
|
+
@click.stop="
|
|
130
|
+
openAttachmentDialog(group.set, group.attachments)
|
|
131
|
+
"
|
|
132
|
+
>
|
|
133
|
+
{{ group.attachments.length }} attachment{{
|
|
134
|
+
group.attachments.length > 1 ? "s" : ""
|
|
135
|
+
}}
|
|
136
|
+
</v-btn>
|
|
137
|
+
</v-col>
|
|
138
|
+
</v-row>
|
|
139
|
+
</v-sheet>
|
|
140
|
+
|
|
141
|
+
<v-sheet
|
|
109
142
|
v-for="item in group.items"
|
|
110
143
|
:key="item[itemValue]"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
isItemSelected(item, group.set)
|
|
114
|
-
? ['bg-grey-lighten-4', 'rounded']
|
|
115
|
-
: []
|
|
144
|
+
:color="
|
|
145
|
+
isItemSelected(item, group.set) ? 'grey-lighten-4' : 'white'
|
|
116
146
|
"
|
|
147
|
+
border="b"
|
|
117
148
|
>
|
|
118
|
-
<
|
|
119
|
-
<v-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
name="list-item
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<v-btn
|
|
161
|
-
icon="mdi-check"
|
|
162
|
-
size="small"
|
|
163
|
-
:variant="
|
|
164
|
-
activeActions[getKey(item, group.set)] === 'approve'
|
|
165
|
-
? 'flat'
|
|
166
|
-
: 'text'
|
|
167
|
-
"
|
|
168
|
-
color="success"
|
|
169
|
-
@click.stop="
|
|
170
|
-
handleActionClick(item, group.set, 'approve')
|
|
149
|
+
<v-row no-gutters align="center" class="px-4 py-2">
|
|
150
|
+
<v-col cols="auto" class="mr-3">
|
|
151
|
+
<v-icon
|
|
152
|
+
size="20"
|
|
153
|
+
:color="
|
|
154
|
+
activeActions[getKey(item, group.set)] === 'approve'
|
|
155
|
+
? 'success'
|
|
156
|
+
: activeActions[getKey(item, group.set)] === 'reject'
|
|
157
|
+
? 'error'
|
|
158
|
+
: 'grey-lighten-2'
|
|
159
|
+
"
|
|
160
|
+
>
|
|
161
|
+
{{
|
|
162
|
+
activeActions[getKey(item, group.set)] === "approve"
|
|
163
|
+
? "mdi-check-circle"
|
|
164
|
+
: activeActions[getKey(item, group.set)] === "reject"
|
|
165
|
+
? "mdi-close-circle"
|
|
166
|
+
: "mdi-circle-outline"
|
|
167
|
+
}}
|
|
168
|
+
</v-icon>
|
|
169
|
+
</v-col>
|
|
170
|
+
<v-col>
|
|
171
|
+
<slot name="list-item" :item="item">
|
|
172
|
+
<v-row no-gutters align-center>
|
|
173
|
+
<v-col cols="12">
|
|
174
|
+
<span
|
|
175
|
+
class="text-body-2 font-weight-medium"
|
|
176
|
+
:class="
|
|
177
|
+
activeActions[getKey(item, group.set)] === 'approve'
|
|
178
|
+
? 'text-decoration-line-through text-medium-emphasis'
|
|
179
|
+
: ''
|
|
180
|
+
"
|
|
181
|
+
>
|
|
182
|
+
{{ getItemValue(item, headers[0].value) }}
|
|
183
|
+
</span>
|
|
184
|
+
</v-col>
|
|
185
|
+
<v-col
|
|
186
|
+
v-if="
|
|
187
|
+
item.timestamp ||
|
|
188
|
+
(headers[1] &&
|
|
189
|
+
getItemValue(item, headers[1].value)) ||
|
|
190
|
+
(headers[2] && getItemValue(item, headers[2].value))
|
|
171
191
|
"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
192
|
+
cols="12"
|
|
193
|
+
>
|
|
194
|
+
<v-row
|
|
195
|
+
no-gutters
|
|
196
|
+
align="center"
|
|
197
|
+
class="ga-3 flex-wrap mt-1"
|
|
198
|
+
>
|
|
199
|
+
<v-col
|
|
200
|
+
v-if="item.timestamp"
|
|
201
|
+
cols="auto"
|
|
202
|
+
class="d-flex align-center ga-1 text-caption text-medium-emphasis pa-0"
|
|
203
|
+
>
|
|
204
|
+
<v-icon size="11">mdi-clock-outline</v-icon>
|
|
205
|
+
{{ formatTimestamp(item.timestamp) }}
|
|
206
|
+
</v-col>
|
|
207
|
+
<v-col
|
|
208
|
+
v-if="
|
|
209
|
+
headers[1] && getItemValue(item, headers[1].value)
|
|
210
|
+
"
|
|
211
|
+
cols="auto"
|
|
212
|
+
class="text-caption text-medium-emphasis pa-0"
|
|
213
|
+
>
|
|
214
|
+
{{ getItemValue(item, headers[1].value) }}
|
|
215
|
+
</v-col>
|
|
216
|
+
<v-col
|
|
217
|
+
v-if="
|
|
218
|
+
headers[2] && getItemValue(item, headers[2].value)
|
|
219
|
+
"
|
|
220
|
+
cols="auto"
|
|
221
|
+
class="text-caption text-medium-emphasis pa-0"
|
|
222
|
+
>
|
|
223
|
+
{{ getItemValue(item, headers[2].value) }}
|
|
224
|
+
</v-col>
|
|
225
|
+
</v-row>
|
|
226
|
+
</v-col>
|
|
227
|
+
</v-row>
|
|
228
|
+
</slot>
|
|
229
|
+
</v-col>
|
|
230
|
+
|
|
231
|
+
<v-col cols="auto">
|
|
232
|
+
<slot
|
|
233
|
+
name="list-item-append"
|
|
234
|
+
:item="item"
|
|
235
|
+
:isSelected="isItemSelected(item, group.set)"
|
|
236
|
+
>
|
|
237
|
+
<v-row
|
|
238
|
+
v-if="canManageScheduleTasks"
|
|
239
|
+
no-gutters
|
|
240
|
+
align="center"
|
|
241
|
+
>
|
|
242
|
+
<v-col cols="auto">
|
|
243
|
+
<v-btn
|
|
244
|
+
icon="mdi-close"
|
|
245
|
+
size="small"
|
|
246
|
+
:variant="
|
|
247
|
+
activeActions[getKey(item, group.set)] === 'reject'
|
|
248
|
+
? 'flat'
|
|
249
|
+
: 'text'
|
|
250
|
+
"
|
|
251
|
+
color="error"
|
|
252
|
+
@click.stop="
|
|
253
|
+
handleActionClick(item, group.set, 'reject')
|
|
254
|
+
"
|
|
255
|
+
/>
|
|
256
|
+
</v-col>
|
|
257
|
+
<v-col cols="auto">
|
|
258
|
+
<v-btn
|
|
259
|
+
icon="mdi-check"
|
|
260
|
+
size="small"
|
|
261
|
+
:variant="
|
|
262
|
+
activeActions[getKey(item, group.set)] === 'approve'
|
|
263
|
+
? 'flat'
|
|
264
|
+
: 'text'
|
|
265
|
+
"
|
|
266
|
+
color="success"
|
|
267
|
+
@click.stop="
|
|
268
|
+
handleActionClick(item, group.set, 'approve')
|
|
269
|
+
"
|
|
270
|
+
/>
|
|
271
|
+
</v-col>
|
|
272
|
+
</v-row>
|
|
273
|
+
</slot>
|
|
274
|
+
</v-col>
|
|
275
|
+
</v-row>
|
|
276
|
+
</v-sheet>
|
|
178
277
|
</template>
|
|
179
|
-
</v-
|
|
278
|
+
</v-sheet>
|
|
180
279
|
|
|
181
280
|
<slot name="footer" />
|
|
182
281
|
</v-card>
|
|
183
282
|
</v-col>
|
|
184
283
|
</v-row>
|
|
185
284
|
|
|
186
|
-
<!-- Attachment Preview Dialog -->
|
|
187
285
|
<v-dialog v-model="showAttachmentDialog" max-width="700" scrollable>
|
|
188
286
|
<v-card>
|
|
189
287
|
<v-card-title class="d-flex align-center pa-4">
|
|
@@ -248,7 +346,6 @@
|
|
|
248
346
|
</v-card>
|
|
249
347
|
</v-dialog>
|
|
250
348
|
|
|
251
|
-
<!-- Full Image Lightbox -->
|
|
252
349
|
<v-dialog v-model="showLightbox" max-width="900">
|
|
253
350
|
<v-card>
|
|
254
351
|
<v-card-actions class="pa-2 justify-end">
|
|
@@ -431,6 +528,34 @@ const allItemsApproved = computed(() => {
|
|
|
431
528
|
);
|
|
432
529
|
});
|
|
433
530
|
|
|
531
|
+
function formatTimestamp(ts: string): string {
|
|
532
|
+
if (!ts) return "";
|
|
533
|
+
const date = new Date(ts);
|
|
534
|
+
return date.toLocaleString("en-SG", {
|
|
535
|
+
day: "2-digit",
|
|
536
|
+
month: "short",
|
|
537
|
+
year: "numeric",
|
|
538
|
+
hour: "2-digit",
|
|
539
|
+
minute: "2-digit",
|
|
540
|
+
hour12: true,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function isGroupComplete(group: { items: any[] }): boolean {
|
|
545
|
+
return (
|
|
546
|
+
group.items.length > 0 &&
|
|
547
|
+
group.items.every((item: any) => item.approve === true)
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function isGroupInProgress(group: { items: any[] }): boolean {
|
|
552
|
+
return (
|
|
553
|
+
group.items.some(
|
|
554
|
+
(item: any) => item.approve === true || item.reject === true
|
|
555
|
+
) && !isGroupComplete(group)
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
434
559
|
function isSetFullyApproved(setNumber: number): boolean {
|
|
435
560
|
const group = groupedItems.value.find((g) => g.set === setNumber);
|
|
436
561
|
if (!group) return false;
|
|
@@ -442,7 +567,7 @@ function isSetFullyApproved(setNumber: number): boolean {
|
|
|
442
567
|
}
|
|
443
568
|
|
|
444
569
|
function getNewApprovedItemsForSet(
|
|
445
|
-
setNumber: number
|
|
570
|
+
setNumber: number
|
|
446
571
|
): Array<{ key: string; item: any; action: "approve" }> {
|
|
447
572
|
const group = groupedItems.value.find((g) => g.set === setNumber);
|
|
448
573
|
if (!group) return [];
|
|
@@ -472,14 +597,14 @@ watch(
|
|
|
472
597
|
internalPage.value = val;
|
|
473
598
|
|
|
474
599
|
itemOrderMap.clear();
|
|
475
|
-
}
|
|
600
|
+
}
|
|
476
601
|
);
|
|
477
602
|
|
|
478
603
|
watch(
|
|
479
604
|
() => props.selected,
|
|
480
605
|
(val) => {
|
|
481
606
|
selected.value = val;
|
|
482
|
-
}
|
|
607
|
+
}
|
|
483
608
|
);
|
|
484
609
|
|
|
485
610
|
watch(selected, (val) => {
|
|
@@ -492,7 +617,7 @@ watch(
|
|
|
492
617
|
if (!items || !Array.isArray(items)) return;
|
|
493
618
|
|
|
494
619
|
Object.keys(persistedActions).forEach(
|
|
495
|
-
(key) => delete persistedActions[key]
|
|
620
|
+
(key) => delete persistedActions[key]
|
|
496
621
|
);
|
|
497
622
|
|
|
498
623
|
items.forEach((group: any) => {
|
|
@@ -511,7 +636,7 @@ watch(
|
|
|
511
636
|
});
|
|
512
637
|
});
|
|
513
638
|
},
|
|
514
|
-
{ immediate: true }
|
|
639
|
+
{ immediate: true }
|
|
515
640
|
);
|
|
516
641
|
|
|
517
642
|
function getKey(item: any, set?: number): string {
|
|
@@ -530,7 +655,7 @@ function isItemSelected(item: any, set?: number): boolean {
|
|
|
530
655
|
|
|
531
656
|
if (typeof selected.value[0] === "object" && "unit" in selected.value[0]) {
|
|
532
657
|
return selected.value.some(
|
|
533
|
-
(s: any) => s.unit === item[props.itemValue] && s.set === set
|
|
658
|
+
(s: any) => s.unit === item[props.itemValue] && s.set === set
|
|
534
659
|
);
|
|
535
660
|
}
|
|
536
661
|
|
|
@@ -540,7 +665,7 @@ function isItemSelected(item: any, set?: number): boolean {
|
|
|
540
665
|
function handleActionClick(
|
|
541
666
|
item: any,
|
|
542
667
|
set: number | undefined,
|
|
543
|
-
action: "approve" | "reject"
|
|
668
|
+
action: "approve" | "reject"
|
|
544
669
|
): void {
|
|
545
670
|
const key = getKey(item, set);
|
|
546
671
|
|
|
@@ -612,6 +737,6 @@ watch(
|
|
|
612
737
|
() => {
|
|
613
738
|
completedSets.value.clear();
|
|
614
739
|
},
|
|
615
|
-
{ deep: true }
|
|
740
|
+
{ deep: true }
|
|
616
741
|
);
|
|
617
742
|
</script>
|