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:
@@ -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";
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
72
components/lancamentos/shared/estabelecimento-logo.tsx
Normal file
72
components/lancamentos/shared/estabelecimento-logo.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
4
components/lancamentos/shared/index.ts
Normal file
4
components/lancamentos/shared/index.ts
Normal 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";
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user