feat(preferencias): permite ocultar resumo do lancamento

This commit is contained in:
Felipe Coutinho
2026-05-31 15:18:07 -03:00
parent cdcc677787
commit 41eecc2538
11 changed files with 3037 additions and 10 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE "preferencias_usuario" ADD COLUMN "mostrar_resumo_lancamento" boolean DEFAULT true NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -204,6 +204,13 @@
"when": 1777648189399,
"tag": "0029_friendly_spitfire",
"breakpoints": true
},
{
"idx": 30,
"version": "7",
"when": 1780150535055,
"tag": "0030_complete_umar",
"breakpoints": true
}
]
}

View File

@@ -82,6 +82,9 @@ export default async function Page() {
userPreferences?.transactionsColumnOrder ?? null
}
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
showTransactionSummary={
userPreferences?.showTransactionSummary ?? true
}
/>
</div>
</Card>

View File

@@ -154,6 +154,9 @@ export const userPreferences = pgTable("preferencias_usuario", {
string[] | null
>(),
attachmentMaxSizeMb: integer("attachment_max_size_mb").notNull().default(50),
showTransactionSummary: boolean("mostrar_resumo_lancamento")
.notNull()
.default(true),
dashboardWidgets: jsonb("dashboard_widgets").$type<{
order: string[];
hidden: string[];
@@ -495,7 +498,7 @@ export const inboxItems = pgTable(
withTimezone: true,
}).notNull(),
// Dados parseados (editáveis pelo usuário antes de processar)
// Dados parseados (editáveis pelo usuário antes de lançar)
parsedName: text("parsed_name"), // Nome do estabelecimento
parsedAmount: numeric("parsed_amount", { precision: 12, scale: 2 }),

View File

@@ -68,6 +68,7 @@ const updatePreferencesSchema = z.object({
statementNoteAsColumn: z.boolean(),
transactionsColumnOrder: z.array(z.string()).nullable(),
attachmentMaxSizeMb: z.number().int().min(1).max(100),
showTransactionSummary: z.boolean(),
});
type ResettableUser = {
@@ -582,6 +583,7 @@ export async function updatePreferencesAction(
statementNoteAsColumn: validated.statementNoteAsColumn,
transactionsColumnOrder: validated.transactionsColumnOrder,
attachmentMaxSizeMb: validated.attachmentMaxSizeMb,
showTransactionSummary: validated.showTransactionSummary,
updatedAt: new Date(),
})
.where(eq(schema.userPreferences.userId, session.user.id));
@@ -592,6 +594,7 @@ export async function updatePreferencesAction(
statementNoteAsColumn: validated.statementNoteAsColumn,
transactionsColumnOrder: validated.transactionsColumnOrder,
attachmentMaxSizeMb: validated.attachmentMaxSizeMb,
showTransactionSummary: validated.showTransactionSummary,
});
}

View File

@@ -42,6 +42,7 @@ interface PreferencesFormProps {
statementNoteAsColumn: boolean;
transactionsColumnOrder: string[] | null;
attachmentMaxSizeMb: number;
showTransactionSummary: boolean;
}
function SortableColumnItem({ id }: { id: string }) {
@@ -85,6 +86,7 @@ export function PreferencesForm({
statementNoteAsColumn: initialExtratoNoteAsColumn,
transactionsColumnOrder: initialColumnOrder,
attachmentMaxSizeMb: initialAttachmentMaxSizeMb,
showTransactionSummary: initialShowTransactionSummary,
}: PreferencesFormProps) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
@@ -104,6 +106,9 @@ export function PreferencesForm({
? initialAttachmentMaxSizeMb
: 50) as AttachmentSizeOption,
);
const [showTransactionSummary, setShowTransactionSummary] = useState(
initialShowTransactionSummary,
);
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
@@ -129,6 +134,7 @@ export function PreferencesForm({
statementNoteAsColumn,
transactionsColumnOrder: columnOrder,
attachmentMaxSizeMb,
showTransactionSummary,
});
if (result.success) {
@@ -172,6 +178,26 @@ export function PreferencesForm({
<Separator />
<section className="flex items-center justify-between max-w-md">
<div className="space-y-2">
<Label htmlFor="show-transaction-summary" className="text-sm">
Resumo da operação
</Label>
<p className="text-sm text-muted-foreground">
Exibe um resumo dos dados preenchidos no final do modal de
lançamento.
</p>
</div>
<Switch
id="show-transaction-summary"
checked={showTransactionSummary}
onCheckedChange={setShowTransactionSummary}
disabled={isPending}
/>
</section>
<Separator />
<section className="space-y-2 max-w-md">
<Label className="text-sm">Ordem das colunas</Label>
<p className="text-sm text-muted-foreground">

View File

@@ -6,6 +6,7 @@ interface UserPreferences {
statementNoteAsColumn: boolean;
transactionsColumnOrder: string[] | null;
attachmentMaxSizeMb: number;
showTransactionSummary: boolean;
}
interface ApiToken {
@@ -34,6 +35,7 @@ export async function fetchUserPreferences(
statementNoteAsColumn: schema.userPreferences.statementNoteAsColumn,
transactionsColumnOrder: schema.userPreferences.transactionsColumnOrder,
attachmentMaxSizeMb: schema.userPreferences.attachmentMaxSizeMb,
showTransactionSummary: schema.userPreferences.showTransactionSummary,
})
.from(schema.userPreferences)
.where(eq(schema.userPreferences.userId, userId))

View File

@@ -17,6 +17,7 @@ import {
buildTransactionInitialState,
deriveCreditCardPeriod,
} from "@/features/transactions/lib/form-helpers";
import { useAppPreferences } from "@/shared/components/providers/app-preferences-provider";
import { Button } from "@/shared/components/ui/button";
import {
Collapsible,
@@ -104,6 +105,7 @@ export function TransactionDialog({
const [pendingUploadFiles, setPendingUploadFiles] = useState<File[]>([]);
const [extrasOpen, setExtrasOpen] = useState(false);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const { showTransactionSummary } = useAppPreferences();
useEffect(() => {
if (dialogOpen) {
@@ -730,15 +732,17 @@ export function TransactionDialog({
</Collapsible>
)}
<div className="mt-3">
<TransactionSummaryCard
formState={formState}
payerOptions={payerOptions}
accountOptions={accountOptions}
cardOptions={cardOptions}
categoryOptions={categoryOptions}
/>
</div>
{showTransactionSummary ? (
<div className="mt-3">
<TransactionSummaryCard
formState={formState}
payerOptions={payerOptions}
accountOptions={accountOptions}
cardOptions={cardOptions}
categoryOptions={categoryOptions}
/>
</div>
) : null}
</div>
{errorMessage ? (

View File

@@ -0,0 +1,31 @@
"use client";
import { createContext, useContext } from "react";
import type { AppPreferences } from "@/shared/lib/preferences/queries";
const DEFAULT_APP_PREFERENCES: AppPreferences = {
showTransactionSummary: true,
};
const AppPreferencesContext = createContext<AppPreferences>(
DEFAULT_APP_PREFERENCES,
);
type AppPreferencesProviderProps = AppPreferences & {
children: React.ReactNode;
};
export function AppPreferencesProvider({
children,
...preferences
}: AppPreferencesProviderProps) {
return (
<AppPreferencesContext.Provider value={preferences}>
{children}
</AppPreferencesContext.Provider>
);
}
export function useAppPreferences() {
return useContext(AppPreferencesContext);
}

View File

@@ -0,0 +1,24 @@
import { eq } from "drizzle-orm";
import { db, schema } from "@/shared/lib/db";
export type AppPreferences = {
showTransactionSummary: boolean;
};
const DEFAULT_APP_PREFERENCES: AppPreferences = {
showTransactionSummary: true,
};
export async function fetchAppPreferences(
userId: string,
): Promise<AppPreferences> {
const [preferences] = await db
.select({
showTransactionSummary: schema.userPreferences.showTransactionSummary,
})
.from(schema.userPreferences)
.where(eq(schema.userPreferences.userId, userId))
.limit(1);
return preferences ?? DEFAULT_APP_PREFERENCES;
}