refactor: migrate from ESLint to Biome and extract SQL queries to data.ts

- Replace ESLint with Biome for linting and formatting
- Configure Biome with tabs, double quotes, and organized imports
- Move all SQL/Drizzle queries from page.tsx files to data.ts files
- Create new data.ts files for: ajustes, dashboard, relatorios/categorias
- Update existing data.ts files: extrato, fatura (add lancamentos queries)
- Remove all drizzle-orm imports from page.tsx files
- Update README.md with new tooling info

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-01-27 13:15:37 +00:00
parent 8ffe61c59b
commit a7f63fb77a
442 changed files with 66141 additions and 69292 deletions

View File

@@ -5,9 +5,9 @@ import type { EligibleInstallment } from "./anticipation-types";
* Calcula o valor total de antecipação baseado nas parcelas selecionadas
*/
export function calculateTotalAnticipationAmount(
installments: EligibleInstallment[]
installments: EligibleInstallment[],
): number {
return installments.reduce((sum, inst) => sum + Number(inst.amount), 0);
return installments.reduce((sum, inst) => sum + Number(inst.amount), 0);
}
/**
@@ -15,16 +15,16 @@ export function calculateTotalAnticipationAmount(
* O período não pode ser anterior ao período da primeira parcela selecionada
*/
export function validateAnticipationPeriod(
period: string,
installments: EligibleInstallment[]
period: string,
installments: EligibleInstallment[],
): boolean {
if (installments.length === 0) return false;
if (installments.length === 0) return false;
const earliestPeriod = installments.reduce((earliest, inst) => {
return inst.period < earliest ? inst.period : earliest;
}, installments[0].period);
const earliestPeriod = installments.reduce((earliest, inst) => {
return inst.period < earliest ? inst.period : earliest;
}, installments[0].period);
return period >= earliestPeriod;
return period >= earliestPeriod;
}
/**
@@ -32,14 +32,14 @@ export function validateAnticipationPeriod(
* Exemplo: "1, 2, 3" ou "5, 6, 7, 8"
*/
export function getAnticipatedInstallmentNumbers(
installments: EligibleInstallment[]
installments: EligibleInstallment[],
): string {
const numbers = installments
.map((inst) => inst.currentInstallment)
.filter((num): num is number => num !== null)
.sort((a, b) => a - b)
.join(", ");
return numbers;
const numbers = installments
.map((inst) => inst.currentInstallment)
.filter((num): num is number => num !== null)
.sort((a, b) => a - b)
.join(", ");
return numbers;
}
/**
@@ -47,34 +47,34 @@ export function getAnticipatedInstallmentNumbers(
* Exemplo: "Parcelas 1-3 de 12" ou "Parcela 5 de 12"
*/
export function formatAnticipatedInstallmentsRange(
installments: EligibleInstallment[]
installments: EligibleInstallment[],
): string {
const numbers = installments
.map((inst) => inst.currentInstallment)
.filter((num): num is number => num !== null)
.sort((a, b) => a - b);
const numbers = installments
.map((inst) => inst.currentInstallment)
.filter((num): num is number => num !== null)
.sort((a, b) => a - b);
if (numbers.length === 0) return "";
if (numbers.length === 1) {
const total = installments[0]?.installmentCount ?? 0;
return `Parcela ${numbers[0]} de ${total}`;
}
if (numbers.length === 0) return "";
if (numbers.length === 1) {
const total = installments[0]?.installmentCount ?? 0;
return `Parcela ${numbers[0]} de ${total}`;
}
const first = numbers[0];
const last = numbers[numbers.length - 1];
const total = installments[0]?.installmentCount ?? 0;
const first = numbers[0];
const last = numbers[numbers.length - 1];
const total = installments[0]?.installmentCount ?? 0;
// Se as parcelas são consecutivas
const isConsecutive = numbers.every((num, i) => {
if (i === 0) return true;
return num === numbers[i - 1]! + 1;
});
// Se as parcelas são consecutivas
const isConsecutive = numbers.every((num, i) => {
if (i === 0) return true;
return num === numbers[i - 1]! + 1;
});
if (isConsecutive) {
return `Parcelas ${first}-${last} de ${total}`;
} else {
return `${numbers.length} parcelas de ${total}`;
}
if (isConsecutive) {
return `Parcelas ${first}-${last} de ${total}`;
} else {
return `${numbers.length} parcelas de ${total}`;
}
}
/**
@@ -82,62 +82,62 @@ export function formatAnticipatedInstallmentsRange(
* Só pode cancelar se o lançamento de antecipação não foi pago
*/
export function canCancelAnticipation(lancamento: Lancamento): boolean {
return lancamento.isSettled !== true;
return lancamento.isSettled !== true;
}
/**
* Ordena parcelas por número da parcela atual
*/
export function sortInstallmentsByNumber(
installments: EligibleInstallment[]
installments: EligibleInstallment[],
): EligibleInstallment[] {
return [...installments].sort((a, b) => {
const aNum = a.currentInstallment ?? 0;
const bNum = b.currentInstallment ?? 0;
return aNum - bNum;
});
return [...installments].sort((a, b) => {
const aNum = a.currentInstallment ?? 0;
const bNum = b.currentInstallment ?? 0;
return aNum - bNum;
});
}
/**
* Calcula quantas parcelas restam após uma antecipação
*/
export function calculateRemainingInstallments(
totalInstallments: number,
anticipatedCount: number
totalInstallments: number,
anticipatedCount: number,
): number {
return Math.max(0, totalInstallments - anticipatedCount);
return Math.max(0, totalInstallments - anticipatedCount);
}
/**
* Valida se as parcelas selecionadas pertencem à mesma série
*/
export function validateInstallmentsSameSeries(
installments: EligibleInstallment[],
seriesId: string
installments: EligibleInstallment[],
_seriesId: string,
): boolean {
// Esta validação será feita no servidor com os dados completos
// Aqui apenas retorna true como placeholder
return installments.length > 0;
// Esta validação será feita no servidor com os dados completos
// Aqui apenas retorna true como placeholder
return installments.length > 0;
}
/**
* Gera descrição automática para o lançamento de antecipação
*/
export function generateAnticipationDescription(
lancamentoName: string,
installmentCount: number
lancamentoName: string,
installmentCount: number,
): string {
return `Antecipação de ${installmentCount} ${
installmentCount === 1 ? "parcela" : "parcelas"
} - ${lancamentoName}`;
return `Antecipação de ${installmentCount} ${
installmentCount === 1 ? "parcela" : "parcelas"
} - ${lancamentoName}`;
}
/**
* Formata nota automática para antecipação
*/
export function generateAnticipationNote(
installments: EligibleInstallment[]
installments: EligibleInstallment[],
): string {
const range = formatAnticipatedInstallmentsRange(installments);
return `Antecipação: ${range}`;
const range = formatAnticipatedInstallmentsRange(installments);
return `Antecipação: ${range}`;
}

View File

@@ -1,66 +1,66 @@
import type {
Categoria,
InstallmentAnticipation,
Lancamento,
Pagador,
Categoria,
InstallmentAnticipation,
Lancamento,
Pagador,
} from "@/db/schema";
/**
* Parcela elegível para antecipação
*/
export type EligibleInstallment = {
id: string;
name: string;
amount: string;
period: string;
purchaseDate: Date;
dueDate: Date | null;
currentInstallment: number | null;
installmentCount: number | null;
paymentMethod: string;
categoriaId: string | null;
pagadorId: string | null;
id: string;
name: string;
amount: string;
period: string;
purchaseDate: Date;
dueDate: Date | null;
currentInstallment: number | null;
installmentCount: number | null;
paymentMethod: string;
categoriaId: string | null;
pagadorId: string | null;
};
/**
* Antecipação com dados completos
*/
export type InstallmentAnticipationWithRelations = InstallmentAnticipation & {
lancamento: Lancamento;
pagador: Pagador | null;
categoria: Categoria | null;
lancamento: Lancamento;
pagador: Pagador | null;
categoria: Categoria | null;
};
/**
* Input para criar antecipação
*/
export type CreateAnticipationInput = {
seriesId: string;
installmentIds: string[];
anticipationPeriod: string;
pagadorId?: string;
categoriaId?: string;
note?: string;
seriesId: string;
installmentIds: string[];
anticipationPeriod: string;
pagadorId?: string;
categoriaId?: string;
note?: string;
};
/**
* Input para cancelar antecipação
*/
export type CancelAnticipationInput = {
anticipationId: string;
anticipationId: string;
};
/**
* Resumo de antecipação para exibição
*/
export type AnticipationSummary = {
id: string;
totalAmount: string;
installmentCount: number;
anticipationPeriod: string;
anticipationDate: Date;
note: string | null;
isSettled: boolean;
lancamentoId: string;
anticipatedInstallments: string[];
id: string;
totalAmount: string;
installmentCount: number;
anticipationPeriod: string;
anticipationDate: Date;
note: string | null;
isSettled: boolean;
lancamentoId: string;
anticipatedInstallments: string[];
};

View File

@@ -6,34 +6,34 @@
* @returns Data da última parcela
*/
export function calculateLastInstallmentDate(
currentPeriod: string,
currentInstallment: number,
totalInstallments: number
currentPeriod: string,
currentInstallment: number,
totalInstallments: number,
): Date {
// Parse do período atual (formato: "YYYY-MM")
const [yearStr, monthStr] = currentPeriod.split("-");
const year = Number.parseInt(yearStr ?? "", 10);
const monthIndex = Number.parseInt(monthStr ?? "", 10) - 1; // 0-indexed
// Parse do período atual (formato: "YYYY-MM")
const [yearStr, monthStr] = currentPeriod.split("-");
const year = Number.parseInt(yearStr ?? "", 10);
const monthIndex = Number.parseInt(monthStr ?? "", 10) - 1; // 0-indexed
if (Number.isNaN(year) || Number.isNaN(monthIndex)) {
return new Date();
}
if (Number.isNaN(year) || Number.isNaN(monthIndex)) {
return new Date();
}
// Cria data do período atual (parcela atual)
const currentDate = new Date(year, monthIndex, 1);
// Cria data do período atual (parcela atual)
const currentDate = new Date(year, monthIndex, 1);
// Calcula quantas parcelas faltam (incluindo a atual)
// Ex: parcela 2 de 6 -> restam 5 parcelas (2, 3, 4, 5, 6)
const remainingInstallments = totalInstallments - currentInstallment + 1;
// Calcula quantas parcelas faltam (incluindo a atual)
// Ex: parcela 2 de 6 -> restam 5 parcelas (2, 3, 4, 5, 6)
const remainingInstallments = totalInstallments - currentInstallment + 1;
// Calcula quantos meses adicionar para chegar na última parcela
// Ex: restam 5 parcelas -> adicionar 4 meses (parcela atual + 4 = 5 parcelas)
const monthsToAdd = remainingInstallments - 1;
// Calcula quantos meses adicionar para chegar na última parcela
// Ex: restam 5 parcelas -> adicionar 4 meses (parcela atual + 4 = 5 parcelas)
const monthsToAdd = remainingInstallments - 1;
// Simplificando: monthsToAdd = totalInstallments - currentInstallment
currentDate.setMonth(currentDate.getMonth() + monthsToAdd);
// Simplificando: monthsToAdd = totalInstallments - currentInstallment
currentDate.setMonth(currentDate.getMonth() + monthsToAdd);
return currentDate;
return currentDate;
}
/**
@@ -41,15 +41,15 @@ export function calculateLastInstallmentDate(
* Exemplo: "Março de 2026"
*/
export function formatLastInstallmentDate(date: Date): string {
const formatter = new Intl.DateTimeFormat("pt-BR", {
month: "long",
year: "numeric",
timeZone: "UTC",
});
const formatter = new Intl.DateTimeFormat("pt-BR", {
month: "long",
year: "numeric",
timeZone: "UTC",
});
const formatted = formatter.format(date);
// Capitaliza a primeira letra
return formatted.charAt(0).toUpperCase() + formatted.slice(1);
const formatted = formatter.format(date);
// Capitaliza a primeira letra
return formatted.charAt(0).toUpperCase() + formatted.slice(1);
}
/**
@@ -57,14 +57,14 @@ export function formatLastInstallmentDate(date: Date): string {
* Exemplo: "qua, 24 set"
*/
export function formatPurchaseDate(date: Date): string {
const formatter = new Intl.DateTimeFormat("pt-BR", {
weekday: "short",
day: "2-digit",
month: "short",
timeZone: "UTC",
});
const formatter = new Intl.DateTimeFormat("pt-BR", {
weekday: "short",
day: "2-digit",
month: "short",
timeZone: "UTC",
});
return formatter.format(date);
return formatter.format(date);
}
/**
@@ -72,8 +72,8 @@ export function formatPurchaseDate(date: Date): string {
* Exemplo: "1 de 6"
*/
export function formatCurrentInstallment(
current: number,
total: number
current: number,
total: number,
): string {
return `${current} de ${total}`;
return `${current} de ${total}`;
}