feat: modernize report scheduling UI and date inputs
This commit is contained in:
parent
8cc513c532
commit
616fe42e10
10 changed files with 384 additions and 60 deletions
128
src/components/ui/date-picker.tsx
Normal file
128
src/components/ui/date-picker.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
"use client"
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { format, parseISO, isValid as isValidDate } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { Calendar as CalendarIcon, XIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Calendar } from "@/components/ui/calendar"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { useLocalTimeZone } from "@/hooks/use-local-time-zone"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
type DatePickerProps = {
|
||||
value?: string | Date | null
|
||||
onChange?: (value: string | null) => void
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
minYear?: number
|
||||
maxYear?: number
|
||||
className?: string
|
||||
align?: "start" | "center" | "end"
|
||||
allowClear?: boolean
|
||||
}
|
||||
|
||||
function normalizeDate(value?: string | Date | null) {
|
||||
if (!value) return undefined
|
||||
if (value instanceof Date) {
|
||||
return isValidDate(value) ? value : undefined
|
||||
}
|
||||
const parsed = parseISO(value)
|
||||
return isValidDate(parsed) ? parsed : undefined
|
||||
}
|
||||
|
||||
export function DatePicker({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Selecionar data",
|
||||
disabled,
|
||||
minYear = 1900,
|
||||
maxYear = new Date().getFullYear() + 5,
|
||||
className,
|
||||
align = "start",
|
||||
allowClear = true,
|
||||
}: DatePickerProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const timeZone = useLocalTimeZone()
|
||||
const selectedDate = useMemo(() => normalizeDate(value), [value])
|
||||
const startMonth = useMemo(() => new Date(minYear, 0, 1), [minYear])
|
||||
const endMonth = useMemo(() => new Date(maxYear, 11, 31), [maxYear])
|
||||
|
||||
const handleSelect = (date: Date | undefined) => {
|
||||
if (!date) {
|
||||
onChange?.(null)
|
||||
return
|
||||
}
|
||||
onChange?.(format(date, "yyyy-MM-dd"))
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
onChange?.(null)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={(next) => !disabled && setOpen(next)}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-full justify-between gap-2 text-left font-normal",
|
||||
!selectedDate && "text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span>
|
||||
{selectedDate
|
||||
? format(selectedDate, "dd/MM/yyyy", { locale: ptBR })
|
||||
: placeholder}
|
||||
</span>
|
||||
{allowClear && selectedDate ? (
|
||||
<XIcon
|
||||
className="size-4 text-muted-foreground"
|
||||
aria-label="Limpar data"
|
||||
role="presentation"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
handleClear()
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CalendarIcon className="size-4 text-muted-foreground" aria-hidden="true" />
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align={align}>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={handleSelect}
|
||||
initialFocus
|
||||
captionLayout="dropdown"
|
||||
startMonth={startMonth}
|
||||
endMonth={endMonth}
|
||||
locale={ptBR}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
{allowClear ? (
|
||||
<div className="border-t border-border/60 p-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full justify-center text-xs text-muted-foreground"
|
||||
onClick={handleClear}
|
||||
>
|
||||
Limpar data
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue