feat: adição de novos ícones SVG e configuração do ambiente

- Adicionados ícones SVG para ChatGPT, Claude, Gemini e OpenRouter
- Implementados ícones para modos claro e escuro do ChatGPT
- Criado script de inicialização para PostgreSQL com extensão pgcrypto
- Adicionado script de configuração de ambiente que faz backup do .env
- Configurado tsconfig.json para TypeScript com opções de compilação
This commit is contained in:
Felipe Coutinho
2025-11-15 15:49:36 -03:00
commit ea0b8618e0
441 changed files with 53569 additions and 0 deletions

View File

@@ -0,0 +1,503 @@
"use client";
import {
createMassLancamentosAction,
deleteLancamentoAction,
deleteLancamentoBulkAction,
deleteMultipleLancamentosAction,
toggleLancamentoSettlementAction,
updateLancamentoBulkAction,
} from "@/app/(dashboard)/lancamentos/actions";
import { ConfirmActionDialog } from "@/components/confirm-action-dialog";
import { useCallback, useState } from "react";
import { toast } from "sonner";
import { AnticipateInstallmentsDialog } from "../dialogs/anticipate-installments-dialog/anticipate-installments-dialog";
import { AnticipationHistoryDialog } from "../dialogs/anticipate-installments-dialog/anticipation-history-dialog";
import { BulkActionDialog, type BulkActionScope } from "../dialogs/bulk-action-dialog";
import { LancamentoDetailsDialog } from "../dialogs/lancamento-details-dialog";
import { LancamentoDialog } from "../dialogs/lancamento-dialog/lancamento-dialog";
import { LancamentosTable } from "../table/lancamentos-table";
import { MassAddDialog, type MassAddFormData } from "../dialogs/mass-add-dialog";
import type {
ContaCartaoFilterOption,
LancamentoFilterOption,
LancamentoItem,
SelectOption,
} from "../types";
interface LancamentosPageProps {
lancamentos: LancamentoItem[];
pagadorOptions: SelectOption[];
splitPagadorOptions: SelectOption[];
defaultPagadorId: string | null;
contaOptions: SelectOption[];
cartaoOptions: SelectOption[];
categoriaOptions: SelectOption[];
pagadorFilterOptions: LancamentoFilterOption[];
categoriaFilterOptions: LancamentoFilterOption[];
contaCartaoFilterOptions: ContaCartaoFilterOption[];
selectedPeriod: string;
estabelecimentos: string[];
allowCreate?: boolean;
defaultCartaoId?: string | null;
defaultPaymentMethod?: string | null;
lockCartaoSelection?: boolean;
lockPaymentMethod?: boolean;
}
export function LancamentosPage({
lancamentos,
pagadorOptions,
splitPagadorOptions,
defaultPagadorId,
contaOptions,
cartaoOptions,
categoriaOptions,
pagadorFilterOptions,
categoriaFilterOptions,
contaCartaoFilterOptions,
selectedPeriod,
estabelecimentos,
allowCreate = true,
defaultCartaoId,
defaultPaymentMethod,
lockCartaoSelection,
lockPaymentMethod,
}: LancamentosPageProps) {
const [selectedLancamento, setSelectedLancamento] =
useState<LancamentoItem | null>(null);
const [editOpen, setEditOpen] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const [massAddOpen, setMassAddOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const [lancamentoToDelete, setLancamentoToDelete] =
useState<LancamentoItem | null>(null);
const [detailsOpen, setDetailsOpen] = useState(false);
const [settlementLoadingId, setSettlementLoadingId] = useState<string | null>(
null
);
const [bulkEditOpen, setBulkEditOpen] = useState(false);
const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
const [pendingEditData, setPendingEditData] = useState<{
id: string;
name: string;
categoriaId: string | undefined;
note: string;
pagadorId: string | undefined;
contaId: string | undefined;
cartaoId: string | undefined;
amount: number;
dueDate: string | null;
boletoPaymentDate: string | null;
lancamento: LancamentoItem;
} | null>(null);
const [pendingDeleteData, setPendingDeleteData] =
useState<LancamentoItem | null>(null);
const [multipleBulkDeleteOpen, setMultipleBulkDeleteOpen] = useState(false);
const [pendingMultipleDeleteData, setPendingMultipleDeleteData] = useState<
LancamentoItem[]
>([]);
const [anticipateOpen, setAnticipateOpen] = useState(false);
const [anticipationHistoryOpen, setAnticipationHistoryOpen] = useState(false);
const [selectedForAnticipation, setSelectedForAnticipation] =
useState<LancamentoItem | null>(null);
const handleToggleSettlement = useCallback(async (item: LancamentoItem) => {
if (item.paymentMethod === "Cartão de crédito") {
toast.info(
"Pagamentos com cartão são conciliados automaticamente. Ajuste pelo cartão."
);
return;
}
const supportedMethods = ["Pix", "Boleto", "Dinheiro", "Cartão de débito"];
if (!supportedMethods.includes(item.paymentMethod)) {
return;
}
const nextValue = !Boolean(item.isSettled);
try {
setSettlementLoadingId(item.id);
const result = await toggleLancamentoSettlementAction({
id: item.id,
value: nextValue,
});
if (!result.success) {
throw new Error(result.error);
}
toast.success(result.message);
} catch (error) {
const message =
error instanceof Error
? error.message
: "Não foi possível atualizar o pagamento.";
toast.error(message);
} finally {
setSettlementLoadingId(null);
}
}, []);
const handleDelete = useCallback(async () => {
if (!lancamentoToDelete) {
return;
}
const result = await deleteLancamentoAction({
id: lancamentoToDelete.id,
});
if (!result.success) {
toast.error(result.error);
throw new Error(result.error);
}
toast.success(result.message);
setDeleteOpen(false);
}, [lancamentoToDelete]);
const handleBulkDelete = useCallback(
async (scope: BulkActionScope) => {
if (!pendingDeleteData) {
return;
}
const result = await deleteLancamentoBulkAction({
id: pendingDeleteData.id,
scope,
});
if (!result.success) {
toast.error(result.error);
throw new Error(result.error);
}
toast.success(result.message);
setBulkDeleteOpen(false);
setPendingDeleteData(null);
},
[pendingDeleteData]
);
const handleBulkEditRequest = useCallback(
(data: {
id: string;
name: string;
categoriaId: string | undefined;
note: string;
pagadorId: string | undefined;
contaId: string | undefined;
cartaoId: string | undefined;
amount: number;
dueDate: string | null;
boletoPaymentDate: string | null;
}) => {
if (!selectedLancamento) {
return;
}
setPendingEditData({
...data,
lancamento: selectedLancamento,
});
setEditOpen(false);
setBulkEditOpen(true);
},
[selectedLancamento]
);
const handleBulkEdit = useCallback(
async (scope: BulkActionScope) => {
if (!pendingEditData) {
return;
}
const result = await updateLancamentoBulkAction({
id: pendingEditData.id,
scope,
name: pendingEditData.name,
categoriaId: pendingEditData.categoriaId,
note: pendingEditData.note,
pagadorId: pendingEditData.pagadorId,
contaId: pendingEditData.contaId,
cartaoId: pendingEditData.cartaoId,
amount: pendingEditData.amount,
dueDate: pendingEditData.dueDate,
boletoPaymentDate: pendingEditData.boletoPaymentDate,
});
if (!result.success) {
toast.error(result.error);
throw new Error(result.error);
}
toast.success(result.message);
setBulkEditOpen(false);
setPendingEditData(null);
},
[pendingEditData]
);
const handleMassAddSubmit = useCallback(async (data: MassAddFormData) => {
const result = await createMassLancamentosAction(data);
if (!result.success) {
toast.error(result.error);
throw new Error(result.error);
}
toast.success(result.message);
}, []);
const handleMultipleBulkDelete = useCallback((items: LancamentoItem[]) => {
setPendingMultipleDeleteData(items);
setMultipleBulkDeleteOpen(true);
}, []);
const confirmMultipleBulkDelete = useCallback(async () => {
if (pendingMultipleDeleteData.length === 0) {
return;
}
const ids = pendingMultipleDeleteData.map((item) => item.id);
const result = await deleteMultipleLancamentosAction({ ids });
if (!result.success) {
toast.error(result.error);
throw new Error(result.error);
}
toast.success(result.message);
setMultipleBulkDeleteOpen(false);
setPendingMultipleDeleteData([]);
}, [pendingMultipleDeleteData]);
const handleCreate = useCallback(() => {
setCreateOpen(true);
}, []);
const handleMassAdd = useCallback(() => {
setMassAddOpen(true);
}, []);
const handleEdit = useCallback((item: LancamentoItem) => {
setSelectedLancamento(item);
setEditOpen(true);
}, []);
const handleConfirmDelete = useCallback((item: LancamentoItem) => {
if (item.seriesId) {
setPendingDeleteData(item);
setBulkDeleteOpen(true);
} else {
setLancamentoToDelete(item);
setDeleteOpen(true);
}
}, []);
const handleViewDetails = useCallback((item: LancamentoItem) => {
setSelectedLancamento(item);
setDetailsOpen(true);
}, []);
const handleAnticipate = useCallback((item: LancamentoItem) => {
setSelectedForAnticipation(item);
setAnticipateOpen(true);
}, []);
const handleViewAnticipationHistory = useCallback((item: LancamentoItem) => {
setSelectedForAnticipation(item);
setAnticipationHistoryOpen(true);
}, []);
return (
<>
<LancamentosTable
data={lancamentos}
pagadorFilterOptions={pagadorFilterOptions}
categoriaFilterOptions={categoriaFilterOptions}
contaCartaoFilterOptions={contaCartaoFilterOptions}
onCreate={allowCreate ? handleCreate : undefined}
onMassAdd={allowCreate ? handleMassAdd : undefined}
onEdit={handleEdit}
onConfirmDelete={handleConfirmDelete}
onBulkDelete={handleMultipleBulkDelete}
onViewDetails={handleViewDetails}
onToggleSettlement={handleToggleSettlement}
onAnticipate={handleAnticipate}
onViewAnticipationHistory={handleViewAnticipationHistory}
isSettlementLoading={(id) => settlementLoadingId === id}
/>
{allowCreate ? (
<LancamentoDialog
mode="create"
open={createOpen}
onOpenChange={setCreateOpen}
pagadorOptions={pagadorOptions}
splitPagadorOptions={splitPagadorOptions}
defaultPagadorId={defaultPagadorId}
contaOptions={contaOptions}
cartaoOptions={cartaoOptions}
categoriaOptions={categoriaOptions}
estabelecimentos={estabelecimentos}
defaultPeriod={selectedPeriod}
defaultCartaoId={defaultCartaoId}
defaultPaymentMethod={defaultPaymentMethod}
lockCartaoSelection={lockCartaoSelection}
lockPaymentMethod={lockPaymentMethod}
/>
) : null}
<LancamentoDialog
mode="update"
open={editOpen && !!selectedLancamento}
onOpenChange={setEditOpen}
pagadorOptions={pagadorOptions}
splitPagadorOptions={splitPagadorOptions}
defaultPagadorId={defaultPagadorId}
contaOptions={contaOptions}
cartaoOptions={cartaoOptions}
categoriaOptions={categoriaOptions}
estabelecimentos={estabelecimentos}
lancamento={selectedLancamento ?? undefined}
defaultPeriod={selectedPeriod}
onBulkEditRequest={handleBulkEditRequest}
/>
<LancamentoDetailsDialog
open={detailsOpen && !!selectedLancamento}
onOpenChange={(open) => {
setDetailsOpen(open);
if (!open) {
setSelectedLancamento(null);
}
}}
lancamento={detailsOpen ? selectedLancamento : null}
/>
<ConfirmActionDialog
open={deleteOpen && !!lancamentoToDelete}
onOpenChange={setDeleteOpen}
title={
lancamentoToDelete
? `Remover lançamento "${lancamentoToDelete.name}"?`
: "Remover lançamento?"
}
description="Essa ação é irreversível e removerá o lançamento de forma permanente."
confirmLabel="Remover"
pendingLabel="Removendo..."
confirmVariant="destructive"
onConfirm={handleDelete}
disabled={!lancamentoToDelete}
/>
<BulkActionDialog
open={bulkDeleteOpen && !!pendingDeleteData}
onOpenChange={setBulkDeleteOpen}
actionType="delete"
seriesType={
pendingDeleteData?.condition === "Parcelado"
? "installment"
: "recurring"
}
currentNumber={pendingDeleteData?.currentInstallment ?? undefined}
totalCount={
pendingDeleteData?.installmentCount ??
pendingDeleteData?.recurrenceCount ??
undefined
}
onConfirm={handleBulkDelete}
/>
<BulkActionDialog
open={bulkEditOpen && !!pendingEditData}
onOpenChange={setBulkEditOpen}
actionType="edit"
seriesType={
pendingEditData?.lancamento.condition === "Parcelado"
? "installment"
: "recurring"
}
currentNumber={
pendingEditData?.lancamento.currentInstallment ?? undefined
}
totalCount={
pendingEditData?.lancamento.installmentCount ??
pendingEditData?.lancamento.recurrenceCount ??
undefined
}
onConfirm={handleBulkEdit}
/>
{allowCreate ? (
<MassAddDialog
open={massAddOpen}
onOpenChange={setMassAddOpen}
onSubmit={handleMassAddSubmit}
pagadorOptions={pagadorOptions}
contaOptions={contaOptions}
cartaoOptions={cartaoOptions}
categoriaOptions={categoriaOptions}
estabelecimentos={estabelecimentos}
selectedPeriod={selectedPeriod}
defaultPagadorId={defaultPagadorId}
/>
) : null}
<ConfirmActionDialog
open={multipleBulkDeleteOpen && pendingMultipleDeleteData.length > 0}
onOpenChange={setMultipleBulkDeleteOpen}
title={`Remover ${pendingMultipleDeleteData.length} ${
pendingMultipleDeleteData.length === 1 ? "lançamento" : "lançamentos"
}?`}
description="Essa ação é irreversível e removerá os lançamentos selecionados de forma permanente."
confirmLabel="Remover"
pendingLabel="Removendo..."
confirmVariant="destructive"
onConfirm={confirmMultipleBulkDelete}
disabled={pendingMultipleDeleteData.length === 0}
/>
{/* Dialogs de Antecipação */}
{selectedForAnticipation && (
<AnticipateInstallmentsDialog
open={anticipateOpen}
onOpenChange={setAnticipateOpen}
seriesId={selectedForAnticipation.seriesId!}
lancamentoName={selectedForAnticipation.name}
categorias={categoriaOptions.map((c) => ({
id: c.value,
name: c.label,
icon: c.icon ?? null,
}))}
pagadores={pagadorOptions.map((p) => ({
id: p.value,
name: p.label,
}))}
defaultPeriod={selectedPeriod}
/>
)}
{selectedForAnticipation && (
<AnticipationHistoryDialog
open={anticipationHistoryOpen}
onOpenChange={setAnticipationHistoryOpen}
seriesId={selectedForAnticipation.seriesId!}
lancamentoName={selectedForAnticipation.name}
onViewLancamento={(lancamentoId) => {
const lancamento = lancamentos.find((l) => l.id === lancamentoId);
if (lancamento) {
setSelectedLancamento(lancamento);
setDetailsOpen(true);
setAnticipationHistoryOpen(false);
}
}}
/>
)}
</>
);
}