Remove unused font file and update font index; initialize database extensions with improved error handling; add EstabelecimentoLogo component for dynamic logo generation.

This commit is contained in:
Felipe Coutinho
2025-12-01 16:35:12 +00:00
parent c91edd0f31
commit 9cf89829f6
40 changed files with 6570 additions and 1114 deletions

View File

@@ -306,10 +306,17 @@ export function LancamentoDialog({
]
);
const title = mode === "create" ? "Novo lançamento" : "Editar lançamento";
const isCopyMode = mode === "create" && Boolean(lancamento);
const title = mode === "create"
? isCopyMode
? "Copiar lançamento"
: "Novo lançamento"
: "Editar lançamento";
const description =
mode === "create"
? "Informe os dados abaixo para registrar um novo lançamento."
? isCopyMode
? "Os dados do lançamento foram copiados. Revise e ajuste conforme necessário antes de salvar."
: "Informe os dados abaixo para registrar um novo lançamento."
: "Atualize as informações do lançamento selecionado.";
const submitLabel = mode === "create" ? "Salvar lançamento" : "Atualizar";

View File

@@ -69,6 +69,9 @@ export function LancamentosPage({
useState<LancamentoItem | null>(null);
const [editOpen, setEditOpen] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const [copyOpen, setCopyOpen] = useState(false);
const [lancamentoToCopy, setLancamentoToCopy] =
useState<LancamentoItem | null>(null);
const [massAddOpen, setMassAddOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const [lancamentoToDelete, setLancamentoToDelete] =
@@ -288,6 +291,11 @@ export function LancamentosPage({
setEditOpen(true);
}, []);
const handleCopy = useCallback((item: LancamentoItem) => {
setLancamentoToCopy(item);
setCopyOpen(true);
}, []);
const handleConfirmDelete = useCallback((item: LancamentoItem) => {
if (item.seriesId) {
setPendingDeleteData(item);
@@ -323,6 +331,7 @@ export function LancamentosPage({
onCreate={allowCreate ? handleCreate : undefined}
onMassAdd={allowCreate ? handleMassAdd : undefined}
onEdit={handleEdit}
onCopy={handleCopy}
onConfirmDelete={handleConfirmDelete}
onBulkDelete={handleMultipleBulkDelete}
onViewDetails={handleViewDetails}
@@ -352,6 +361,26 @@ export function LancamentosPage({
/>
) : null}
<LancamentoDialog
mode="create"
open={copyOpen && !!lancamentoToCopy}
onOpenChange={(open) => {
setCopyOpen(open);
if (!open) {
setLancamentoToCopy(null);
}
}}
pagadorOptions={pagadorOptions}
splitPagadorOptions={splitPagadorOptions}
defaultPagadorId={defaultPagadorId}
contaOptions={contaOptions}
cartaoOptions={cartaoOptions}
categoriaOptions={categoriaOptions}
estabelecimentos={estabelecimentos}
lancamento={lancamentoToCopy ?? undefined}
defaultPeriod={selectedPeriod}
/>
<LancamentoDialog
mode="update"
open={editOpen && !!selectedLancamento}

View File

@@ -0,0 +1,72 @@
"use client";
import { cn } from "@/lib/utils/ui";
interface EstabelecimentoLogoProps {
name: string;
size?: number;
className?: string;
}
const COLOR_PALETTE = [
"bg-purple-400 dark:bg-purple-600",
"bg-pink-400 dark:bg-pink-600",
"bg-red-400 dark:bg-red-600",
"bg-orange-400 dark:bg-orange-600",
"bg-indigo-400 dark:bg-indigo-600",
"bg-violet-400 dark:bg-violet-600",
"bg-fuchsia-400 dark:bg-fuchsia-600",
"bg-rose-400 dark:bg-rose-600",
"bg-amber-400 dark:bg-amber-600",
"bg-emerald-400 dark:bg-emerald-600",
];
function getInitials(name: string): string {
if (!name || !name.trim()) return "?";
const words = name.trim().split(/\s+/);
if (words.length === 1) {
return words[0]?.[0]?.toUpperCase() || "?";
}
const firstInitial = words[0]?.[0]?.toUpperCase() || "";
const secondInitial = words[1]?.[0]?.toUpperCase() || "";
return `${firstInitial}${secondInitial}`;
}
function generateColorFromName(name: string): string {
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = name.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % COLOR_PALETTE.length;
return COLOR_PALETTE[index] || "bg-gray-400";
}
export function EstabelecimentoLogo({
name,
size = 32,
className,
}: EstabelecimentoLogoProps) {
const initials = getInitials(name);
const colorClass = generateColorFromName(name);
return (
<div
className={cn(
"flex items-center justify-center rounded-md text-white font-medium shrink-0 ",
colorClass,
className
)}
style={{
width: size,
height: size,
fontSize: size * 0.4,
}}
>
{initials}
</div>
);
}

View File

@@ -0,0 +1,4 @@
export { AnticipationCard } from "./anticipation-card";
export { EstabelecimentoInput } from "./estabelecimento-input";
export { InstallmentTimeline } from "./installment-timeline";
export { EstabelecimentoLogo } from "./estabelecimento-logo";

View File

@@ -1,15 +1,5 @@
"use client";
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
useCallback,
useEffect,
useMemo,
useState,
useTransition,
type ReactNode,
} from "react";
import { Button } from "@/components/ui/button";
import {
Command,
@@ -39,15 +29,25 @@ import {
LANCAMENTO_TRANSACTION_TYPES,
} from "@/lib/lancamentos/constants";
import { cn } from "@/lib/utils/ui";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
useCallback,
useEffect,
useMemo,
useState,
useTransition,
type ReactNode,
} from "react";
import {
TransactionTypeSelectContent,
ConditionSelectContent,
PaymentMethodSelectContent,
CategoriaSelectContent,
PagadorSelectContent,
ConditionSelectContent,
ContaCartaoSelectContent,
PagadorSelectContent,
PaymentMethodSelectContent,
TransactionTypeSelectContent,
} from "../select-items";
import { RiCheckLine, RiExpandUpDownLine } from "@remixicon/react";
import type { ContaCartaoFilterOption, LancamentoFilterOption } from "../types";
const FILTER_EMPTY_VALUE = "__all";
@@ -337,7 +337,7 @@ export function LancamentosFilters({
"Categoria"
)}
</span>
<ChevronsUpDownIcon className="ml-2 size-4 shrink-0 opacity-50" />
<RiExpandUpDownLine className="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-[220px] p-0">
@@ -355,7 +355,7 @@ export function LancamentosFilters({
>
Todas
{categoriaValue === FILTER_EMPTY_VALUE ? (
<CheckIcon className="ml-auto size-4" />
<RiCheckLine className="ml-auto size-4" />
) : null}
</CommandItem>
{categoriaOptions.map((option) => (
@@ -372,7 +372,7 @@ export function LancamentosFilters({
icon={option.icon}
/>
{categoriaValue === option.slug ? (
<CheckIcon className="ml-auto size-4" />
<RiCheckLine className="ml-auto size-4" />
) : null}
</CommandItem>
))}

View File

@@ -51,6 +51,7 @@ import {
RiCheckLine,
RiDeleteBin5Line,
RiEyeLine,
RiFileCopyLine,
RiGroupLine,
RiHistoryLine,
RiMoreFill,
@@ -72,6 +73,7 @@ import {
import Image from "next/image";
import Link from "next/link";
import { useMemo, useState } from "react";
import { EstabelecimentoLogo } from "../shared/estabelecimento-logo";
import type {
ContaCartaoFilterOption,
LancamentoFilterOption,
@@ -90,6 +92,7 @@ const resolveLogoSrc = (logo: string | null) => {
type BuildColumnsArgs = {
onEdit?: (item: LancamentoItem) => void;
onCopy?: (item: LancamentoItem) => void;
onConfirmDelete?: (item: LancamentoItem) => void;
onViewDetails?: (item: LancamentoItem) => void;
onToggleSettlement?: (item: LancamentoItem) => void;
@@ -101,6 +104,7 @@ type BuildColumnsArgs = {
const buildColumns = ({
onEdit,
onCopy,
onConfirmDelete,
onViewDetails,
onToggleSettlement,
@@ -111,6 +115,7 @@ const buildColumns = ({
}: BuildColumnsArgs): ColumnDef<LancamentoItem>[] => {
const noop = () => undefined;
const handleEdit = onEdit ?? noop;
const handleCopy = onCopy ?? noop;
const handleConfirmDelete = onConfirmDelete ?? noop;
const handleViewDetails = onViewDetails ?? noop;
const handleToggleSettlement = onToggleSettlement ?? noop;
@@ -180,6 +185,7 @@ const buildColumns = ({
return (
<span className="flex items-center gap-2">
<EstabelecimentoLogo name={name} size={28} />
<Tooltip>
<TooltipTrigger asChild>
<span className="line-clamp-2 max-w-[180px] font-bold truncate">
@@ -522,6 +528,12 @@ const buildColumns = ({
<RiPencilLine className="size-4" />
Editar
</DropdownMenuItem>
{row.original.categoriaName !== "Pagamentos" && (
<DropdownMenuItem onSelect={() => handleCopy(row.original)}>
<RiFileCopyLine className="size-4" />
Copiar
</DropdownMenuItem>
)}
<DropdownMenuItem
variant="destructive"
onSelect={() => handleConfirmDelete(row.original)}
@@ -583,6 +595,7 @@ type LancamentosTableProps = {
onCreate?: () => void;
onMassAdd?: () => void;
onEdit?: (item: LancamentoItem) => void;
onCopy?: (item: LancamentoItem) => void;
onConfirmDelete?: (item: LancamentoItem) => void;
onBulkDelete?: (items: LancamentoItem[]) => void;
onViewDetails?: (item: LancamentoItem) => void;
@@ -602,6 +615,7 @@ export function LancamentosTable({
onCreate,
onMassAdd,
onEdit,
onCopy,
onConfirmDelete,
onBulkDelete,
onViewDetails,
@@ -625,6 +639,7 @@ export function LancamentosTable({
() =>
buildColumns({
onEdit,
onCopy,
onConfirmDelete,
onViewDetails,
onToggleSettlement,
@@ -635,6 +650,7 @@ export function LancamentosTable({
}),
[
onEdit,
onCopy,
onConfirmDelete,
onViewDetails,
onToggleSettlement,