forked from git.gladyson/openmonetis
refactor: migrate from ESLint to Biome and extract SQL queries to data.ts
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,87 +1,87 @@
|
||||
"use server";
|
||||
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db, schema } from "@/lib/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db, schema } from "@/lib/db";
|
||||
|
||||
export type WidgetPreferences = {
|
||||
order: string[];
|
||||
hidden: string[];
|
||||
order: string[];
|
||||
hidden: string[];
|
||||
};
|
||||
|
||||
export async function updateWidgetPreferences(
|
||||
preferences: WidgetPreferences,
|
||||
preferences: WidgetPreferences,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
// Check if preferences exist
|
||||
const existing = await db
|
||||
.select({ id: schema.userPreferences.id })
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, user.id))
|
||||
.limit(1);
|
||||
// Check if preferences exist
|
||||
const existing = await db
|
||||
.select({ id: schema.userPreferences.id })
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, user.id))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(schema.userPreferences)
|
||||
.set({
|
||||
dashboardWidgets: preferences,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.userPreferences.userId, user.id));
|
||||
} else {
|
||||
await db.insert(schema.userPreferences).values({
|
||||
userId: user.id,
|
||||
dashboardWidgets: preferences,
|
||||
});
|
||||
}
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(schema.userPreferences)
|
||||
.set({
|
||||
dashboardWidgets: preferences,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.userPreferences.userId, user.id));
|
||||
} else {
|
||||
await db.insert(schema.userPreferences).values({
|
||||
userId: user.id,
|
||||
dashboardWidgets: preferences,
|
||||
});
|
||||
}
|
||||
|
||||
revalidatePath("/dashboard");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error updating widget preferences:", error);
|
||||
return { success: false, error: "Erro ao salvar preferências" };
|
||||
}
|
||||
revalidatePath("/dashboard");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error updating widget preferences:", error);
|
||||
return { success: false, error: "Erro ao salvar preferências" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetWidgetPreferences(): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
await db
|
||||
.update(schema.userPreferences)
|
||||
.set({
|
||||
dashboardWidgets: null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.userPreferences.userId, user.id));
|
||||
await db
|
||||
.update(schema.userPreferences)
|
||||
.set({
|
||||
dashboardWidgets: null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.userPreferences.userId, user.id));
|
||||
|
||||
revalidatePath("/dashboard");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error resetting widget preferences:", error);
|
||||
return { success: false, error: "Erro ao resetar preferências" };
|
||||
}
|
||||
revalidatePath("/dashboard");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error resetting widget preferences:", error);
|
||||
return { success: false, error: "Erro ao resetar preferências" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWidgetPreferences(): Promise<WidgetPreferences | null> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
const result = await db
|
||||
.select({ dashboardWidgets: schema.userPreferences.dashboardWidgets })
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, user.id))
|
||||
.limit(1);
|
||||
const result = await db
|
||||
.select({ dashboardWidgets: schema.userPreferences.dashboardWidgets })
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, user.id))
|
||||
.limit(1);
|
||||
|
||||
return result[0]?.dashboardWidgets ?? null;
|
||||
} catch (error) {
|
||||
console.error("Error getting widget preferences:", error);
|
||||
return null;
|
||||
}
|
||||
return result[0]?.dashboardWidgets ?? null;
|
||||
} catch (error) {
|
||||
console.error("Error getting widget preferences:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
import {
|
||||
RiArrowRightLine,
|
||||
RiArrowUpDoubleLine,
|
||||
RiBarChartBoxLine,
|
||||
RiBarcodeLine,
|
||||
RiBillLine,
|
||||
RiExchangeLine,
|
||||
RiLineChartLine,
|
||||
RiMoneyDollarCircleLine,
|
||||
RiNumbersLine,
|
||||
RiPieChartLine,
|
||||
RiRefreshLine,
|
||||
RiSlideshowLine,
|
||||
RiStore2Line,
|
||||
RiStore3Line,
|
||||
RiWallet3Line,
|
||||
} from "@remixicon/react";
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import { BoletosWidget } from "@/components/dashboard/boletos-widget";
|
||||
import { ExpensesByCategoryWidgetWithChart } from "@/components/dashboard/expenses-by-category-widget-with-chart";
|
||||
import { IncomeByCategoryWidgetWithChart } from "@/components/dashboard/income-by-category-widget-with-chart";
|
||||
@@ -13,201 +32,182 @@ import { RecentTransactionsWidget } from "@/components/dashboard/recent-transact
|
||||
import { RecurringExpensesWidget } from "@/components/dashboard/recurring-expenses-widget";
|
||||
import { TopEstablishmentsWidget } from "@/components/dashboard/top-establishments-widget";
|
||||
import { TopExpensesWidget } from "@/components/dashboard/top-expenses-widget";
|
||||
import {
|
||||
RiArrowRightLine,
|
||||
RiArrowUpDoubleLine,
|
||||
RiBarChartBoxLine,
|
||||
RiBarcodeLine,
|
||||
RiBillLine,
|
||||
RiExchangeLine,
|
||||
RiLineChartLine,
|
||||
RiMoneyDollarCircleLine,
|
||||
RiNumbersLine,
|
||||
RiPieChartLine,
|
||||
RiRefreshLine,
|
||||
RiSlideshowLine,
|
||||
RiStore2Line,
|
||||
RiStore3Line,
|
||||
RiWallet3Line,
|
||||
} from "@remixicon/react";
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import type { DashboardData } from "./fetch-dashboard-data";
|
||||
|
||||
export type WidgetConfig = {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
icon: ReactNode;
|
||||
component: (props: { data: DashboardData; period: string }) => ReactNode;
|
||||
action?: ReactNode;
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
icon: ReactNode;
|
||||
component: (props: { data: DashboardData; period: string }) => ReactNode;
|
||||
action?: ReactNode;
|
||||
};
|
||||
|
||||
export const widgetsConfig: WidgetConfig[] = [
|
||||
{
|
||||
id: "my-accounts",
|
||||
title: "Minhas Contas",
|
||||
subtitle: "Saldo consolidado disponível",
|
||||
icon: <RiBarChartBoxLine className="size-4" />,
|
||||
component: ({ data, period }) => (
|
||||
<MyAccountsWidget
|
||||
accounts={data.accountsSnapshot.accounts}
|
||||
totalBalance={data.accountsSnapshot.totalBalance}
|
||||
period={period}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "invoices",
|
||||
title: "Faturas",
|
||||
subtitle: "Resumo das faturas do período",
|
||||
icon: <RiBillLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<InvoicesWidget invoices={data.invoicesSnapshot.invoices} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "boletos",
|
||||
title: "Boletos",
|
||||
subtitle: "Controle de boletos do período",
|
||||
icon: <RiBarcodeLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<BoletosWidget boletos={data.boletosSnapshot.boletos} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "payment-status",
|
||||
title: "Status de Pagamento",
|
||||
subtitle: "Valores Confirmados E Pendentes",
|
||||
icon: <RiWallet3Line className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PaymentStatusWidget data={data.paymentStatusData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "income-expense-balance",
|
||||
title: "Receita, Despesa e Balanço",
|
||||
subtitle: "Últimos 6 Meses",
|
||||
icon: <RiLineChartLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<IncomeExpenseBalanceWidget data={data.incomeExpenseBalanceData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "recent-transactions",
|
||||
title: "Lançamentos Recentes",
|
||||
subtitle: "Últimas 5 despesas registradas",
|
||||
icon: <RiExchangeLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<RecentTransactionsWidget data={data.recentTransactionsData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "payment-conditions",
|
||||
title: "Condições de Pagamentos",
|
||||
subtitle: "Análise das condições",
|
||||
icon: <RiSlideshowLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PaymentConditionsWidget data={data.paymentConditionsData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "payment-methods",
|
||||
title: "Formas de Pagamento",
|
||||
subtitle: "Distribuição das despesas",
|
||||
icon: <RiMoneyDollarCircleLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PaymentMethodsWidget data={data.paymentMethodsData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "recurring-expenses",
|
||||
title: "Lançamentos Recorrentes",
|
||||
subtitle: "Despesas recorrentes do período",
|
||||
icon: <RiRefreshLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<RecurringExpensesWidget data={data.recurringExpensesData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "installment-expenses",
|
||||
title: "Lançamentos Parcelados",
|
||||
subtitle: "Acompanhe as parcelas abertas",
|
||||
icon: <RiNumbersLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<InstallmentExpensesWidget data={data.installmentExpensesData} />
|
||||
),
|
||||
action: (
|
||||
<Link
|
||||
href="/dashboard/analise-parcelas"
|
||||
className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors inline-flex items-center gap-1"
|
||||
>
|
||||
Análise
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "top-expenses",
|
||||
title: "Maiores Gastos do Mês",
|
||||
subtitle: "Top 10 Despesas",
|
||||
icon: <RiArrowUpDoubleLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<TopExpensesWidget
|
||||
allExpenses={data.topExpensesAll}
|
||||
cardOnlyExpenses={data.topExpensesCardOnly}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "top-establishments",
|
||||
title: "Top Estabelecimentos",
|
||||
subtitle: "Frequência de gastos no período",
|
||||
icon: <RiStore2Line className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<TopEstablishmentsWidget data={data.topEstablishmentsData} />
|
||||
),
|
||||
action: (
|
||||
<Link
|
||||
href="/top-estabelecimentos"
|
||||
className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors inline-flex items-center gap-1"
|
||||
>
|
||||
Ver mais
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "purchases-by-category",
|
||||
title: "Lançamentos por Categorias",
|
||||
subtitle: "Distribuição de lançamentos por categoria",
|
||||
icon: <RiStore3Line className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PurchasesByCategoryWidget data={data.purchasesByCategoryData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "income-by-category",
|
||||
title: "Categorias por Receitas",
|
||||
subtitle: "Distribuição de receitas por categoria",
|
||||
icon: <RiPieChartLine className="size-4" />,
|
||||
component: ({ data, period }) => (
|
||||
<IncomeByCategoryWidgetWithChart
|
||||
data={data.incomeByCategoryData}
|
||||
period={period}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "expenses-by-category",
|
||||
title: "Categorias por Despesas",
|
||||
subtitle: "Distribuição de despesas por categoria",
|
||||
icon: <RiPieChartLine className="size-4" />,
|
||||
component: ({ data, period }) => (
|
||||
<ExpensesByCategoryWidgetWithChart
|
||||
data={data.expensesByCategoryData}
|
||||
period={period}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "my-accounts",
|
||||
title: "Minhas Contas",
|
||||
subtitle: "Saldo consolidado disponível",
|
||||
icon: <RiBarChartBoxLine className="size-4" />,
|
||||
component: ({ data, period }) => (
|
||||
<MyAccountsWidget
|
||||
accounts={data.accountsSnapshot.accounts}
|
||||
totalBalance={data.accountsSnapshot.totalBalance}
|
||||
period={period}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "invoices",
|
||||
title: "Faturas",
|
||||
subtitle: "Resumo das faturas do período",
|
||||
icon: <RiBillLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<InvoicesWidget invoices={data.invoicesSnapshot.invoices} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "boletos",
|
||||
title: "Boletos",
|
||||
subtitle: "Controle de boletos do período",
|
||||
icon: <RiBarcodeLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<BoletosWidget boletos={data.boletosSnapshot.boletos} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "payment-status",
|
||||
title: "Status de Pagamento",
|
||||
subtitle: "Valores Confirmados E Pendentes",
|
||||
icon: <RiWallet3Line className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PaymentStatusWidget data={data.paymentStatusData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "income-expense-balance",
|
||||
title: "Receita, Despesa e Balanço",
|
||||
subtitle: "Últimos 6 Meses",
|
||||
icon: <RiLineChartLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<IncomeExpenseBalanceWidget data={data.incomeExpenseBalanceData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "recent-transactions",
|
||||
title: "Lançamentos Recentes",
|
||||
subtitle: "Últimas 5 despesas registradas",
|
||||
icon: <RiExchangeLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<RecentTransactionsWidget data={data.recentTransactionsData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "payment-conditions",
|
||||
title: "Condições de Pagamentos",
|
||||
subtitle: "Análise das condições",
|
||||
icon: <RiSlideshowLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PaymentConditionsWidget data={data.paymentConditionsData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "payment-methods",
|
||||
title: "Formas de Pagamento",
|
||||
subtitle: "Distribuição das despesas",
|
||||
icon: <RiMoneyDollarCircleLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PaymentMethodsWidget data={data.paymentMethodsData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "recurring-expenses",
|
||||
title: "Lançamentos Recorrentes",
|
||||
subtitle: "Despesas recorrentes do período",
|
||||
icon: <RiRefreshLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<RecurringExpensesWidget data={data.recurringExpensesData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "installment-expenses",
|
||||
title: "Lançamentos Parcelados",
|
||||
subtitle: "Acompanhe as parcelas abertas",
|
||||
icon: <RiNumbersLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<InstallmentExpensesWidget data={data.installmentExpensesData} />
|
||||
),
|
||||
action: (
|
||||
<Link
|
||||
href="/dashboard/analise-parcelas"
|
||||
className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors inline-flex items-center gap-1"
|
||||
>
|
||||
Análise
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "top-expenses",
|
||||
title: "Maiores Gastos do Mês",
|
||||
subtitle: "Top 10 Despesas",
|
||||
icon: <RiArrowUpDoubleLine className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<TopExpensesWidget
|
||||
allExpenses={data.topExpensesAll}
|
||||
cardOnlyExpenses={data.topExpensesCardOnly}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "top-establishments",
|
||||
title: "Top Estabelecimentos",
|
||||
subtitle: "Frequência de gastos no período",
|
||||
icon: <RiStore2Line className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<TopEstablishmentsWidget data={data.topEstablishmentsData} />
|
||||
),
|
||||
action: (
|
||||
<Link
|
||||
href="/top-estabelecimentos"
|
||||
className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors inline-flex items-center gap-1"
|
||||
>
|
||||
Ver mais
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "purchases-by-category",
|
||||
title: "Lançamentos por Categorias",
|
||||
subtitle: "Distribuição de lançamentos por categoria",
|
||||
icon: <RiStore3Line className="size-4" />,
|
||||
component: ({ data }) => (
|
||||
<PurchasesByCategoryWidget data={data.purchasesByCategoryData} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "income-by-category",
|
||||
title: "Categorias por Receitas",
|
||||
subtitle: "Distribuição de receitas por categoria",
|
||||
icon: <RiPieChartLine className="size-4" />,
|
||||
component: ({ data, period }) => (
|
||||
<IncomeByCategoryWidgetWithChart
|
||||
data={data.incomeByCategoryData}
|
||||
period={period}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "expenses-by-category",
|
||||
title: "Categorias por Despesas",
|
||||
subtitle: "Distribuição de despesas por categoria",
|
||||
icon: <RiPieChartLine className="size-4" />,
|
||||
component: ({ data, period }) => (
|
||||
<ExpensesByCategoryWidgetWithChart
|
||||
data={data.expensesByCategoryData}
|
||||
period={period}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user