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>
93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
|
import { createCustomer, type Customer } from '@/lib/api/customers';
|
|
import { customerSchema, type CustomerFormValues } from '@/lib/validators/customers';
|
|
import { ApiError } from '@/lib/api/client';
|
|
import { toast } from '@/lib/toast';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import { FormField } from '@/components/ui/form-field';
|
|
import { Input } from '@/components/ui/input';
|
|
import { ResponsiveDialog } from '@/components/ui/responsive-dialog';
|
|
|
|
interface CustomerQuickCreateDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onCreated: (customer: Customer) => void;
|
|
}
|
|
|
|
/**
|
|
* Cadastro rápido de cliente sem sair do fluxo de nova venda (Etapa 17,
|
|
* critério de aceite "não perder o que já foi preenchido na venda") — só
|
|
* os campos mínimos; o resto pode ser completado depois em
|
|
* `/app/clientes/:id`.
|
|
*/
|
|
export function CustomerQuickCreateDialog({ open, onOpenChange, onCreated }: CustomerQuickCreateDialogProps) {
|
|
const queryClient = useQueryClient();
|
|
const form = useForm<CustomerFormValues>({
|
|
resolver: zodResolver(customerSchema),
|
|
defaultValues: { razao_social: '', documento: '', email: '', telefone_whatsapp: '' },
|
|
});
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (values: CustomerFormValues) =>
|
|
createCustomer({
|
|
razao_social: values.razao_social,
|
|
documento: values.documento || undefined,
|
|
email: values.email || undefined,
|
|
telefone_whatsapp: values.telefone_whatsapp || undefined,
|
|
}),
|
|
onSuccess: (customer) => {
|
|
queryClient.invalidateQueries({ queryKey: ['tenant', 'customers'] });
|
|
toast.success('Cliente cadastrado.');
|
|
form.reset({ razao_social: '', documento: '', email: '', telefone_whatsapp: '' });
|
|
onCreated(customer);
|
|
onOpenChange(false);
|
|
},
|
|
onError: (err) => {
|
|
toast.error(err instanceof ApiError ? err.message : 'Não foi possível cadastrar o cliente.');
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ResponsiveDialog
|
|
open={open}
|
|
onOpenChange={onOpenChange}
|
|
title="Cadastro rápido de cliente"
|
|
description="Só o essencial — complete o resto depois no cadastro do cliente."
|
|
footer={
|
|
<>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
Cancelar
|
|
</Button>
|
|
<Button loading={mutation.isPending} onClick={form.handleSubmit((values) => mutation.mutate(values))}>
|
|
Cadastrar e usar
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<form
|
|
className="flex flex-col gap-4 px-4 pb-4 sm:px-0 sm:pb-0"
|
|
onSubmit={form.handleSubmit((values) => mutation.mutate(values))}
|
|
>
|
|
<FormField label="Razão social" error={form.formState.errors.razao_social?.message} required>
|
|
<Input {...form.register('razao_social')} />
|
|
</FormField>
|
|
<FormField label="Documento (CPF/CNPJ)" error={form.formState.errors.documento?.message}>
|
|
<Input {...form.register('documento')} />
|
|
</FormField>
|
|
<FormField label="Telefone/WhatsApp" error={form.formState.errors.telefone_whatsapp?.message}>
|
|
<Input {...form.register('telefone_whatsapp')} />
|
|
</FormField>
|
|
<FormField label="E-mail" error={form.formState.errors.email?.message}>
|
|
<Input type="email" {...form.register('email')} />
|
|
</FormField>
|
|
</form>
|
|
</ResponsiveDialog>
|
|
);
|
|
}
|