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>
This commit is contained in:
2026-08-16 09:41:13 -03:00
co-authored by Claude Sonnet 5
parent f9738e54da
commit 323912748f
179 changed files with 29312 additions and 22 deletions
+128
View File
@@ -0,0 +1,128 @@
'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 };