docs: expandir documentação do README e adicionar importação em massa de lançamentos
- Expande README.md com estatísticas detalhadas do projeto (200 componentes, 15+ tabelas, 20+ widgets) - Adiciona descrição completa da stack técnica e versões - Documenta estrutura de diretórios de forma abrangente - Inclui diagramas de schema de banco de dados e fluxos de dados - Adiciona seção de destaques e funcionalidades recentes - Implementa diálogo de importação em massa de lançamentos (bulk-import-dialog.tsx) - Adiciona fontes AISans (Regular e Semibold) ao projeto - Remove classe bg-muted das páginas de autenticação - Adiciona /docs ao .gitignore - Limpa código não utilizado em componentes de lançamentos e páginas do dashboard - Atualiza dependências no package.json
This commit is contained in:
@@ -91,8 +91,10 @@ const resolveLogoSrc = (logo: string | null) => {
|
||||
};
|
||||
|
||||
type BuildColumnsArgs = {
|
||||
currentUserId: string;
|
||||
onEdit?: (item: LancamentoItem) => void;
|
||||
onCopy?: (item: LancamentoItem) => void;
|
||||
onImport?: (item: LancamentoItem) => void;
|
||||
onConfirmDelete?: (item: LancamentoItem) => void;
|
||||
onViewDetails?: (item: LancamentoItem) => void;
|
||||
onToggleSettlement?: (item: LancamentoItem) => void;
|
||||
@@ -103,8 +105,10 @@ type BuildColumnsArgs = {
|
||||
};
|
||||
|
||||
const buildColumns = ({
|
||||
currentUserId,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onImport,
|
||||
onConfirmDelete,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
@@ -116,6 +120,7 @@ const buildColumns = ({
|
||||
const noop = () => undefined;
|
||||
const handleEdit = onEdit ?? noop;
|
||||
const handleCopy = onCopy ?? noop;
|
||||
const handleImport = onImport ?? noop;
|
||||
const handleConfirmDelete = onConfirmDelete ?? noop;
|
||||
const handleViewDetails = onViewDetails ?? noop;
|
||||
const handleToggleSettlement = onToggleSettlement ?? noop;
|
||||
@@ -419,6 +424,7 @@ const buildColumns = ({
|
||||
contaLogo,
|
||||
cartaoId,
|
||||
contaId,
|
||||
userId,
|
||||
} = row.original;
|
||||
const label = cartaoName ?? contaName;
|
||||
const logoSrc = resolveLogoSrc(cartaoLogo ?? contaLogo);
|
||||
@@ -428,20 +434,14 @@ const buildColumns = ({
|
||||
? `/contas/${contaId}/extrato`
|
||||
: null;
|
||||
const Icon = cartaoId ? RiBankCard2Line : contaId ? RiBankLine : null;
|
||||
const isOwnData = userId === currentUserId;
|
||||
|
||||
if (!label) {
|
||||
return "—";
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href ?? "#"}
|
||||
className={cn(
|
||||
"flex items-center gap-2",
|
||||
href ? "underline " : "pointer-events-none"
|
||||
)}
|
||||
aria-disabled={!href}
|
||||
>
|
||||
const content = (
|
||||
<>
|
||||
{logoSrc ? (
|
||||
<Image
|
||||
src={logoSrc}
|
||||
@@ -455,6 +455,23 @@ const buildColumns = ({
|
||||
{Icon ? (
|
||||
<Icon className="size-4 text-muted-foreground" aria-hidden />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
if (!isOwnData) {
|
||||
return <div className="flex items-center gap-2">{content}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href ?? "#"}
|
||||
className={cn(
|
||||
"flex items-center gap-2",
|
||||
href ? "underline " : "pointer-events-none"
|
||||
)}
|
||||
aria-disabled={!href}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
@@ -526,30 +543,41 @@ const buildColumns = ({
|
||||
<RiEyeLine className="size-4" />
|
||||
Detalhes
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => handleEdit(row.original)}
|
||||
disabled={row.original.readonly}
|
||||
>
|
||||
<RiPencilLine className="size-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
{row.original.categoriaName !== "Pagamentos" && (
|
||||
{row.original.userId === currentUserId && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => handleEdit(row.original)}
|
||||
disabled={row.original.readonly}
|
||||
>
|
||||
<RiPencilLine className="size-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{row.original.categoriaName !== "Pagamentos" && row.original.userId === currentUserId && (
|
||||
<DropdownMenuItem onSelect={() => handleCopy(row.original)}>
|
||||
<RiFileCopyLine className="size-4" />
|
||||
Copiar
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => handleConfirmDelete(row.original)}
|
||||
disabled={row.original.readonly}
|
||||
>
|
||||
<RiDeleteBin5Line className="size-4" />
|
||||
Remover
|
||||
</DropdownMenuItem>
|
||||
{row.original.categoriaName !== "Pagamentos" && row.original.userId !== currentUserId && (
|
||||
<DropdownMenuItem onSelect={() => handleImport(row.original)}>
|
||||
<RiFileCopyLine className="size-4" />
|
||||
Importar para Minha Conta
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{row.original.userId === currentUserId && (
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => handleConfirmDelete(row.original)}
|
||||
disabled={row.original.readonly}
|
||||
>
|
||||
<RiDeleteBin5Line className="size-4" />
|
||||
Remover
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{/* Opções de Antecipação */}
|
||||
{row.original.condition === "Parcelado" &&
|
||||
{row.original.userId === currentUserId &&
|
||||
row.original.condition === "Parcelado" &&
|
||||
row.original.seriesId && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -594,6 +622,7 @@ const buildColumns = ({
|
||||
|
||||
type LancamentosTableProps = {
|
||||
data: LancamentoItem[];
|
||||
currentUserId: string;
|
||||
pagadorFilterOptions?: LancamentoFilterOption[];
|
||||
categoriaFilterOptions?: LancamentoFilterOption[];
|
||||
contaCartaoFilterOptions?: ContaCartaoFilterOption[];
|
||||
@@ -601,8 +630,10 @@ type LancamentosTableProps = {
|
||||
onMassAdd?: () => void;
|
||||
onEdit?: (item: LancamentoItem) => void;
|
||||
onCopy?: (item: LancamentoItem) => void;
|
||||
onImport?: (item: LancamentoItem) => void;
|
||||
onConfirmDelete?: (item: LancamentoItem) => void;
|
||||
onBulkDelete?: (items: LancamentoItem[]) => void;
|
||||
onBulkImport?: (items: LancamentoItem[]) => void;
|
||||
onViewDetails?: (item: LancamentoItem) => void;
|
||||
onToggleSettlement?: (item: LancamentoItem) => void;
|
||||
onAnticipate?: (item: LancamentoItem) => void;
|
||||
@@ -614,6 +645,7 @@ type LancamentosTableProps = {
|
||||
|
||||
export function LancamentosTable({
|
||||
data,
|
||||
currentUserId,
|
||||
pagadorFilterOptions = [],
|
||||
categoriaFilterOptions = [],
|
||||
contaCartaoFilterOptions = [],
|
||||
@@ -621,8 +653,10 @@ export function LancamentosTable({
|
||||
onMassAdd,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onImport,
|
||||
onConfirmDelete,
|
||||
onBulkDelete,
|
||||
onBulkImport,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
onAnticipate,
|
||||
@@ -643,8 +677,10 @@ export function LancamentosTable({
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
buildColumns({
|
||||
currentUserId,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onImport,
|
||||
onConfirmDelete,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
@@ -654,8 +690,10 @@ export function LancamentosTable({
|
||||
showActions,
|
||||
}),
|
||||
[
|
||||
currentUserId,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onImport,
|
||||
onConfirmDelete,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
@@ -693,6 +731,10 @@ export function LancamentosTable({
|
||||
0
|
||||
);
|
||||
|
||||
// Check if all data belongs to current user to determine if filters should be shown
|
||||
const isOwnData = data.every((item) => item.userId === currentUserId);
|
||||
const shouldShowFilters = showFilters && isOwnData;
|
||||
|
||||
const handleBulkDelete = () => {
|
||||
if (onBulkDelete && selectedCount > 0) {
|
||||
const selectedItems = selectedRows.map((row) => row.original);
|
||||
@@ -701,8 +743,16 @@ export function LancamentosTable({
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkImport = () => {
|
||||
if (onBulkImport && selectedCount > 0) {
|
||||
const selectedItems = selectedRows.map((row) => row.original);
|
||||
onBulkImport(selectedItems);
|
||||
setRowSelection({});
|
||||
}
|
||||
};
|
||||
|
||||
const showTopControls =
|
||||
Boolean(onCreate) || Boolean(onMassAdd) || showFilters;
|
||||
Boolean(onCreate) || Boolean(onMassAdd) || shouldShowFilters;
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
@@ -738,10 +788,10 @@ export function LancamentosTable({
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<span className={showFilters ? "hidden sm:block" : ""} />
|
||||
<span className={shouldShowFilters ? "hidden sm:block" : ""} />
|
||||
)}
|
||||
|
||||
{showFilters ? (
|
||||
{shouldShowFilters ? (
|
||||
<LancamentosFilters
|
||||
pagadorOptions={pagadorFilterOptions}
|
||||
categoriaOptions={categoriaFilterOptions}
|
||||
@@ -752,7 +802,7 @@ export function LancamentosTable({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{selectedCount > 0 && onBulkDelete ? (
|
||||
{selectedCount > 0 && onBulkDelete && selectedRows.every(row => row.original.userId === currentUserId) ? (
|
||||
<div className="flex flex-wrap items-center gap-3 rounded-lg border bg-muted/50 px-4 py-2">
|
||||
<div className="flex flex-col text-sm text-muted-foreground sm:flex-row sm:items-center sm:gap-2">
|
||||
<span>
|
||||
@@ -782,6 +832,36 @@ export function LancamentosTable({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{selectedCount > 0 && onBulkImport && selectedRows.some(row => row.original.userId !== currentUserId) ? (
|
||||
<div className="flex flex-wrap items-center gap-3 rounded-lg border bg-muted/50 px-4 py-2">
|
||||
<div className="flex flex-col text-sm text-muted-foreground sm:flex-row sm:items-center sm:gap-2">
|
||||
<span>
|
||||
{selectedCount}{" "}
|
||||
{selectedCount === 1 ? "item selecionado" : "itens selecionados"}
|
||||
</span>
|
||||
<span className="hidden sm:inline" aria-hidden>
|
||||
-
|
||||
</span>
|
||||
<span>
|
||||
Total:{" "}
|
||||
<MoneyValues
|
||||
amount={selectedTotal}
|
||||
className="inline font-medium text-foreground"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleBulkImport}
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="ml-auto"
|
||||
>
|
||||
<RiFileCopyLine className="size-4" />
|
||||
Importar selecionados
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Card className="py-2">
|
||||
<CardContent className="px-2 py-4 sm:px-4">
|
||||
{hasRows ? (
|
||||
|
||||
Reference in New Issue
Block a user