From 59b4dea07121926124108555a96accef3827ba41 Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Mon, 30 Mar 2026 18:46:28 +0000 Subject: [PATCH] =?UTF-8?q?feat(prefer=C3=AAncias):=20configura=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20tamanho=20m=C3=A1ximo=20de=20anexo=20por=20arqui?= =?UTF-8?q?vo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../accounts/[accountId]/statement/page.tsx | 1 + .../cards/[cardId]/invoice/page.tsx | 1 + .../categories/[categoryId]/page.tsx | 1 + src/app/(dashboard)/payers/[payerId]/page.tsx | 1 + src/app/(dashboard)/transactions/page.tsx | 1 + src/features/settings/actions.ts | 3 + .../settings/components/preferences-form.tsx | 79 ++++++++++++++++--- src/features/settings/queries.ts | 2 + .../transactions/attachments-config.ts | 7 +- .../attachments/attachment-file-picker.tsx | 11 ++- 10 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx b/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx index 9b6aad1..597b846 100644 --- a/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx +++ b/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx @@ -190,6 +190,7 @@ export default async function Page({ params, searchParams }: PageProps) { allowCreate={false} noteAsColumn={userPreferences?.statementNoteAsColumn ?? false} columnOrder={userPreferences?.transactionsColumnOrder ?? null} + attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50} /> diff --git a/src/app/(dashboard)/cards/[cardId]/invoice/page.tsx b/src/app/(dashboard)/cards/[cardId]/invoice/page.tsx index 08d13a8..0687991 100644 --- a/src/app/(dashboard)/cards/[cardId]/invoice/page.tsx +++ b/src/app/(dashboard)/cards/[cardId]/invoice/page.tsx @@ -202,6 +202,7 @@ export default async function Page({ params, searchParams }: PageProps) { allowCreate noteAsColumn={userPreferences?.statementNoteAsColumn ?? false} columnOrder={userPreferences?.transactionsColumnOrder ?? null} + attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50} defaultCardId={card.id} defaultPaymentMethod="Cartão de crédito" lockCardSelection diff --git a/src/app/(dashboard)/categories/[categoryId]/page.tsx b/src/app/(dashboard)/categories/[categoryId]/page.tsx index 2c06b75..fa263c2 100644 --- a/src/app/(dashboard)/categories/[categoryId]/page.tsx +++ b/src/app/(dashboard)/categories/[categoryId]/page.tsx @@ -99,6 +99,7 @@ export default async function Page({ params, searchParams }: PageProps) { allowCreate={true} noteAsColumn={userPreferences?.statementNoteAsColumn ?? false} columnOrder={userPreferences?.transactionsColumnOrder ?? null} + attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50} /> ); diff --git a/src/app/(dashboard)/payers/[payerId]/page.tsx b/src/app/(dashboard)/payers/[payerId]/page.tsx index 3554da0..60be28b 100644 --- a/src/app/(dashboard)/payers/[payerId]/page.tsx +++ b/src/app/(dashboard)/payers/[payerId]/page.tsx @@ -390,6 +390,7 @@ export default async function Page({ params, searchParams }: PageProps) { allowCreate={canEdit} noteAsColumn={userPreferences?.statementNoteAsColumn ?? false} columnOrder={userPreferences?.transactionsColumnOrder ?? null} + attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50} importPayerOptions={loggedUserOptionSets?.payerOptions} importSplitPayerOptions={loggedUserOptionSets?.splitPayerOptions} importDefaultPayerId={loggedUserOptionSets?.defaultPayerId} diff --git a/src/app/(dashboard)/transactions/page.tsx b/src/app/(dashboard)/transactions/page.tsx index e9246fa..f4f922b 100644 --- a/src/app/(dashboard)/transactions/page.tsx +++ b/src/app/(dashboard)/transactions/page.tsx @@ -102,6 +102,7 @@ export default async function Page({ searchParams }: PageProps) { }} noteAsColumn={userPreferences?.statementNoteAsColumn ?? false} columnOrder={userPreferences?.transactionsColumnOrder ?? null} + attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50} /> ); diff --git a/src/features/settings/actions.ts b/src/features/settings/actions.ts index 3d14343..3899263 100644 --- a/src/features/settings/actions.ts +++ b/src/features/settings/actions.ts @@ -65,6 +65,7 @@ const resetAccountSchema = z.object({ const updatePreferencesSchema = z.object({ statementNoteAsColumn: z.boolean(), transactionsColumnOrder: z.array(z.string()).nullable(), + attachmentMaxSizeMb: z.number().int().min(1).max(100), }); type ResettableUser = { @@ -561,6 +562,7 @@ export async function updatePreferencesAction( .set({ statementNoteAsColumn: validated.statementNoteAsColumn, transactionsColumnOrder: validated.transactionsColumnOrder, + attachmentMaxSizeMb: validated.attachmentMaxSizeMb, updatedAt: new Date(), }) .where(eq(schema.userPreferences.userId, session.user.id)); @@ -570,6 +572,7 @@ export async function updatePreferencesAction( userId: session.user.id, statementNoteAsColumn: validated.statementNoteAsColumn, transactionsColumnOrder: validated.transactionsColumnOrder, + attachmentMaxSizeMb: validated.attachmentMaxSizeMb, }); } diff --git a/src/features/settings/components/preferences-form.tsx b/src/features/settings/components/preferences-form.tsx index 0a61b95..e46a8b4 100644 --- a/src/features/settings/components/preferences-form.tsx +++ b/src/features/settings/components/preferences-form.tsx @@ -21,17 +21,27 @@ import { useRouter } from "next/navigation"; import { useState, useTransition } from "react"; import { toast } from "sonner"; import { updatePreferencesAction } from "@/features/settings/actions"; +import { + ATTACHMENT_SIZE_OPTIONS, + type AttachmentSizeOption, +} from "@/features/transactions/attachments-config"; import { DEFAULT_LANCAMENTOS_COLUMN_ORDER, LANCAMENTOS_COLUMN_LABELS, } from "@/features/transactions/column-order"; import { Button } from "@/shared/components/ui/button"; import { Label } from "@/shared/components/ui/label"; +import { Separator } from "@/shared/components/ui/separator"; import { Switch } from "@/shared/components/ui/switch"; +import { + ToggleGroup, + ToggleGroupItem, +} from "@/shared/components/ui/toggle-group"; interface PreferencesFormProps { statementNoteAsColumn: boolean; transactionsColumnOrder: string[] | null; + attachmentMaxSizeMb: number; } function SortableColumnItem({ id }: { id: string }) { @@ -74,6 +84,7 @@ function SortableColumnItem({ id }: { id: string }) { export function PreferencesForm({ statementNoteAsColumn: initialExtratoNoteAsColumn, transactionsColumnOrder: initialColumnOrder, + attachmentMaxSizeMb: initialAttachmentMaxSizeMb, }: PreferencesFormProps) { const router = useRouter(); const [isPending, startTransition] = useTransition(); @@ -85,6 +96,14 @@ export function PreferencesForm({ ? initialColumnOrder : DEFAULT_LANCAMENTOS_COLUMN_ORDER, ); + const [attachmentMaxSizeMb, setAttachmentMaxSizeMb] = + useState( + (ATTACHMENT_SIZE_OPTIONS.includes( + initialAttachmentMaxSizeMb as AttachmentSizeOption, + ) + ? initialAttachmentMaxSizeMb + : 50) as AttachmentSizeOption, + ); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), @@ -109,6 +128,7 @@ export function PreferencesForm({ const result = await updatePreferencesAction({ statementNoteAsColumn, transactionsColumnOrder: columnOrder, + attachmentMaxSizeMb, }); if (result.success) { @@ -122,19 +142,18 @@ export function PreferencesForm({ return (
- {/* Seção: Extrato / Lançamentos */} + {/* Seção: Lançamentos */}
-

Extrato e lançamentos

+

Lançamentos

- Como exibir anotações e a ordem das colunas na tabela de - movimentações. + Configurações de exibição da tabela de movimentações.

-
-
-
+
+ + + +
+ +

+ Configurações de upload de arquivos nos lançamentos. +

+ +
+ +

+ Limite aplicado ao upload de PDFs e imagens. +

+ { + if (val) + setAttachmentMaxSizeMb(Number(val) as AttachmentSizeOption); + }} + className="flex flex-wrap gap-2 justify-start" + > + {ATTACHMENT_SIZE_OPTIONS.map((size) => ( + + {size} MB + + ))} + +
+
diff --git a/src/features/settings/queries.ts b/src/features/settings/queries.ts index 7cd3f12..02f8c4a 100644 --- a/src/features/settings/queries.ts +++ b/src/features/settings/queries.ts @@ -5,6 +5,7 @@ import { db, schema } from "@/shared/lib/db"; export interface UserPreferences { statementNoteAsColumn: boolean; transactionsColumnOrder: string[] | null; + attachmentMaxSizeMb: number; } export interface ApiToken { @@ -32,6 +33,7 @@ export async function fetchUserPreferences( .select({ statementNoteAsColumn: schema.userPreferences.statementNoteAsColumn, transactionsColumnOrder: schema.userPreferences.transactionsColumnOrder, + attachmentMaxSizeMb: schema.userPreferences.attachmentMaxSizeMb, }) .from(schema.userPreferences) .where(eq(schema.userPreferences.userId, userId)) diff --git a/src/features/transactions/attachments-config.ts b/src/features/transactions/attachments-config.ts index a9257f5..78be6ca 100644 --- a/src/features/transactions/attachments-config.ts +++ b/src/features/transactions/attachments-config.ts @@ -5,4 +5,9 @@ export const ALLOWED_MIME_TYPES = [ "image/webp", ] as const; -export const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB +export const DEFAULT_MAX_FILE_SIZE_MB = 50; + +export const MAX_FILE_SIZE = DEFAULT_MAX_FILE_SIZE_MB * 1024 * 1024; // 50MB (fallback) + +export const ATTACHMENT_SIZE_OPTIONS = [5, 10, 25, 50, 100] as const; +export type AttachmentSizeOption = (typeof ATTACHMENT_SIZE_OPTIONS)[number]; diff --git a/src/features/transactions/components/attachments/attachment-file-picker.tsx b/src/features/transactions/components/attachments/attachment-file-picker.tsx index e42789a..396c060 100644 --- a/src/features/transactions/components/attachments/attachment-file-picker.tsx +++ b/src/features/transactions/components/attachments/attachment-file-picker.tsx @@ -5,19 +5,22 @@ import { useRef } from "react"; import { toast } from "sonner"; import { ALLOWED_MIME_TYPES, - MAX_FILE_SIZE, + DEFAULT_MAX_FILE_SIZE_MB, } from "@/features/transactions/attachments-config"; import { Button } from "@/shared/components/ui/button"; interface AttachmentFilePickerProps { file: File | null; onChange: (file: File | null) => void; + maxSizeMb?: number; } export function AttachmentFilePicker({ file, onChange, + maxSizeMb = DEFAULT_MAX_FILE_SIZE_MB, }: AttachmentFilePickerProps) { + const maxFileSizeBytes = maxSizeMb * 1024 * 1024; const inputRef = useRef(null); function handleFileChange(e: React.ChangeEvent) { @@ -37,8 +40,8 @@ export function AttachmentFilePicker({ return; } - if (selected.size > MAX_FILE_SIZE) { - toast.error("O arquivo deve ter no máximo 50MB."); + if (selected.size > maxFileSizeBytes) { + toast.error(`O arquivo deve ter no máximo ${maxSizeMb}MB.`); return; } @@ -83,7 +86,7 @@ export function AttachmentFilePicker({ Adicionar anexo - PDF, JPEG, PNG ou WebP · máx. 50 MB + PDF, JPEG, PNG ou WebP · máx. {maxSizeMb} MB )}