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>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { useMutation } from '@tanstack/react-query';
|
|
|
|
import { marcarRecebimentoPago, type FormaPagamento } from '@/lib/api/sale-payments';
|
|
import { ApiError } from '@/lib/api/client';
|
|
import { formatCurrency } from '@/lib/format';
|
|
import { toast } from '@/lib/toast';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import { ResponsiveDialog } from '@/components/ui/responsive-dialog';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
|
|
const FORMAS: { value: FormaPagamento; label: string }[] = [
|
|
{ value: 'pix', label: 'Pix' },
|
|
{ value: 'boleto', label: 'Boleto' },
|
|
{ value: 'cartao', label: 'Cartão' },
|
|
{ value: 'dinheiro', label: 'Dinheiro' },
|
|
];
|
|
|
|
interface MarcarPagoDialogProps {
|
|
parcela: { id: string; valor: string } | null;
|
|
onOpenChange: (open: boolean) => void;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
/** Reaproveitado no detalhe da venda (aba Parcelas) e em `/app/recebimentos`. */
|
|
export function MarcarPagoDialog({ parcela, onOpenChange, onSuccess }: MarcarPagoDialogProps) {
|
|
const [forma, setForma] = React.useState<FormaPagamento | ''>('');
|
|
|
|
React.useEffect(() => {
|
|
setForma('');
|
|
}, [parcela?.id]);
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: () => marcarRecebimentoPago(parcela!.id, forma as FormaPagamento),
|
|
onSuccess: () => {
|
|
toast.success('Parcela marcada como paga.');
|
|
onOpenChange(false);
|
|
onSuccess();
|
|
},
|
|
onError: (err) => {
|
|
toast.error(err instanceof ApiError ? err.message : 'Não foi possível marcar a parcela como paga.');
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ResponsiveDialog
|
|
open={parcela !== null}
|
|
onOpenChange={onOpenChange}
|
|
title="Marcar parcela como paga"
|
|
description={parcela ? `Valor: ${formatCurrency(parcela.valor)}` : undefined}
|
|
footer={
|
|
<>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={mutation.isPending}>
|
|
Cancelar
|
|
</Button>
|
|
<Button disabled={!forma} loading={mutation.isPending} onClick={() => mutation.mutate()}>
|
|
Confirmar pagamento
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<div className="flex flex-col gap-4 px-4 pb-4 sm:px-0 sm:pb-0">
|
|
<Select value={forma} onValueChange={(value) => setForma(value as FormaPagamento)}>
|
|
<SelectTrigger className="w-full" aria-label="Forma de pagamento">
|
|
<SelectValue placeholder="Selecione a forma de pagamento" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{FORMAS.map((f) => (
|
|
<SelectItem key={f.value} value={f.value}>
|
|
{f.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</ResponsiveDialog>
|
|
);
|
|
}
|