mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 10:11:13 -05:00
Reverted to commit 10950a4034
This commit is contained in:
@@ -94,14 +94,10 @@ export function FormerNamesEditor({ names, onChange, currentName }: FormerNamesE
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<Label className="text-xs">Date Changed</Label>
|
||||
<DatePicker
|
||||
date={name.date_changed ? new Date(name.date_changed) : undefined}
|
||||
onSelect={(date) => updateName(actualIndex, 'date_changed', date || undefined)}
|
||||
placeholder="Select or type date"
|
||||
allowTextEntry={true}
|
||||
fromYear={1800}
|
||||
toYear={new Date().getFullYear()}
|
||||
/>
|
||||
<DatePicker
|
||||
date={name.date_changed ? new Date(name.date_changed) : undefined}
|
||||
onSelect={(date) => updateName(actualIndex, 'date_changed', date || undefined)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -180,15 +180,13 @@ export function ReviewForm({
|
||||
{/* Visit Date */}
|
||||
<div className="space-y-2">
|
||||
<Label>Visit Date</Label>
|
||||
<DatePicker
|
||||
date={watch('visit_date') ? new Date(watch('visit_date')) : undefined}
|
||||
onSelect={(date) => setValue('visit_date', date ? date.toISOString().split('T')[0] : undefined)}
|
||||
placeholder="When did you visit?"
|
||||
disableFuture={true}
|
||||
fromYear={1950}
|
||||
allowTextEntry={true}
|
||||
dateFormat="yyyy-MM-dd"
|
||||
/>
|
||||
<DatePicker
|
||||
date={watch('visit_date') ? new Date(watch('visit_date')) : undefined}
|
||||
onSelect={(date) => setValue('visit_date', date ? date.toISOString().split('T')[0] : undefined)}
|
||||
placeholder="When did you visit?"
|
||||
disableFuture={true}
|
||||
fromYear={1950}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Select the date of your visit to help others understand when this experience occurred.
|
||||
</p>
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { CaptionProps, useNavigation } from "react-day-picker";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { YearGridSelector } from "@/components/ui/year-grid-selector";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface CustomCaptionProps extends CaptionProps {
|
||||
fromYear?: number;
|
||||
toYear?: number;
|
||||
}
|
||||
|
||||
export function CustomCalendarCaption({
|
||||
displayMonth,
|
||||
fromYear = 1800,
|
||||
toYear = new Date().getFullYear() + 10,
|
||||
}: CustomCaptionProps) {
|
||||
const { goToMonth, nextMonth, previousMonth, currentMonth } = useNavigation();
|
||||
|
||||
const handleYearSelect = (year: number) => {
|
||||
const newDate = new Date(displayMonth);
|
||||
newDate.setFullYear(year);
|
||||
goToMonth(newDate);
|
||||
};
|
||||
|
||||
const monthName = displayMonth.toLocaleString("en-US", { month: "long" });
|
||||
const selectedYear = displayMonth.getFullYear();
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center px-2 py-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
onClick={() => previousMonth && goToMonth(previousMonth)}
|
||||
disabled={!previousMonth}
|
||||
type="button"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{monthName}</span>
|
||||
<YearGridSelector
|
||||
selectedYear={selectedYear}
|
||||
onYearSelect={handleYearSelect}
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"h-7 px-2 text-sm font-medium hover:bg-accent",
|
||||
"focus:bg-accent focus:text-accent-foreground"
|
||||
)}
|
||||
type="button"
|
||||
>
|
||||
{selectedYear}
|
||||
</Button>
|
||||
</YearGridSelector>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
onClick={() => nextMonth && goToMonth(nextMonth)}
|
||||
disabled={!nextMonth}
|
||||
type="button"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,20 +4,13 @@ import { DayPicker } from "react-day-picker";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { CustomCalendarCaption } from "@/components/ui/calendar-custom-caption";
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker> & {
|
||||
enableYearGrid?: boolean;
|
||||
};
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
function Calendar({ className, classNames, showOutsideDays = true, enableYearGrid = false, ...props }: CalendarProps) {
|
||||
const captionLayout = enableYearGrid ? undefined : (props.captionLayout || "dropdown-buttons");
|
||||
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
captionLayout={captionLayout}
|
||||
fromYear={props.fromYear || 1800}
|
||||
toYear={props.toYear || new Date().getFullYear() + 10}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
@@ -49,15 +42,6 @@ function Calendar({ className, classNames, showOutsideDays = true, enableYearGri
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
Caption: enableYearGrid
|
||||
? (captionProps) => (
|
||||
<CustomCalendarCaption
|
||||
{...captionProps}
|
||||
fromYear={props.fromYear || 1800}
|
||||
toYear={props.toYear || new Date().getFullYear() + 10}
|
||||
/>
|
||||
)
|
||||
: undefined,
|
||||
IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
|
||||
IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
|
||||
}}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { format, parse, isValid } from "date-fns";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DatePicker, DatePickerProps } from "./date-picker";
|
||||
|
||||
interface DateInputWithFeedbackProps extends DatePickerProps {
|
||||
showFeedback?: boolean;
|
||||
}
|
||||
|
||||
export function DateInputWithFeedback({
|
||||
showFeedback = true,
|
||||
onSelect,
|
||||
...props
|
||||
}: DateInputWithFeedbackProps) {
|
||||
const [parseStatus, setParseStatus] = React.useState<{
|
||||
isValid: boolean;
|
||||
message: string;
|
||||
parsed?: Date;
|
||||
}>({ isValid: true, message: "" });
|
||||
|
||||
const parseDate = (input: string): Date | null => {
|
||||
if (!input) return null;
|
||||
|
||||
// Try ISO format: 2005-06-15
|
||||
let parsed = parse(input, "yyyy-MM-dd", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try US format: 06/15/2005
|
||||
parsed = parse(input, "MM/dd/yyyy", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try European format: 15/06/2005
|
||||
parsed = parse(input, "dd/MM/yyyy", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try short format: 6/15/05
|
||||
parsed = parse(input, "M/d/yy", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try short year format: 2005-6-15
|
||||
parsed = parse(input, "yyyy-M-d", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleSelect = (date: Date | undefined) => {
|
||||
if (date) {
|
||||
setParseStatus({
|
||||
isValid: true,
|
||||
message: `✓ ${format(date, "PPP")}`,
|
||||
parsed: date
|
||||
});
|
||||
} else {
|
||||
setParseStatus({ isValid: true, message: "" });
|
||||
}
|
||||
onSelect?.(date);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<DatePicker
|
||||
allowTextEntry
|
||||
onSelect={handleSelect}
|
||||
{...props}
|
||||
/>
|
||||
{showFeedback && parseStatus.message && (
|
||||
<p className={cn(
|
||||
"text-xs",
|
||||
parseStatus.isValid
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-amber-600 dark:text-amber-400"
|
||||
)}>
|
||||
{parseStatus.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { format, parse, isValid } from "date-fns";
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import {
|
||||
Popover,
|
||||
@@ -22,9 +21,6 @@ export interface DatePickerProps {
|
||||
disablePast?: boolean;
|
||||
fromYear?: number;
|
||||
toYear?: number;
|
||||
allowTextEntry?: boolean;
|
||||
dateFormat?: string;
|
||||
enableYearGrid?: boolean;
|
||||
}
|
||||
|
||||
export function DatePicker({
|
||||
@@ -37,66 +33,12 @@ export function DatePicker({
|
||||
disablePast = false,
|
||||
fromYear,
|
||||
toYear,
|
||||
allowTextEntry = false,
|
||||
dateFormat = "yyyy-MM-dd",
|
||||
enableYearGrid = false,
|
||||
}: DatePickerProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [textInput, setTextInput] = React.useState("");
|
||||
const [isTyping, setIsTyping] = React.useState(false);
|
||||
|
||||
const parseDate = (input: string): Date | null => {
|
||||
if (!input) return null;
|
||||
|
||||
// Try ISO format: 2005-06-15
|
||||
let parsed = parse(input, "yyyy-MM-dd", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try US format: 06/15/2005
|
||||
parsed = parse(input, "MM/dd/yyyy", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try European format: 15/06/2005
|
||||
parsed = parse(input, "dd/MM/yyyy", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try short format: 6/15/05
|
||||
parsed = parse(input, "M/d/yy", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
// Try short year format: 2005-6-15
|
||||
parsed = parse(input, "yyyy-M-d", new Date());
|
||||
if (isValid(parsed)) return parsed;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setTextInput(value);
|
||||
setIsTyping(true);
|
||||
|
||||
const parsed = parseDate(value);
|
||||
if (parsed) {
|
||||
onSelect?.(parsed);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsTyping(false);
|
||||
if (date) {
|
||||
setTextInput(format(date, dateFormat));
|
||||
} else {
|
||||
setTextInput("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (selectedDate: Date | undefined) => {
|
||||
onSelect?.(selectedDate);
|
||||
setOpen(false);
|
||||
if (selectedDate) {
|
||||
setTextInput(format(selectedDate, dateFormat));
|
||||
}
|
||||
};
|
||||
|
||||
const getDisabledDates = (date: Date) => {
|
||||
@@ -106,49 +48,6 @@ export function DatePicker({
|
||||
return false;
|
||||
};
|
||||
|
||||
if (allowTextEntry) {
|
||||
return (
|
||||
<div className="relative flex gap-1">
|
||||
<Input
|
||||
type="text"
|
||||
value={isTyping ? textInput : (date ? format(date, dateFormat) : "")}
|
||||
onChange={handleTextChange}
|
||||
onFocus={() => setIsTyping(true)}
|
||||
onBlur={handleBlur}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className={cn("flex-1", className)}
|
||||
/>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={disabled}
|
||||
className="shrink-0"
|
||||
type="button"
|
||||
>
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={handleSelect}
|
||||
disabled={getDisabledDates}
|
||||
initialFocus
|
||||
className="p-3 pointer-events-auto"
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
enableYearGrid={enableYearGrid}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -160,7 +59,6 @@ export function DatePicker({
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
type="button"
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
<span className="truncate">
|
||||
@@ -178,7 +76,6 @@ export function DatePicker({
|
||||
className="p-3 pointer-events-auto"
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
enableYearGrid={enableYearGrid}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -128,7 +128,6 @@ export function FlexibleDateInput({
|
||||
disablePast={disablePast}
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
allowTextEntry={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
|
||||
interface YearGridSelectorProps {
|
||||
selectedYear: number;
|
||||
onYearSelect: (year: number) => void;
|
||||
fromYear?: number;
|
||||
toYear?: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function YearGridSelector({
|
||||
selectedYear,
|
||||
onYearSelect,
|
||||
fromYear = 1800,
|
||||
toYear = new Date().getFullYear() + 10,
|
||||
children,
|
||||
}: YearGridSelectorProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [view, setView] = React.useState<"year" | "decade">("year");
|
||||
const [startYear, setStartYear] = React.useState(() => {
|
||||
// Start from the decade containing the selected year
|
||||
return Math.floor(selectedYear / 10) * 10;
|
||||
});
|
||||
|
||||
const handleYearSelect = (year: number) => {
|
||||
onYearSelect(year);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleDecadeSelect = (decade: number) => {
|
||||
setStartYear(decade);
|
||||
setView("year");
|
||||
};
|
||||
|
||||
const navigateYears = (direction: "prev" | "next") => {
|
||||
setStartYear((prev) => prev + (direction === "next" ? 12 : -12));
|
||||
};
|
||||
|
||||
const navigateDecades = (direction: "prev" | "next") => {
|
||||
setStartYear((prev) => prev + (direction === "next" ? 120 : -120));
|
||||
};
|
||||
|
||||
const renderYearGrid = () => {
|
||||
const years: number[] = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const year = startYear + i;
|
||||
if (year >= fromYear && year <= toYear) {
|
||||
years.push(year);
|
||||
}
|
||||
}
|
||||
|
||||
const endYear = startYear + 11;
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<div className="p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => navigateYears("prev")}
|
||||
disabled={startYear <= fromYear}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-sm font-medium hover:bg-accent"
|
||||
onClick={() => setView("decade")}
|
||||
>
|
||||
{startYear} - {endYear}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => navigateYears("next")}
|
||||
disabled={endYear >= toYear}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-1">
|
||||
{years.map((year) => (
|
||||
<Button
|
||||
key={year}
|
||||
variant={year === selectedYear ? "default" : "ghost"}
|
||||
className={cn(
|
||||
"h-9 w-full text-sm font-normal",
|
||||
year === currentYear && year !== selectedYear && "border border-primary"
|
||||
)}
|
||||
onClick={() => handleYearSelect(year)}
|
||||
>
|
||||
{year}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDecadeGrid = () => {
|
||||
const decades: number[] = [];
|
||||
const decadeStart = Math.floor(startYear / 100) * 100;
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const decade = decadeStart + i * 10;
|
||||
if (decade >= Math.floor(fromYear / 10) * 10 && decade <= Math.ceil(toYear / 10) * 10) {
|
||||
decades.push(decade);
|
||||
}
|
||||
}
|
||||
|
||||
const decadeEnd = decadeStart + 110;
|
||||
|
||||
return (
|
||||
<div className="p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => navigateDecades("prev")}
|
||||
disabled={decadeStart <= Math.floor(fromYear / 100) * 100}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-sm font-medium hover:bg-accent"
|
||||
onClick={() => setView("year")}
|
||||
>
|
||||
{decadeStart}s - {decadeEnd}s
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => navigateDecades("next")}
|
||||
disabled={decadeEnd >= Math.ceil(toYear / 100) * 100}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{decades.map((decade) => (
|
||||
<Button
|
||||
key={decade}
|
||||
variant="ghost"
|
||||
className="h-10 w-full text-sm font-normal"
|
||||
onClick={() => handleDecadeSelect(decade)}
|
||||
>
|
||||
{decade}s
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
{view === "year" ? renderYearGrid() : renderDecadeGrid()}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user