mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
feat: Implement flexible date input
This commit is contained in:
43
src/components/ui/flexible-date-display.tsx
Normal file
43
src/components/ui/flexible-date-display.tsx
Normal file
@@ -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 <span className={className || "text-muted-foreground"}>{fallback}</span>;
|
||||
}
|
||||
|
||||
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
||||
|
||||
// Check for invalid date
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
return <span className={className || "text-muted-foreground"}>{fallback}</span>;
|
||||
}
|
||||
|
||||
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 <span className={className}>{formatted}</span>;
|
||||
}
|
||||
176
src/components/ui/flexible-date-input.tsx
Normal file
176
src/components/ui/flexible-date-input.tsx
Normal file
@@ -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<DatePrecision>(precision);
|
||||
const [yearValue, setYearValue] = React.useState<string>(
|
||||
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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<div className={cn("space-y-2", className)}>
|
||||
{label && <Label>{label}</Label>}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
{localPrecision === 'day' && (
|
||||
<DatePicker
|
||||
date={value}
|
||||
onSelect={(date) => onChange(date, 'day')}
|
||||
placeholder={getPlaceholderText()}
|
||||
disabled={disabled}
|
||||
disableFuture={disableFuture}
|
||||
disablePast={disablePast}
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
/>
|
||||
)}
|
||||
|
||||
{localPrecision === 'month' && (
|
||||
<MonthYearPicker
|
||||
date={value}
|
||||
onSelect={(date) => onChange(date, 'month')}
|
||||
placeholder={getPlaceholderText()}
|
||||
disabled={disabled}
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
/>
|
||||
)}
|
||||
|
||||
{localPrecision === 'year' && (
|
||||
<Input
|
||||
type="number"
|
||||
value={yearValue}
|
||||
onChange={handleYearChange}
|
||||
placeholder={getPlaceholderText()}
|
||||
disabled={disabled}
|
||||
min={fromYear}
|
||||
max={toYear}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Select
|
||||
value={localPrecision}
|
||||
onValueChange={(value) => handlePrecisionChange(value as DatePrecision)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="w-[160px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="day">Use Full Date</SelectItem>
|
||||
<SelectItem value="month">Use Month/Year</SelectItem>
|
||||
<SelectItem value="year">Use Year Only</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user