'use client'; import * as React from 'react'; import { useFieldArray, useForm, type UseFormRegisterReturn } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery } from '@tanstack/react-query'; import { Plus, Trash2 } from 'lucide-react'; import { listCustomers } from '@/lib/api/customers'; import { listProducts } from '@/lib/api/products'; import type { SaleInput } from '@/lib/api/sales'; import { saleFormSchema, type SaleFormValues } from '@/lib/validators/sales'; import { formatCurrency, formatDate } from '@/lib/format'; import { CustomerQuickCreateDialog } from '@/components/domain/customer-quick-create-dialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Combobox } from '@/components/ui/combobox'; import { FormField } from '@/components/ui/form-field'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; function toCents(value: number): number { return Math.round(value * 100); } function todayIso(): string { return new Date().toISOString().slice(0, 10); } /** Calcula os vencimentos (30 em 30 dias) só para pré-visualização — o backend recalcula de verdade. */ function previewVencimentosAutomaticos(dataVenda: string, numeroParcelas: number): Date[] { const base = dataVenda ? new Date(`${dataVenda}T00:00:00Z`) : new Date(); return Array.from({ length: numeroParcelas }, (_, i) => { const d = new Date(base); d.setUTCDate(d.getUTCDate() + 30 * (i + 1)); return d; }); } export interface VendaFormSubmitHandlers { /** Só no modo `create` — salva como orçamento. */ onSaveDraft?: (input: SaleInput) => void; /** No `create`, confirma a venda; no `edit`, salva as alterações (venda continua em orçamento). */ onConfirmOrSave: (input: SaleInput) => void; } interface VendaFormProps extends VendaFormSubmitHandlers { mode: 'create' | 'edit'; defaultValues: SaleFormValues; submittingDraft?: boolean; submittingConfirmOrSave?: boolean; onCancel: () => void; } export function VendaForm({ mode, defaultValues, submittingDraft, submittingConfirmOrSave, onSaveDraft, onConfirmOrSave, onCancel, }: VendaFormProps) { const [quickCreateOpen, setQuickCreateOpen] = React.useState(false); const form = useForm({ resolver: zodResolver(saleFormSchema), defaultValues }); const itensArray = useFieldArray({ control: form.control, name: 'itens' }); const parcelasArray = useFieldArray({ control: form.control, name: 'parcelasManuais' }); const customerId = form.watch('customer_id'); const itens = form.watch('itens'); const desconto = form.watch('desconto') || 0; const dataVenda = form.watch('data_venda'); const modoParcelas = form.watch('modoParcelas'); const numeroParcelas = form.watch('numeroParcelas'); const parcelasManuais = form.watch('parcelasManuais') ?? []; const { data: customersData } = useQuery({ queryKey: ['tenant', 'customers', 'combobox'], queryFn: () => listCustomers({ ativo: true, limit: 100 }), }); const { data: productsData } = useQuery({ queryKey: ['tenant', 'produtos', 'combobox'], queryFn: () => listProducts({ ativo: true, limit: 100 }), }); const customerOptions = (customersData?.data ?? []).map((c) => ({ value: c.id, label: c.nome_fantasia || c.razao_social, })); const productOptions = (productsData?.data ?? []).map((p) => ({ value: p.id, label: p.sku ? `${p.nome} (${p.sku})` : p.nome, })); const subtotalItens = itens.reduce((acc, item) => acc + item.quantidade * item.valor_unitario, 0); const total = Math.max(0, subtotalItens - desconto); const descontoExcedeItens = desconto > subtotalItens; const somaParcelasManuais = parcelasManuais.reduce((acc, p) => acc + (p.valor || 0), 0); const diffParcelas = toCents(total) - toCents(somaParcelasManuais); const parcelasBatem = modoParcelas === 'automatico' || diffParcelas === 0; function addItem(productId: string) { const produto = productsData?.data.find((p) => p.id === productId); if (!produto) return; if (itensArray.fields.some((f) => f.product_service_id === productId)) return; itensArray.append({ product_service_id: produto.id, nome: produto.nome, quantidade: 1, valor_unitario: Number(produto.valor_venda), valor_catalogo: Number(produto.valor_venda), }); } function buildInput(values: SaleFormValues): SaleInput | null { if (values.modoParcelas === 'manual') { if (!values.parcelasManuais || values.parcelasManuais.length === 0) { form.setError('parcelasManuais', { message: 'Adicione pelo menos uma parcela' }); return null; } if (!parcelasBatem) { form.setError('parcelasManuais', { message: `A soma das parcelas não bate com o total da venda (diferença de ${formatCurrency(Math.abs(diffParcelas) / 100)})`, }); return null; } } else if (!values.numeroParcelas || values.numeroParcelas < 1) { form.setError('numeroParcelas', { message: 'Informe o número de parcelas' }); return null; } return { customer_id: values.customer_id, data_venda: values.data_venda, desconto: values.desconto || undefined, observacoes: values.observacoes || undefined, itens: values.itens.map((item) => ({ product_service_id: item.product_service_id, quantidade: item.quantidade, valor_unitario: item.valor_unitario, })), ...(values.modoParcelas === 'manual' ? { parcelas: values.parcelasManuais!.map((p) => ({ valor: p.valor, vencimento: p.vencimento })) } : { numero_parcelas: values.numeroParcelas }), }; } const vencimentosPreview = modoParcelas === 'automatico' && numeroParcelas ? previewVencimentosAutomaticos(dataVenda, numeroParcelas) : []; return (
Cliente e data
form.setValue('customer_id', value, { shouldValidate: true })} placeholder="Selecionar cliente..." searchPlaceholder="Buscar cliente..." />