Refactor event form to use separate date/time inputs with validation
All checks were successful
Build And Push Image / docker (push) Successful in 1m26s

- Split combined datetime pickers into separate date and time fields
- Add validation for past dates and time consistency
- Implement error message display with dismissible alerts
- Add watchers to combine date/time values into ISO strings
- Set minimum date constraints to prevent past date selection
- Add delete endpoint for events
This commit is contained in:
2025-08-13 22:23:06 +02:00
parent 9ee0b2f14e
commit 44aee8f2f9
4 changed files with 371 additions and 29 deletions

View File

@@ -81,23 +81,58 @@
<!-- Date and Time -->
<v-col cols="12" md="6">
<VDateInput
v-model="startDateModel"
label="Start Date & Time*"
:rules="[v => !!v || 'Start date is required']"
v-model="startDate"
label="Start Date*"
:rules="[
v => !!v || 'Start date is required',
v => !v || new Date(v).getTime() >= new Date().setHours(0,0,0,0) || 'Start date cannot be in the past'
]"
variant="outlined"
prepend-inner-icon="mdi-calendar"
required
:min="new Date().toISOString().split('T')[0]"
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="startTime"
label="Start Time*"
type="time"
:rules="[v => !!v || 'Start time is required']"
variant="outlined"
prepend-inner-icon="mdi-clock"
required
/>
</v-col>
<v-col cols="12" md="6">
<VDateInput
v-model="endDateModel"
label="End Date & Time*"
:rules="[v => !!v || 'End date is required']"
v-model="endDate"
label="End Date*"
:rules="[
v => !!v || 'End date is required',
v => !v || new Date(v).getTime() >= new Date().setHours(0,0,0,0) || 'End date cannot be in the past',
v => !v || !startDate || new Date(v).getTime() >= new Date(startDate).getTime() || 'End date must be same or after start date'
]"
variant="outlined"
prepend-inner-icon="mdi-calendar"
:min="startDateModel"
:min="startDate || new Date().toISOString().split('T')[0]"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="endTime"
label="End Time*"
type="time"
:rules="[
v => !!v || 'End time is required',
v => !validateEndTime() || 'End time must be after start time when on same date'
]"
variant="outlined"
prepend-inner-icon="mdi-clock"
required
/>
</v-col>
@@ -241,6 +276,18 @@
</v-form>
</v-card-text>
<!-- Error message display -->
<v-card-text v-if="errorMessage" class="pt-0">
<v-alert
type="error"
variant="tonal"
closable
@click:close="errorMessage = null"
>
{{ errorMessage }}
</v-alert>
</v-card-text>
<v-card-actions class="pa-4">
<v-spacer />
<v-btn
@@ -297,7 +344,13 @@ const memberPricingEnabled = ref(true);
const isRecurring = ref(false);
const recurrenceFrequency = ref('weekly');
// Date picker state
// Date and time picker state
const startDate = ref<string>('');
const startTime = ref<string>('');
const endDate = ref<string>('');
const endTime = ref<string>('');
// Legacy date model refs for backward compatibility
const startDateModel = ref<Date | null>(null);
const endDateModel = ref<Date | null>(null);
@@ -427,30 +480,45 @@ watch(recurrenceFrequency, (newValue) => {
}
});
// Watch for separate date and time changes to combine them
watch([startDate, startTime], ([newDate, newTime]) => {
if (newDate && newTime) {
const combinedDateTime = new Date(`${newDate}T${newTime}`);
eventData.start_datetime = combinedDateTime.toISOString();
console.log('[CreateEventDialog] Combined start datetime:', eventData.start_datetime);
}
});
watch([endDate, endTime], ([newDate, newTime]) => {
if (newDate && newTime) {
const combinedDateTime = new Date(`${newDate}T${newTime}`);
eventData.end_datetime = combinedDateTime.toISOString();
console.log('[CreateEventDialog] Combined end datetime:', eventData.end_datetime);
}
});
// Watch for prefilled dates
watch(() => props.prefilledDate, (newDate) => {
if (newDate) {
const startDate = new Date(newDate);
startDateModel.value = startDate;
eventData.start_datetime = startDate.toISOString();
const prefillDate = new Date(newDate);
startDate.value = prefillDate.toISOString().split('T')[0];
startTime.value = prefillDate.toTimeString().substring(0, 5);
// Set end date 2 hours later if not provided
if (!props.prefilledEndDate) {
const endDate = new Date(startDate);
endDate.setHours(endDate.getHours() + 2);
endDateModel.value = endDate;
eventData.end_datetime = endDate.toISOString();
const endDateTime = new Date(prefillDate);
endDateTime.setHours(endDateTime.getHours() + 2);
endDate.value = endDateTime.toISOString().split('T')[0];
endTime.value = endDateTime.toTimeString().substring(0, 5);
}
}
}, { immediate: true });
watch(() => props.prefilledEndDate, (newEndDate) => {
if (newEndDate) {
const endDate = new Date(newEndDate);
endDateModel.value = endDate;
eventData.end_datetime = endDate.toISOString();
const prefillEndDate = new Date(newEndDate);
endDate.value = prefillEndDate.toISOString().split('T')[0];
endTime.value = prefillEndDate.toTimeString().substring(0, 5);
}
}, { immediate: true });
@@ -472,6 +540,22 @@ const onDatePickerClosed = () => {
// This handler ensures the date picker behaves correctly on mobile and desktop
};
// Validation functions
const validateEndTime = () => {
if (!startDate.value || !endDate.value || !startTime.value || !endTime.value) {
return false; // Return false (no error) if not all fields are filled
}
// Only validate if start and end are on the same date
if (startDate.value === endDate.value) {
const start = startTime.value;
const end = endTime.value;
return start >= end; // Return true if there's an error (start >= end)
}
return false; // No error if different dates
};
// Methods
const resetForm = () => {
eventData.title = '';
@@ -512,31 +596,81 @@ const close = () => {
resetForm();
};
// Error handling
const errorMessage = ref<string | null>(null);
const handleSubmit = async () => {
if (!form.value) return;
const isValid = await form.value.validate();
if (!isValid.valid) return;
// Clear previous errors
errorMessage.value = null;
// Validate that we have proper date/time combination
if (!startDate.value || !startTime.value) {
errorMessage.value = 'Start date and time are required';
return;
}
if (!endDate.value || !endTime.value) {
errorMessage.value = 'End date and time are required';
return;
}
loading.value = true;
try {
// Ensure datetime strings are properly formatted
const startDate = new Date(eventData.start_datetime);
const endDate = new Date(eventData.end_datetime);
// Combine date and time properly
const startDateTime = new Date(`${startDate.value}T${startTime.value}`);
const endDateTime = new Date(`${endDate.value}T${endTime.value}`);
// Validate start is not in the past
if (startDateTime < new Date()) {
errorMessage.value = 'Event start time cannot be in the past';
loading.value = false;
return;
}
// Validate end is after start
if (endDateTime <= startDateTime) {
errorMessage.value = 'Event end time must be after start time';
loading.value = false;
return;
}
const formattedEventData = {
...eventData,
start_datetime: startDate.toISOString(),
end_datetime: endDate.toISOString()
start_datetime: startDateTime.toISOString(),
end_datetime: endDateTime.toISOString()
};
console.log('[CreateEventDialog] Creating event with data:', formattedEventData);
const newEvent = await createEvent(formattedEventData);
emit('event-created', newEvent);
close();
} catch (error: any) {
console.error('Error creating event:', error);
// Parse error message for better UX
let userErrorMessage = 'Failed to create event';
if (error?.data?.message) {
userErrorMessage = error.data.message;
} else if (error?.message) {
if (error.message.includes('past')) {
userErrorMessage = 'Event date cannot be in the past';
} else if (error.message.includes('validation')) {
userErrorMessage = 'Please check all required fields';
} else {
userErrorMessage = error.message;
}
}
errorMessage.value = userErrorMessage;
} finally {
loading.value = false;
}