feat: implementar melhorias em filtros, orçamentos e exportação

Refatoração de Filtros:
  - Move filtros select para Drawer lateral direito
  - Mantém busca fora do Drawer para acesso rápido
  - Adiciona indicador visual de filtros ativos
  - Implementa aplicação instantânea de filtros
  - Adiciona border-dashed e aumenta input de busca

  Cópia de Orçamentos:
  - Implementa funcionalidade de copiar orçamentos do mês anterior
  - Adiciona server action com validações e tratamento de erros
  - Cria modal de confirmação para a ação
  - Evita duplicações automáticas

  Exportação de Lançamentos:
  - Adiciona exportação em CSV, XLSX e PDF
  - Integra botão de exportação nos filtros
  - Segue padrão de Relatórios de Categorias
  - Inclui formatação específica por formato
This commit is contained in:
Felipe Coutinho
2026-01-05 15:49:16 +00:00
parent 901e423959
commit 147857c5bd
6 changed files with 731 additions and 205 deletions

View File

@@ -1,10 +1,13 @@
"use client";
import { deleteBudgetAction } from "@/app/(dashboard)/orcamentos/actions";
import {
deleteBudgetAction,
duplicatePreviousMonthBudgetsAction,
} from "@/app/(dashboard)/orcamentos/actions";
import { ConfirmActionDialog } from "@/components/confirm-action-dialog";
import { EmptyState } from "@/components/empty-state";
import { Button } from "@/components/ui/button";
import { RiAddCircleLine, RiFundsLine } from "@remixicon/react";
import { RiAddCircleLine, RiFileCopyLine, RiFundsLine } from "@remixicon/react";
import { useCallback, useState } from "react";
import { toast } from "sonner";
import { Card } from "../ui/card";
@@ -29,6 +32,7 @@ export function BudgetsPage({
const [selectedBudget, setSelectedBudget] = useState<Budget | null>(null);
const [removeOpen, setRemoveOpen] = useState(false);
const [budgetToRemove, setBudgetToRemove] = useState<Budget | null>(null);
const [duplicateOpen, setDuplicateOpen] = useState(false);
const hasBudgets = budgets.length > 0;
@@ -72,6 +76,21 @@ export function BudgetsPage({
throw new Error(result.error);
}, [budgetToRemove]);
const handleDuplicateConfirm = useCallback(async () => {
const result = await duplicatePreviousMonthBudgetsAction({
period: selectedPeriod,
});
if (result.success) {
toast.success(result.message);
setDuplicateOpen(false);
return;
}
toast.error(result.error);
throw new Error(result.error);
}, [selectedPeriod]);
const removeTitle = budgetToRemove
? `Remover orçamento de "${
budgetToRemove.category?.name ?? "categoria removida"
@@ -86,7 +105,7 @@ export function BudgetsPage({
return (
<>
<div className="flex w-full flex-col gap-6">
<div className="flex justify-start">
<div className="flex justify-start gap-4">
<BudgetDialog
mode="create"
categories={categories}
@@ -98,6 +117,14 @@ export function BudgetsPage({
</Button>
}
/>
<Button
variant="outline"
disabled={categories.length === 0}
onClick={() => setDuplicateOpen(true)}
>
<RiFileCopyLine className="size-4" />
Copiar orçamentos do último mês
</Button>
</div>
{hasBudgets ? (
@@ -142,6 +169,16 @@ export function BudgetsPage({
confirmVariant="destructive"
onConfirm={handleRemoveConfirm}
/>
<ConfirmActionDialog
open={duplicateOpen}
onOpenChange={setDuplicateOpen}
title="Copiar orçamentos do último mês?"
description="Isso copiará os limites definidos no mês anterior para as categorias que ainda não possuem orçamento neste mês."
confirmLabel="Copiar orçamentos"
pendingLabel="Copiando..."
onConfirm={handleDuplicateConfirm}
/>
</>
);
}