feat: bulk delete com escopo para mesma série + recorrente só consome limite após data

Aproveitamento parcial da PR #18 (fechada):

- Bulk delete: quando todos os selecionados pertencem à mesma série
  (parcelado/recorrente), abre dialog de escopo com 3 opções ao invés
  de deletar direto
- Recorrente no cartão: despesa recorrente só consome limite quando
  purchaseDate <= current_date (cartões ativos, inativos e relatório)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-02-28 13:54:08 +00:00
parent f77f729b14
commit 15b2ee18e1
5 changed files with 50 additions and 4 deletions

View File

@@ -5,6 +5,15 @@ Todas as mudanças notáveis deste projeto serão documentadas neste arquivo.
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
## [1.7.4] - 2026-02-28
### Alterado
- Card de análise de parcelas (`/dashboard/analise-parcelas`): layout empilhado no mobile — nome/cartão e valores Total/Pendente em linhas separadas ao invés de lado-a-lado, evitando truncamento
- Página de top estabelecimentos (`/top-estabelecimentos`): cards "Top Estabelecimentos por Frequência" e "Principais Categorias" empilhados verticalmente no mobile (`grid-cols-1 lg:grid-cols-2`)
- Padding da lista de parcelas expandida reduzido no mobile (`px-2 sm:px-8`)
- Ajustes gerais de responsividade em navbar, filtros, skeletons, widgets e dialogs (26 componentes)
## [1.7.3] - 2026-02-27
### Adicionado

View File

@@ -1,4 +1,4 @@
import { and, eq, ilike, isNull, not, or, sql } from "drizzle-orm";
import { and, eq, ilike, isNull, ne, not, or, sql } from "drizzle-orm";
import { cartoes, contas, lancamentos } from "@/db/schema";
import { db } from "@/lib/db";
import { loadLogoOptions } from "@/lib/logo/options";
@@ -72,6 +72,11 @@ export async function fetchCardsForUser(userId: string): Promise<{
and(
eq(lancamentos.userId, userId),
or(isNull(lancamentos.isSettled), eq(lancamentos.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`,
),
),
)
.groupBy(lancamentos.cartaoId),
@@ -164,6 +169,11 @@ export async function fetchInativosForUser(userId: string): Promise<{
and(
eq(lancamentos.userId, userId),
or(isNull(lancamentos.isSettled), eq(lancamentos.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`,
),
),
)
.groupBy(lancamentos.cartaoId),

View File

@@ -296,6 +296,17 @@ export function LancamentosPage({
}, []);
const handleMultipleBulkDelete = useCallback((items: LancamentoItem[]) => {
// Se todos os selecionados são da mesma série (parcelado/recorrente), abrir dialog de escopo
const withSeries = items.filter((i) => i.seriesId);
const sameSeries =
withSeries.length > 0 &&
withSeries.length === items.length &&
withSeries.every((i) => i.seriesId === withSeries[0]?.seriesId);
if (sameSeries && withSeries[0]) {
setPendingDeleteData(withSeries[0]);
setBulkDeleteOpen(true);
return;
}
setPendingMultipleDeleteData(items);
setMultipleBulkDeleteOpen(true);
}, []);

View File

@@ -1,4 +1,16 @@
import { and, eq, gte, ilike, inArray, lte, not, sum } from "drizzle-orm";
import {
and,
eq,
gte,
ilike,
inArray,
lte,
ne,
not,
or,
sql,
sum,
} from "drizzle-orm";
import {
cartoes,
categorias,
@@ -97,7 +109,7 @@ export async function fetchCartoesReportData(
const cardIds = allCards.map((c) => c.id);
// Fetch current period usage by card
// Fetch current period usage by card (recorrente só conta quando a data da ocorrência já passou)
const currentUsageData = await db
.select({
cartaoId: lancamentos.cartaoId,
@@ -112,6 +124,10 @@ export async function fetchCartoesReportData(
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
eq(lancamentos.transactionType, DESPESA),
inArray(lancamentos.cartaoId, cardIds),
or(
ne(lancamentos.condition, "Recorrente"),
sql`${lancamentos.purchaseDate} <= current_date`,
),
),
)
.groupBy(lancamentos.cartaoId);

View File

@@ -1,6 +1,6 @@
{
"name": "openmonetis",
"version": "1.7.3",
"version": "1.7.4",
"private": true,
"scripts": {
"dev": "next dev --turbopack",