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
This commit is contained in:
committed by
Benoit TELLIER
parent
12776d36a4
commit
627e9bf44b
@@ -1,5 +1,8 @@
|
||||
import moment from "moment-timezone";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
/**
|
||||
* Helper functions for date/time string manipulation
|
||||
@@ -9,6 +12,8 @@ export const DATETIME_WITH_SECONDS_LENGTH = 19;
|
||||
export const DATETIME_FORMAT_WITH_SECONDS = "YYYY-MM-DDTHH:mm:ss";
|
||||
export const DATETIME_FORMAT_WITHOUT_SECONDS = "YYYY-MM-DDTHH:mm";
|
||||
|
||||
export const TIME_PARSE_FORMATS = ["HH:mm", "H:mm", "HHmm", "Hmm", "HH", "H"];
|
||||
|
||||
/**
|
||||
* Detect datetime format based on string length
|
||||
* @param datetime - Datetime string to analyze
|
||||
@@ -83,3 +88,35 @@ export const dtDate = (d: Dayjs) => d.format("YYYY-MM-DD");
|
||||
|
||||
/** Extract time “HH:mm” */
|
||||
export const dtTime = (d: Dayjs) => d.format("HH:mm");
|
||||
|
||||
/**
|
||||
* Parse flexible time string input into Dayjs object
|
||||
* Handles formats like: "HH:mm", "HHmm", "Hmm", "HH", "H"
|
||||
* Also handles 3-digit input normalization (e.g. "830" -> "08:30")
|
||||
*/
|
||||
export function parseTimeInput(
|
||||
value: string,
|
||||
currentDate: Dayjs | null
|
||||
): Dayjs | null {
|
||||
let trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize 3-digit input (e.g. "830" → "0830" → 08:30, "123" → "0123" → 01:23)
|
||||
if (/^\d{3}$/.test(trimmed)) {
|
||||
trimmed = trimmed.padStart(4, "0");
|
||||
}
|
||||
|
||||
const parsed = dayjs(trimmed, TIME_PARSE_FORMATS, true);
|
||||
if (parsed.isValid()) {
|
||||
const baseDate = currentDate || dayjs();
|
||||
return baseDate
|
||||
.hour(parsed.hour())
|
||||
.minute(parsed.minute())
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -45,9 +45,12 @@ export function validateEventForm(params: ValidationParams): ValidationResult {
|
||||
let dateTimeError = "";
|
||||
|
||||
// Determine which fields are visible based on UI mode
|
||||
const showFullFields = showMore || hasEndDateChanged;
|
||||
const showFullFields =
|
||||
showMore ||
|
||||
allday ||
|
||||
hasEndDateChanged ||
|
||||
(!showMore && !allday && startDate !== endDate);
|
||||
const showTimeOnly = !allday && !showFullFields;
|
||||
const showDateOnly = allday;
|
||||
|
||||
// Validate start date
|
||||
if (!startDate || startDate.trim() === "") {
|
||||
@@ -123,29 +126,6 @@ export function validateEventForm(params: ValidationParams): ValidationResult {
|
||||
dateTimeError = "Invalid time format";
|
||||
}
|
||||
}
|
||||
} else if (showDateOnly) {
|
||||
// 2 fields mode: validate date only
|
||||
if (!endDate || endDate.trim() === "") {
|
||||
isDateTimeValid = false;
|
||||
dateTimeError = "End date is required";
|
||||
} else {
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isValid = isDateTimeValid;
|
||||
|
||||
Reference in New Issue
Block a user