mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
refactor: alinha features financeiras ao novo naming
This commit is contained in:
@@ -9,20 +9,20 @@ import {
|
||||
fetchAccountSummary,
|
||||
} from "@/features/accounts/statement-queries";
|
||||
import { fetchUserPreferences } from "@/features/settings/queries";
|
||||
import { LancamentosPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||
import {
|
||||
buildLancamentoWhere,
|
||||
buildTransactionWhere,
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
buildSlugMaps,
|
||||
extractLancamentoSearchFilters,
|
||||
extractTransactionSearchFilters,
|
||||
getSingleParam,
|
||||
mapLancamentosData,
|
||||
mapTransactionsData,
|
||||
type ResolvedSearchParams,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
import {
|
||||
fetchLancamentoFilterSources,
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
} from "@/features/transactions/queries";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -41,7 +41,7 @@ const capitalize = (value: string) =>
|
||||
value.length > 0 ? value[0]?.toUpperCase().concat(value.slice(1)) : value;
|
||||
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
const { accountId: contaId } = await params;
|
||||
const { accountId } = await params;
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
|
||||
@@ -52,9 +52,9 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
year,
|
||||
} = parsePeriodParam(periodoParamRaw);
|
||||
|
||||
const searchFilters = extractLancamentoSearchFilters(resolvedSearchParams);
|
||||
const searchFilters = extractTransactionSearchFilters(resolvedSearchParams);
|
||||
|
||||
const account = await fetchAccountData(userId, contaId);
|
||||
const account = await fetchAccountData(userId, accountId);
|
||||
|
||||
if (!account) {
|
||||
notFound();
|
||||
@@ -67,16 +67,16 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
estabelecimentos,
|
||||
userPreferences,
|
||||
] = await Promise.all([
|
||||
fetchLancamentoFilterSources(userId),
|
||||
fetchTransactionFilterSources(userId),
|
||||
loadLogoOptions(),
|
||||
fetchAccountSummary(userId, contaId, selectedPeriod),
|
||||
fetchAccountSummary(userId, accountId, selectedPeriod),
|
||||
fetchRecentEstablishments(userId),
|
||||
fetchUserPreferences(userId),
|
||||
]);
|
||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||
const slugMaps = buildSlugMaps(sluggedFilters);
|
||||
|
||||
const filters = buildLancamentoWhere({
|
||||
const filters = buildTransactionWhere({
|
||||
userId,
|
||||
period: selectedPeriod,
|
||||
filters: searchFilters,
|
||||
@@ -84,9 +84,9 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
accountId: account.id,
|
||||
});
|
||||
|
||||
const lancamentoRows = await fetchAccountLancamentos(filters);
|
||||
const transactionRows = await fetchAccountLancamentos(filters);
|
||||
|
||||
const lancamentosData = mapLancamentosData(lancamentoRows);
|
||||
const transactionData = mapTransactionsData(transactionRows);
|
||||
|
||||
const { openingBalance, currentBalance, totalIncomes, totalExpenses } =
|
||||
accountSummary;
|
||||
@@ -105,18 +105,18 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
};
|
||||
|
||||
const {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
payerFilterOptions,
|
||||
categoryFilterOptions,
|
||||
accountCardFilterOptions,
|
||||
} = buildOptionSets({
|
||||
...sluggedFilters,
|
||||
pagadorRows: filterSources.pagadorRows,
|
||||
payerRows: filterSources.payerRows,
|
||||
limitContaId: account.id,
|
||||
});
|
||||
|
||||
@@ -157,21 +157,21 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
<section className="flex flex-col gap-4">
|
||||
<LancamentosSection
|
||||
currentUserId={userId}
|
||||
lancamentos={lancamentosData}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
pagadorFilterOptions={pagadorFilterOptions}
|
||||
categoriaFilterOptions={categoriaFilterOptions}
|
||||
contaCartaoFilterOptions={contaCartaoFilterOptions}
|
||||
transactions={transactionData}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
payerFilterOptions={payerFilterOptions}
|
||||
categoryFilterOptions={categoryFilterOptions}
|
||||
accountCardFilterOptions={accountCardFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
estabelecimentos={estabelecimentos}
|
||||
allowCreate={false}
|
||||
noteAsColumn={userPreferences?.extratoNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.lancamentosColumnOrder ?? null}
|
||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function OrcamentosLoading() {
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className="rounded-2xl border p-6 space-y-4">
|
||||
{/* Categoria com ícone */}
|
||||
{/* Category com ícone */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Skeleton className="size-10 rounded-2xl bg-foreground/10" />
|
||||
<div className="flex-1 space-y-2">
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { RiPencilLine } from "@remixicon/react";
|
||||
import { notFound } from "next/navigation";
|
||||
import type { Conta } from "@/db/schema";
|
||||
import type { FinancialAccount } from "@/db/schema";
|
||||
import { CardDialog } from "@/features/cards/components/card-dialog";
|
||||
import type { Card } from "@/features/cards/components/types";
|
||||
import { InvoiceSummaryCard } from "@/features/invoices/components/invoice-summary-card";
|
||||
import {
|
||||
fetchCardData,
|
||||
fetchCardLancamentos,
|
||||
fetchCardTransactions,
|
||||
fetchInvoiceData,
|
||||
} from "@/features/invoices/queries";
|
||||
import { fetchUserPreferences } from "@/features/settings/queries";
|
||||
import { LancamentosPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||
import {
|
||||
buildLancamentoWhere,
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
buildSlugMaps,
|
||||
extractLancamentoSearchFilters,
|
||||
buildTransactionWhere,
|
||||
extractTransactionSearchFilters,
|
||||
getSingleParam,
|
||||
mapLancamentosData,
|
||||
mapTransactionsData,
|
||||
type ResolvedSearchParams,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
import {
|
||||
fetchLancamentoFilterSources,
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
} from "@/features/transactions/queries";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -39,7 +39,7 @@ type PageProps = {
|
||||
};
|
||||
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
const { cardId: cartaoId } = await params;
|
||||
const { cardId } = await params;
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
|
||||
@@ -50,9 +50,9 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
year,
|
||||
} = parsePeriodParam(periodoParamRaw);
|
||||
|
||||
const searchFilters = extractLancamentoSearchFilters(resolvedSearchParams);
|
||||
const searchFilters = extractTransactionSearchFilters(resolvedSearchParams);
|
||||
|
||||
const card = await fetchCardData(userId, cartaoId);
|
||||
const card = await fetchCardData(userId, cardId);
|
||||
|
||||
if (!card) {
|
||||
notFound();
|
||||
@@ -65,16 +65,16 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
estabelecimentos,
|
||||
userPreferences,
|
||||
] = await Promise.all([
|
||||
fetchLancamentoFilterSources(userId),
|
||||
fetchTransactionFilterSources(userId),
|
||||
loadLogoOptions(),
|
||||
fetchInvoiceData(userId, cartaoId, selectedPeriod),
|
||||
fetchInvoiceData(userId, cardId, selectedPeriod),
|
||||
fetchRecentEstablishments(userId),
|
||||
fetchUserPreferences(userId),
|
||||
]);
|
||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||
const slugMaps = buildSlugMaps(sluggedFilters);
|
||||
|
||||
const filters = buildLancamentoWhere({
|
||||
const filters = buildTransactionWhere({
|
||||
userId,
|
||||
period: selectedPeriod,
|
||||
filters: searchFilters,
|
||||
@@ -82,35 +82,39 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
cardId: card.id,
|
||||
});
|
||||
|
||||
const lancamentoRows = await fetchCardLancamentos(filters);
|
||||
const transactionRows = await fetchCardTransactions(filters);
|
||||
|
||||
const lancamentosData = mapLancamentosData(lancamentoRows);
|
||||
const transactionData = mapTransactionsData(transactionRows);
|
||||
|
||||
const {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
payerFilterOptions,
|
||||
categoryFilterOptions,
|
||||
accountCardFilterOptions,
|
||||
} = buildOptionSets({
|
||||
...sluggedFilters,
|
||||
pagadorRows: filterSources.pagadorRows,
|
||||
payerRows: filterSources.payerRows,
|
||||
limitCartaoId: card.id,
|
||||
});
|
||||
|
||||
const accountOptions = filterSources.contaRows.map((conta: Conta) => ({
|
||||
id: conta.id,
|
||||
name: conta.name ?? "Conta",
|
||||
logo: conta.logo ?? null,
|
||||
}));
|
||||
const cardDialogAccounts = filterSources.accountRows.map(
|
||||
(financialAccount: FinancialAccount) => ({
|
||||
id: financialAccount.id,
|
||||
name: financialAccount.name ?? "FinancialAccount",
|
||||
logo: financialAccount.logo ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
const contaName =
|
||||
filterSources.contaRows.find((conta: Conta) => conta.id === card.contaId)
|
||||
?.name ?? "Conta";
|
||||
const accountName =
|
||||
filterSources.accountRows.find(
|
||||
(financialAccount: FinancialAccount) =>
|
||||
financialAccount.id === card.accountId,
|
||||
)?.name ?? "FinancialAccount";
|
||||
|
||||
const cardDialogData: Card = {
|
||||
id: card.id,
|
||||
@@ -125,9 +129,9 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
card.limit !== null && card.limit !== undefined
|
||||
? Number(card.limit)
|
||||
: null,
|
||||
contaId: card.contaId,
|
||||
contaName,
|
||||
limitInUse: null,
|
||||
accountId: card.accountId,
|
||||
accountName,
|
||||
limitInUse: 0,
|
||||
limitAvailable: null,
|
||||
};
|
||||
|
||||
@@ -145,7 +149,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
|
||||
<section className="flex flex-col gap-4">
|
||||
<InvoiceSummaryCard
|
||||
cartaoId={card.id}
|
||||
cardId={card.id}
|
||||
period={selectedPeriod}
|
||||
cardName={card.name}
|
||||
cardBrand={card.brand ?? null}
|
||||
@@ -163,7 +167,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
mode="update"
|
||||
card={cardDialogData}
|
||||
logoOptions={logoOptions}
|
||||
accounts={accountOptions}
|
||||
accounts={cardDialogAccounts}
|
||||
trigger={
|
||||
<Button
|
||||
type="button"
|
||||
@@ -183,24 +187,24 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
<section className="flex flex-col gap-4">
|
||||
<LancamentosSection
|
||||
currentUserId={userId}
|
||||
lancamentos={lancamentosData}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
pagadorFilterOptions={pagadorFilterOptions}
|
||||
categoriaFilterOptions={categoriaFilterOptions}
|
||||
contaCartaoFilterOptions={contaCartaoFilterOptions}
|
||||
transactions={transactionData}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
payerFilterOptions={payerFilterOptions}
|
||||
categoryFilterOptions={categoryFilterOptions}
|
||||
accountCardFilterOptions={accountCardFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
estabelecimentos={estabelecimentos}
|
||||
allowCreate
|
||||
noteAsColumn={userPreferences?.extratoNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.lancamentosColumnOrder ?? null}
|
||||
defaultCartaoId={card.id}
|
||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||
defaultCardId={card.id}
|
||||
defaultPaymentMethod="Cartão de crédito"
|
||||
lockCartaoSelection
|
||||
lockCardSelection
|
||||
lockPaymentMethod
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -2,14 +2,14 @@ import { notFound } from "next/navigation";
|
||||
import { CategoryDetailHeader } from "@/features/categories/components/category-detail-header";
|
||||
import { fetchCategoryDetails } from "@/features/dashboard/categories/category-details-queries";
|
||||
import { fetchUserPreferences } from "@/features/settings/queries";
|
||||
import { LancamentosPage } from "@/features/transactions/components/page/transactions-page";
|
||||
import { TransactionsPage } from "@/features/transactions/components/page/transactions-page";
|
||||
import {
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
import {
|
||||
fetchLancamentoFilterSources,
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
} from "@/features/transactions/queries";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
@@ -42,7 +42,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
const [detail, filterSources, estabelecimentos, userPreferences] =
|
||||
await Promise.all([
|
||||
fetchCategoryDetails(userId, categoryId, selectedPeriod),
|
||||
fetchLancamentoFilterSources(userId),
|
||||
fetchTransactionFilterSources(userId),
|
||||
fetchRecentEstablishments(userId),
|
||||
fetchUserPreferences(userId),
|
||||
]);
|
||||
@@ -53,18 +53,18 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
|
||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||
const {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
pagadorFilterOptions,
|
||||
categoriaFilterOptions,
|
||||
contaCartaoFilterOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
payerFilterOptions,
|
||||
categoryFilterOptions,
|
||||
accountCardFilterOptions,
|
||||
} = buildOptionSets({
|
||||
...sluggedFilters,
|
||||
pagadorRows: filterSources.pagadorRows,
|
||||
payerRows: filterSources.payerRows,
|
||||
});
|
||||
|
||||
const currentPeriodLabel = displayPeriod(detail.period);
|
||||
@@ -82,23 +82,23 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
percentageChange={detail.percentageChange}
|
||||
transactionCount={detail.transactions.length}
|
||||
/>
|
||||
<LancamentosPage
|
||||
<TransactionsPage
|
||||
currentUserId={userId}
|
||||
lancamentos={detail.transactions}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
pagadorFilterOptions={pagadorFilterOptions}
|
||||
categoriaFilterOptions={categoriaFilterOptions}
|
||||
contaCartaoFilterOptions={contaCartaoFilterOptions}
|
||||
transactions={detail.transactions}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
payerFilterOptions={payerFilterOptions}
|
||||
categoryFilterOptions={categoryFilterOptions}
|
||||
accountCardFilterOptions={accountCardFilterOptions}
|
||||
selectedPeriod={detail.period}
|
||||
estabelecimentos={estabelecimentos}
|
||||
allowCreate={true}
|
||||
noteAsColumn={userPreferences?.extratoNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.lancamentosColumnOrder ?? null}
|
||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -24,12 +24,12 @@ export default async function Page() {
|
||||
pendingItems={pendingItems}
|
||||
processedItems={processedItems}
|
||||
discardedItems={discardedItems}
|
||||
pagadorOptions={dialogData.pagadorOptions}
|
||||
splitPagadorOptions={dialogData.splitPagadorOptions}
|
||||
defaultPagadorId={dialogData.defaultPagadorId}
|
||||
contaOptions={dialogData.contaOptions}
|
||||
cartaoOptions={dialogData.cartaoOptions}
|
||||
categoriaOptions={dialogData.categoriaOptions}
|
||||
payerOptions={dialogData.payerOptions}
|
||||
splitPayerOptions={dialogData.splitPayerOptions}
|
||||
defaultPayerId={dialogData.defaultPayerId}
|
||||
accountOptions={dialogData.accountOptions}
|
||||
cardOptions={dialogData.cardOptions}
|
||||
categoryOptions={dialogData.categoryOptions}
|
||||
estabelecimentos={dialogData.estabelecimentos}
|
||||
appLogoMap={appLogoMap}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PreferencesForm } from "@/features/settings/components/preferences-form
|
||||
import { UpdateEmailForm } from "@/features/settings/components/update-email-form";
|
||||
import { UpdateNameForm } from "@/features/settings/components/update-name-form";
|
||||
import { UpdatePasswordForm } from "@/features/settings/components/update-password-form";
|
||||
import { fetchAjustesPageData } from "@/features/settings/queries";
|
||||
import { fetchSettingsPageData } from "@/features/settings/queries";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
import {
|
||||
Tabs,
|
||||
@@ -32,7 +32,7 @@ export default async function Page() {
|
||||
const userEmail = session.user.email || "";
|
||||
|
||||
const { authProvider, userPreferences, userApiTokens } =
|
||||
await fetchAjustesPageData(session.user.id);
|
||||
await fetchSettingsPageData(session.user.id);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
@@ -71,11 +71,11 @@ export default async function Page() {
|
||||
</p>
|
||||
</div>
|
||||
<PreferencesForm
|
||||
extratoNoteAsColumn={
|
||||
userPreferences?.extratoNoteAsColumn ?? false
|
||||
statementNoteAsColumn={
|
||||
userPreferences?.statementNoteAsColumn ?? false
|
||||
}
|
||||
lancamentosColumnOrder={
|
||||
userPreferences?.lancamentosColumnOrder ?? null
|
||||
transactionsColumnOrder={
|
||||
userPreferences?.transactionsColumnOrder ?? null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { categorias, contas, lancamentos, pagadores } from "@/db/schema";
|
||||
import {
|
||||
categories,
|
||||
financialAccounts,
|
||||
payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import {
|
||||
INITIAL_BALANCE_CATEGORY_NAME,
|
||||
INITIAL_BALANCE_CONDITION,
|
||||
@@ -17,7 +22,7 @@ import {
|
||||
} from "@/shared/lib/actions/helpers";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { noteSchema, uuidSchema } from "@/shared/lib/schemas/common";
|
||||
import {
|
||||
TRANSFER_CATEGORY_NAME,
|
||||
@@ -67,10 +72,10 @@ const accountBaseSchema = z.object({
|
||||
|
||||
const createAccountSchema = accountBaseSchema;
|
||||
const updateAccountSchema = accountBaseSchema.extend({
|
||||
id: uuidSchema("Conta"),
|
||||
id: uuidSchema("FinancialAccount"),
|
||||
});
|
||||
const deleteAccountSchema = z.object({
|
||||
id: uuidSchema("Conta"),
|
||||
id: uuidSchema("FinancialAccount"),
|
||||
});
|
||||
|
||||
type AccountCreateInput = z.infer<typeof createAccountSchema>;
|
||||
@@ -91,7 +96,7 @@ export async function createAccountAction(
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const [createdAccount] = await tx
|
||||
.insert(contas)
|
||||
.insert(financialAccounts)
|
||||
.values({
|
||||
name: data.name,
|
||||
accountType: data.accountType,
|
||||
@@ -103,7 +108,7 @@ export async function createAccountAction(
|
||||
excludeInitialBalanceFromIncome: data.excludeInitialBalanceFromIncome,
|
||||
userId: user.id,
|
||||
})
|
||||
.returning({ id: contas.id, name: contas.name });
|
||||
.returning({ id: financialAccounts.id, name: financialAccounts.name });
|
||||
|
||||
if (!createdAccount) {
|
||||
throw new Error("Não foi possível criar a conta.");
|
||||
@@ -114,37 +119,37 @@ export async function createAccountAction(
|
||||
}
|
||||
|
||||
const [category, adminPagador] = await Promise.all([
|
||||
tx.query.categorias.findFirst({
|
||||
tx.query.categories.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(categorias.userId, user.id),
|
||||
eq(categorias.name, INITIAL_BALANCE_CATEGORY_NAME),
|
||||
eq(categories.userId, user.id),
|
||||
eq(categories.name, INITIAL_BALANCE_CATEGORY_NAME),
|
||||
),
|
||||
}),
|
||||
tx.query.pagadores.findFirst({
|
||||
tx.query.payers.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(pagadores.userId, user.id),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(payers.userId, user.id),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!category) {
|
||||
throw new Error(
|
||||
'Categoria "Saldo inicial" não encontrada. Crie-a antes de definir um saldo inicial.',
|
||||
'Category "Saldo inicial" não encontrada. Crie-a antes de definir um saldo inicial.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!adminPagador) {
|
||||
throw new Error(
|
||||
"Pagador com papel administrador não encontrado. Crie um pagador admin antes de definir um saldo inicial.",
|
||||
"Payer com papel administrador não encontrado. Crie um pagador admin antes de definir um saldo inicial.",
|
||||
);
|
||||
}
|
||||
|
||||
const { date, period } = getTodayInfo();
|
||||
|
||||
await tx.insert(lancamentos).values({
|
||||
await tx.insert(transactions).values({
|
||||
condition: INITIAL_BALANCE_CONDITION,
|
||||
name: `Saldo inicial - ${createdAccount.name}`,
|
||||
paymentMethod: INITIAL_BALANCE_PAYMENT_METHOD,
|
||||
@@ -155,17 +160,17 @@ export async function createAccountAction(
|
||||
period,
|
||||
isSettled: true,
|
||||
userId: user.id,
|
||||
contaId: createdAccount.id,
|
||||
categoriaId: category.id,
|
||||
pagadorId: adminPagador.id,
|
||||
accountId: createdAccount.id,
|
||||
categoryId: category.id,
|
||||
payerId: adminPagador.id,
|
||||
});
|
||||
});
|
||||
|
||||
revalidateForEntity("contas");
|
||||
revalidateForEntity("accounts");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Conta criada com sucesso.",
|
||||
message: "FinancialAccount criada com sucesso.",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
@@ -182,7 +187,7 @@ export async function updateAccountAction(
|
||||
const logoFile = normalizeFilePath(data.logo);
|
||||
|
||||
const [updated] = await db
|
||||
.update(contas)
|
||||
.update(financialAccounts)
|
||||
.set({
|
||||
name: data.name,
|
||||
accountType: data.accountType,
|
||||
@@ -193,21 +198,26 @@ export async function updateAccountAction(
|
||||
excludeFromBalance: data.excludeFromBalance,
|
||||
excludeInitialBalanceFromIncome: data.excludeInitialBalanceFromIncome,
|
||||
})
|
||||
.where(and(eq(contas.id, data.id), eq(contas.userId, user.id)))
|
||||
.where(
|
||||
and(
|
||||
eq(financialAccounts.id, data.id),
|
||||
eq(financialAccounts.userId, user.id),
|
||||
),
|
||||
)
|
||||
.returning();
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Conta não encontrada.",
|
||||
error: "FinancialAccount não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("contas");
|
||||
revalidateForEntity("accounts");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Conta atualizada com sucesso.",
|
||||
message: "FinancialAccount atualizada com sucesso.",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
@@ -222,22 +232,27 @@ export async function deleteAccountAction(
|
||||
const data = deleteAccountSchema.parse(input);
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(contas)
|
||||
.where(and(eq(contas.id, data.id), eq(contas.userId, user.id)))
|
||||
.returning({ id: contas.id });
|
||||
.delete(financialAccounts)
|
||||
.where(
|
||||
and(
|
||||
eq(financialAccounts.id, data.id),
|
||||
eq(financialAccounts.userId, user.id),
|
||||
),
|
||||
)
|
||||
.returning({ id: financialAccounts.id });
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Conta não encontrada.",
|
||||
error: "FinancialAccount não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("contas");
|
||||
revalidateForEntity("accounts");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Conta removida com sucesso.",
|
||||
message: "FinancialAccount removida com sucesso.",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
@@ -246,8 +261,8 @@ export async function deleteAccountAction(
|
||||
|
||||
// Transfer between accounts
|
||||
const transferSchema = z.object({
|
||||
fromAccountId: uuidSchema("Conta de origem"),
|
||||
toAccountId: uuidSchema("Conta de destino"),
|
||||
fromAccountId: uuidSchema("FinancialAccount de origem"),
|
||||
toAccountId: uuidSchema("FinancialAccount de destino"),
|
||||
amount: z
|
||||
.string()
|
||||
.trim()
|
||||
@@ -265,7 +280,7 @@ const transferSchema = z.object({
|
||||
.min(1, "Informe o período."),
|
||||
});
|
||||
|
||||
type TransferInput = z.infer<typeof transferSchema>;
|
||||
type TransferInput = z.input<typeof transferSchema>;
|
||||
|
||||
export async function transferBetweenAccountsAction(
|
||||
input: TransferInput,
|
||||
@@ -288,64 +303,64 @@ export async function transferBetweenAccountsAction(
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
// Verify both accounts exist and belong to the user
|
||||
const [fromAccount, toAccount] = await Promise.all([
|
||||
tx.query.contas.findFirst({
|
||||
tx.query.financialAccounts.findFirst({
|
||||
columns: { id: true, name: true },
|
||||
where: and(
|
||||
eq(contas.id, data.fromAccountId),
|
||||
eq(contas.userId, user.id),
|
||||
eq(financialAccounts.id, data.fromAccountId),
|
||||
eq(financialAccounts.userId, user.id),
|
||||
),
|
||||
}),
|
||||
tx.query.contas.findFirst({
|
||||
tx.query.financialAccounts.findFirst({
|
||||
columns: { id: true, name: true },
|
||||
where: and(
|
||||
eq(contas.id, data.toAccountId),
|
||||
eq(contas.userId, user.id),
|
||||
eq(financialAccounts.id, data.toAccountId),
|
||||
eq(financialAccounts.userId, user.id),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!fromAccount) {
|
||||
throw new Error("Conta de origem não encontrada.");
|
||||
throw new Error("FinancialAccount de origem não encontrada.");
|
||||
}
|
||||
|
||||
if (!toAccount) {
|
||||
throw new Error("Conta de destino não encontrada.");
|
||||
throw new Error("FinancialAccount de destino não encontrada.");
|
||||
}
|
||||
|
||||
// Get the transfer category
|
||||
const transferCategory = await tx.query.categorias.findFirst({
|
||||
const transferCategory = await tx.query.categories.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(categorias.userId, user.id),
|
||||
eq(categorias.name, TRANSFER_CATEGORY_NAME),
|
||||
eq(categories.userId, user.id),
|
||||
eq(categories.name, TRANSFER_CATEGORY_NAME),
|
||||
),
|
||||
});
|
||||
|
||||
if (!transferCategory) {
|
||||
throw new Error(
|
||||
`Categoria "${TRANSFER_CATEGORY_NAME}" não encontrada. Por favor, crie esta categoria antes de fazer transferências.`,
|
||||
`Category "${TRANSFER_CATEGORY_NAME}" não encontrada. Por favor, crie esta categoria antes de fazer transferências.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the admin payer
|
||||
const adminPagador = await tx.query.pagadores.findFirst({
|
||||
const adminPagador = await tx.query.payers.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(pagadores.userId, user.id),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(payers.userId, user.id),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
});
|
||||
|
||||
if (!adminPagador) {
|
||||
throw new Error(
|
||||
"Pagador administrador não encontrado. Por favor, crie um pagador admin.",
|
||||
"Payer administrador não encontrado. Por favor, crie um pagador admin.",
|
||||
);
|
||||
}
|
||||
|
||||
const transferNote = `de ${fromAccount.name} -> ${toAccount.name}`;
|
||||
|
||||
// Create outgoing transaction (transfer from source account)
|
||||
await tx.insert(lancamentos).values({
|
||||
await tx.insert(transactions).values({
|
||||
condition: TRANSFER_CONDITION,
|
||||
name: TRANSFER_ESTABLISHMENT_SAIDA,
|
||||
paymentMethod: TRANSFER_PAYMENT_METHOD,
|
||||
@@ -356,14 +371,14 @@ export async function transferBetweenAccountsAction(
|
||||
period: data.period,
|
||||
isSettled: true,
|
||||
userId: user.id,
|
||||
contaId: fromAccount.id,
|
||||
categoriaId: transferCategory.id,
|
||||
pagadorId: adminPagador.id,
|
||||
accountId: fromAccount.id,
|
||||
categoryId: transferCategory.id,
|
||||
payerId: adminPagador.id,
|
||||
transferId,
|
||||
});
|
||||
|
||||
// Create incoming transaction (transfer to destination account)
|
||||
await tx.insert(lancamentos).values({
|
||||
await tx.insert(transactions).values({
|
||||
condition: TRANSFER_CONDITION,
|
||||
name: TRANSFER_ESTABLISHMENT_ENTRADA,
|
||||
paymentMethod: TRANSFER_PAYMENT_METHOD,
|
||||
@@ -374,15 +389,15 @@ export async function transferBetweenAccountsAction(
|
||||
period: data.period,
|
||||
isSettled: true,
|
||||
userId: user.id,
|
||||
contaId: toAccount.id,
|
||||
categoriaId: transferCategory.id,
|
||||
pagadorId: adminPagador.id,
|
||||
accountId: toAccount.id,
|
||||
categoryId: transferCategory.id,
|
||||
payerId: adminPagador.id,
|
||||
transferId,
|
||||
});
|
||||
});
|
||||
|
||||
revalidateForEntity("contas");
|
||||
revalidateForEntity("lancamentos");
|
||||
revalidateForEntity("accounts");
|
||||
revalidateForEntity("transactions");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -33,10 +33,10 @@ import { AccountFormFields } from "./account-form-fields";
|
||||
import type { Account, AccountFormValues } from "./types";
|
||||
|
||||
const DEFAULT_ACCOUNT_TYPES = [
|
||||
"Conta Corrente",
|
||||
"Conta Poupança",
|
||||
"FinancialAccount Corrente",
|
||||
"FinancialAccount Poupança",
|
||||
"Carteira Digital",
|
||||
"Conta Investimento",
|
||||
"FinancialAccount Investimento",
|
||||
"Pré-Pago | VR/VA",
|
||||
] as const;
|
||||
|
||||
@@ -167,7 +167,7 @@ export function AccountDialog({
|
||||
const accountId = account?.id;
|
||||
|
||||
if (mode === "update" && !accountId) {
|
||||
const message = "Conta inválida.";
|
||||
const message = "FinancialAccount inválida.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
|
||||
@@ -110,10 +110,7 @@ export function AccountFormFields({
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="exclude-from-balance"
|
||||
checked={
|
||||
values.excludeFromBalance === true ||
|
||||
values.excludeFromBalance === "true"
|
||||
}
|
||||
checked={Boolean(values.excludeFromBalance)}
|
||||
onCheckedChange={(checked) =>
|
||||
onChange("excludeFromBalance", checked ? "true" : "false")
|
||||
}
|
||||
@@ -130,10 +127,7 @@ export function AccountFormFields({
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="exclude-initial-balance-from-income"
|
||||
checked={
|
||||
values.excludeInitialBalanceFromIncome === true ||
|
||||
values.excludeInitialBalanceFromIncome === "true"
|
||||
}
|
||||
checked={Boolean(values.excludeInitialBalanceFromIncome)}
|
||||
onCheckedChange={(checked) =>
|
||||
onChange(
|
||||
"excludeInitialBalanceFromIncome",
|
||||
|
||||
@@ -142,7 +142,7 @@ export function AccountStatementCard({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Informações da Conta */}
|
||||
{/* Informações da FinancialAccount */}
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 pt-2 border-t border-border/60 border-dashed">
|
||||
<DetailItem
|
||||
label="Tipo da conta"
|
||||
|
||||
@@ -115,9 +115,7 @@ export function AccountsPage({
|
||||
<EmptyState
|
||||
media={<RiBankLine className="size-6 text-primary" />}
|
||||
title={
|
||||
isArchived
|
||||
? "Nenhuma conta arquivada"
|
||||
: "Nenhuma conta cadastrada"
|
||||
isArchived ? "Nenhuma conta archived" : "Nenhuma conta cadastrada"
|
||||
}
|
||||
description={
|
||||
isArchived
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { transferBetweenAccountsAction } from "@/features/accounts/actions";
|
||||
import type { AccountData } from "@/features/accounts/queries";
|
||||
import { ContaCartaoSelectContent } from "@/features/transactions/components/select-items";
|
||||
import { AccountCardSelectContent } from "@/features/transactions/components/select-items";
|
||||
import { PeriodPicker } from "@/shared/components/period-picker";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { CurrencyInput } from "@/shared/components/ui/currency-input";
|
||||
@@ -157,12 +157,12 @@ export function TransferDialog({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 sm:col-span-2">
|
||||
<Label htmlFor="from-account">Conta de origem</Label>
|
||||
<Label htmlFor="from-account">FinancialAccount de origem</Label>
|
||||
<Select value={fromAccountId} disabled>
|
||||
<SelectTrigger id="from-account" className="w-full">
|
||||
<SelectValue>
|
||||
{fromAccount && (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={fromAccount.name}
|
||||
logo={fromAccount.logo}
|
||||
isCartao={false}
|
||||
@@ -173,7 +173,7 @@ export function TransferDialog({
|
||||
<SelectContent>
|
||||
{fromAccount && (
|
||||
<SelectItem value={fromAccount.id}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={fromAccount.name}
|
||||
logo={fromAccount.logo}
|
||||
isCartao={false}
|
||||
@@ -185,7 +185,7 @@ export function TransferDialog({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 sm:col-span-2">
|
||||
<Label htmlFor="to-account">Conta de destino</Label>
|
||||
<Label htmlFor="to-account">FinancialAccount de destino</Label>
|
||||
{availableAccounts.length === 0 ? (
|
||||
<div className="rounded-md border border-border bg-muted p-3 text-sm text-muted-foreground">
|
||||
É necessário ter mais de uma conta cadastrada para realizar
|
||||
@@ -201,7 +201,7 @@ export function TransferDialog({
|
||||
(acc) => acc.id === toAccountId,
|
||||
);
|
||||
return selectedAccount ? (
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={selectedAccount.name}
|
||||
logo={selectedAccount.logo}
|
||||
isCartao={false}
|
||||
@@ -213,7 +213,7 @@ export function TransferDialog({
|
||||
<SelectContent className="w-full">
|
||||
{availableAccounts.map((account) => (
|
||||
<SelectItem key={account.id} value={account.id}>
|
||||
<ContaCartaoSelectContent
|
||||
<AccountCardSelectContent
|
||||
label={account.name}
|
||||
logo={account.logo}
|
||||
isCartao={false}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { and, eq, ilike, not, sql } from "drizzle-orm";
|
||||
import { contas, lancamentos, pagadores } from "@/db/schema";
|
||||
import { financialAccounts, payers, transactions } from "@/db/schema";
|
||||
import { INITIAL_BALANCE_NOTE } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { loadLogoOptions } from "@/shared/lib/logo/options";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
|
||||
export type AccountData = {
|
||||
id: string;
|
||||
@@ -20,58 +20,59 @@ export type AccountData = {
|
||||
|
||||
export async function fetchAccountsForUser(
|
||||
userId: string,
|
||||
): Promise<{ accounts: AccountData[]; logoOptions: LogoOption[] }> {
|
||||
): Promise<{ accounts: AccountData[]; logoOptions: string[] }> {
|
||||
const [accountRows, logoOptions] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
id: contas.id,
|
||||
name: contas.name,
|
||||
accountType: contas.accountType,
|
||||
status: contas.status,
|
||||
note: contas.note,
|
||||
logo: contas.logo,
|
||||
initialBalance: contas.initialBalance,
|
||||
excludeFromBalance: contas.excludeFromBalance,
|
||||
excludeInitialBalanceFromIncome: contas.excludeInitialBalanceFromIncome,
|
||||
id: financialAccounts.id,
|
||||
name: financialAccounts.name,
|
||||
accountType: financialAccounts.accountType,
|
||||
status: financialAccounts.status,
|
||||
note: financialAccounts.note,
|
||||
logo: financialAccounts.logo,
|
||||
initialBalance: financialAccounts.initialBalance,
|
||||
excludeFromBalance: financialAccounts.excludeFromBalance,
|
||||
excludeInitialBalanceFromIncome:
|
||||
financialAccounts.excludeInitialBalanceFromIncome,
|
||||
balanceMovements: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${lancamentos.amount}
|
||||
when ${transactions.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${transactions.amount}
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(contas)
|
||||
.from(financialAccounts)
|
||||
.leftJoin(
|
||||
lancamentos,
|
||||
transactions,
|
||||
and(
|
||||
eq(lancamentos.contaId, contas.id),
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.isSettled, true),
|
||||
eq(transactions.accountId, financialAccounts.id),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.isSettled, true),
|
||||
),
|
||||
)
|
||||
.leftJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.leftJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(contas.userId, userId),
|
||||
not(ilike(contas.status, "inativa")),
|
||||
sql`(${lancamentos.id} IS NULL OR ${pagadores.role} = ${PAGADOR_ROLE_ADMIN})`,
|
||||
eq(financialAccounts.userId, userId),
|
||||
not(ilike(financialAccounts.status, "inativa")),
|
||||
sql`(${transactions.id} IS NULL OR ${payers.role} = ${PAYER_ROLE_ADMIN})`,
|
||||
),
|
||||
)
|
||||
.groupBy(
|
||||
contas.id,
|
||||
contas.name,
|
||||
contas.accountType,
|
||||
contas.status,
|
||||
contas.note,
|
||||
contas.logo,
|
||||
contas.initialBalance,
|
||||
contas.excludeFromBalance,
|
||||
contas.excludeInitialBalanceFromIncome,
|
||||
financialAccounts.id,
|
||||
financialAccounts.name,
|
||||
financialAccounts.accountType,
|
||||
financialAccounts.status,
|
||||
financialAccounts.note,
|
||||
financialAccounts.logo,
|
||||
financialAccounts.initialBalance,
|
||||
financialAccounts.excludeFromBalance,
|
||||
financialAccounts.excludeInitialBalanceFromIncome,
|
||||
),
|
||||
loadLogoOptions(),
|
||||
]);
|
||||
@@ -94,60 +95,61 @@ export async function fetchAccountsForUser(
|
||||
return { accounts, logoOptions };
|
||||
}
|
||||
|
||||
export async function fetchInativosForUser(
|
||||
export async function fetchInactiveForUser(
|
||||
userId: string,
|
||||
): Promise<{ accounts: AccountData[]; logoOptions: LogoOption[] }> {
|
||||
): Promise<{ accounts: AccountData[]; logoOptions: string[] }> {
|
||||
const [accountRows, logoOptions] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
id: contas.id,
|
||||
name: contas.name,
|
||||
accountType: contas.accountType,
|
||||
status: contas.status,
|
||||
note: contas.note,
|
||||
logo: contas.logo,
|
||||
initialBalance: contas.initialBalance,
|
||||
excludeFromBalance: contas.excludeFromBalance,
|
||||
excludeInitialBalanceFromIncome: contas.excludeInitialBalanceFromIncome,
|
||||
id: financialAccounts.id,
|
||||
name: financialAccounts.name,
|
||||
accountType: financialAccounts.accountType,
|
||||
status: financialAccounts.status,
|
||||
note: financialAccounts.note,
|
||||
logo: financialAccounts.logo,
|
||||
initialBalance: financialAccounts.initialBalance,
|
||||
excludeFromBalance: financialAccounts.excludeFromBalance,
|
||||
excludeInitialBalanceFromIncome:
|
||||
financialAccounts.excludeInitialBalanceFromIncome,
|
||||
balanceMovements: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${lancamentos.amount}
|
||||
when ${transactions.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${transactions.amount}
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(contas)
|
||||
.from(financialAccounts)
|
||||
.leftJoin(
|
||||
lancamentos,
|
||||
transactions,
|
||||
and(
|
||||
eq(lancamentos.contaId, contas.id),
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.isSettled, true),
|
||||
eq(transactions.accountId, financialAccounts.id),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.isSettled, true),
|
||||
),
|
||||
)
|
||||
.leftJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.leftJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(contas.userId, userId),
|
||||
ilike(contas.status, "inativa"),
|
||||
sql`(${lancamentos.id} IS NULL OR ${pagadores.role} = ${PAGADOR_ROLE_ADMIN})`,
|
||||
eq(financialAccounts.userId, userId),
|
||||
ilike(financialAccounts.status, "inativa"),
|
||||
sql`(${transactions.id} IS NULL OR ${payers.role} = ${PAYER_ROLE_ADMIN})`,
|
||||
),
|
||||
)
|
||||
.groupBy(
|
||||
contas.id,
|
||||
contas.name,
|
||||
contas.accountType,
|
||||
contas.status,
|
||||
contas.note,
|
||||
contas.logo,
|
||||
contas.initialBalance,
|
||||
contas.excludeFromBalance,
|
||||
contas.excludeInitialBalanceFromIncome,
|
||||
financialAccounts.id,
|
||||
financialAccounts.name,
|
||||
financialAccounts.accountType,
|
||||
financialAccounts.status,
|
||||
financialAccounts.note,
|
||||
financialAccounts.logo,
|
||||
financialAccounts.initialBalance,
|
||||
financialAccounts.excludeFromBalance,
|
||||
financialAccounts.excludeInitialBalanceFromIncome,
|
||||
),
|
||||
loadLogoOptions(),
|
||||
]);
|
||||
@@ -173,11 +175,11 @@ export async function fetchInativosForUser(
|
||||
export async function fetchAllAccountsForUser(userId: string): Promise<{
|
||||
activeAccounts: AccountData[];
|
||||
archivedAccounts: AccountData[];
|
||||
logoOptions: LogoOption[];
|
||||
logoOptions: string[];
|
||||
}> {
|
||||
const [activeData, archivedData] = await Promise.all([
|
||||
fetchAccountsForUser(userId),
|
||||
fetchInativosForUser(userId),
|
||||
fetchInactiveForUser(userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { and, desc, eq, lt, type SQL, sql } from "drizzle-orm";
|
||||
import { contas, lancamentos, pagadores } from "@/db/schema";
|
||||
import { financialAccounts, payers, transactions } from "@/db/schema";
|
||||
import { INITIAL_BALANCE_NOTE } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
|
||||
export type AccountSummaryData = {
|
||||
openingBalance: number;
|
||||
@@ -11,8 +11,8 @@ export type AccountSummaryData = {
|
||||
totalExpenses: number;
|
||||
};
|
||||
|
||||
export async function fetchAccountData(userId: string, contaId: string) {
|
||||
const account = await db.query.contas.findFirst({
|
||||
export async function fetchAccountData(userId: string, accountId: string) {
|
||||
const account = await db.query.financialAccounts.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -22,7 +22,10 @@ export async function fetchAccountData(userId: string, contaId: string) {
|
||||
logo: true,
|
||||
note: true,
|
||||
},
|
||||
where: and(eq(contas.id, contaId), eq(contas.userId, userId)),
|
||||
where: and(
|
||||
eq(financialAccounts.id, accountId),
|
||||
eq(financialAccounts.userId, userId),
|
||||
),
|
||||
});
|
||||
|
||||
return account;
|
||||
@@ -30,7 +33,7 @@ export async function fetchAccountData(userId: string, contaId: string) {
|
||||
|
||||
export async function fetchAccountSummary(
|
||||
userId: string,
|
||||
contaId: string,
|
||||
accountId: string,
|
||||
selectedPeriod: string,
|
||||
): Promise<AccountSummaryData> {
|
||||
const [periodSummary] = await db
|
||||
@@ -39,8 +42,8 @@ export async function fetchAccountSummary(
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${lancamentos.amount}
|
||||
when ${transactions.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${transactions.amount}
|
||||
end
|
||||
),
|
||||
0
|
||||
@@ -50,8 +53,8 @@ export async function fetchAccountSummary(
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
when ${lancamentos.transactionType} = 'Receita' then ${lancamentos.amount}
|
||||
when ${transactions.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
when ${transactions.transactionType} = 'Receita' then ${transactions.amount}
|
||||
else 0
|
||||
end
|
||||
),
|
||||
@@ -62,8 +65,8 @@ export async function fetchAccountSummary(
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
when ${lancamentos.transactionType} = 'Despesa' then ${lancamentos.amount}
|
||||
when ${transactions.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
when ${transactions.transactionType} = 'Despesa' then ${transactions.amount}
|
||||
else 0
|
||||
end
|
||||
),
|
||||
@@ -71,15 +74,15 @@ export async function fetchAccountSummary(
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.from(transactions)
|
||||
.innerJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.contaId, contaId),
|
||||
eq(lancamentos.period, selectedPeriod),
|
||||
eq(lancamentos.isSettled, true),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.accountId, accountId),
|
||||
eq(transactions.period, selectedPeriod),
|
||||
eq(transactions.isSettled, true),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -89,27 +92,27 @@ export async function fetchAccountSummary(
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${lancamentos.amount}
|
||||
when ${transactions.note} = ${INITIAL_BALANCE_NOTE} then 0
|
||||
else ${transactions.amount}
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.from(transactions)
|
||||
.innerJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.contaId, contaId),
|
||||
lt(lancamentos.period, selectedPeriod),
|
||||
eq(lancamentos.isSettled, true),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.accountId, accountId),
|
||||
lt(transactions.period, selectedPeriod),
|
||||
eq(transactions.isSettled, true),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
);
|
||||
|
||||
const account = await fetchAccountData(userId, contaId);
|
||||
const account = await fetchAccountData(userId, accountId);
|
||||
if (!account) {
|
||||
throw new Error("Account not found");
|
||||
}
|
||||
@@ -135,17 +138,17 @@ export async function fetchAccountLancamentos(
|
||||
settledOnly = true,
|
||||
) {
|
||||
const allFilters = settledOnly
|
||||
? [...filters, eq(lancamentos.isSettled, true)]
|
||||
? [...filters, eq(transactions.isSettled, true)]
|
||||
: filters;
|
||||
|
||||
return db.query.lancamentos.findMany({
|
||||
return db.query.transactions.findMany({
|
||||
where: and(...allFilters),
|
||||
with: {
|
||||
pagador: true,
|
||||
conta: true,
|
||||
cartao: true,
|
||||
categoria: true,
|
||||
payer: true,
|
||||
financialAccount: true,
|
||||
card: true,
|
||||
category: true,
|
||||
},
|
||||
orderBy: desc(lancamentos.purchaseDate),
|
||||
orderBy: desc(transactions.purchaseDate),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function SignupForm({ className, ...props }: DivProps) {
|
||||
},
|
||||
onSuccess: () => {
|
||||
setLoadingEmail(false);
|
||||
toast.success("Conta criada com sucesso!");
|
||||
toast.success("FinancialAccount criada com sucesso!");
|
||||
router.replace("/dashboard");
|
||||
},
|
||||
onError: (ctx) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { and, eq, ne } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { categorias, orcamentos } from "@/db/schema";
|
||||
import { budgets, categories } from "@/db/schema";
|
||||
import {
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { getPreviousPeriod } from "@/shared/utils/period";
|
||||
|
||||
const budgetBaseSchema = z.object({
|
||||
categoriaId: uuidSchema("Categoria"),
|
||||
categoryId: uuidSchema("Category"),
|
||||
period: periodSchema,
|
||||
amount: z
|
||||
.string({ message: "Informe o valor limite." })
|
||||
@@ -48,21 +48,21 @@ type BudgetCreateInput = z.input<typeof createBudgetSchema>;
|
||||
type BudgetUpdateInput = z.input<typeof updateBudgetSchema>;
|
||||
type BudgetDeleteInput = z.input<typeof deleteBudgetSchema>;
|
||||
type BudgetCopyRow = {
|
||||
categoriaId: string | null;
|
||||
categoryId: string | null;
|
||||
amount: unknown;
|
||||
};
|
||||
|
||||
const ensureCategory = async (userId: string, categoriaId: string) => {
|
||||
const category = await db.query.categorias.findFirst({
|
||||
const ensureCategory = async (userId: string, categoryId: string) => {
|
||||
const category = await db.query.categories.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
type: true,
|
||||
},
|
||||
where: and(eq(categorias.id, categoriaId), eq(categorias.userId, userId)),
|
||||
where: and(eq(categories.id, categoryId), eq(categories.userId, userId)),
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
throw new Error("Categoria não encontrada.");
|
||||
throw new Error("Category não encontrada.");
|
||||
}
|
||||
|
||||
if (category.type !== "despesa") {
|
||||
@@ -77,15 +77,15 @@ export async function createBudgetAction(
|
||||
const user = await getUser();
|
||||
const data = createBudgetSchema.parse(input);
|
||||
|
||||
await ensureCategory(user.id, data.categoriaId);
|
||||
await ensureCategory(user.id, data.categoryId);
|
||||
|
||||
const duplicateConditions = [
|
||||
eq(orcamentos.userId, user.id),
|
||||
eq(orcamentos.period, data.period),
|
||||
eq(orcamentos.categoriaId, data.categoriaId),
|
||||
eq(budgets.userId, user.id),
|
||||
eq(budgets.period, data.period),
|
||||
eq(budgets.categoryId, data.categoryId),
|
||||
] as const;
|
||||
|
||||
const duplicate = await db.query.orcamentos.findFirst({
|
||||
const duplicate = await db.query.budgets.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(...duplicateConditions),
|
||||
});
|
||||
@@ -98,14 +98,14 @@ export async function createBudgetAction(
|
||||
};
|
||||
}
|
||||
|
||||
await db.insert(orcamentos).values({
|
||||
await db.insert(budgets).values({
|
||||
amount: formatDecimalForDbRequired(data.amount),
|
||||
period: data.period,
|
||||
userId: user.id,
|
||||
categoriaId: data.categoriaId,
|
||||
categoryId: data.categoryId,
|
||||
});
|
||||
|
||||
revalidateForEntity("orcamentos");
|
||||
revalidateForEntity("budgets");
|
||||
|
||||
return { success: true, message: "Orçamento criado com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -120,16 +120,16 @@ export async function updateBudgetAction(
|
||||
const user = await getUser();
|
||||
const data = updateBudgetSchema.parse(input);
|
||||
|
||||
await ensureCategory(user.id, data.categoriaId);
|
||||
await ensureCategory(user.id, data.categoryId);
|
||||
|
||||
const duplicateConditions = [
|
||||
eq(orcamentos.userId, user.id),
|
||||
eq(orcamentos.period, data.period),
|
||||
eq(orcamentos.categoriaId, data.categoriaId),
|
||||
ne(orcamentos.id, data.id),
|
||||
eq(budgets.userId, user.id),
|
||||
eq(budgets.period, data.period),
|
||||
eq(budgets.categoryId, data.categoryId),
|
||||
ne(budgets.id, data.id),
|
||||
] as const;
|
||||
|
||||
const duplicate = await db.query.orcamentos.findFirst({
|
||||
const duplicate = await db.query.budgets.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(...duplicateConditions),
|
||||
});
|
||||
@@ -143,14 +143,14 @@ export async function updateBudgetAction(
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(orcamentos)
|
||||
.update(budgets)
|
||||
.set({
|
||||
amount: formatDecimalForDbRequired(data.amount),
|
||||
period: data.period,
|
||||
categoriaId: data.categoriaId,
|
||||
categoryId: data.categoryId,
|
||||
})
|
||||
.where(and(eq(orcamentos.id, data.id), eq(orcamentos.userId, user.id)))
|
||||
.returning({ id: orcamentos.id });
|
||||
.where(and(eq(budgets.id, data.id), eq(budgets.userId, user.id)))
|
||||
.returning({ id: budgets.id });
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
@@ -159,7 +159,7 @@ export async function updateBudgetAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("orcamentos");
|
||||
revalidateForEntity("budgets");
|
||||
|
||||
return { success: true, message: "Orçamento atualizado com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -175,9 +175,9 @@ export async function deleteBudgetAction(
|
||||
const data = deleteBudgetSchema.parse(input);
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(orcamentos)
|
||||
.where(and(eq(orcamentos.id, data.id), eq(orcamentos.userId, user.id)))
|
||||
.returning({ id: orcamentos.id });
|
||||
.delete(budgets)
|
||||
.where(and(eq(budgets.id, data.id), eq(budgets.userId, user.id)))
|
||||
.returning({ id: budgets.id });
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
@@ -186,7 +186,7 @@ export async function deleteBudgetAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("orcamentos");
|
||||
revalidateForEntity("budgets");
|
||||
|
||||
return { success: true, message: "Orçamento removido com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -211,10 +211,10 @@ export async function duplicatePreviousMonthBudgetsAction(
|
||||
const previousPeriod = getPreviousPeriod(data.period);
|
||||
|
||||
// Buscar orçamentos do mês anterior
|
||||
const previousBudgets = (await db.query.orcamentos.findMany({
|
||||
const previousBudgets = (await db.query.budgets.findMany({
|
||||
where: and(
|
||||
eq(orcamentos.userId, user.id),
|
||||
eq(orcamentos.period, previousPeriod),
|
||||
eq(budgets.userId, user.id),
|
||||
eq(budgets.period, previousPeriod),
|
||||
),
|
||||
})) as BudgetCopyRow[];
|
||||
|
||||
@@ -226,41 +226,38 @@ export async function duplicatePreviousMonthBudgetsAction(
|
||||
}
|
||||
|
||||
// Buscar orçamentos existentes do mês atual
|
||||
const currentBudgets = (await db.query.orcamentos.findMany({
|
||||
where: and(
|
||||
eq(orcamentos.userId, user.id),
|
||||
eq(orcamentos.period, data.period),
|
||||
),
|
||||
const currentBudgets = (await db.query.budgets.findMany({
|
||||
where: and(eq(budgets.userId, user.id), eq(budgets.period, data.period)),
|
||||
})) as BudgetCopyRow[];
|
||||
|
||||
// Filtrar para evitar duplicatas
|
||||
const existingCategoryIds = new Set(
|
||||
currentBudgets.map((b) => b.categoriaId),
|
||||
currentBudgets.map((b) => b.categoryId),
|
||||
);
|
||||
|
||||
const budgetsToCopy = previousBudgets.filter(
|
||||
(b) => b.categoriaId && !existingCategoryIds.has(b.categoriaId),
|
||||
(b) => b.categoryId && !existingCategoryIds.has(b.categoryId),
|
||||
);
|
||||
|
||||
if (budgetsToCopy.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"Todas as categorias do mês anterior já possuem orçamento neste mês.",
|
||||
"Todas as categories do mês anterior já possuem orçamento neste mês.",
|
||||
};
|
||||
}
|
||||
|
||||
// Inserir novos orçamentos
|
||||
await db.insert(orcamentos).values(
|
||||
await db.insert(budgets).values(
|
||||
budgetsToCopy.map((b) => ({
|
||||
amount: b.amount,
|
||||
amount: b.amount as string,
|
||||
period: data.period,
|
||||
userId: user.id,
|
||||
categoriaId: b.categoriaId as string,
|
||||
categoryId: b.categoryId as string,
|
||||
})),
|
||||
);
|
||||
|
||||
revalidateForEntity("orcamentos");
|
||||
revalidateForEntity("budgets");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -30,7 +30,7 @@ const buildUsagePercent = (spent: number, limit: number) => {
|
||||
};
|
||||
|
||||
const formatCategoryName = (budget: Budget) =>
|
||||
budget.category?.name ?? "Categoria removida";
|
||||
budget.category?.name ?? "Category removida";
|
||||
|
||||
export function BudgetCard({
|
||||
budget,
|
||||
|
||||
@@ -50,7 +50,7 @@ const buildInitialValues = ({
|
||||
budget?: Budget;
|
||||
defaultPeriod: string;
|
||||
}): BudgetFormValues => ({
|
||||
categoriaId: budget?.category?.id ?? "",
|
||||
categoryId: budget?.category?.id ?? "",
|
||||
period: budget?.period ?? defaultPeriod,
|
||||
amount: budget ? (Math.round(budget.amount * 100) / 100).toFixed(2) : "",
|
||||
});
|
||||
@@ -113,7 +113,7 @@ export function BudgetDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
if (formState.categoriaId.length === 0) {
|
||||
if (formState.categoryId.length === 0) {
|
||||
const message = "Selecione uma categoria.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
@@ -135,7 +135,7 @@ export function BudgetDialog({
|
||||
}
|
||||
|
||||
const payload = {
|
||||
categoriaId: formState.categoriaId,
|
||||
categoryId: formState.categoryId,
|
||||
period: formState.period,
|
||||
amount: formState.amount,
|
||||
};
|
||||
@@ -207,10 +207,10 @@ export function BudgetDialog({
|
||||
) : (
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="budget-category">Categoria</Label>
|
||||
<Label htmlFor="budget-category">Category</Label>
|
||||
<Select
|
||||
value={formState.categoriaId}
|
||||
onValueChange={(value) => updateField("categoriaId", value)}
|
||||
value={formState.categoryId}
|
||||
onValueChange={(value) => updateField("categoryId", value)}
|
||||
>
|
||||
<SelectTrigger id="budget-category" className="w-full">
|
||||
<SelectValue placeholder="Selecione uma categoria" />
|
||||
|
||||
@@ -14,7 +14,7 @@ export type Budget = {
|
||||
};
|
||||
|
||||
export type BudgetFormValues = {
|
||||
categoriaId: string;
|
||||
categoryId: string;
|
||||
period: string;
|
||||
amount: string;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
|
||||
import {
|
||||
categorias,
|
||||
lancamentos,
|
||||
type Orcamento,
|
||||
orcamentos,
|
||||
pagadores,
|
||||
type Budget,
|
||||
budgets,
|
||||
categories,
|
||||
payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
|
||||
const toNumber = (value: string | number | null | undefined) => {
|
||||
if (typeof value === "number") return value;
|
||||
@@ -46,28 +46,28 @@ export async function fetchBudgetsForUser(
|
||||
categoriesOptions: CategoryOption[];
|
||||
}> {
|
||||
const [budgetRows, categoryRows] = await Promise.all([
|
||||
db.query.orcamentos.findMany({
|
||||
db.query.budgets.findMany({
|
||||
where: and(
|
||||
eq(orcamentos.userId, userId),
|
||||
eq(orcamentos.period, selectedPeriod),
|
||||
eq(budgets.userId, userId),
|
||||
eq(budgets.period, selectedPeriod),
|
||||
),
|
||||
with: {
|
||||
categoria: true,
|
||||
category: true,
|
||||
},
|
||||
}),
|
||||
db.query.categorias.findMany({
|
||||
db.query.categories.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
icon: true,
|
||||
},
|
||||
where: and(eq(categorias.userId, userId), eq(categorias.type, "despesa")),
|
||||
orderBy: asc(categorias.name),
|
||||
where: and(eq(categories.userId, userId), eq(categories.type, "despesa")),
|
||||
orderBy: asc(categories.name),
|
||||
}),
|
||||
]);
|
||||
|
||||
const categoryIds = budgetRows
|
||||
.map((budget: Orcamento) => budget.categoriaId)
|
||||
.map((budget) => budget.categoryId)
|
||||
.filter((id: string | null): id is string => Boolean(id));
|
||||
|
||||
let totalsByCategory = new Map<string, number>();
|
||||
@@ -75,50 +75,48 @@ export async function fetchBudgetsForUser(
|
||||
if (categoryIds.length > 0) {
|
||||
const totals = await db
|
||||
.select({
|
||||
categoriaId: lancamentos.categoriaId,
|
||||
totalAmount: sum(lancamentos.amount).as("totalAmount"),
|
||||
categoryId: transactions.categoryId,
|
||||
totalAmount: sum(transactions.amount).as("totalAmount"),
|
||||
})
|
||||
.from(lancamentos)
|
||||
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.from(transactions)
|
||||
.innerJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.period, selectedPeriod),
|
||||
eq(lancamentos.transactionType, "Despesa"),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
inArray(lancamentos.categoriaId, categoryIds),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.period, selectedPeriod),
|
||||
eq(transactions.transactionType, "Despesa"),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
inArray(transactions.categoryId, categoryIds),
|
||||
or(
|
||||
isNull(lancamentos.note),
|
||||
sql`${lancamentos.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
|
||||
isNull(transactions.note),
|
||||
sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
|
||||
),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.categoriaId);
|
||||
.groupBy(transactions.categoryId);
|
||||
|
||||
totalsByCategory = new Map(
|
||||
totals.map(
|
||||
(row: { categoriaId: string | null; totalAmount: string | null }) => [
|
||||
row.categoriaId ?? "",
|
||||
(row: { categoryId: string | null; totalAmount: string | null }) => [
|
||||
row.categoryId ?? "",
|
||||
Math.abs(toNumber(row.totalAmount)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const budgets = budgetRows
|
||||
.map((budget: Orcamento) => ({
|
||||
const budgetList = budgetRows
|
||||
.map((budget) => ({
|
||||
id: budget.id,
|
||||
amount: toNumber(budget.amount),
|
||||
spent: totalsByCategory.get(budget.categoriaId ?? "") ?? 0,
|
||||
spent: totalsByCategory.get(budget.categoryId ?? "") ?? 0,
|
||||
period: budget.period,
|
||||
createdAt: budget.createdAt.toISOString(),
|
||||
category: budget.categoria
|
||||
? {
|
||||
id: budget.categoria.id,
|
||||
name: budget.categoria.name,
|
||||
icon: budget.categoria.icon,
|
||||
}
|
||||
: null,
|
||||
category: (() => {
|
||||
type Cat = { id: string; name: string; icon: string | null };
|
||||
const cat = budget.category as Cat | null | undefined;
|
||||
return cat ? { id: cat.id, name: cat.name, icon: cat.icon } : null;
|
||||
})(),
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
(a.category?.name ?? "").localeCompare(b.category?.name ?? "", "pt-BR", {
|
||||
@@ -132,5 +130,5 @@ export async function fetchBudgetsForUser(
|
||||
icon: category.icon,
|
||||
}));
|
||||
|
||||
return { budgets, categoriesOptions };
|
||||
return { budgets: budgetList, categoriesOptions };
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { cartoes, contas } from "@/db/schema";
|
||||
import { cards, financialAccounts } from "@/db/schema";
|
||||
import {
|
||||
type ActionResult,
|
||||
handleActionError,
|
||||
@@ -40,7 +40,7 @@ const cardBaseSchema = z.object({
|
||||
.string({ message: "Selecione um logo." })
|
||||
.trim()
|
||||
.min(1, "Selecione um logo."),
|
||||
contaId: uuidSchema("Conta"),
|
||||
accountId: uuidSchema("FinancialAccount"),
|
||||
});
|
||||
|
||||
const createCardSchema = cardBaseSchema;
|
||||
@@ -55,14 +55,17 @@ type CardCreateInput = z.infer<typeof createCardSchema>;
|
||||
type CardUpdateInput = z.infer<typeof updateCardSchema>;
|
||||
type CardDeleteInput = z.infer<typeof deleteCardSchema>;
|
||||
|
||||
async function assertAccountOwnership(userId: string, contaId: string) {
|
||||
const account = await db.query.contas.findFirst({
|
||||
async function assertAccountOwnership(userId: string, accountId: string) {
|
||||
const account = await db.query.financialAccounts.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(eq(contas.id, contaId), eq(contas.userId, userId)),
|
||||
where: and(
|
||||
eq(financialAccounts.id, accountId),
|
||||
eq(financialAccounts.userId, userId),
|
||||
),
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error("Conta vinculada não encontrada.");
|
||||
throw new Error("FinancialAccount vinculada não encontrada.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +76,11 @@ export async function createCardAction(
|
||||
const user = await getUser();
|
||||
const data = createCardSchema.parse(input);
|
||||
|
||||
await assertAccountOwnership(user.id, data.contaId);
|
||||
await assertAccountOwnership(user.id, data.accountId);
|
||||
|
||||
const logoFile = normalizeFilePath(data.logo);
|
||||
|
||||
await db.insert(cartoes).values({
|
||||
await db.insert(cards).values({
|
||||
name: data.name,
|
||||
brand: data.brand,
|
||||
status: data.status,
|
||||
@@ -86,11 +89,11 @@ export async function createCardAction(
|
||||
note: data.note ?? null,
|
||||
limit: formatDecimalForDb(data.limit),
|
||||
logo: logoFile,
|
||||
contaId: data.contaId,
|
||||
accountId: data.accountId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
revalidateForEntity("cartoes");
|
||||
revalidateForEntity("cards");
|
||||
|
||||
return { success: true, message: "Cartão criado com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -105,12 +108,12 @@ export async function updateCardAction(
|
||||
const user = await getUser();
|
||||
const data = updateCardSchema.parse(input);
|
||||
|
||||
await assertAccountOwnership(user.id, data.contaId);
|
||||
await assertAccountOwnership(user.id, data.accountId);
|
||||
|
||||
const logoFile = normalizeFilePath(data.logo);
|
||||
|
||||
const [updated] = await db
|
||||
.update(cartoes)
|
||||
.update(cards)
|
||||
.set({
|
||||
name: data.name,
|
||||
brand: data.brand,
|
||||
@@ -120,9 +123,9 @@ export async function updateCardAction(
|
||||
note: data.note ?? null,
|
||||
limit: formatDecimalForDb(data.limit),
|
||||
logo: logoFile,
|
||||
contaId: data.contaId,
|
||||
accountId: data.accountId,
|
||||
})
|
||||
.where(and(eq(cartoes.id, data.id), eq(cartoes.userId, user.id)))
|
||||
.where(and(eq(cards.id, data.id), eq(cards.userId, user.id)))
|
||||
.returning();
|
||||
|
||||
if (!updated) {
|
||||
@@ -132,7 +135,7 @@ export async function updateCardAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("cartoes");
|
||||
revalidateForEntity("cards");
|
||||
|
||||
return { success: true, message: "Cartão atualizado com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -148,9 +151,9 @@ export async function deleteCardAction(
|
||||
const data = deleteCardSchema.parse(input);
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(cartoes)
|
||||
.where(and(eq(cartoes.id, data.id), eq(cartoes.userId, user.id)))
|
||||
.returning({ id: cartoes.id });
|
||||
.delete(cards)
|
||||
.where(and(eq(cards.id, data.id), eq(cards.userId, user.id)))
|
||||
.returning({ id: cards.id });
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
@@ -159,7 +162,7 @@ export async function deleteCardAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("cartoes");
|
||||
revalidateForEntity("cards");
|
||||
|
||||
return { success: true, message: "Cartão removido com sucesso." };
|
||||
} catch (error) {
|
||||
|
||||
@@ -70,7 +70,7 @@ const buildInitialValues = ({
|
||||
limit: formatLimitInput(card?.limit ?? null),
|
||||
note: card?.note ?? "",
|
||||
logo: selectedLogo,
|
||||
contaId: card?.contaId ?? accounts[0]?.id ?? "",
|
||||
accountId: card?.accountId ?? accounts[0]?.id ?? "",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -146,7 +146,7 @@ export function CardDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formState.contaId) {
|
||||
if (!formState.accountId) {
|
||||
const message = "Selecione a conta vinculada.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
@@ -163,7 +163,7 @@ export function CardDialog({
|
||||
limit: rawLimit ? Number(rawLimit) : null,
|
||||
note: formState.note.trim() || null,
|
||||
logo: formState.logo,
|
||||
contaId: formState.contaId,
|
||||
accountId: formState.accountId,
|
||||
};
|
||||
|
||||
if (!payload.logo) {
|
||||
|
||||
@@ -160,10 +160,10 @@ export function CardFormFields({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 sm:col-span-2">
|
||||
<Label htmlFor="card-account">Conta vinculada</Label>
|
||||
<Label htmlFor="card-account">FinancialAccount vinculada</Label>
|
||||
<Select
|
||||
value={values.contaId}
|
||||
onValueChange={(value) => onChange("contaId", value)}
|
||||
value={values.accountId}
|
||||
onValueChange={(value) => onChange("accountId", value)}
|
||||
disabled={accountOptions.length === 0}
|
||||
>
|
||||
<SelectTrigger id="card-account" className="w-full">
|
||||
@@ -174,10 +174,10 @@ export function CardFormFields({
|
||||
: "Selecione a conta"
|
||||
}
|
||||
>
|
||||
{values.contaId &&
|
||||
{values.accountId &&
|
||||
(() => {
|
||||
const selectedAccount = accountOptions.find(
|
||||
(acc) => acc.id === values.contaId,
|
||||
(acc) => acc.id === values.accountId,
|
||||
);
|
||||
return selectedAccount ? (
|
||||
<AccountSelectContent
|
||||
|
||||
@@ -33,7 +33,7 @@ interface CardItemProps {
|
||||
limit: number | null;
|
||||
limitInUse?: number | null;
|
||||
limitAvailable?: number | null;
|
||||
contaName: string;
|
||||
accountName: string;
|
||||
logo?: string | null;
|
||||
note?: string | null;
|
||||
onEdit?: () => void;
|
||||
@@ -52,14 +52,14 @@ export function CardItem({
|
||||
limit,
|
||||
limitInUse,
|
||||
limitAvailable,
|
||||
contaName: _contaName,
|
||||
accountName: _accountName,
|
||||
logo,
|
||||
note,
|
||||
onEdit,
|
||||
onInvoice,
|
||||
onRemove,
|
||||
}: CardItemProps) {
|
||||
void _contaName;
|
||||
void _accountName;
|
||||
|
||||
const limitTotal = limit ?? null;
|
||||
const used =
|
||||
|
||||
@@ -142,7 +142,7 @@ export function CardsPage({
|
||||
limit={card.limit}
|
||||
limitInUse={card.limitInUse ?? null}
|
||||
limitAvailable={card.limitAvailable ?? card.limit ?? null}
|
||||
contaName={card.contaName}
|
||||
accountName={card.accountName}
|
||||
logo={card.logo}
|
||||
note={card.note}
|
||||
onEdit={() => handleEdit(card)}
|
||||
|
||||
@@ -8,10 +8,10 @@ export type Card = {
|
||||
note: string | null;
|
||||
logo: string | null;
|
||||
limit: number | null;
|
||||
contaId: string;
|
||||
contaName: string;
|
||||
limitInUse?: number | null;
|
||||
limitAvailable?: number | null;
|
||||
accountId: string;
|
||||
accountName: string;
|
||||
limitInUse: number;
|
||||
limitAvailable: number | null;
|
||||
};
|
||||
|
||||
export type CardFormValues = {
|
||||
@@ -23,5 +23,5 @@ export type CardFormValues = {
|
||||
limit: string;
|
||||
note: string;
|
||||
logo: string;
|
||||
contaId: string;
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { and, eq, ilike, isNull, ne, not, or, sql } from "drizzle-orm";
|
||||
import { cartoes, contas, lancamentos } from "@/db/schema";
|
||||
import { cards, financialAccounts, transactions } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { loadLogoOptions } from "@/shared/lib/logo/options";
|
||||
|
||||
export type CardData = {
|
||||
id: string;
|
||||
name: string;
|
||||
brand: string | null;
|
||||
status: string | null;
|
||||
closingDay: number;
|
||||
dueDay: number;
|
||||
brand: string;
|
||||
status: string;
|
||||
closingDay: string;
|
||||
dueDay: string;
|
||||
note: string | null;
|
||||
logo: string | null;
|
||||
limit: number | null;
|
||||
limitInUse: number;
|
||||
limitAvailable: number | null;
|
||||
contaId: string;
|
||||
contaName: string;
|
||||
accountId: string;
|
||||
accountName: string;
|
||||
};
|
||||
|
||||
export type AccountSimple = {
|
||||
@@ -28,20 +28,14 @@ export type AccountSimple = {
|
||||
export async function fetchCardsForUser(userId: string): Promise<{
|
||||
cards: CardData[];
|
||||
accounts: AccountSimple[];
|
||||
logoOptions: LogoOption[];
|
||||
logoOptions: string[];
|
||||
}> {
|
||||
const [cardRows, accountRows, logoOptions, usageRows] = await Promise.all([
|
||||
db.query.cartoes.findMany({
|
||||
orderBy: (
|
||||
card: typeof cartoes.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(card.name)],
|
||||
where: and(
|
||||
eq(cartoes.userId, userId),
|
||||
not(ilike(cartoes.status, "inativo")),
|
||||
),
|
||||
db.query.cards.findMany({
|
||||
orderBy: (table, { desc }) => [desc(table.name)],
|
||||
where: and(eq(cards.userId, userId), not(ilike(cards.status, "inativo"))),
|
||||
with: {
|
||||
conta: {
|
||||
financialAccount: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -49,12 +43,9 @@ export async function fetchCardsForUser(userId: string): Promise<{
|
||||
},
|
||||
},
|
||||
}),
|
||||
db.query.contas.findMany({
|
||||
orderBy: (
|
||||
account: typeof contas.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(account.name)],
|
||||
where: eq(contas.userId, userId),
|
||||
db.query.financialAccounts.findMany({
|
||||
orderBy: (table, { desc }) => [desc(table.name)],
|
||||
where: eq(financialAccounts.userId, userId),
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -64,37 +55,35 @@ export async function fetchCardsForUser(userId: string): Promise<{
|
||||
loadLogoOptions(),
|
||||
db
|
||||
.select({
|
||||
cartaoId: lancamentos.cartaoId,
|
||||
total: sql<number>`coalesce(sum(${lancamentos.amount}), 0)`,
|
||||
cardId: transactions.cardId,
|
||||
total: sql<number>`coalesce(sum(${transactions.amount}), 0)`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
or(isNull(lancamentos.isSettled), eq(lancamentos.isSettled, false)),
|
||||
eq(transactions.userId, userId),
|
||||
or(isNull(transactions.isSettled), eq(transactions.isSettled, false)),
|
||||
// Recorrente no cartão: só consome limite quando a data da ocorrência já passou
|
||||
or(
|
||||
ne(lancamentos.condition, "Recorrente"),
|
||||
sql`${lancamentos.purchaseDate} <= current_date`,
|
||||
ne(transactions.condition, "Recorrente"),
|
||||
sql`${transactions.purchaseDate} <= current_date`,
|
||||
),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.cartaoId),
|
||||
.groupBy(transactions.cardId),
|
||||
]);
|
||||
|
||||
const usageMap = new Map<string, number>();
|
||||
usageRows.forEach(
|
||||
(row: { cartaoId: string | null; total: number | null }) => {
|
||||
if (!row.cartaoId) return;
|
||||
usageMap.set(row.cartaoId, Number(row.total ?? 0));
|
||||
},
|
||||
);
|
||||
usageRows.forEach((row: { cardId: string | null; total: number | null }) => {
|
||||
if (!row.cardId) return;
|
||||
usageMap.set(row.cardId, Number(row.total ?? 0));
|
||||
});
|
||||
|
||||
const cards = cardRows.map((card) => ({
|
||||
const cardList = cardRows.map((card) => ({
|
||||
id: card.id,
|
||||
name: card.name,
|
||||
brand: card.brand,
|
||||
status: card.status,
|
||||
brand: card.brand ?? "",
|
||||
status: card.status ?? "",
|
||||
closingDay: card.closingDay,
|
||||
dueDay: card.dueDay,
|
||||
note: card.note,
|
||||
@@ -112,8 +101,10 @@ export async function fetchCardsForUser(userId: string): Promise<{
|
||||
const inUse = total < 0 ? Math.abs(total) : 0;
|
||||
return Math.max(Number(card.limit) - inUse, 0);
|
||||
})(),
|
||||
contaId: card.contaId,
|
||||
contaName: card.conta?.name ?? "Conta não encontrada",
|
||||
accountId: card.accountId,
|
||||
accountName:
|
||||
(card.financialAccount as { name?: string } | null)?.name ??
|
||||
"Conta não encontrada",
|
||||
}));
|
||||
|
||||
const accounts = accountRows.map((account) => ({
|
||||
@@ -122,23 +113,20 @@ export async function fetchCardsForUser(userId: string): Promise<{
|
||||
logo: account.logo,
|
||||
}));
|
||||
|
||||
return { cards, accounts, logoOptions };
|
||||
return { cards: cardList, accounts, logoOptions };
|
||||
}
|
||||
|
||||
export async function fetchInativosForUser(userId: string): Promise<{
|
||||
export async function fetchInactiveForUser(userId: string): Promise<{
|
||||
cards: CardData[];
|
||||
accounts: AccountSimple[];
|
||||
logoOptions: LogoOption[];
|
||||
logoOptions: string[];
|
||||
}> {
|
||||
const [cardRows, accountRows, logoOptions, usageRows] = await Promise.all([
|
||||
db.query.cartoes.findMany({
|
||||
orderBy: (
|
||||
card: typeof cartoes.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(card.name)],
|
||||
where: and(eq(cartoes.userId, userId), ilike(cartoes.status, "inativo")),
|
||||
db.query.cards.findMany({
|
||||
orderBy: (table, { desc }) => [desc(table.name)],
|
||||
where: and(eq(cards.userId, userId), ilike(cards.status, "inativo")),
|
||||
with: {
|
||||
conta: {
|
||||
financialAccount: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -146,12 +134,9 @@ export async function fetchInativosForUser(userId: string): Promise<{
|
||||
},
|
||||
},
|
||||
}),
|
||||
db.query.contas.findMany({
|
||||
orderBy: (
|
||||
account: typeof contas.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(account.name)],
|
||||
where: eq(contas.userId, userId),
|
||||
db.query.financialAccounts.findMany({
|
||||
orderBy: (table, { desc }) => [desc(table.name)],
|
||||
where: eq(financialAccounts.userId, userId),
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -161,37 +146,35 @@ export async function fetchInativosForUser(userId: string): Promise<{
|
||||
loadLogoOptions(),
|
||||
db
|
||||
.select({
|
||||
cartaoId: lancamentos.cartaoId,
|
||||
total: sql<number>`coalesce(sum(${lancamentos.amount}), 0)`,
|
||||
cardId: transactions.cardId,
|
||||
total: sql<number>`coalesce(sum(${transactions.amount}), 0)`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
or(isNull(lancamentos.isSettled), eq(lancamentos.isSettled, false)),
|
||||
eq(transactions.userId, userId),
|
||||
or(isNull(transactions.isSettled), eq(transactions.isSettled, false)),
|
||||
// Recorrente no cartão: só consome limite quando a data da ocorrência já passou
|
||||
or(
|
||||
ne(lancamentos.condition, "Recorrente"),
|
||||
sql`${lancamentos.purchaseDate} <= current_date`,
|
||||
ne(transactions.condition, "Recorrente"),
|
||||
sql`${transactions.purchaseDate} <= current_date`,
|
||||
),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.cartaoId),
|
||||
.groupBy(transactions.cardId),
|
||||
]);
|
||||
|
||||
const usageMap = new Map<string, number>();
|
||||
usageRows.forEach(
|
||||
(row: { cartaoId: string | null; total: number | null }) => {
|
||||
if (!row.cartaoId) return;
|
||||
usageMap.set(row.cartaoId, Number(row.total ?? 0));
|
||||
},
|
||||
);
|
||||
usageRows.forEach((row: { cardId: string | null; total: number | null }) => {
|
||||
if (!row.cardId) return;
|
||||
usageMap.set(row.cardId, Number(row.total ?? 0));
|
||||
});
|
||||
|
||||
const cards = cardRows.map((card) => ({
|
||||
const cardList = cardRows.map((card) => ({
|
||||
id: card.id,
|
||||
name: card.name,
|
||||
brand: card.brand,
|
||||
status: card.status,
|
||||
brand: card.brand ?? "",
|
||||
status: card.status ?? "",
|
||||
closingDay: card.closingDay,
|
||||
dueDay: card.dueDay,
|
||||
note: card.note,
|
||||
@@ -209,8 +192,10 @@ export async function fetchInativosForUser(userId: string): Promise<{
|
||||
const inUse = total < 0 ? Math.abs(total) : 0;
|
||||
return Math.max(Number(card.limit) - inUse, 0);
|
||||
})(),
|
||||
contaId: card.contaId,
|
||||
contaName: card.conta?.name ?? "Conta não encontrada",
|
||||
accountId: card.accountId,
|
||||
accountName:
|
||||
(card.financialAccount as { name?: string } | null)?.name ??
|
||||
"Conta não encontrada",
|
||||
}));
|
||||
|
||||
const accounts = accountRows.map((account) => ({
|
||||
@@ -219,18 +204,18 @@ export async function fetchInativosForUser(userId: string): Promise<{
|
||||
logo: account.logo,
|
||||
}));
|
||||
|
||||
return { cards, accounts, logoOptions };
|
||||
return { cards: cardList, accounts, logoOptions };
|
||||
}
|
||||
|
||||
export async function fetchAllCardsForUser(userId: string): Promise<{
|
||||
activeCards: CardData[];
|
||||
archivedCards: CardData[];
|
||||
accounts: AccountSimple[];
|
||||
logoOptions: LogoOption[];
|
||||
logoOptions: string[];
|
||||
}> {
|
||||
const [activeData, archivedData] = await Promise.all([
|
||||
fetchCardsForUser(userId),
|
||||
fetchInativosForUser(userId),
|
||||
fetchInactiveForUser(userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { categorias } from "@/db/schema";
|
||||
import { categories } from "@/db/schema";
|
||||
import {
|
||||
type ActionResult,
|
||||
handleActionError,
|
||||
@@ -32,10 +32,10 @@ const categoryBaseSchema = z.object({
|
||||
|
||||
const createCategorySchema = categoryBaseSchema;
|
||||
const updateCategorySchema = categoryBaseSchema.extend({
|
||||
id: uuidSchema("Categoria"),
|
||||
id: uuidSchema("Category"),
|
||||
});
|
||||
const deleteCategorySchema = z.object({
|
||||
id: uuidSchema("Categoria"),
|
||||
id: uuidSchema("Category"),
|
||||
});
|
||||
|
||||
type CategoryCreateInput = z.infer<typeof createCategorySchema>;
|
||||
@@ -49,16 +49,16 @@ export async function createCategoryAction(
|
||||
const user = await getUser();
|
||||
const data = createCategorySchema.parse(input);
|
||||
|
||||
await db.insert(categorias).values({
|
||||
await db.insert(categories).values({
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
icon: data.icon,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
revalidateForEntity("categorias");
|
||||
revalidateForEntity("categories");
|
||||
|
||||
return { success: true, message: "Categoria criada com sucesso." };
|
||||
return { success: true, message: "Category criada com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
@@ -72,19 +72,19 @@ export async function updateCategoryAction(
|
||||
const data = updateCategorySchema.parse(input);
|
||||
|
||||
// Buscar categoria antes de atualizar para verificar restrições
|
||||
const categoria = await db.query.categorias.findFirst({
|
||||
const categoria = await db.query.categories.findFirst({
|
||||
columns: { id: true, name: true },
|
||||
where: and(eq(categorias.id, data.id), eq(categorias.userId, user.id)),
|
||||
where: and(eq(categories.id, data.id), eq(categories.userId, user.id)),
|
||||
});
|
||||
|
||||
if (!categoria) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Categoria não encontrada.",
|
||||
error: "Category não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
// Bloquear edição das categorias protegidas
|
||||
// Bloquear edição das categories protegidas
|
||||
const categoriasProtegidas = [
|
||||
"Transferência interna",
|
||||
"Saldo inicial",
|
||||
@@ -98,25 +98,25 @@ export async function updateCategoryAction(
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(categorias)
|
||||
.update(categories)
|
||||
.set({
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
icon: data.icon,
|
||||
})
|
||||
.where(and(eq(categorias.id, data.id), eq(categorias.userId, user.id)))
|
||||
.where(and(eq(categories.id, data.id), eq(categories.userId, user.id)))
|
||||
.returning();
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Categoria não encontrada.",
|
||||
error: "Category não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("categorias");
|
||||
revalidateForEntity("categories");
|
||||
|
||||
return { success: true, message: "Categoria atualizada com sucesso." };
|
||||
return { success: true, message: "Category atualizada com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
@@ -130,19 +130,19 @@ export async function deleteCategoryAction(
|
||||
const data = deleteCategorySchema.parse(input);
|
||||
|
||||
// Buscar categoria antes de deletar para verificar restrições
|
||||
const categoria = await db.query.categorias.findFirst({
|
||||
const categoria = await db.query.categories.findFirst({
|
||||
columns: { id: true, name: true },
|
||||
where: and(eq(categorias.id, data.id), eq(categorias.userId, user.id)),
|
||||
where: and(eq(categories.id, data.id), eq(categories.userId, user.id)),
|
||||
});
|
||||
|
||||
if (!categoria) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Categoria não encontrada.",
|
||||
error: "Category não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
// Bloquear remoção das categorias protegidas
|
||||
// Bloquear remoção das categories protegidas
|
||||
const categoriasProtegidas = [
|
||||
"Transferência interna",
|
||||
"Saldo inicial",
|
||||
@@ -156,20 +156,20 @@ export async function deleteCategoryAction(
|
||||
}
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(categorias)
|
||||
.where(and(eq(categorias.id, data.id), eq(categorias.userId, user.id)))
|
||||
.returning({ id: categorias.id });
|
||||
.delete(categories)
|
||||
.where(and(eq(categories.id, data.id), eq(categories.userId, user.id)))
|
||||
.returning({ id: categories.id });
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Categoria não encontrada.",
|
||||
error: "Category não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("categorias");
|
||||
revalidateForEntity("categories");
|
||||
|
||||
return { success: true, message: "Categoria removida com sucesso." };
|
||||
return { success: true, message: "Category removida com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export function CategoryDialog({
|
||||
setErrorMessage(null);
|
||||
|
||||
if (mode === "update" && !category?.id) {
|
||||
const message = "Categoria inválida.";
|
||||
const message = "Category inválida.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
|
||||
@@ -64,6 +64,7 @@ export function CategoryIconBadge({
|
||||
style={{ backgroundColor: bgColor }}
|
||||
>
|
||||
{IconComponent ? (
|
||||
// @ts-expect-error icon accepts style but type is too narrow
|
||||
<IconComponent className={variant.icon} style={{ color }} />
|
||||
) : (
|
||||
<span className={cn("uppercase", variant.text)} style={{ color }}>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function CategoryIcon({ name, className }: CategoryIconProps) {
|
||||
if (!IconComponent) {
|
||||
return (
|
||||
<span className={cn("text-xs text-muted-foreground", className)}>
|
||||
{name ?? "Categoria"}
|
||||
{name ?? "Category"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { type Categoria, categorias } from "@/db/schema";
|
||||
import { type Category, categories } from "@/db/schema";
|
||||
import type { CategoryType } from "@/features/categories/components/types";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
@@ -13,11 +13,11 @@ export type CategoryData = {
|
||||
export async function fetchCategoriesForUser(
|
||||
userId: string,
|
||||
): Promise<CategoryData[]> {
|
||||
const categoryRows = await db.query.categorias.findMany({
|
||||
where: eq(categorias.userId, userId),
|
||||
const categoryRows = await db.query.categories.findMany({
|
||||
where: eq(categories.userId, userId),
|
||||
});
|
||||
|
||||
return categoryRows.map((category: Categoria) => ({
|
||||
return categoryRows.map((category: Category) => ({
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
type: category.type as CategoryType,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { preLancamentos } from "@/db/schema";
|
||||
import { inboxItems } from "@/db/schema";
|
||||
import {
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
@@ -52,12 +52,12 @@ export async function markInboxAsProcessedAction(
|
||||
// Verificar se item existe e pertence ao usuário
|
||||
const [item] = await db
|
||||
.select()
|
||||
.from(preLancamentos)
|
||||
.from(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(preLancamentos.status, "pending"),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
eq(inboxItems.status, "pending"),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
@@ -68,7 +68,7 @@ export async function markInboxAsProcessedAction(
|
||||
|
||||
// Marcar item como processado
|
||||
await db
|
||||
.update(preLancamentos)
|
||||
.update(inboxItems)
|
||||
.set({
|
||||
status: "processed",
|
||||
processedAt: new Date(),
|
||||
@@ -76,8 +76,8 @@ export async function markInboxAsProcessedAction(
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -99,12 +99,12 @@ export async function discardInboxItemAction(
|
||||
// Verificar se item existe e pertence ao usuário
|
||||
const [item] = await db
|
||||
.select()
|
||||
.from(preLancamentos)
|
||||
.from(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(preLancamentos.status, "pending"),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
eq(inboxItems.status, "pending"),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
@@ -115,7 +115,7 @@ export async function discardInboxItemAction(
|
||||
|
||||
// Marcar item como descartado
|
||||
await db
|
||||
.update(preLancamentos)
|
||||
.update(inboxItems)
|
||||
.set({
|
||||
status: "discarded",
|
||||
discardedAt: new Date(),
|
||||
@@ -123,8 +123,8 @@ export async function discardInboxItemAction(
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -145,7 +145,7 @@ export async function bulkDiscardInboxItemsAction(
|
||||
|
||||
// Marcar todos os itens como descartados
|
||||
await db
|
||||
.update(preLancamentos)
|
||||
.update(inboxItems)
|
||||
.set({
|
||||
status: "discarded",
|
||||
discardedAt: new Date(),
|
||||
@@ -153,9 +153,9 @@ export async function bulkDiscardInboxItemsAction(
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
inArray(preLancamentos.id, data.inboxItemIds),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(preLancamentos.status, "pending"),
|
||||
inArray(inboxItems.id, data.inboxItemIds),
|
||||
eq(inboxItems.userId, user.id),
|
||||
eq(inboxItems.status, "pending"),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -178,13 +178,13 @@ export async function restoreDiscardedInboxItemAction(
|
||||
const data = restoreDiscardedInboxSchema.parse(input);
|
||||
|
||||
const [item] = await db
|
||||
.select({ id: preLancamentos.id })
|
||||
.from(preLancamentos)
|
||||
.select({ id: inboxItems.id })
|
||||
.from(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(preLancamentos.status, "discarded"),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
eq(inboxItems.status, "discarded"),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
@@ -197,7 +197,7 @@ export async function restoreDiscardedInboxItemAction(
|
||||
}
|
||||
|
||||
await db
|
||||
.update(preLancamentos)
|
||||
.update(inboxItems)
|
||||
.set({
|
||||
status: "pending",
|
||||
discardedAt: null,
|
||||
@@ -205,8 +205,8 @@ export async function restoreDiscardedInboxItemAction(
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -226,12 +226,12 @@ export async function deleteInboxItemAction(
|
||||
const data = deleteInboxSchema.parse(input);
|
||||
|
||||
const [item] = await db
|
||||
.select({ status: preLancamentos.status })
|
||||
.from(preLancamentos)
|
||||
.select({ status: inboxItems.status })
|
||||
.from(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
@@ -248,11 +248,11 @@ export async function deleteInboxItemAction(
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(preLancamentos)
|
||||
.delete(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.id, data.inboxItemId),
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(inboxItems.id, data.inboxItemId),
|
||||
eq(inboxItems.userId, user.id),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -272,14 +272,11 @@ export async function bulkDeleteInboxItemsAction(
|
||||
const data = bulkDeleteInboxSchema.parse(input);
|
||||
|
||||
const result = await db
|
||||
.delete(preLancamentos)
|
||||
.delete(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.userId, user.id),
|
||||
eq(preLancamentos.status, data.status),
|
||||
),
|
||||
and(eq(inboxItems.userId, user.id), eq(inboxItems.status, data.status)),
|
||||
)
|
||||
.returning({ id: preLancamentos.id });
|
||||
.returning({ id: inboxItems.id });
|
||||
|
||||
revalidateInbox();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
markInboxAsProcessedAction,
|
||||
restoreDiscardedInboxItemAction,
|
||||
} from "@/features/inbox/actions";
|
||||
import { LancamentoDialog } from "@/features/transactions/components/dialogs/transaction-dialog/transaction-dialog";
|
||||
import { TransactionDialog } from "@/features/transactions/components/dialogs/transaction-dialog/transaction-dialog";
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
import { EmptyState } from "@/shared/components/empty-state";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -29,12 +29,12 @@ interface InboxPageProps {
|
||||
pendingItems: InboxItem[];
|
||||
processedItems: InboxItem[];
|
||||
discardedItems: InboxItem[];
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
payerOptions: SelectOption[];
|
||||
splitPayerOptions: SelectOption[];
|
||||
defaultPayerId: string | null;
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
estabelecimentos: string[];
|
||||
appLogoMap: Record<string, string>;
|
||||
}
|
||||
@@ -43,12 +43,12 @@ export function InboxPage({
|
||||
pendingItems,
|
||||
processedItems,
|
||||
discardedItems,
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
estabelecimentos,
|
||||
appLogoMap,
|
||||
}: InboxPageProps) {
|
||||
@@ -272,14 +272,14 @@ export function InboxPage({
|
||||
const appName = itemToProcess?.sourceAppName?.toLowerCase();
|
||||
if (!appName) return null;
|
||||
|
||||
for (const option of cartaoOptions) {
|
||||
for (const option of cardOptions) {
|
||||
const label = option.label.toLowerCase();
|
||||
if (label.includes(appName) || appName.includes(label)) {
|
||||
return option.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [itemToProcess?.sourceAppName, cartaoOptions]);
|
||||
}, [itemToProcess?.sourceAppName, cardOptions]);
|
||||
|
||||
const renderEmptyState = (message: string) => (
|
||||
<Card className="flex min-h-[50vh] w-full items-center justify-center py-12">
|
||||
@@ -378,21 +378,21 @@ export function InboxPage({
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<LancamentoDialog
|
||||
<TransactionDialog
|
||||
mode="create"
|
||||
open={processOpen}
|
||||
onOpenChange={handleProcessOpenChange}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
payerOptions={payerOptions}
|
||||
splitPayerOptions={splitPayerOptions}
|
||||
defaultPayerId={defaultPayerId}
|
||||
accountOptions={accountOptions}
|
||||
cardOptions={cardOptions}
|
||||
categoryOptions={categoryOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
defaultPurchaseDate={defaultPurchaseDate}
|
||||
defaultName={defaultName}
|
||||
defaultAmount={defaultAmount}
|
||||
defaultCartaoId={matchedCartaoId}
|
||||
defaultCardId={matchedCartaoId}
|
||||
defaultPaymentMethod={matchedCartaoId ? "Cartão de crédito" : null}
|
||||
forceShowTransactionType
|
||||
onSuccess={handleLancamentoSuccess}
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface InboxItem {
|
||||
parsedName: string | null;
|
||||
parsedAmount: string | null;
|
||||
status: string;
|
||||
lancamentoId: string | null;
|
||||
transactionId: string | null;
|
||||
processedAt: Date | null;
|
||||
discardedAt: Date | null;
|
||||
createdAt: Date;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { cartoes, categorias, contas, preLancamentos } from "@/db/schema";
|
||||
import { cards, categories, financialAccounts, inboxItems } from "@/db/schema";
|
||||
import type {
|
||||
InboxItem,
|
||||
SelectOption,
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
buildSluggedFilters,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
import {
|
||||
fetchLancamentoFilterSources,
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
} from "@/features/transactions/queries";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
@@ -20,11 +20,9 @@ export async function fetchInboxItems(
|
||||
): Promise<InboxItem[]> {
|
||||
const items = await db
|
||||
.select()
|
||||
.from(preLancamentos)
|
||||
.where(
|
||||
and(eq(preLancamentos.userId, userId), eq(preLancamentos.status, status)),
|
||||
)
|
||||
.orderBy(desc(preLancamentos.createdAt));
|
||||
.from(inboxItems)
|
||||
.where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status)))
|
||||
.orderBy(desc(inboxItems.createdAt));
|
||||
|
||||
return items;
|
||||
}
|
||||
@@ -35,54 +33,57 @@ export async function fetchInboxItemById(
|
||||
): Promise<InboxItem | null> {
|
||||
const [item] = await db
|
||||
.select()
|
||||
.from(preLancamentos)
|
||||
.where(
|
||||
and(eq(preLancamentos.id, itemId), eq(preLancamentos.userId, userId)),
|
||||
)
|
||||
.from(inboxItems)
|
||||
.where(and(eq(inboxItems.id, itemId), eq(inboxItems.userId, userId)))
|
||||
.limit(1);
|
||||
|
||||
return item ?? null;
|
||||
}
|
||||
|
||||
export async function fetchCategoriasForSelect(
|
||||
export async function fetchCategoriesForSelect(
|
||||
userId: string,
|
||||
type?: string,
|
||||
): Promise<SelectOption[]> {
|
||||
const query = db
|
||||
.select({ id: categorias.id, name: categorias.name })
|
||||
.from(categorias)
|
||||
const rows = await db
|
||||
.select({ id: categories.id, name: categories.name })
|
||||
.from(categories)
|
||||
.where(
|
||||
type
|
||||
? and(eq(categorias.userId, userId), eq(categorias.type, type))
|
||||
: eq(categorias.userId, userId),
|
||||
? and(eq(categories.userId, userId), eq(categories.type, type))
|
||||
: eq(categories.userId, userId),
|
||||
)
|
||||
.orderBy(categorias.name);
|
||||
.orderBy(categories.name);
|
||||
|
||||
return query;
|
||||
return rows.map((row) => ({ value: row.id, label: row.name }));
|
||||
}
|
||||
|
||||
export async function fetchContasForSelect(
|
||||
export async function fetchAccountsForSelect(
|
||||
userId: string,
|
||||
): Promise<SelectOption[]> {
|
||||
const items = await db
|
||||
.select({ id: contas.id, name: contas.name })
|
||||
.from(contas)
|
||||
.where(and(eq(contas.userId, userId), eq(contas.status, "ativo")))
|
||||
.orderBy(contas.name);
|
||||
const rows = await db
|
||||
.select({ id: financialAccounts.id, name: financialAccounts.name })
|
||||
.from(financialAccounts)
|
||||
.where(
|
||||
and(
|
||||
eq(financialAccounts.userId, userId),
|
||||
eq(financialAccounts.status, "ativo"),
|
||||
),
|
||||
)
|
||||
.orderBy(financialAccounts.name);
|
||||
|
||||
return items;
|
||||
return rows.map((row) => ({ value: row.id, label: row.name }));
|
||||
}
|
||||
|
||||
export async function fetchCartoesForSelect(
|
||||
export async function fetchCardsForSelect(
|
||||
userId: string,
|
||||
): Promise<(SelectOption & { lastDigits?: string })[]> {
|
||||
const items = await db
|
||||
.select({ id: cartoes.id, name: cartoes.name })
|
||||
.from(cartoes)
|
||||
.where(and(eq(cartoes.userId, userId), eq(cartoes.status, "ativo")))
|
||||
.orderBy(cartoes.name);
|
||||
const rows = await db
|
||||
.select({ id: cards.id, name: cards.name })
|
||||
.from(cards)
|
||||
.where(and(eq(cards.userId, userId), eq(cards.status, "ativo")))
|
||||
.orderBy(cards.name);
|
||||
|
||||
return items;
|
||||
return rows.map((row) => ({ value: row.id, label: row.name }));
|
||||
}
|
||||
|
||||
export async function fetchAppLogoMap(
|
||||
@@ -90,13 +91,13 @@ export async function fetchAppLogoMap(
|
||||
): Promise<Record<string, string>> {
|
||||
const [userCartoes, userContas] = await Promise.all([
|
||||
db
|
||||
.select({ name: cartoes.name, logo: cartoes.logo })
|
||||
.from(cartoes)
|
||||
.where(eq(cartoes.userId, userId)),
|
||||
.select({ name: cards.name, logo: cards.logo })
|
||||
.from(cards)
|
||||
.where(eq(cards.userId, userId)),
|
||||
db
|
||||
.select({ name: contas.name, logo: contas.logo })
|
||||
.from(contas)
|
||||
.where(eq(contas.userId, userId)),
|
||||
.select({ name: financialAccounts.name, logo: financialAccounts.logo })
|
||||
.from(financialAccounts)
|
||||
.where(eq(financialAccounts.userId, userId)),
|
||||
]);
|
||||
|
||||
const logoMap: Record<string, string> = {};
|
||||
@@ -112,54 +113,51 @@ export async function fetchAppLogoMap(
|
||||
|
||||
export async function fetchPendingInboxCount(userId: string): Promise<number> {
|
||||
const items = await db
|
||||
.select({ id: preLancamentos.id })
|
||||
.from(preLancamentos)
|
||||
.select({ id: inboxItems.id })
|
||||
.from(inboxItems)
|
||||
.where(
|
||||
and(
|
||||
eq(preLancamentos.userId, userId),
|
||||
eq(preLancamentos.status, "pending"),
|
||||
),
|
||||
and(eq(inboxItems.userId, userId), eq(inboxItems.status, "pending")),
|
||||
);
|
||||
|
||||
return items.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all data needed for the LancamentoDialog in inbox context
|
||||
* Fetch all data needed for the TransactionDialog in inbox context
|
||||
*/
|
||||
export async function fetchInboxDialogData(userId: string): Promise<{
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
payerOptions: SelectOption[];
|
||||
splitPayerOptions: SelectOption[];
|
||||
defaultPayerId: string | null;
|
||||
accountOptions: SelectOption[];
|
||||
cardOptions: SelectOption[];
|
||||
categoryOptions: SelectOption[];
|
||||
estabelecimentos: string[];
|
||||
}> {
|
||||
const filterSources = await fetchLancamentoFilterSources(userId);
|
||||
const filterSources = await fetchTransactionFilterSources(userId);
|
||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||
|
||||
const {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
} = buildOptionSets({
|
||||
...sluggedFilters,
|
||||
pagadorRows: filterSources.pagadorRows,
|
||||
payerRows: filterSources.payerRows,
|
||||
});
|
||||
|
||||
const estabelecimentos = await fetchRecentEstablishments(userId);
|
||||
|
||||
return {
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
payerOptions,
|
||||
splitPayerOptions,
|
||||
defaultPayerId,
|
||||
accountOptions,
|
||||
cardOptions,
|
||||
categoryOptions,
|
||||
estabelecimentos,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
cartoes,
|
||||
categorias,
|
||||
faturas,
|
||||
lancamentos,
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { cards, categories, invoices, payers, transactions } from "@/db/schema";
|
||||
import { buildInvoicePaymentNote } from "@/shared/lib/accounts/constants";
|
||||
import { revalidateForEntity } from "@/shared/lib/actions/helpers";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
@@ -19,7 +13,7 @@ import {
|
||||
type InvoicePaymentStatus,
|
||||
PERIOD_FORMAT_REGEX,
|
||||
} from "@/shared/lib/invoices";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import {
|
||||
getBusinessTodayDate,
|
||||
parseLocalDateString,
|
||||
@@ -29,7 +23,7 @@ const isValidPaymentDate = (value: string) =>
|
||||
!Number.isNaN(parseLocalDateString(value).getTime());
|
||||
|
||||
const updateInvoicePaymentStatusSchema = z.object({
|
||||
cartaoId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
cardId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
@@ -53,7 +47,7 @@ type ActionResult =
|
||||
| { success: false; error: string };
|
||||
|
||||
const successMessageByStatus: Record<InvoicePaymentStatus, string> = {
|
||||
[INVOICE_PAYMENT_STATUS.PAID]: "Fatura marcada como paga.",
|
||||
[INVOICE_PAYMENT_STATUS.PAID]: "Invoice marcada como paga.",
|
||||
[INVOICE_PAYMENT_STATUS.PENDING]: "Pagamento da fatura foi revertido.",
|
||||
};
|
||||
|
||||
@@ -68,36 +62,36 @@ export async function updateInvoicePaymentStatusAction(
|
||||
const data = updateInvoicePaymentStatusSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const card = await tx.query.cartoes.findFirst({
|
||||
columns: { id: true, contaId: true, name: true },
|
||||
where: and(eq(cartoes.id, data.cartaoId), eq(cartoes.userId, user.id)),
|
||||
const card = await tx.query.cards.findFirst({
|
||||
columns: { id: true, accountId: true, name: true },
|
||||
where: and(eq(cards.id, data.cardId), eq(cards.userId, user.id)),
|
||||
});
|
||||
|
||||
if (!card) {
|
||||
throw new Error("Cartão não encontrado.");
|
||||
}
|
||||
|
||||
const existingInvoice = await tx.query.faturas.findFirst({
|
||||
const existingInvoice = await tx.query.invoices.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
where: and(
|
||||
eq(faturas.cartaoId, data.cartaoId),
|
||||
eq(faturas.userId, user.id),
|
||||
eq(faturas.period, data.period),
|
||||
eq(invoices.cardId, data.cardId),
|
||||
eq(invoices.userId, user.id),
|
||||
eq(invoices.period, data.period),
|
||||
),
|
||||
});
|
||||
|
||||
if (existingInvoice) {
|
||||
await tx
|
||||
.update(faturas)
|
||||
.update(invoices)
|
||||
.set({
|
||||
paymentStatus: data.status,
|
||||
})
|
||||
.where(eq(faturas.id, existingInvoice.id));
|
||||
.where(eq(invoices.id, existingInvoice.id));
|
||||
} else {
|
||||
await tx.insert(faturas).values({
|
||||
cartaoId: data.cartaoId,
|
||||
await tx.insert(invoices).values({
|
||||
cardId: data.cardId,
|
||||
period: data.period,
|
||||
paymentStatus: data.status,
|
||||
userId: user.id,
|
||||
@@ -107,13 +101,13 @@ export async function updateInvoicePaymentStatusAction(
|
||||
const shouldMarkAsPaid = data.status === INVOICE_PAYMENT_STATUS.PAID;
|
||||
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.update(transactions)
|
||||
.set({ isSettled: shouldMarkAsPaid })
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.cartaoId, card.id),
|
||||
eq(lancamentos.period, data.period),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.cardId, card.id),
|
||||
eq(transactions.period, data.period),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -124,39 +118,39 @@ export async function updateInvoicePaymentStatusAction(
|
||||
.select({
|
||||
total: sql<number>`
|
||||
coalesce(
|
||||
sum(${lancamentos.amount}),
|
||||
sum(${transactions.amount}),
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.leftJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.from(transactions)
|
||||
.leftJoin(payers, eq(transactions.payerId, payers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.cartaoId, card.id),
|
||||
eq(lancamentos.period, data.period),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.cardId, card.id),
|
||||
eq(transactions.period, data.period),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
);
|
||||
|
||||
const adminShare = Number(adminShareRow?.total ?? 0);
|
||||
const adminPayableAmount = Math.abs(Math.min(adminShare, 0));
|
||||
|
||||
if (adminPayableAmount > 0 && card.contaId) {
|
||||
const adminPagador = await tx.query.pagadores.findFirst({
|
||||
if (adminPayableAmount > 0 && card.accountId) {
|
||||
const adminPagador = await tx.query.payers.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(pagadores.userId, user.id),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(payers.userId, user.id),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
});
|
||||
|
||||
const paymentCategory = await tx.query.categorias.findFirst({
|
||||
const paymentCategory = await tx.query.categories.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(categorias.userId, user.id),
|
||||
eq(categorias.name, "Pagamentos"),
|
||||
eq(categories.userId, user.id),
|
||||
eq(categories.name, "Pagamentos"),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -178,42 +172,42 @@ export async function updateInvoicePaymentStatusAction(
|
||||
period: data.period,
|
||||
isSettled: true,
|
||||
userId: user.id,
|
||||
contaId: card.contaId,
|
||||
categoriaId: paymentCategory?.id ?? null,
|
||||
pagadorId: adminPagador.id,
|
||||
accountId: card.accountId,
|
||||
categoryId: paymentCategory?.id ?? null,
|
||||
payerId: adminPagador.id,
|
||||
};
|
||||
|
||||
const existingPayment = await tx.query.lancamentos.findFirst({
|
||||
const existingPayment = await tx.query.transactions.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.note, invoiceNote),
|
||||
),
|
||||
});
|
||||
|
||||
if (existingPayment) {
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.update(transactions)
|
||||
.set(payload)
|
||||
.where(eq(lancamentos.id, existingPayment.id));
|
||||
.where(eq(transactions.id, existingPayment.id));
|
||||
} else {
|
||||
await tx.insert(lancamentos).values(payload);
|
||||
await tx.insert(transactions).values(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await tx
|
||||
.delete(lancamentos)
|
||||
.delete(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.note, invoiceNote),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
revalidateForEntity("cartoes");
|
||||
revalidateForEntity("cards");
|
||||
|
||||
return { success: true, message: successMessageByStatus[data.status] };
|
||||
} catch (error) {
|
||||
@@ -232,7 +226,7 @@ export async function updateInvoicePaymentStatusAction(
|
||||
}
|
||||
|
||||
const updatePaymentDateSchema = z.object({
|
||||
cartaoId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
cardId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
@@ -253,9 +247,9 @@ export async function updatePaymentDateAction(
|
||||
const data = updatePaymentDateSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
const card = await tx.query.cartoes.findFirst({
|
||||
const card = await tx.query.cards.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(eq(cartoes.id, data.cartaoId), eq(cartoes.userId, user.id)),
|
||||
where: and(eq(cards.id, data.cardId), eq(cards.userId, user.id)),
|
||||
});
|
||||
|
||||
if (!card) {
|
||||
@@ -264,11 +258,11 @@ export async function updatePaymentDateAction(
|
||||
|
||||
const invoiceNote = buildInvoicePaymentNote(card.id, data.period);
|
||||
|
||||
const existingPayment = await tx.query.lancamentos.findFirst({
|
||||
const existingPayment = await tx.query.transactions.findFirst({
|
||||
columns: { id: true },
|
||||
where: and(
|
||||
eq(lancamentos.userId, user.id),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.note, invoiceNote),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -277,14 +271,14 @@ export async function updatePaymentDateAction(
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(lancamentos)
|
||||
.update(transactions)
|
||||
.set({
|
||||
purchaseDate: parseLocalDateString(data.paymentDate),
|
||||
})
|
||||
.where(eq(lancamentos.id, existingPayment.id));
|
||||
.where(eq(transactions.id, existingPayment.id));
|
||||
});
|
||||
|
||||
revalidateForEntity("cartoes");
|
||||
revalidateForEntity("cards");
|
||||
|
||||
return { success: true, message: "Data de pagamento atualizada." };
|
||||
} catch (error) {
|
||||
|
||||
@@ -33,7 +33,7 @@ import { cn } from "@/shared/utils/ui";
|
||||
import { EditPaymentDateDialog } from "./edit-payment-date-dialog";
|
||||
|
||||
type InvoiceSummaryCardProps = {
|
||||
cartaoId: string;
|
||||
cardId: string;
|
||||
period: string;
|
||||
cardName: string;
|
||||
cardBrand: string | null;
|
||||
@@ -74,7 +74,7 @@ const getCardStatusDotColor = (status: string | null) => {
|
||||
};
|
||||
|
||||
export function InvoiceSummaryCard({
|
||||
cartaoId,
|
||||
cardId,
|
||||
period,
|
||||
cardName,
|
||||
cardBrand,
|
||||
@@ -113,7 +113,7 @@ export function InvoiceSummaryCard({
|
||||
const handleAction = () => {
|
||||
startTransition(async () => {
|
||||
const result = await updateInvoicePaymentStatusAction({
|
||||
cartaoId,
|
||||
cardId,
|
||||
period,
|
||||
status: targetStatus,
|
||||
paymentDate:
|
||||
@@ -136,7 +136,7 @@ export function InvoiceSummaryCard({
|
||||
setPaymentDate(newDate);
|
||||
startTransition(async () => {
|
||||
const result = await updatePaymentDateAction({
|
||||
cartaoId,
|
||||
cardId,
|
||||
period,
|
||||
paymentDate: newDate.toISOString().split("T")[0] ?? "",
|
||||
});
|
||||
@@ -177,7 +177,7 @@ export function InvoiceSummaryCard({
|
||||
{cardName}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fatura de {periodLabel}
|
||||
Invoice de {periodLabel}
|
||||
</p>
|
||||
</div>
|
||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { and, desc, eq, type SQL, sum } from "drizzle-orm";
|
||||
import { cartoes, faturas, lancamentos } from "@/db/schema";
|
||||
import { cards, invoices, transactions } from "@/db/schema";
|
||||
import { buildInvoicePaymentNote } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import {
|
||||
@@ -18,8 +18,8 @@ const toNumber = (value: string | number | null | undefined) => {
|
||||
return Number.isNaN(parsed) ? 0 : parsed;
|
||||
};
|
||||
|
||||
export async function fetchCardData(userId: string, cartaoId: string) {
|
||||
const card = await db.query.cartoes.findFirst({
|
||||
export async function fetchCardData(userId: string, cardId: string) {
|
||||
const card = await db.query.cards.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -30,9 +30,9 @@ export async function fetchCardData(userId: string, cartaoId: string) {
|
||||
limit: true,
|
||||
status: true,
|
||||
note: true,
|
||||
contaId: true,
|
||||
accountId: true,
|
||||
},
|
||||
where: and(eq(cartoes.id, cartaoId), eq(cartoes.userId, userId)),
|
||||
where: and(eq(cards.id, cardId), eq(cards.userId, userId)),
|
||||
});
|
||||
|
||||
return card;
|
||||
@@ -40,7 +40,7 @@ export async function fetchCardData(userId: string, cartaoId: string) {
|
||||
|
||||
export async function fetchInvoiceData(
|
||||
userId: string,
|
||||
cartaoId: string,
|
||||
cardId: string,
|
||||
selectedPeriod: string,
|
||||
): Promise<{
|
||||
totalAmount: number;
|
||||
@@ -48,26 +48,26 @@ export async function fetchInvoiceData(
|
||||
paymentDate: Date | null;
|
||||
}> {
|
||||
const [invoiceRow, totalRow] = await Promise.all([
|
||||
db.query.faturas.findFirst({
|
||||
db.query.invoices.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
period: true,
|
||||
paymentStatus: true,
|
||||
},
|
||||
where: and(
|
||||
eq(faturas.cartaoId, cartaoId),
|
||||
eq(faturas.userId, userId),
|
||||
eq(faturas.period, selectedPeriod),
|
||||
eq(invoices.cardId, cardId),
|
||||
eq(invoices.userId, userId),
|
||||
eq(invoices.period, selectedPeriod),
|
||||
),
|
||||
}),
|
||||
db
|
||||
.select({ totalAmount: sum(lancamentos.amount) })
|
||||
.from(lancamentos)
|
||||
.select({ totalAmount: sum(transactions.amount) })
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.cartaoId, cartaoId),
|
||||
eq(lancamentos.period, selectedPeriod),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.cardId, cardId),
|
||||
eq(transactions.period, selectedPeriod),
|
||||
),
|
||||
),
|
||||
]);
|
||||
@@ -85,14 +85,14 @@ export async function fetchInvoiceData(
|
||||
// Buscar data do pagamento se a fatura estiver paga
|
||||
let paymentDate: Date | null = null;
|
||||
if (invoiceStatus === INVOICE_PAYMENT_STATUS.PAID) {
|
||||
const invoiceNote = buildInvoicePaymentNote(cartaoId, selectedPeriod);
|
||||
const paymentLancamento = await db.query.lancamentos.findFirst({
|
||||
const invoiceNote = buildInvoicePaymentNote(cardId, selectedPeriod);
|
||||
const paymentLancamento = await db.query.transactions.findFirst({
|
||||
columns: {
|
||||
purchaseDate: true,
|
||||
},
|
||||
where: and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.note, invoiceNote),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.note, invoiceNote),
|
||||
),
|
||||
});
|
||||
paymentDate = paymentLancamento?.purchaseDate
|
||||
@@ -103,15 +103,15 @@ export async function fetchInvoiceData(
|
||||
return { totalAmount, invoiceStatus, paymentDate };
|
||||
}
|
||||
|
||||
export async function fetchCardLancamentos(filters: SQL[]) {
|
||||
return db.query.lancamentos.findMany({
|
||||
export async function fetchCardTransactions(filters: SQL[]) {
|
||||
return db.query.transactions.findMany({
|
||||
where: and(...filters),
|
||||
with: {
|
||||
pagador: true,
|
||||
conta: true,
|
||||
cartao: true,
|
||||
categoria: true,
|
||||
payer: true,
|
||||
financialAccount: true,
|
||||
card: true,
|
||||
category: true,
|
||||
},
|
||||
orderBy: desc(lancamentos.purchaseDate),
|
||||
orderBy: desc(transactions.purchaseDate),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { anotacoes } from "@/db/schema";
|
||||
import { notes } from "@/db/schema";
|
||||
import {
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
@@ -64,8 +64,8 @@ const deleteNoteSchema = z.object({
|
||||
id: uuidSchema("Anotação"),
|
||||
});
|
||||
|
||||
type NoteCreateInput = z.infer<typeof createNoteSchema>;
|
||||
type NoteUpdateInput = z.infer<typeof updateNoteSchema>;
|
||||
type NoteCreateInput = z.input<typeof createNoteSchema>;
|
||||
type NoteUpdateInput = z.input<typeof updateNoteSchema>;
|
||||
type NoteDeleteInput = z.infer<typeof deleteNoteSchema>;
|
||||
|
||||
export async function createNoteAction(
|
||||
@@ -75,7 +75,7 @@ export async function createNoteAction(
|
||||
const user = await getUser();
|
||||
const data = createNoteSchema.parse(input);
|
||||
|
||||
await db.insert(anotacoes).values({
|
||||
await db.insert(notes).values({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
@@ -84,7 +84,7 @@ export async function createNoteAction(
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("notes");
|
||||
|
||||
return { success: true, message: "Anotação criada com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -100,7 +100,7 @@ export async function updateNoteAction(
|
||||
const data = updateNoteSchema.parse(input);
|
||||
|
||||
const [updated] = await db
|
||||
.update(anotacoes)
|
||||
.update(notes)
|
||||
.set({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
@@ -110,8 +110,8 @@ export async function updateNoteAction(
|
||||
? JSON.stringify(data.tasks)
|
||||
: null,
|
||||
})
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
.where(and(eq(notes.id, data.id), eq(notes.userId, user.id)))
|
||||
.returning({ id: notes.id });
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
@@ -120,7 +120,7 @@ export async function updateNoteAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("notes");
|
||||
|
||||
return { success: true, message: "Anotação atualizada com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -136,9 +136,9 @@ export async function deleteNoteAction(
|
||||
const data = deleteNoteSchema.parse(input);
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(anotacoes)
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
.delete(notes)
|
||||
.where(and(eq(notes.id, data.id), eq(notes.userId, user.id)))
|
||||
.returning({ id: notes.id });
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
@@ -147,7 +147,7 @@ export async function deleteNoteAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("notes");
|
||||
|
||||
return { success: true, message: "Anotação removida com sucesso." };
|
||||
} catch (error) {
|
||||
@@ -157,12 +157,12 @@ export async function deleteNoteAction(
|
||||
|
||||
const arquivarNoteSchema = z.object({
|
||||
id: uuidSchema("Anotação"),
|
||||
arquivada: z.boolean(),
|
||||
archived: z.boolean(),
|
||||
});
|
||||
|
||||
type NoteArquivarInput = z.infer<typeof arquivarNoteSchema>;
|
||||
|
||||
export async function arquivarAnotacaoAction(
|
||||
export async function archiveNoteAction(
|
||||
input: NoteArquivarInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
@@ -170,12 +170,12 @@ export async function arquivarAnotacaoAction(
|
||||
const data = arquivarNoteSchema.parse(input);
|
||||
|
||||
const [updated] = await db
|
||||
.update(anotacoes)
|
||||
.update(notes)
|
||||
.set({
|
||||
arquivada: data.arquivada,
|
||||
archived: data.archived,
|
||||
})
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
.where(and(eq(notes.id, data.id), eq(notes.userId, user.id)))
|
||||
.returning({ id: notes.id });
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
@@ -184,12 +184,12 @@ export async function arquivarAnotacaoAction(
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("notes");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: data.arquivada
|
||||
? "Anotação arquivada com sucesso."
|
||||
message: data.archived
|
||||
? "Anotação archived com sucesso."
|
||||
: "Anotação desarquivada com sucesso.",
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -215,7 +215,7 @@ export function NoteDialog({
|
||||
setDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
setErrorMessage(result.error);
|
||||
setErrorMessage(result.error ?? null);
|
||||
toast.error(result.error);
|
||||
titleRef.current?.focus();
|
||||
});
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
import { RiAddCircleLine, RiTodoLine } from "@remixicon/react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
arquivarAnotacaoAction,
|
||||
deleteNoteAction,
|
||||
} from "@/features/notes/actions";
|
||||
import { archiveNoteAction, deleteNoteAction } from "@/features/notes/actions";
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
import { EmptyState } from "@/shared/components/empty-state";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -115,9 +112,9 @@ export function NotesPage({ notes, archivedNotes }: NotesPageProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await arquivarAnotacaoAction({
|
||||
const result = await archiveNoteAction({
|
||||
id: noteToArquivar.id,
|
||||
arquivada: !isArquivadas,
|
||||
archived: !isArquivadas,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
@@ -171,7 +168,7 @@ export function NotesPage({ notes, archivedNotes }: NotesPageProps) {
|
||||
media={<RiTodoLine className="size-6 text-primary" />}
|
||||
title={
|
||||
isArchived
|
||||
? "Nenhuma anotação arquivada"
|
||||
? "Nenhuma anotação archived"
|
||||
: "Nenhuma anotação registrada"
|
||||
}
|
||||
description={
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface Note {
|
||||
description: string;
|
||||
type: NoteType;
|
||||
tasks?: Task[];
|
||||
arquivada: boolean;
|
||||
archived: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { type Anotacao, anotacoes } from "@/db/schema";
|
||||
import { type Note, notes } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
export type Task = {
|
||||
@@ -14,20 +14,17 @@ export type NoteData = {
|
||||
description: string;
|
||||
type: "nota" | "tarefa";
|
||||
tasks?: Task[];
|
||||
arquivada: boolean;
|
||||
archived: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export async function fetchNotesForUser(userId: string): Promise<NoteData[]> {
|
||||
const noteRows = await db.query.anotacoes.findMany({
|
||||
where: and(eq(anotacoes.userId, userId), eq(anotacoes.arquivada, false)),
|
||||
orderBy: (
|
||||
note: typeof anotacoes.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(note.createdAt)],
|
||||
const noteRows = await db.query.notes.findMany({
|
||||
where: and(eq(notes.userId, userId), eq(notes.archived, false)),
|
||||
orderBy: (table, { desc }) => [desc(table.createdAt)],
|
||||
});
|
||||
|
||||
return noteRows.map((note: Anotacao) => {
|
||||
return noteRows.map((note: Note) => {
|
||||
let tasks: Task[] | undefined;
|
||||
|
||||
// Parse tasks if they exist
|
||||
@@ -46,7 +43,7 @@ export async function fetchNotesForUser(userId: string): Promise<NoteData[]> {
|
||||
description: (note.description ?? "").trim(),
|
||||
type: (note.type ?? "nota") as "nota" | "tarefa",
|
||||
tasks,
|
||||
arquivada: note.arquivada,
|
||||
archived: note.archived,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
};
|
||||
});
|
||||
@@ -57,24 +54,21 @@ export async function fetchAllNotesForUser(
|
||||
): Promise<{ activeNotes: NoteData[]; archivedNotes: NoteData[] }> {
|
||||
const [activeNotes, archivedNotes] = await Promise.all([
|
||||
fetchNotesForUser(userId),
|
||||
fetchArquivadasForUser(userId),
|
||||
fetchArchivedForUser(userId),
|
||||
]);
|
||||
|
||||
return { activeNotes, archivedNotes };
|
||||
}
|
||||
|
||||
export async function fetchArquivadasForUser(
|
||||
export async function fetchArchivedForUser(
|
||||
userId: string,
|
||||
): Promise<NoteData[]> {
|
||||
const noteRows = await db.query.anotacoes.findMany({
|
||||
where: and(eq(anotacoes.userId, userId), eq(anotacoes.arquivada, true)),
|
||||
orderBy: (
|
||||
note: typeof anotacoes.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(note.createdAt)],
|
||||
const noteRows = await db.query.notes.findMany({
|
||||
where: and(eq(notes.userId, userId), eq(notes.archived, true)),
|
||||
orderBy: (table, { desc }) => [desc(table.createdAt)],
|
||||
});
|
||||
|
||||
return noteRows.map((note: Anotacao) => {
|
||||
return noteRows.map((note: Note) => {
|
||||
let tasks: Task[] | undefined;
|
||||
|
||||
// Parse tasks if they exist
|
||||
@@ -93,7 +87,7 @@ export async function fetchArquivadasForUser(
|
||||
description: (note.description ?? "").trim(),
|
||||
type: (note.type ?? "nota") as "nota" | "tarefa",
|
||||
tasks,
|
||||
arquivada: note.arquivada,
|
||||
archived: note.archived,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,10 +6,10 @@ import { and, eq, isNull, ne } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { headers } from "next/headers";
|
||||
import { z } from "zod";
|
||||
import { account, pagadores, tokensApi } from "@/db/schema";
|
||||
import { account, apiTokens, payers } from "@/db/schema";
|
||||
import { auth } from "@/shared/lib/auth/config";
|
||||
import { db, schema } from "@/shared/lib/db";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
|
||||
type ActionResponse<T = void> = {
|
||||
success: boolean;
|
||||
@@ -47,14 +47,12 @@ const updateEmailSchema = z
|
||||
});
|
||||
|
||||
const deleteAccountSchema = z.object({
|
||||
confirmation: z.literal("DELETAR", {
|
||||
errorMap: () => ({ message: 'Você deve digitar "DELETAR" para confirmar' }),
|
||||
}),
|
||||
confirmation: z.literal("DELETAR"),
|
||||
});
|
||||
|
||||
const updatePreferencesSchema = z.object({
|
||||
extratoNoteAsColumn: z.boolean(),
|
||||
lancamentosColumnOrder: z.array(z.string()).nullable(),
|
||||
statementNoteAsColumn: z.boolean(),
|
||||
transactionsColumnOrder: z.array(z.string()).nullable(),
|
||||
});
|
||||
|
||||
// Actions
|
||||
@@ -85,12 +83,12 @@ export async function updateNameAction(
|
||||
|
||||
// Sincronizar nome com o pagador admin
|
||||
await db
|
||||
.update(pagadores)
|
||||
.update(payers)
|
||||
.set({ name: fullName })
|
||||
.where(
|
||||
and(
|
||||
eq(pagadores.userId, session.user.id),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
eq(payers.userId, session.user.id),
|
||||
eq(payers.role, PAYER_ROLE_ADMIN),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -147,7 +145,7 @@ export async function updatePasswordAction(
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"Não é possível alterar senha para contas autenticadas via Google",
|
||||
"Não é possível alterar senha para financialAccounts autenticadas via Google",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,8 +168,8 @@ export async function updatePasswordAction(
|
||||
|
||||
// Verificar se o erro é de senha incorreta
|
||||
if (
|
||||
authError?.message?.includes("password") ||
|
||||
authError?.message?.includes("incorrect")
|
||||
(authError as Error)?.message?.includes("password") ||
|
||||
(authError as Error)?.message?.includes("incorrect")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -253,7 +251,7 @@ export async function updateEmailAction(
|
||||
if (!storedHash) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Conta de credencial não encontrada.",
|
||||
error: "FinancialAccount de credencial não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -350,7 +348,7 @@ export async function deleteAccountAction(
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Conta deletada com sucesso",
|
||||
message: "FinancialAccount deletada com sucesso",
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
@@ -360,7 +358,7 @@ export async function deleteAccountAction(
|
||||
};
|
||||
}
|
||||
|
||||
console.error("Erro ao deletar conta:", error);
|
||||
console.error("Erro ao deletar financialAccount:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Erro ao deletar conta. Tente novamente.",
|
||||
@@ -388,8 +386,8 @@ export async function updatePreferencesAction(
|
||||
// Check if preferences exist, if not create them
|
||||
const existingResult = await db
|
||||
.select()
|
||||
.from(schema.preferenciasUsuario)
|
||||
.where(eq(schema.preferenciasUsuario.userId, session.user.id))
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, session.user.id))
|
||||
.limit(1);
|
||||
|
||||
const existing = existingResult[0] || null;
|
||||
@@ -397,19 +395,19 @@ export async function updatePreferencesAction(
|
||||
if (existing) {
|
||||
// Update existing preferences
|
||||
await db
|
||||
.update(schema.preferenciasUsuario)
|
||||
.update(schema.userPreferences)
|
||||
.set({
|
||||
extratoNoteAsColumn: validated.extratoNoteAsColumn,
|
||||
lancamentosColumnOrder: validated.lancamentosColumnOrder,
|
||||
statementNoteAsColumn: validated.statementNoteAsColumn,
|
||||
transactionsColumnOrder: validated.transactionsColumnOrder,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.preferenciasUsuario.userId, session.user.id));
|
||||
.where(eq(schema.userPreferences.userId, session.user.id));
|
||||
} else {
|
||||
// Create new preferences
|
||||
await db.insert(schema.preferenciasUsuario).values({
|
||||
await db.insert(schema.userPreferences).values({
|
||||
userId: session.user.id,
|
||||
extratoNoteAsColumn: validated.extratoNoteAsColumn,
|
||||
lancamentosColumnOrder: validated.lancamentosColumnOrder,
|
||||
statementNoteAsColumn: validated.statementNoteAsColumn,
|
||||
transactionsColumnOrder: validated.transactionsColumnOrder,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -480,7 +478,7 @@ export async function createApiTokenAction(
|
||||
|
||||
// Save to database
|
||||
const [newToken] = await db
|
||||
.insert(tokensApi)
|
||||
.insert(apiTokens)
|
||||
.values({
|
||||
userId: session.user.id,
|
||||
name: validated.name,
|
||||
@@ -488,7 +486,7 @@ export async function createApiTokenAction(
|
||||
tokenPrefix,
|
||||
expiresAt: null, // No expiration for now
|
||||
})
|
||||
.returning({ id: tokensApi.id });
|
||||
.returning({ id: apiTokens.id });
|
||||
|
||||
revalidatePath("/settings");
|
||||
|
||||
@@ -536,12 +534,12 @@ export async function revokeApiTokenAction(
|
||||
// Find token and verify ownership
|
||||
const [existingToken] = await db
|
||||
.select()
|
||||
.from(tokensApi)
|
||||
.from(apiTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(tokensApi.id, validated.tokenId),
|
||||
eq(tokensApi.userId, session.user.id),
|
||||
isNull(tokensApi.revokedAt),
|
||||
eq(apiTokens.id, validated.tokenId),
|
||||
eq(apiTokens.userId, session.user.id),
|
||||
isNull(apiTokens.revokedAt),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
@@ -555,11 +553,11 @@ export async function revokeApiTokenAction(
|
||||
|
||||
// Revoke token
|
||||
await db
|
||||
.update(tokensApi)
|
||||
.update(apiTokens)
|
||||
.set({
|
||||
revokedAt: new Date(),
|
||||
})
|
||||
.where(eq(tokensApi.id, validated.tokenId));
|
||||
.where(eq(apiTokens.id, validated.tokenId));
|
||||
|
||||
revalidatePath("/settings");
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export function DeleteAccountForm() {
|
||||
const handleDelete = () => {
|
||||
startTransition(async () => {
|
||||
const result = await deleteAccountAction({
|
||||
confirmation,
|
||||
confirmation: confirmation as "DELETAR",
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -79,8 +79,8 @@ export function PasskeysForm() {
|
||||
setPasskeys(
|
||||
(data ?? []).map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
deviceType: p.deviceType,
|
||||
name: p.name ?? null,
|
||||
deviceType: p.deviceType as string,
|
||||
createdAt: p.createdAt ? new Date(p.createdAt) : null,
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -30,8 +30,8 @@ import { Label } from "@/shared/components/ui/label";
|
||||
import { Switch } from "@/shared/components/ui/switch";
|
||||
|
||||
interface PreferencesFormProps {
|
||||
extratoNoteAsColumn: boolean;
|
||||
lancamentosColumnOrder: string[] | null;
|
||||
statementNoteAsColumn: boolean;
|
||||
transactionsColumnOrder: string[] | null;
|
||||
}
|
||||
|
||||
function SortableColumnItem({ id }: { id: string }) {
|
||||
@@ -72,12 +72,12 @@ function SortableColumnItem({ id }: { id: string }) {
|
||||
}
|
||||
|
||||
export function PreferencesForm({
|
||||
extratoNoteAsColumn: initialExtratoNoteAsColumn,
|
||||
lancamentosColumnOrder: initialColumnOrder,
|
||||
statementNoteAsColumn: initialExtratoNoteAsColumn,
|
||||
transactionsColumnOrder: initialColumnOrder,
|
||||
}: PreferencesFormProps) {
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [extratoNoteAsColumn, setExtratoNoteAsColumn] = useState(
|
||||
const [statementNoteAsColumn, setExtratoNoteAsColumn] = useState(
|
||||
initialExtratoNoteAsColumn,
|
||||
);
|
||||
const [columnOrder, setColumnOrder] = useState<string[]>(
|
||||
@@ -107,8 +107,8 @@ export function PreferencesForm({
|
||||
|
||||
startTransition(async () => {
|
||||
const result = await updatePreferencesAction({
|
||||
extratoNoteAsColumn,
|
||||
lancamentosColumnOrder: columnOrder,
|
||||
statementNoteAsColumn,
|
||||
transactionsColumnOrder: columnOrder,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
@@ -145,7 +145,7 @@ export function PreferencesForm({
|
||||
</div>
|
||||
<Switch
|
||||
id="extrato-note-column"
|
||||
checked={extratoNoteAsColumn}
|
||||
checked={statementNoteAsColumn}
|
||||
onCheckedChange={setExtratoNoteAsColumn}
|
||||
disabled={isPending}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { tokensApi } from "@/db/schema";
|
||||
import { apiTokens } from "@/db/schema";
|
||||
import { db, schema } from "@/shared/lib/db";
|
||||
|
||||
export interface UserPreferences {
|
||||
extratoNoteAsColumn: boolean;
|
||||
lancamentosColumnOrder: string[] | null;
|
||||
statementNoteAsColumn: boolean;
|
||||
transactionsColumnOrder: string[] | null;
|
||||
}
|
||||
|
||||
export interface ApiToken {
|
||||
@@ -30,11 +30,11 @@ export async function fetchUserPreferences(
|
||||
): Promise<UserPreferences | null> {
|
||||
const result = await db
|
||||
.select({
|
||||
extratoNoteAsColumn: schema.preferenciasUsuario.extratoNoteAsColumn,
|
||||
lancamentosColumnOrder: schema.preferenciasUsuario.lancamentosColumnOrder,
|
||||
statementNoteAsColumn: schema.userPreferences.statementNoteAsColumn,
|
||||
transactionsColumnOrder: schema.userPreferences.transactionsColumnOrder,
|
||||
})
|
||||
.from(schema.preferenciasUsuario)
|
||||
.where(eq(schema.preferenciasUsuario.userId, userId))
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!result[0]) return null;
|
||||
@@ -45,21 +45,21 @@ export async function fetchUserPreferences(
|
||||
export async function fetchApiTokens(userId: string): Promise<ApiToken[]> {
|
||||
return db
|
||||
.select({
|
||||
id: tokensApi.id,
|
||||
name: tokensApi.name,
|
||||
tokenPrefix: tokensApi.tokenPrefix,
|
||||
lastUsedAt: tokensApi.lastUsedAt,
|
||||
lastUsedIp: tokensApi.lastUsedIp,
|
||||
createdAt: tokensApi.createdAt,
|
||||
expiresAt: tokensApi.expiresAt,
|
||||
revokedAt: tokensApi.revokedAt,
|
||||
id: apiTokens.id,
|
||||
name: apiTokens.name,
|
||||
tokenPrefix: apiTokens.tokenPrefix,
|
||||
lastUsedAt: apiTokens.lastUsedAt,
|
||||
lastUsedIp: apiTokens.lastUsedIp,
|
||||
createdAt: apiTokens.createdAt,
|
||||
expiresAt: apiTokens.expiresAt,
|
||||
revokedAt: apiTokens.revokedAt,
|
||||
})
|
||||
.from(tokensApi)
|
||||
.where(eq(tokensApi.userId, userId))
|
||||
.orderBy(desc(tokensApi.createdAt));
|
||||
.from(apiTokens)
|
||||
.where(eq(apiTokens.userId, userId))
|
||||
.orderBy(desc(apiTokens.createdAt));
|
||||
}
|
||||
|
||||
export async function fetchAjustesPageData(userId: string) {
|
||||
export async function fetchSettingsPageData(userId: string) {
|
||||
const [authProvider, userPreferences, userApiTokens] = await Promise.all([
|
||||
fetchAuthProvider(userId),
|
||||
fetchUserPreferences(userId),
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import {
|
||||
LANCAMENTO_CONDITIONS,
|
||||
LANCAMENTO_PAYMENT_METHODS,
|
||||
LANCAMENTO_TRANSACTION_TYPES,
|
||||
PAYMENT_METHODS,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
|
||||
export const INITIAL_BALANCE_CATEGORY_NAME = "Saldo inicial";
|
||||
export const INITIAL_BALANCE_NOTE = "saldo inicial";
|
||||
|
||||
export const INITIAL_BALANCE_CONDITION =
|
||||
LANCAMENTO_CONDITIONS.find((condition) => condition === "À vista") ??
|
||||
TRANSACTION_CONDITIONS.find((condition) => condition === "À vista") ??
|
||||
"À vista";
|
||||
export const INITIAL_BALANCE_PAYMENT_METHOD =
|
||||
LANCAMENTO_PAYMENT_METHODS.find((method) => method === "Pix") ?? "Pix";
|
||||
PAYMENT_METHODS.find((method) => method === "Pix") ?? "Pix";
|
||||
export const INITIAL_BALANCE_TRANSACTION_TYPE =
|
||||
LANCAMENTO_TRANSACTION_TYPES.find((type) => type === "Receita") ?? "Receita";
|
||||
TRANSACTION_TYPES.find((type) => type === "Receita") ?? "Receita";
|
||||
|
||||
export const ACCOUNT_AUTO_INVOICE_NOTE_PREFIX = "AUTO_FATURA:";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { categorias } from "@/db/schema";
|
||||
import { categories } from "@/db/schema";
|
||||
import type { CategoryType } from "@/shared/lib/categories/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
@@ -42,7 +42,7 @@ export const DEFAULT_CATEGORIES: DefaultCategory[] = [
|
||||
{ name: "Outras receitas", type: "receita", icon: "RiMore2Line" },
|
||||
{ name: "Saldo inicial", type: "receita", icon: "RiWallet2Line" },
|
||||
|
||||
// Categoria especial para transferências entre contas
|
||||
// Category especial para transferências entre financialAccounts
|
||||
{
|
||||
name: "Transferência interna",
|
||||
type: "receita",
|
||||
@@ -59,9 +59,9 @@ export async function seedDefaultCategoriesForUser(userId: string | undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await db.query.categorias.findFirst({
|
||||
const existing = await db.query.categories.findFirst({
|
||||
columns: { id: true },
|
||||
where: eq(categorias.userId, userId),
|
||||
where: eq(categories.userId, userId),
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -72,7 +72,7 @@ export async function seedDefaultCategoriesForUser(userId: string | undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.insert(categorias).values(
|
||||
await db.insert(categories).values(
|
||||
DEFAULT_CATEGORIES.map((category) => ({
|
||||
name: category.name,
|
||||
type: category.type,
|
||||
|
||||
Reference in New Issue
Block a user