refactor: Split EventFormFields.tsx into smaller modules

- Extract helper functions to utils/dateTimeHelpers.ts and utils/dateTimeFormatters.ts
- Extract validation logic to utils/formValidation.ts
- Extract FieldWithLabel component to components/FieldWithLabel.tsx
- Extract DateTimeFields component to components/DateTimeFields.tsx
- Extract all-day toggle logic to hooks/useAllDayToggle.ts
- Update imports in EventModal.tsx, EventUpdateModal.tsx, EventDisplay.tsx, eventHandlers.ts
- Maintain all existing functionality without breaking changes
This commit is contained in:
lenhanphung
2025-10-24 16:23:10 +07:00
committed by Benoit TELLIER
parent 701f2f2b71
commit 01f79553c0
11 changed files with 657 additions and 441 deletions
@@ -0,0 +1,80 @@
/**
* Date/time formatting utilities
*/
/**
* Format a Date object to local datetime string (YYYY-MM-DDTHH:mm)
* @param date - Date object to format
* @param timeZone - Optional timezone for formatting
* @returns Formatted datetime string
*/
export function formatLocalDateTime(date: Date, timeZone?: string): string {
// Guard against invalid or undefined dates
if (!date || isNaN(date.getTime())) {
return "";
}
if (timeZone) {
const formatter = new Intl.DateTimeFormat("en-CA", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone,
});
const formatted = formatter.format(date);
return formatted.replace(", ", "T");
}
const pad = (n: number) => n.toString().padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
date.getDate()
)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
/**
* Format an ISO datetime string in a specific timezone
* @param isoString - ISO datetime string
* @param timezone - Target timezone
* @returns Formatted datetime string in target timezone
*/
export function formatDateTimeInTimezone(
isoString: string,
timezone: string
): string {
// Parse the ISO string as UTC
const utcDate = new Date(isoString);
// Format the date in the target timezone
const formatter = new Intl.DateTimeFormat("en-CA", {
timeZone: timezone,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
const parts = formatter.formatToParts(utcDate);
const getValue = (type: string) =>
parts.find((p) => p.type === type)?.value || "";
return `${getValue("year")}-${getValue("month")}-${getValue("day")}T${getValue("hour")}:${getValue("minute")}`;
}
/**
* Get current time rounded to nearest 30 minutes
* @returns Rounded Date object
*/
export function getRoundedCurrentTime(): Date {
const now = new Date();
const minutes = now.getMinutes();
const roundedMinutes = minutes < 30 ? 0 : 30;
now.setMinutes(roundedMinutes);
now.setSeconds(0);
now.setMilliseconds(0);
return now;
}
@@ -0,0 +1,32 @@
/**
* Helper functions for date/time string manipulation
*/
/**
* Split datetime string (YYYY-MM-DDTHH:mm) into date and time parts
* @param datetime - ISO datetime string
* @returns Object with date and time strings
*/
export function splitDateTime(datetime: string): {
date: string;
time: string;
} {
if (!datetime) return { date: "", time: "" };
const parts = datetime.split("T");
return {
date: parts[0] || "",
time: parts[1]?.slice(0, 5) || "", // HH:mm only
};
}
/**
* Combine date and time strings into datetime string
* @param date - Date string (YYYY-MM-DD)
* @param time - Time string (HH:mm)
* @returns Combined datetime string or date only if no time
*/
export function combineDateTime(date: string, time: string): string {
if (!date) return "";
if (!time) return date; // Date only for all-day
return `${date}T${time}`;
}
@@ -0,0 +1,96 @@
import { combineDateTime } from "./dateTimeHelpers";
/**
* Validation parameters for event form
*/
export interface ValidationParams {
title: string;
startDate: string;
startTime: string;
endDate: string;
endTime: string;
allday: boolean;
showValidationErrors: boolean;
}
/**
* Validation result for event form
*/
export interface ValidationResult {
isValid: boolean;
errors: {
title: string;
dateTime: string;
};
}
/**
* Validate event form fields
* @param params - Validation parameters
* @returns Validation result with errors
*/
export function validateEventForm(params: ValidationParams): ValidationResult {
const {
title,
startDate,
startTime,
endDate,
endTime,
allday,
showValidationErrors,
} = params;
const isTitleValid = title.trim().length > 0;
const shouldShowTitleError = showValidationErrors && !isTitleValid;
let isDateTimeValid = true;
let dateTimeError = "";
// 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 date
else if (!endDate || endDate.trim() === "") {
isDateTimeValid = false;
dateTimeError = "End date is required";
}
// Validate end time (if not all-day)
else if (!allday && (!endTime || endTime.trim() === "")) {
isDateTimeValid = false;
dateTimeError = "End time is required";
}
// Validate end > start
else {
const startDateTime = allday
? new Date(startDate + "T00:00:00")
: new Date(combineDateTime(startDate, startTime));
const endDateTime = allday
? new Date(endDate + "T00:00:00")
: 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 time must be after start time";
}
}
const isValid = isTitleValid && isDateTimeValid;
return {
isValid,
errors: {
title: shouldShowTitleError ? "Title is required" : "",
dateTime: showValidationErrors ? dateTimeError : "",
},
};
}