Files
workavia-calendar-front/src/components/Event/utils/formValidation.ts
T
lenhanphung 627e9bf44b feat(DateTimeFields): implement free typing time input with dropdown
- Replace default TimePicker field with custom EditableTimeField
- Allow free typing with multiple formats (HH:mm, HHmm, H, etc.)
- Show dropdown on click while maintaining typing capability
- Center-align time input text
2025-12-15 15:33:07 +01:00

140 lines
4.0 KiB
TypeScript

import { combineDateTime } from "./dateTimeHelpers";
/**
* Validation parameters for event form
*/
export interface ValidationParams {
startDate: string;
startTime: string;
endDate: string;
endTime: string;
allday: boolean;
showValidationErrors: boolean;
hasEndDateChanged?: boolean;
showMore?: boolean;
}
/**
* Validation result for event form
*/
export interface ValidationResult {
isValid: boolean;
errors: {
dateTime: string;
};
}
/**
* Validate event form fields
* @param params - Validation parameters
* @returns Validation result with errors
*/
export function validateEventForm(params: ValidationParams): ValidationResult {
const {
startDate,
startTime,
endDate,
endTime,
allday,
showValidationErrors,
hasEndDateChanged = false,
showMore = false,
} = params;
let isDateTimeValid = true;
let dateTimeError = "";
// Determine which fields are visible based on UI mode
const showFullFields =
showMore ||
allday ||
hasEndDateChanged ||
(!showMore && !allday && startDate !== endDate);
const showTimeOnly = !allday && !showFullFields;
// Validate start date
if (!startDate || startDate.trim() === "") {
isDateTimeValid = false;
dateTimeError = "Start date is required";
}
// Validate start time (if not all-day)
else if (!allday && (!startTime || startTime.trim() === "")) {
isDateTimeValid = false;
dateTimeError = "Start time is required";
}
// Validate end fields based on UI mode
else if (showFullFields) {
// 4 fields mode: validate both end date and end time
if (!endDate || endDate.trim() === "") {
isDateTimeValid = false;
dateTimeError = "End date is required";
} else if (!allday && (!endTime || endTime.trim() === "")) {
isDateTimeValid = false;
dateTimeError = "End time is required";
} else {
// Validate total datetime
if (allday) {
const toLocalDate = (ymd: string) => {
const [y, m, d] = ymd.split("-").map((v) => parseInt(v, 10));
if (!y || !m || !d) return new Date(NaN);
return new Date(y, m - 1, d);
};
const startOnly = toLocalDate(startDate);
const endOnly = toLocalDate(endDate);
if (isNaN(startOnly.getTime()) || isNaN(endOnly.getTime())) {
isDateTimeValid = false;
dateTimeError = "Invalid date";
} else if (endOnly < startOnly) {
isDateTimeValid = false;
dateTimeError = "End date must be on or after start date";
}
} else {
const startDateTime = new Date(combineDateTime(startDate, startTime));
const endDateTime = new Date(combineDateTime(endDate, endTime));
if (isNaN(startDateTime.getTime()) || isNaN(endDateTime.getTime())) {
isDateTimeValid = false;
dateTimeError = "Invalid date/time";
} else if (endDateTime <= startDateTime) {
isDateTimeValid = false;
dateTimeError = "End date/time must be after start date/time";
}
}
}
} else if (showTimeOnly) {
// 3 fields mode: validate time only (end time > start time, same day)
if (!endTime || endTime.trim() === "") {
isDateTimeValid = false;
dateTimeError = "End time is required";
} else {
// Compare times only (same day is assumed)
const startTimeParts = startTime.split(":");
const endTimeParts = endTime.split(":");
if (startTimeParts.length === 2 && endTimeParts.length === 2) {
const startMinutes =
parseInt(startTimeParts[0]) * 60 + parseInt(startTimeParts[1]);
const endMinutes =
parseInt(endTimeParts[0]) * 60 + parseInt(endTimeParts[1]);
if (endMinutes <= startMinutes) {
isDateTimeValid = false;
dateTimeError = "End time must be after start time";
}
} else {
isDateTimeValid = false;
dateTimeError = "Invalid time format";
}
}
}
const isValid = isDateTimeValid;
return {
isValid,
errors: {
dateTime: showValidationErrors ? dateTimeError : "",
},
};
}