1<template>
2 <Dialog
3 v-model:open="dialog.show"
4 @after-leave="resetForm"
5 :title="dialog.isEditing ? 'Edit workday' : 'Add workday'"
6 >
7 <template #default>
8 <div class="flex flex-col gap-4">
9 <div>
10 <FormControl
11 :type="'select'"
12 size="sm"
13 variant="subtle"
14 placeholder="Select Workday"
15 label="Workday"
16 v-model="workDayData.workday"
17 :options="[
18 {
19 label: 'Monday',
20 value: 'Monday',
21 },
22 {
23 label: 'Tuesday',
24 value: 'Tuesday',
25 },
26 {
27 label: 'Wednesday',
28 value: 'Wednesday',
29 },
30 {
31 label: 'Thursday',
32 value: 'Thursday',
33 },
34 {
35 label: 'Friday',
36 value: 'Friday',
37 },
38 {
39 label: 'Saturday',
40 value: 'Saturday',
41 },
42 {
43 label: 'Sunday',
44 value: 'Sunday',
45 },
46 ]"
47 :class="{ 'border-outline-red-4': errors.workday }"
48 @blur="validateField('workday')"
49 />
50 <ErrorMessage :message="errors.workday" class="mt-2" />
51 </div>
52
53 <div>
54 <FormControl
55 :type="'time'"
56 size="sm"
57 variant="subtle"
58 placeholder="Start Time"
59 label="Start Time"
60 v-model="workDayData.start_time"
61 :class="{ 'border-outline-red-4': errors.start_time }"
62 @blur="validateField('start_time')"
63 />
64 <ErrorMessage :message="errors.start_time" class="mt-2" />
65 </div>
66
67 <div>
68 <FormControl
69 :type="'time'"
70 size="sm"
71 variant="subtle"
72 placeholder="End Time"
73 label="End Time"
74 v-model="workDayData.end_time"
75 :class="{ 'border-outline-red-4': errors.end_time }"
76 @blur="validateTimeRange"
77 />
78 <ErrorMessage :message="errors.end_time" class="mt-2" />
79 </div>
80 </div>
81 </template>
82 <template #actions>
83 <div
84 class="flex"
85 :class="{
86 'justify-between': dialog.isEditing,
87 'justify-end': !dialog.isEditing,
88 }"
89 >
90 <div v-if="dialog.isEditing">
91 <Button
92 variant="subtle"
93 :theme="isConfirmingDelete ? 'red' : 'gray'"
94 :label="isConfirmingDelete ? 'Confirm Delete' : 'Delete'"
95 @click="deleteWorkDay"
96 icon-left="lucide-trash-2"
97 />
98 </div>
99 <div class="flex gap-2">
100 <Button
101 variant="subtle"
102 theme="gray"
103 @click="dialog.show = false"
104 label="Cancel"
105 />
106 <Button variant="solid" @click="onSave" label="Save" />
107 </div>
108 </div>
109 </template>
110 </Dialog>
111</template>
112
113<script setup lang="ts">
114import { ref, reactive, watch } from "vue";
115import { Dialog, FormControl, Button, toast } from "frappe-ui";
116import { __ } from "@/translation";
117
118const isConfirmingDelete = ref(false);
119const props = defineProps({
120 workDaysList: {
121 type: Array<any>,
122 required: true,
123 },
124});
125
126interface DialogData {
127 show: boolean;
128 isEditing: boolean;
129 data?: any;
130}
131
132const dialog = defineModel<DialogData>({
133 required: true,
134 default: () => ({
135 show: false,
136 isEditing: false,
137 data: {
138 workday: "",
139 start_time: "",
140 end_time: "",
141 },
142 }),
143});
144
145const workDayData = reactive({
146 workday: "",
147 start_time: "",
148 end_time: "",
149});
150
151const errors = reactive({
152 workday: "",
153 start_time: "",
154 end_time: "",
155});
156
157const deleteWorkDay = (event) => {
158 event.preventDefault();
159 if (!isConfirmingDelete.value) {
160 isConfirmingDelete.value = true;
161 return;
162 }
163
164 const item = props.workDaysList.findIndex(
165 (item) => item.workday === workDayData.workday
166 );
167 if (item !== -1) {
168 props.workDaysList.splice(item, 1);
169 }
170};
171
172function formatTimeToHHMMSS(timeStr: string) {
173 if (!timeStr) return "";
174
175 if (/^\d{1,2}:\d{2}:\d{2}$/.test(timeStr)) {
176 const [hours, minutes, seconds] = timeStr.split(":");
177 return `${hours.padStart(2, "0")}:${minutes.padStart(
178 2,
179 "0"
180 )}:${seconds.padStart(2, "0")}`;
181 }
182
183 if (/^\d{1,2}:\d{2}$/.test(timeStr)) {
184 const [hours, minutes] = timeStr.split(":");
185 return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}:00`;
186 }
187
188 return "";
189}
190
191function resetForm() {
192 workDayData.workday = "";
193 workDayData.start_time = "";
194 workDayData.end_time = "";
195 errors.workday = "";
196 errors.start_time = "";
197 errors.end_time = "";
198 isConfirmingDelete.value = false;
199}
200
201const validateField = (field: string) => {
202 if (!workDayData[field as keyof typeof workDayData]) {
203 errors[field as keyof typeof errors] = "This field is required";
204 return false;
205 }
206 errors[field as keyof typeof errors] = "";
207 return true;
208};
209
210const validateTimeRange = () => {
211 if (!workDayData.start_time || !workDayData.end_time) {
212 if (!workDayData.start_time) errors.start_time = "Start time is required";
213 if (!workDayData.end_time) errors.end_time = "End time is required";
214 return false;
215 }
216
217 const [startHours, startMinutes] = workDayData.start_time
218 .split(":")
219 .map(Number);
220 const [endHours, endMinutes] = workDayData.end_time.split(":").map(Number);
221
222 if (
223 endHours < startHours ||
224 (endHours === startHours && endMinutes <= startMinutes)
225 ) {
226 errors.end_time = "End time must be after start time";
227 return false;
228 }
229
230 errors.end_time = "";
231 return true;
232};
233
234const validateForm = () => {
235 const isWorkdayValid = validateField("workday");
236 const isStartTimeValid = validateField("start_time");
237 const isEndTimeValid = validateField("end_time") && validateTimeRange();
238
239 return isWorkdayValid && isStartTimeValid && isEndTimeValid;
240};
241
242const onSave = () => {
243 if (!validateForm()) {
244 toast.error(__("Please fix the errors in the form"));
245 return;
246 }
247
248 try {
249 if (dialog.value.isEditing) {
250 const itemIndex = props.workDaysList.findIndex(
251 (item) => item.workday === dialog.value.data?.workday
252 );
253 if (itemIndex !== -1) {
254 const updatedItem = {
255 ...props.workDaysList[itemIndex],
256 ...workDayData,
257 };
258 props.workDaysList.splice(itemIndex, 1, updatedItem);
259 toast.success(__("Workday updated successfully."));
260 }
261 } else {
262 const isDuplicate = props.workDaysList.some(
263 (item) => item.workday === workDayData.workday
264 );
265
266 if (isDuplicate) {
267 errors.workday = "This workday already exists";
268 toast.error(__("A workday with this name already exists"));
269 return;
270 }
271
272 const newWorkDay = { ...workDayData };
273 props.workDaysList.push(newWorkDay);
274 toast.success(__("Workday added successfully."));
275 }
276 dialog.value.show = false;
277 } catch (error) {
278 toast.error(__(`Failed to save workday: ${error}`));
279 }
280};
281
282watch(
283 () => dialog.value.show,
284 (isOpen) => {
285 if (isOpen) {
286 if (dialog.value.isEditing && dialog.value.data) {
287 workDayData.workday = dialog.value.data.workday;
288 workDayData.start_time = formatTimeToHHMMSS(
289 dialog.value.data.start_time
290 );
291 workDayData.end_time = formatTimeToHHMMSS(dialog.value.data.end_time);
292 } else {
293 resetForm();
294 }
295 }
296 }
297);
298</script>
299