sistema-de-chamados/src/components/date-range-button.tsx
2025-11-19 09:40:39 -03:00

173 lines
5 KiB
TypeScript

"use client"
import { useEffect, useMemo, useState } from "react"
import type { DateRange } from "react-day-picker"
import { IconEraser } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { IconCalendar } from "@tabler/icons-react"
export type DateRangeValue = {
from: string | null
to: string | null
}
type DateRangeButtonProps = {
from: string | null
to: string | null
onChange: (next: DateRangeValue) => void
className?: string
clearLabel?: string
align?: "left" | "center"
}
function strToDate(value?: string | null): Date | undefined {
if (!value) return undefined
const [y, m, d] = value.split("-").map(Number)
if (!y || !m || !d) return undefined
return new Date(y, m - 1, d)
}
function dateToStr(value?: Date): string | null {
if (!value) return null
const y = value.getFullYear()
const m = String(value.getMonth() + 1).padStart(2, "0")
const d = String(value.getDate()).padStart(2, "0")
return `${y}-${m}-${d}`
}
function formatPtBR(value?: Date): string {
return value ? value.toLocaleDateString("pt-BR") : ""
}
export function DateRangeButton({
from,
to,
onChange,
className,
clearLabel = "Limpar período",
align = "left",
}: DateRangeButtonProps) {
const [open, setOpen] = useState(false)
const range: DateRange | undefined = useMemo(
() => ({
from: strToDate(from),
to: strToDate(to),
}),
[from, to],
)
const [draftRange, setDraftRange] = useState<DateRange | undefined>(range)
useEffect(() => {
if (!open) {
setDraftRange(range)
}
}, [open, range])
const displayRange = open ? draftRange ?? range : range
const label = (() => {
if (displayRange?.from && displayRange?.to) {
return `${formatPtBR(displayRange.from)} - ${formatPtBR(displayRange.to)}`
}
if (displayRange?.from && !displayRange?.to) {
return `${formatPtBR(displayRange.from)} - …`
}
return "Período"
})()
const handleSelect = (next?: DateRange) => {
if (!next?.from && !next?.to) {
setDraftRange(undefined)
return
}
if (!draftRange?.from || draftRange?.to) {
setDraftRange(next?.from ? { from: next.from, to: undefined } : undefined)
return
}
if (!draftRange.from) {
setDraftRange(undefined)
return
}
const start = draftRange.from
const rawSecond =
next?.to && next.to.getTime() !== start.getTime()
? next.to
: next?.from ?? next?.to ?? start
if (!rawSecond) {
setDraftRange({ from: start, to: undefined })
return
}
const fromDate = rawSecond < start ? rawSecond : start
const toDate = rawSecond < start ? start : rawSecond
const finalRange: DateRange = { from: fromDate, to: toDate }
setDraftRange(finalRange)
const nextFrom = dateToStr(finalRange.from)
const nextTo = dateToStr(finalRange.to) ?? nextFrom
onChange({ from: nextFrom, to: nextTo })
setOpen(false)
}
const clearEnabled = Boolean(from || to || draftRange?.from || draftRange?.to)
return (
<Popover
open={open}
onOpenChange={(next) => {
setOpen(next)
if (!next) {
setDraftRange(range)
} else {
setDraftRange(range)
}
}}
>
<PopoverTrigger asChild>
<Button
variant="outline"
className={`flex h-10 w-full items-center gap-2 rounded-2xl border-slate-300 bg-white/95 text-sm font-semibold text-neutral-700 ${align === "center" ? "justify-center text-center" : "justify-start text-left"} ${className ?? ""}`}
>
<IconCalendar className="size-4 text-neutral-500" />
<span className="truncate">{label}</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto overflow-hidden p-0" align="end">
<Calendar
className="w-full"
mode="range"
defaultMonth={draftRange?.from ?? range?.from}
selected={draftRange ?? range}
onSelect={handleSelect}
fixedWeeks
showOutsideDays
/>
<div className="flex items-center justify-center gap-3 border-t border-border/70 bg-slate-50/80 px-3 py-2">
<button
type="button"
onClick={() => {
onChange({ from: null, to: null })
setDraftRange(undefined)
setOpen(false)
}}
className="inline-flex items-center gap-2 rounded-full px-3 py-1 text-sm font-medium text-neutral-600 transition hover:text-neutral-900 disabled:opacity-40"
disabled={!clearEnabled}
>
<IconEraser className="size-4" />
<span>{clearLabel}</span>
</button>
{!draftRange?.to && draftRange?.from ? (
<span className="text-xs text-neutral-500">Selecione a data final</span>
) : null}
</div>
</PopoverContent>
</Popover>
)
}