Final working solution: shadcn date picker with timezone fix

- Implemented shadcn/ui date picker with Czech localization
- Added month/year dropdown navigation for easy date selection
- Fixed critical timezone bug causing "No valid days found" error
  - Changed from toISOString() to local date formatting
  - Dates now correctly sent as 2025-01-01 instead of 2024-12-31
- Calendar auto-closes after date selection
- All features tested and working:
  - Journey calculation with correct date ranges
  - "Vyplnit na web" button visible and functional
  - Excel export working
  - Backend successfully processes January 2025 data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Docker Config Backup
2025-10-13 07:45:06 +02:00
parent ffdfac65de
commit b38452413d
22 changed files with 2612 additions and 653 deletions

View File

@@ -0,0 +1,144 @@
"use client"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export type CalendarProps = React.ComponentProps<typeof DayPicker> & {
buttonVariant?: "ghost" | "outline"
}
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
...props
}: CalendarProps) {
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background group/calendar p-3 [--cell-size:2rem]",
className
)}
captionLayout={captionLayout}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months
),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
month_caption: cn(
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size] text-sm font-medium",
defaultClassNames.month_caption
),
dropdowns: cn(
"flex items-center gap-1",
defaultClassNames.dropdowns
),
dropdown: cn(
"flex h-[--cell-size] items-center justify-center rounded-md border border-input bg-background px-3 text-sm font-medium ring-offset-background hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
defaultClassNames.dropdown
),
dropdown_icon: cn("ml-2 h-4 w-4", defaultClassNames.dropdown_icon),
weekdays: cn(
"mt-2 flex w-full flex-row",
defaultClassNames.weekdays
),
weekday: cn(
"flex h-[--cell-size] w-[--cell-size] items-center justify-center text-xs font-normal text-muted-foreground",
defaultClassNames.weekday
),
week: cn("mt-0.5 flex w-full flex-row", defaultClassNames.week),
day: cn(
"flex h-[--cell-size] w-[--cell-size] items-center justify-center rounded-md p-0 text-sm",
defaultClassNames.day
),
day_button: cn(
buttonVariants({ variant: buttonVariant }),
"h-[--cell-size] w-[--cell-size] select-none p-0 font-normal hover:bg-accent hover:text-accent-foreground focus-visible:z-10 aria-selected:opacity-100",
defaultClassNames.day_button
),
range_start: cn("day-range-start", defaultClassNames.range_start),
range_end: cn("day-range-end", defaultClassNames.range_end),
selected: cn(
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
defaultClassNames.selected
),
today: cn(
"bg-accent text-accent-foreground",
defaultClassNames.today
),
outside: cn(
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
range_middle: cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground",
defaultClassNames.range_middle
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Chevron: ({ orientation }) => {
const Icon = orientation === "left" ? ChevronLeft : ChevronRight
return <Icon className="h-4 w-4" />
},
Dropdown: (props) => {
const { value, onChange, options } = props
const selected = options?.find((option) => option.value === value)
const handleChange = (newValue: string) => {
const changeEvent = {
target: { value: newValue },
} as React.ChangeEvent<HTMLSelectElement>
onChange?.(changeEvent)
}
return (
<Select value={value?.toString()} onValueChange={handleChange}>
<SelectTrigger className="h-7 w-fit gap-1 border-none px-2 py-1 font-medium shadow-none hover:bg-accent focus:ring-0">
<SelectValue>{selected?.label}</SelectValue>
</SelectTrigger>
<SelectContent position="popper" className="min-w-[var(--radix-popper-anchor-width)]">
{options?.map((option) => (
<SelectItem
key={option.value}
value={option.value?.toString() ?? ""}
disabled={option.disabled}
>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
},
}}
{...props}
/>
)
}
Calendar.displayName = "Calendar"
export { Calendar }