diff --git a/src/components/ui/flexible-date-display.tsx b/src/components/ui/flexible-date-display.tsx new file mode 100644 index 00000000..821d1bc6 --- /dev/null +++ b/src/components/ui/flexible-date-display.tsx @@ -0,0 +1,43 @@ +import { format } from 'date-fns'; +import type { DatePrecision } from './flexible-date-input'; + +interface FlexibleDateDisplayProps { + date?: string | Date | null; + precision?: DatePrecision; + fallback?: string; + className?: string; +} + +export function FlexibleDateDisplay({ + date, + precision = 'day', + fallback = 'Unknown', + className +}: FlexibleDateDisplayProps) { + if (!date) { + return {fallback}; + } + + const dateObj = typeof date === 'string' ? new Date(date) : date; + + // Check for invalid date + if (isNaN(dateObj.getTime())) { + return {fallback}; + } + + let formatted: string; + switch (precision) { + case 'year': + formatted = format(dateObj, 'yyyy'); + break; + case 'month': + formatted = format(dateObj, 'MMMM yyyy'); + break; + case 'day': + default: + formatted = format(dateObj, 'PPP'); + break; + } + + return {formatted}; +} diff --git a/src/components/ui/flexible-date-input.tsx b/src/components/ui/flexible-date-input.tsx new file mode 100644 index 00000000..fd1c8e4d --- /dev/null +++ b/src/components/ui/flexible-date-input.tsx @@ -0,0 +1,176 @@ +import * as React from "react"; +import { format } from "date-fns"; +import { CalendarIcon } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { DatePicker } from "@/components/ui/date-picker"; +import { MonthYearPicker } from "@/components/ui/month-year-picker"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +export type DatePrecision = 'day' | 'month' | 'year'; + +interface FlexibleDateInputProps { + value?: Date; + precision?: DatePrecision; + onChange: (date: Date | undefined, precision: DatePrecision) => void; + placeholder?: string; + disabled?: boolean; + className?: string; + disableFuture?: boolean; + disablePast?: boolean; + fromYear?: number; + toYear?: number; + label?: string; +} + +export function FlexibleDateInput({ + value, + precision = 'day', + onChange, + placeholder = "Select date", + disabled = false, + className, + disableFuture = false, + disablePast = false, + fromYear = 1800, + toYear = new Date().getFullYear() + 10, + label, +}: FlexibleDateInputProps) { + const [localPrecision, setLocalPrecision] = React.useState(precision); + const [yearValue, setYearValue] = React.useState( + value && precision === 'year' ? value.getFullYear().toString() : '' + ); + + React.useEffect(() => { + setLocalPrecision(precision); + }, [precision]); + + React.useEffect(() => { + if (value && precision === 'year') { + setYearValue(value.getFullYear().toString()); + } + }, [value, precision]); + + const handlePrecisionChange = (newPrecision: DatePrecision) => { + setLocalPrecision(newPrecision); + + // If we have a value, adjust it to the new precision + if (value) { + const year = value.getFullYear(); + const month = value.getMonth(); + + let newDate: Date; + switch (newPrecision) { + case 'year': + newDate = new Date(year, 0, 1); // January 1st + setYearValue(year.toString()); + break; + case 'month': + newDate = new Date(year, month, 1); // 1st of month + break; + case 'day': + default: + newDate = value; // Keep existing date + break; + } + onChange(newDate, newPrecision); + } else { + onChange(undefined, newPrecision); + } + }; + + const handleYearChange = (e: React.ChangeEvent) => { + const yearStr = e.target.value; + setYearValue(yearStr); + + const yearNum = parseInt(yearStr, 10); + if (yearStr && !isNaN(yearNum) && yearNum >= fromYear && yearNum <= toYear) { + const newDate = new Date(yearNum, 0, 1); + onChange(newDate, 'year'); + } else if (!yearStr) { + onChange(undefined, 'year'); + } + }; + + const getPlaceholderText = () => { + switch (localPrecision) { + case 'year': + return 'Enter year (e.g., 2005)'; + case 'month': + return 'Select month and year'; + case 'day': + default: + return placeholder; + } + }; + + return ( +
+ {label && } + +
+
+ {localPrecision === 'day' && ( + onChange(date, 'day')} + placeholder={getPlaceholderText()} + disabled={disabled} + disableFuture={disableFuture} + disablePast={disablePast} + fromYear={fromYear} + toYear={toYear} + /> + )} + + {localPrecision === 'month' && ( + onChange(date, 'month')} + placeholder={getPlaceholderText()} + disabled={disabled} + fromYear={fromYear} + toYear={toYear} + /> + )} + + {localPrecision === 'year' && ( + + )} +
+ + +
+
+ ); +}