refactor(sidebar): reorganizar navegação e aplicar formatação Biome

- Simplifica estrutura da sidebar combinando seções "Visão Geral" e "Gestão Financeira"
- Renomeia itens de relatórios para maior clareza ("Tendências", "Uso de Cartões")
- Aplica correções de formatação do Biome (ordenação de imports, quebras de linha)
- Remove código comentado não utilizado
- Adiciona migração 0014 do Drizzle

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-01-29 13:16:39 +00:00
parent 120da3659b
commit df1d149e4a
43 changed files with 4446 additions and 2515 deletions

View File

@@ -5,7 +5,7 @@ import { and, eq, isNull, ne } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { z } from "zod"; import { z } from "zod";
import { tokensApi, pagadores } from "@/db/schema"; import { pagadores, tokensApi } from "@/db/schema";
import { auth } from "@/lib/auth/config"; import { auth } from "@/lib/auth/config";
import { db, schema } from "@/lib/db"; import { db, schema } from "@/lib/db";
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants"; import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";

View File

@@ -11,10 +11,10 @@ import {
cartoes, cartoes,
categorias, categorias,
contas, contas,
insightsSalvos,
lancamentos, lancamentos,
orcamentos, orcamentos,
pagadores, pagadores,
insightsSalvos,
} from "@/db/schema"; } from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants"; import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants";
import { getUser } from "@/lib/auth/server"; import { getUser } from "@/lib/auth/server";
@@ -752,7 +752,10 @@ export async function saveInsightsAction(
modelId, modelId,
data: JSON.stringify(data), data: JSON.stringify(data),
}) })
.returning({ id: insightsSalvos.id, createdAt: insightsSalvos.createdAt }); .returning({
id: insightsSalvos.id,
createdAt: insightsSalvos.createdAt,
});
const insertedRecord = result[0]; const insertedRecord = result[0];
if (!insertedRecord) { if (!insertedRecord) {

View File

@@ -4,8 +4,8 @@ import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { z } from "zod"; import { z } from "zod";
import { import {
categorias,
antecipacoesParcelas, antecipacoesParcelas,
categorias,
lancamentos, lancamentos,
pagadores, pagadores,
} from "@/db/schema"; } from "@/db/schema";
@@ -320,10 +320,7 @@ export async function getInstallmentAnticipationsAction(
eq(antecipacoesParcelas.lancamentoId, lancamentos.id), eq(antecipacoesParcelas.lancamentoId, lancamentos.id),
) )
.leftJoin(pagadores, eq(antecipacoesParcelas.pagadorId, pagadores.id)) .leftJoin(pagadores, eq(antecipacoesParcelas.pagadorId, pagadores.id))
.leftJoin( .leftJoin(categorias, eq(antecipacoesParcelas.categoriaId, categorias.id))
categorias,
eq(antecipacoesParcelas.categoriaId, categorias.id),
)
.where( .where(
and( and(
eq(antecipacoesParcelas.seriesId, validatedSeriesId), eq(antecipacoesParcelas.seriesId, validatedSeriesId),

View File

@@ -2,10 +2,10 @@ import { and, desc, eq, type SQL } from "drizzle-orm";
import { import {
cartoes, cartoes,
categorias, categorias,
compartilhamentosPagador,
contas, contas,
lancamentos, lancamentos,
pagadores, pagadores,
compartilhamentosPagador,
user as usersTable, user as usersTable,
} from "@/db/schema"; } from "@/db/schema";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
@@ -30,7 +30,10 @@ export async function fetchPagadorShares(
userEmail: usersTable.email, userEmail: usersTable.email,
}) })
.from(compartilhamentosPagador) .from(compartilhamentosPagador)
.innerJoin(usersTable, eq(compartilhamentosPagador.sharedWithUserId, usersTable.id)) .innerJoin(
usersTable,
eq(compartilhamentosPagador.sharedWithUserId, usersTable.id),
)
.where(eq(compartilhamentosPagador.pagadorId, pagadorId)); .where(eq(compartilhamentosPagador.pagadorId, pagadorId));
return shareRows.map((share) => ({ return shareRows.map((share) => ({

View File

@@ -4,7 +4,7 @@ import { randomBytes } from "node:crypto";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { z } from "zod"; import { z } from "zod";
import { pagadores, compartilhamentosPagador, user } from "@/db/schema"; import { compartilhamentosPagador, pagadores, user } from "@/db/schema";
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers"; import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
import type { ActionResult } from "@/lib/actions/types"; import type { ActionResult } from "@/lib/actions/types";
import { getUser } from "@/lib/auth/server"; import { getUser } from "@/lib/auth/server";
@@ -287,7 +287,9 @@ export async function deletePagadorShareAction(
}; };
} }
await db.delete(compartilhamentosPagador).where(eq(compartilhamentosPagador.id, data.shareId)); await db
.delete(compartilhamentosPagador)
.where(eq(compartilhamentosPagador.id, data.shareId));
revalidate(); revalidate();
revalidatePath(`/pagadores/${existing.pagadorId}`); revalidatePath(`/pagadores/${existing.pagadorId}`);

View File

@@ -1,5 +1,3 @@
import { and, desc, eq, gte } from "drizzle-orm"; import { and, desc, eq, gte } from "drizzle-orm";
import type { import type {
InboxItem, InboxItem,
@@ -9,8 +7,8 @@ import {
cartoes, cartoes,
categorias, categorias,
contas, contas,
preLancamentos,
lancamentos, lancamentos,
preLancamentos,
} from "@/db/schema"; } from "@/db/schema";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { import {
@@ -26,7 +24,9 @@ export async function fetchInboxItems(
const items = await db const items = await db
.select() .select()
.from(preLancamentos) .from(preLancamentos)
.where(and(eq(preLancamentos.userId, userId), eq(preLancamentos.status, status))) .where(
and(eq(preLancamentos.userId, userId), eq(preLancamentos.status, status)),
)
.orderBy(desc(preLancamentos.createdAt)); .orderBy(desc(preLancamentos.createdAt));
return items; return items;
@@ -39,7 +39,9 @@ export async function fetchInboxItemById(
const [item] = await db const [item] = await db
.select() .select()
.from(preLancamentos) .from(preLancamentos)
.where(and(eq(preLancamentos.id, itemId), eq(preLancamentos.userId, userId))) .where(
and(eq(preLancamentos.id, itemId), eq(preLancamentos.userId, userId)),
)
.limit(1); .limit(1);
return item ?? null; return item ?? null;
@@ -91,7 +93,10 @@ export async function fetchPendingInboxCount(userId: string): Promise<number> {
.select({ id: preLancamentos.id }) .select({ id: preLancamentos.id })
.from(preLancamentos) .from(preLancamentos)
.where( .where(
and(eq(preLancamentos.userId, userId), eq(preLancamentos.status, "pending")), and(
eq(preLancamentos.userId, userId),
eq(preLancamentos.status, "pending"),
),
); );
return items.length; return items.length;

View File

@@ -2,7 +2,7 @@ import { RiBankCard2Line } from "@remixicon/react";
import PageDescription from "@/components/page-description"; import PageDescription from "@/components/page-description";
export const metadata = { export const metadata = {
title: "Relatório de Cartões | Opensheets", title: "Uso de Cartões | Opensheets",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -14,7 +14,7 @@ export default function RootLayout({
<section className="space-y-6 px-6"> <section className="space-y-6 px-6">
<PageDescription <PageDescription
icon={<RiBankCard2Line />} icon={<RiBankCard2Line />}
title="Relatório de Cartões" title="Uso de Cartões"
subtitle="Análise detalhada do uso dos seus cartões de crédito." subtitle="Análise detalhada do uso dos seus cartões de crédito."
/> />
{children} {children}

View File

@@ -2,7 +2,7 @@ import { RiFileChartLine } from "@remixicon/react";
import PageDescription from "@/components/page-description"; import PageDescription from "@/components/page-description";
export const metadata = { export const metadata = {
title: "Relatórios | Opensheets", title: "Tendências | Opensheets",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -14,7 +14,7 @@ export default function RootLayout({
<section className="space-y-6 px-6"> <section className="space-y-6 px-6">
<PageDescription <PageDescription
icon={<RiFileChartLine />} icon={<RiFileChartLine />}
title="Relatórios de Categorias" title="Tendências"
subtitle="Acompanhe a evolução dos seus gastos e receitas por categoria ao longo do tempo." subtitle="Acompanhe a evolução dos seus gastos e receitas por categoria ao longo do tempo."
/> />
{children} {children}

View File

@@ -1,5 +1,3 @@
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { tokensApi } from "@/db/schema"; import { tokensApi } from "@/db/schema";

View File

@@ -1,5 +1,3 @@
import { headers } from "next/headers"; import { headers } from "next/headers";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";

View File

@@ -1,5 +1,3 @@
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";

View File

@@ -1,5 +1,3 @@
import { desc, eq } from "drizzle-orm"; import { desc, eq } from "drizzle-orm";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";

View File

@@ -1,5 +1,3 @@
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { tokensApi } from "@/db/schema"; import { tokensApi } from "@/db/schema";

View File

@@ -1,9 +1,7 @@
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";
import { tokensApi, preLancamentos } from "@/db/schema"; import { preLancamentos, tokensApi } from "@/db/schema";
import { extractBearerToken, hashToken } from "@/lib/auth/api-token"; import { extractBearerToken, hashToken } from "@/lib/auth/api-token";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { inboxBatchSchema } from "@/lib/schemas/inbox"; import { inboxBatchSchema } from "@/lib/schemas/inbox";
@@ -103,7 +101,6 @@ export async function POST(request: Request) {
notificationTimestamp: item.notificationTimestamp, notificationTimestamp: item.notificationTimestamp,
parsedName: item.parsedName, parsedName: item.parsedName,
parsedAmount: item.parsedAmount?.toString(), parsedAmount: item.parsedAmount?.toString(),
parsedTransactionType: item.parsedTransactionType,
status: "pending", status: "pending",
}) })
.returning({ id: preLancamentos.id }); .returning({ id: preLancamentos.id });

View File

@@ -1,9 +1,7 @@
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";
import { tokensApi, preLancamentos } from "@/db/schema"; import { preLancamentos, tokensApi } from "@/db/schema";
import { extractBearerToken, hashToken } from "@/lib/auth/api-token"; import { extractBearerToken, hashToken } from "@/lib/auth/api-token";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { inboxItemSchema } from "@/lib/schemas/inbox"; import { inboxItemSchema } from "@/lib/schemas/inbox";
@@ -92,7 +90,6 @@ export async function POST(request: Request) {
notificationTimestamp: data.notificationTimestamp, notificationTimestamp: data.notificationTimestamp,
parsedName: data.parsedName, parsedName: data.parsedName,
parsedAmount: data.parsedAmount?.toString(), parsedAmount: data.parsedAmount?.toString(),
parsedTransactionType: data.parsedTransactionType,
status: "pending", status: "pending",
}) })
.returning({ id: preLancamentos.id }); .returning({ id: preLancamentos.id });

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { RiCalendarCheckLine, RiLoader4Line } from "@remixicon/react"; import { RiCalendarCheckLine, RiLoader4Line } from "@remixicon/react";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { getInstallmentAnticipationsAction } from "@/app/(dashboard)/lancamentos/anticipation-actions"; import { getInstallmentAnticipationsAction } from "@/app/(dashboard)/lancamentos/anticipation-actions";
import { import {
@@ -52,14 +52,8 @@ export function AnticipationHistoryDialog({
onOpenChange, onOpenChange,
); );
// Buscar antecipações ao abrir o dialog // Define loadAnticipations before it's used in useEffect
useEffect(() => { const loadAnticipations = useCallback(async () => {
if (dialogOpen) {
loadAnticipations();
}
}, [dialogOpen, loadAnticipations]);
const loadAnticipations = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
@@ -80,7 +74,14 @@ export function AnticipationHistoryDialog({
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; }, [seriesId]);
// Buscar antecipações ao abrir o dialog
useEffect(() => {
if (dialogOpen) {
loadAnticipations();
}
}, [dialogOpen, loadAnticipations]);
const handleCanceled = () => { const handleCanceled = () => {
// Recarregar lista após cancelamento // Recarregar lista após cancelamento

View File

@@ -24,7 +24,6 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils/ui";
import type { InboxItem } from "./types"; import type { InboxItem } from "./types";
interface InboxCardProps { interface InboxCardProps {
@@ -41,7 +40,6 @@ export function InboxCard({
onViewDetails, onViewDetails,
}: InboxCardProps) { }: InboxCardProps) {
const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null; const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null;
const isReceita = item.parsedTransactionType === "Receita";
// O timestamp vem do app Android em horário local mas salvo como UTC // O timestamp vem do app Android em horário local mas salvo como UTC
// Precisamos interpretar o valor UTC como se fosse horário de Brasília // Precisamos interpretar o valor UTC como se fosse horário de Brasília
@@ -78,16 +76,7 @@ export function InboxCard({
</span> </span>
</CardTitle> </CardTitle>
{amount !== null && ( {amount !== null && (
<MoneyValues <MoneyValues amount={amount} className="text-sm" />
amount={isReceita ? amount : -amount}
showPositiveSign={isReceita}
className={cn(
"text-sm",
isReceita
? "text-green-600 dark:text-green-400"
: "text-foreground",
)}
/>
)} )}
</div> </div>

View File

@@ -3,7 +3,6 @@
import { format } from "date-fns"; import { format } from "date-fns";
import { ptBR } from "date-fns/locale"; import { ptBR } from "date-fns/locale";
import MoneyValues from "@/components/money-values"; import MoneyValues from "@/components/money-values";
import { TypeBadge } from "@/components/type-badge";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -15,7 +14,6 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils/ui";
import type { InboxItem } from "./types"; import type { InboxItem } from "./types";
interface InboxDetailsDialogProps { interface InboxDetailsDialogProps {
@@ -32,7 +30,6 @@ export function InboxDetailsDialog({
if (!item) return null; if (!item) return null;
const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null; const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null;
const isReceita = item.parsedTransactionType === "Receita";
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
@@ -86,30 +83,11 @@ export function InboxDetailsDialog({
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-muted-foreground">Valor</span> <span className="text-muted-foreground">Valor</span>
{amount !== null ? ( {amount !== null ? (
<MoneyValues <MoneyValues amount={amount} className="text-sm" />
amount={isReceita ? amount : -amount}
showPositiveSign={isReceita}
className={cn(
"text-sm",
isReceita
? "text-green-600 dark:text-green-400"
: "text-foreground",
)}
/>
) : ( ) : (
<span className="text-muted-foreground">Não extraído</span> <span className="text-muted-foreground">Não extraído</span>
)} )}
</div> </div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Tipo</span>
{item.parsedTransactionType ? (
<TypeBadge type={item.parsedTransactionType} />
) : (
<span className="text-muted-foreground">
Não identificado
</span>
)}
</div>
</div> </div>
</div> </div>

View File

@@ -139,9 +139,6 @@ export function InboxPage({
? String(Math.abs(Number(itemToProcess.parsedAmount))) ? String(Math.abs(Number(itemToProcess.parsedAmount)))
: null; : null;
const defaultTransactionType =
itemToProcess?.parsedTransactionType === "Receita" ? "Receita" : "Despesa";
return ( return (
<> <>
<div className="flex w-full flex-col gap-6"> <div className="flex w-full flex-col gap-6">
@@ -182,7 +179,6 @@ export function InboxPage({
defaultPurchaseDate={defaultPurchaseDate} defaultPurchaseDate={defaultPurchaseDate}
defaultName={defaultName} defaultName={defaultName}
defaultAmount={defaultAmount} defaultAmount={defaultAmount}
defaultTransactionType={defaultTransactionType}
forceShowTransactionType forceShowTransactionType
onSuccess={handleLancamentoSuccess} onSuccess={handleLancamentoSuccess}
/> />

View File

@@ -1,5 +1,3 @@
import type { SelectOption as LancamentoSelectOption } from "@/components/lancamentos/types"; import type { SelectOption as LancamentoSelectOption } from "@/components/lancamentos/types";
export interface InboxItem { export interface InboxItem {
@@ -11,7 +9,6 @@ export interface InboxItem {
notificationTimestamp: Date; notificationTimestamp: Date;
parsedName: string | null; parsedName: string | null;
parsedAmount: string | null; parsedAmount: string | null;
parsedTransactionType: string | null;
status: string; status: string;
lancamentoId: string | null; lancamentoId: string | null;
processedAt: Date | null; processedAt: Date | null;
@@ -25,7 +22,6 @@ export interface ProcessInboxInput {
name: string; name: string;
amount: number; amount: number;
purchaseDate: string; purchaseDate: string;
transactionType: "Despesa" | "Receita";
condition: string; condition: string;
paymentMethod: string; paymentMethod: string;
categoriaId: string; categoriaId: string;

View File

@@ -53,7 +53,7 @@ export function AppSidebar({
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton <SidebarMenuButton
asChild asChild
className="data-[slot=sidebar-menu-button]:px-1.5! hover:bg-transparent active:bg-transparent pt-4 justify-center hover:scale-105 transition-all duration-200" className="data-[slot=sidebar-menu-button]:px-1.5! hover:bg-transparent active:bg-transparent pt-4 justify-center hover:scale-105 transition-all duration-200"
> >
<a href="/dashboard"> <a href="/dashboard">
<LogoContent /> <LogoContent />

View File

@@ -85,18 +85,13 @@ export function createSidebarNavData(
return { return {
navMain: [ navMain: [
{ {
title: "Visão Geral", title: "Gestão Financeira",
items: [ items: [
{ {
title: "Dashboard", title: "Dashboard",
url: "/dashboard", url: "/dashboard",
icon: RiDashboardLine, icon: RiDashboardLine,
}, },
],
},
{
title: "Gestão Financeira",
items: [
{ {
title: "Lançamentos", title: "Lançamentos",
url: "/lancamentos", url: "/lancamentos",
@@ -164,11 +159,6 @@ export function createSidebarNavData(
url: "/categorias", url: "/categorias",
icon: RiPriceTag3Line, icon: RiPriceTag3Line,
}, },
],
},
{
title: "Análise e Anotações",
items: [
{ {
title: "Anotações", title: "Anotações",
url: "/anotacoes", url: "/anotacoes",
@@ -182,23 +172,23 @@ export function createSidebarNavData(
}, },
], ],
}, },
],
},
{
title: "Análise",
items: [
{ {
title: "Insights", title: "Insights",
url: "/insights", url: "/insights",
icon: RiSparklingLine, icon: RiSparklingLine,
}, },
],
},
{
title: "Relatórios",
items: [
{ {
title: "Categorias", title: "Tendências",
url: "/relatorios/categorias", url: "/relatorios/categorias",
icon: RiFileChartLine, icon: RiFileChartLine,
}, },
{ {
title: "Cartões", title: "Uso de Cartões",
url: "/relatorios/cartoes", url: "/relatorios/cartoes",
icon: RiBankCard2Line, icon: RiBankCard2Line,
}, },
@@ -206,11 +196,6 @@ export function createSidebarNavData(
}, },
], ],
navSecondary: [ navSecondary: [
// {
// title: "Changelog",
// url: "/changelog",
// icon: RiGitCommitLine,
// },
{ {
title: "Ajustes", title: "Ajustes",
url: "/ajustes", url: "/ajustes",

View File

@@ -113,9 +113,11 @@ export function NavMain({ sections }: { sections: NavSection[] }) {
return ( return (
<> <>
{sections.map((section) => ( {sections.map((section, index) => (
<SidebarGroup key={section.title}> <SidebarGroup key={section.title}>
<SidebarGroupLabel>{section.title}</SidebarGroupLabel> <SidebarGroupLabel className="text-xs text-muted-foreground/60">
{section.title}
</SidebarGroupLabel>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
{section.items.map((item) => { {section.items.map((item) => {

View File

@@ -451,7 +451,6 @@ export const preLancamentos = pgTable(
// Dados parseados (editáveis pelo usuário antes de processar) // Dados parseados (editáveis pelo usuário antes de processar)
parsedName: text("parsed_name"), // Nome do estabelecimento parsedName: text("parsed_name"), // Nome do estabelecimento
parsedAmount: numeric("parsed_amount", { precision: 12, scale: 2 }), parsedAmount: numeric("parsed_amount", { precision: 12, scale: 2 }),
parsedTransactionType: text("parsed_transaction_type"), // Despesa, Receita
// Status de processamento // Status de processamento
status: text("status").notNull().default("pending"), // pending, processed, discarded status: text("status").notNull().default("pending"), // pending, processed, discarded
@@ -527,7 +526,9 @@ export const antecipacoesParcelas = pgTable(
.defaultNow(), .defaultNow(),
}, },
(table) => ({ (table) => ({
seriesIdIdx: index("antecipacoes_parcelas_series_id_idx").on(table.seriesId), seriesIdIdx: index("antecipacoes_parcelas_series_id_idx").on(
table.seriesId,
),
userIdIdx: index("antecipacoes_parcelas_user_id_idx").on(table.userId), userIdIdx: index("antecipacoes_parcelas_user_id_idx").on(table.userId),
}), }),
); );

View File

@@ -0,0 +1 @@
ALTER TABLE "pre_lancamentos" DROP COLUMN "parsed_transaction_type";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,104 +1,111 @@
{ {
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "7",
"when": 1762993507299, "when": 1762993507299,
"tag": "0000_flashy_manta", "tag": "0000_flashy_manta",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "7", "version": "7",
"when": 1765199006435, "when": 1765199006435,
"tag": "0001_young_mister_fear", "tag": "0001_young_mister_fear",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 2, "idx": 2,
"version": "7", "version": "7",
"when": 1765200545692, "when": 1765200545692,
"tag": "0002_slimy_flatman", "tag": "0002_slimy_flatman",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 3, "idx": 3,
"version": "7", "version": "7",
"when": 1767102605526, "when": 1767102605526,
"tag": "0003_green_korg", "tag": "0003_green_korg",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 4, "idx": 4,
"version": "7", "version": "7",
"when": 1767104066872, "when": 1767104066872,
"tag": "0004_acoustic_mach_iv", "tag": "0004_acoustic_mach_iv",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 5, "idx": 5,
"version": "7", "version": "7",
"when": 1767106121811, "when": 1767106121811,
"tag": "0005_adorable_bruce_banner", "tag": "0005_adorable_bruce_banner",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 6, "idx": 6,
"version": "7", "version": "7",
"when": 1767107487318, "when": 1767107487318,
"tag": "0006_youthful_mister_fear", "tag": "0006_youthful_mister_fear",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 7, "idx": 7,
"version": "7", "version": "7",
"when": 1767118780033, "when": 1767118780033,
"tag": "0007_sturdy_kate_bishop", "tag": "0007_sturdy_kate_bishop",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 8, "idx": 8,
"version": "7", "version": "7",
"when": 1767125796314, "when": 1767125796314,
"tag": "0008_fat_stick", "tag": "0008_fat_stick",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 9, "idx": 9,
"version": "7", "version": "7",
"when": 1768925100873, "when": 1768925100873,
"tag": "0009_add_dashboard_widgets", "tag": "0009_add_dashboard_widgets",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 10, "idx": 10,
"version": "7", "version": "7",
"when": 1769369834242, "when": 1769369834242,
"tag": "0010_lame_psynapse", "tag": "0010_lame_psynapse",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 11, "idx": 11,
"version": "7", "version": "7",
"when": 1769447087678, "when": 1769447087678,
"tag": "0011_remove_unused_inbox_columns", "tag": "0011_remove_unused_inbox_columns",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 12, "idx": 12,
"version": "7", "version": "7",
"when": 1769533200000, "when": 1769533200000,
"tag": "0012_rename_tables_to_portuguese", "tag": "0012_rename_tables_to_portuguese",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 13, "idx": 13,
"version": "7", "version": "7",
"when": 1769523352777, "when": 1769523352777,
"tag": "0013_fancy_rick_jones", "tag": "0013_fancy_rick_jones",
"breakpoints": true "breakpoints": true
} },
] {
"idx": 14,
"version": "7",
"when": 1769619226903,
"tag": "0014_yielding_jack_flag",
"breakpoints": true
}
]
} }

View File

@@ -1,5 +1,3 @@
import crypto from "node:crypto"; import crypto from "node:crypto";
const JWT_SECRET = const JWT_SECRET =

View File

@@ -1,5 +1,3 @@
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle";
import type { GoogleProfile } from "better-auth/social-providers"; import type { GoogleProfile } from "better-auth/social-providers";

View File

@@ -1,5 +1,3 @@
import { headers } from "next/headers"; import { headers } from "next/headers";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { auth } from "@/lib/auth/config"; import { auth } from "@/lib/auth/config";

View File

@@ -1,5 +1,3 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { categorias } from "@/db/schema"; import { categorias } from "@/db/schema";
import type { CategoryType } from "@/lib/categorias/constants"; import type { CategoryType } from "@/lib/categorias/constants";

View File

@@ -1,5 +1,3 @@
import { calculatePercentageChange } from "@/lib/utils/math"; import { calculatePercentageChange } from "@/lib/utils/math";
import { safeToNumber } from "@/lib/utils/number"; import { safeToNumber } from "@/lib/utils/number";

View File

@@ -1,5 +1,3 @@
import type { SelectOption } from "@/components/lancamentos/types"; import type { SelectOption } from "@/components/lancamentos/types";
/** /**

View File

@@ -1,5 +1,3 @@
import type { LancamentoItem } from "@/components/lancamentos/types"; import type { LancamentoItem } from "@/components/lancamentos/types";
import { getTodayDateString } from "@/lib/utils/date"; import { getTodayDateString } from "@/lib/utils/date";
import { derivePeriodFromDate } from "@/lib/utils/period"; import { derivePeriodFromDate } from "@/lib/utils/period";

View File

@@ -1,5 +1,3 @@
import { readdir } from "node:fs/promises"; import { readdir } from "node:fs/promises";
import path from "node:path"; import path from "node:path";

View File

@@ -1,5 +1,9 @@
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { pagadores, compartilhamentosPagador, user as usersTable } from "@/db/schema"; import {
compartilhamentosPagador,
pagadores,
user as usersTable,
} from "@/db/schema";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
export type PagadorWithAccess = typeof pagadores.$inferSelect & { export type PagadorWithAccess = typeof pagadores.$inferSelect & {
@@ -24,7 +28,10 @@ export async function fetchPagadoresWithAccess(
ownerEmail: usersTable.email, ownerEmail: usersTable.email,
}) })
.from(compartilhamentosPagador) .from(compartilhamentosPagador)
.innerJoin(pagadores, eq(compartilhamentosPagador.pagadorId, pagadores.id)) .innerJoin(
pagadores,
eq(compartilhamentosPagador.pagadorId, pagadores.id),
)
.leftJoin(usersTable, eq(pagadores.userId, usersTable.id)) .leftJoin(usersTable, eq(pagadores.userId, usersTable.id))
.where(eq(compartilhamentosPagador.sharedWithUserId, userId)), .where(eq(compartilhamentosPagador.sharedWithUserId, userId)),
]); ]);

View File

@@ -1,5 +1,3 @@
import { DEFAULT_PAGADOR_AVATAR } from "./constants"; import { DEFAULT_PAGADOR_AVATAR } from "./constants";
/** /**

View File

@@ -1,5 +1,3 @@
import { format } from "date-fns"; import { format } from "date-fns";
import { ptBR } from "date-fns/locale"; import { ptBR } from "date-fns/locale";
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm"; import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";

View File

@@ -1,5 +1,3 @@
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm"; import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";
import { categorias, lancamentos, pagadores } from "@/db/schema"; import { categorias, lancamentos, pagadores } from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants"; import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants";

View File

@@ -1,5 +1,3 @@
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers"; import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
import { calculatePercentageChange } from "@/lib/utils/math"; import { calculatePercentageChange } from "@/lib/utils/math";
import { buildPeriodRange, MONTH_NAMES, parsePeriod } from "@/lib/utils/period"; import { buildPeriodRange, MONTH_NAMES, parsePeriod } from "@/lib/utils/period";

View File

@@ -1,5 +1,3 @@
import { z } from "zod"; import { z } from "zod";
export const inboxItemSchema = z.object({ export const inboxItemSchema = z.object({
@@ -10,7 +8,6 @@ export const inboxItemSchema = z.object({
notificationTimestamp: z.string().transform((val) => new Date(val)), notificationTimestamp: z.string().transform((val) => new Date(val)),
parsedName: z.string().optional(), parsedName: z.string().optional(),
parsedAmount: z.coerce.number().optional(), parsedAmount: z.coerce.number().optional(),
parsedTransactionType: z.enum(["Despesa", "Receita"]).optional(),
clientId: z.string().optional(), // ID local do app para rastreamento clientId: z.string().optional(), // ID local do app para rastreamento
}); });

View File

@@ -1,5 +1,3 @@
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";