Files
tiagoandClaude Sonnet 5 323912748f Frontend completo (Etapas 11-18) e extensões de backend (Etapas 13 e 18)
Adiciona o frontend Next.js em frontend/ (setup, design system,
autenticação/tema, layout/navegação, área master, área tenant completa:
clientes, produtos, vendas, recebimentos, whatsapp, e-mail, alertas,
equipe e perfil). Estende o backend de forma aditiva e autorizada:
cookie httpOnly para refresh token (Etapa 13) e CRUD de equipe +
edição de perfil do tenant (Etapa 18, módulo src/modules/equipe/ novo
e PUT /tenant/perfil). Detalhes completos em Etapas/V2/*.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 09:41:13 -03:00

129 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import * as React from 'react';
import { format } from 'date-fns';
import { ptBR } from 'date-fns/locale';
import { CalendarIcon } from 'lucide-react';
import type { DateRange } from 'react-day-picker';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
interface DatePickerProps {
value?: Date;
onChange: (date: Date | undefined) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
id?: string;
'aria-invalid'?: boolean;
'aria-describedby'?: string;
}
/** Seleção de data única — usada em formulários (ex: vencimento de parcela). */
function DatePicker({
value,
onChange,
placeholder = 'Selecionar data',
disabled,
className,
id,
...aria
}: DatePickerProps) {
const [open, setOpen] = React.useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant="outline"
disabled={disabled}
className={cn(
'w-full justify-start font-normal',
!value && 'text-muted-foreground',
className,
)}
{...aria}
>
<CalendarIcon />
{value ? format(value, "dd 'de' MMMM 'de' yyyy", { locale: ptBR }) : placeholder}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={value}
onSelect={(date) => {
onChange(date);
setOpen(false);
}}
locale={ptBR}
autoFocus
/>
</PopoverContent>
</Popover>
);
}
interface DateRangePickerProps {
value?: DateRange;
onChange: (range: DateRange | undefined) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
id?: string;
}
/** Seleção de período — usada nos filtros de dashboard/listagens (seção 7 do schema). */
function DateRangePicker({
value,
onChange,
placeholder = 'Selecionar período',
disabled,
className,
id,
}: DateRangePickerProps) {
const [open, setOpen] = React.useState(false);
const label = React.useMemo(() => {
if (!value?.from) return placeholder;
if (!value.to) return format(value.from, 'dd/MM/yyyy', { locale: ptBR });
return `${format(value.from, 'dd/MM/yyyy', { locale: ptBR })} – ${format(value.to, 'dd/MM/yyyy', { locale: ptBR })}`;
}, [value, placeholder]);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant="outline"
disabled={disabled}
className={cn(
'w-full justify-start font-normal',
!value?.from && 'text-muted-foreground',
className,
)}
>
<CalendarIcon />
{label}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
selected={value}
onSelect={onChange}
locale={ptBR}
numberOfMonths={2}
autoFocus
/>
</PopoverContent>
</Popover>
);
}
export { DatePicker, DateRangePicker };