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

@@ -3,7 +3,7 @@
import { RiLoader2Line } from "@remixicon/react";
const LoadingSpinner = () => (
<RiLoader2Line size={"20"} className="animate-spin" />
<RiLoader2Line size={"20"} className="animate-spin" />
);
export default LoadingSpinner;

View File

@@ -1,118 +1,118 @@
"use client";
import { Card } from "@/components/ui/card";
import { useMonthPeriod } from "@/hooks/use-month-period";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useTransition } from "react";
import { Card } from "@/components/ui/card";
import { useMonthPeriod } from "@/hooks/use-month-period";
import LoadingSpinner from "./loading-spinner";
import NavigationButton from "./nav-button";
import ReturnButton from "./return-button";
export default function MonthNavigation() {
const {
monthNames,
currentMonth,
currentYear,
defaultMonth,
defaultYear,
buildHref,
} = useMonthPeriod();
const {
monthNames,
currentMonth,
currentYear,
defaultMonth,
defaultYear,
buildHref,
} = useMonthPeriod();
const router = useRouter();
const [isPending, startTransition] = useTransition();
const router = useRouter();
const [isPending, startTransition] = useTransition();
const currentMonthLabel = useMemo(
() => currentMonth.charAt(0).toUpperCase() + currentMonth.slice(1),
[currentMonth]
);
const currentMonthLabel = useMemo(
() => currentMonth.charAt(0).toUpperCase() + currentMonth.slice(1),
[currentMonth],
);
const currentMonthIndex = useMemo(
() => monthNames.indexOf(currentMonth),
[monthNames, currentMonth]
);
const currentMonthIndex = useMemo(
() => monthNames.indexOf(currentMonth),
[monthNames, currentMonth],
);
const prevTarget = useMemo(() => {
let idx = currentMonthIndex - 1;
let year = currentYear;
if (idx < 0) {
idx = monthNames.length - 1;
year = (parseInt(currentYear) - 1).toString();
}
return buildHref(monthNames[idx], year);
}, [currentMonthIndex, currentYear, monthNames, buildHref]);
const prevTarget = useMemo(() => {
let idx = currentMonthIndex - 1;
let year = currentYear;
if (idx < 0) {
idx = monthNames.length - 1;
year = (parseInt(currentYear, 10) - 1).toString();
}
return buildHref(monthNames[idx], year);
}, [currentMonthIndex, currentYear, monthNames, buildHref]);
const nextTarget = useMemo(() => {
let idx = currentMonthIndex + 1;
let year = currentYear;
if (idx >= monthNames.length) {
idx = 0;
year = (parseInt(currentYear) + 1).toString();
}
return buildHref(monthNames[idx], year);
}, [currentMonthIndex, currentYear, monthNames, buildHref]);
const nextTarget = useMemo(() => {
let idx = currentMonthIndex + 1;
let year = currentYear;
if (idx >= monthNames.length) {
idx = 0;
year = (parseInt(currentYear, 10) + 1).toString();
}
return buildHref(monthNames[idx], year);
}, [currentMonthIndex, currentYear, monthNames, buildHref]);
const returnTarget = useMemo(
() => buildHref(defaultMonth, defaultYear),
[buildHref, defaultMonth, defaultYear]
);
const returnTarget = useMemo(
() => buildHref(defaultMonth, defaultYear),
[buildHref, defaultMonth, defaultYear],
);
const isDifferentFromCurrent =
currentMonth !== defaultMonth || currentYear !== defaultYear.toString();
const isDifferentFromCurrent =
currentMonth !== defaultMonth || currentYear !== defaultYear.toString();
// Prefetch otimizado: apenas meses adjacentes (M-1, M+1) e mês atual
// Isso melhora a performance da navegação sem sobrecarregar o cliente
useEffect(() => {
// Prefetch do mês anterior e próximo para navegação instantânea
router.prefetch(prevTarget);
router.prefetch(nextTarget);
// Prefetch otimizado: apenas meses adjacentes (M-1, M+1) e mês atual
// Isso melhora a performance da navegação sem sobrecarregar o cliente
useEffect(() => {
// Prefetch do mês anterior e próximo para navegação instantânea
router.prefetch(prevTarget);
router.prefetch(nextTarget);
// Prefetch do mês atual se não estivermos nele
if (isDifferentFromCurrent) {
router.prefetch(returnTarget);
}
}, [router, prevTarget, nextTarget, returnTarget, isDifferentFromCurrent]);
// Prefetch do mês atual se não estivermos nele
if (isDifferentFromCurrent) {
router.prefetch(returnTarget);
}
}, [router, prevTarget, nextTarget, returnTarget, isDifferentFromCurrent]);
const handleNavigate = (href: string) => {
startTransition(() => {
router.replace(href, { scroll: false });
});
};
const handleNavigate = (href: string) => {
startTransition(() => {
router.replace(href, { scroll: false });
});
};
return (
<Card className="sticky top-0 z-30 w-full flex-row bg-month-picker text-month-picker-foreground p-4">
<div className="flex items-center gap-1">
<NavigationButton
direction="left"
disabled={isPending}
onClick={() => handleNavigate(prevTarget)}
/>
return (
<Card className="sticky top-0 z-30 w-full flex-row bg-month-picker text-month-picker-foreground p-4">
<div className="flex items-center gap-1">
<NavigationButton
direction="left"
disabled={isPending}
onClick={() => handleNavigate(prevTarget)}
/>
<div className="flex items-center">
<div
className="mx-1 space-x-1 capitalize font-bold"
aria-current={!isDifferentFromCurrent ? "date" : undefined}
aria-label={`Período selecionado: ${currentMonthLabel} de ${currentYear}`}
>
<span>{currentMonthLabel}</span>
<span>{currentYear}</span>
</div>
<div className="flex items-center">
<div
className="mx-1 space-x-1 capitalize font-bold"
aria-current={!isDifferentFromCurrent ? "date" : undefined}
aria-label={`Período selecionado: ${currentMonthLabel} de ${currentYear}`}
>
<span>{currentMonthLabel}</span>
<span>{currentYear}</span>
</div>
{isPending && <LoadingSpinner />}
</div>
{isPending && <LoadingSpinner />}
</div>
<NavigationButton
direction="right"
disabled={isPending}
onClick={() => handleNavigate(nextTarget)}
/>
</div>
<NavigationButton
direction="right"
disabled={isPending}
onClick={() => handleNavigate(nextTarget)}
/>
</div>
{isDifferentFromCurrent && (
<ReturnButton
disabled={isPending}
onClick={() => handleNavigate(returnTarget)}
/>
)}
</Card>
);
{isDifferentFromCurrent && (
<ReturnButton
disabled={isPending}
onClick={() => handleNavigate(returnTarget)}
/>
)}
</Card>
);
}

View File

@@ -4,28 +4,28 @@ import { RiArrowLeftSLine, RiArrowRightSLine } from "@remixicon/react";
import React from "react";
interface NavigationButtonProps {
direction: "left" | "right";
disabled?: boolean;
onClick: () => void;
direction: "left" | "right";
disabled?: boolean;
onClick: () => void;
}
const NavigationButton = React.memo(
({ direction, disabled, onClick }: NavigationButtonProps) => {
const Icon = direction === "left" ? RiArrowLeftSLine : RiArrowRightSLine;
({ direction, disabled, onClick }: NavigationButtonProps) => {
const Icon = direction === "left" ? RiArrowLeftSLine : RiArrowRightSLine;
return (
<button
onClick={onClick}
className="text-month-picker-foreground transition-all duration-200 cursor-pointer rounded-lg p-1 hover:bg-month-picker-foreground/10 focus:outline-hidden focus:ring-2 focus:ring-month-picker-foreground/30 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"
disabled={disabled}
aria-label={`Navegar para o mês ${
direction === "left" ? "anterior" : "seguinte"
}`}
>
<Icon className="text-primary" size={18} />
</button>
);
}
return (
<button
onClick={onClick}
className="text-month-picker-foreground transition-all duration-200 cursor-pointer rounded-lg p-1 hover:bg-month-picker-foreground/10 focus:outline-hidden focus:ring-2 focus:ring-month-picker-foreground/30 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"
disabled={disabled}
aria-label={`Navegar para o mês ${
direction === "left" ? "anterior" : "seguinte"
}`}
>
<Icon className="text-primary" size={18} />
</button>
);
},
);
NavigationButton.displayName = "NavigationButton";

View File

@@ -4,22 +4,22 @@ import React from "react";
import { Button } from "../ui/button";
interface ReturnButtonProps {
disabled?: boolean;
onClick: () => void;
disabled?: boolean;
onClick: () => void;
}
const ReturnButton = React.memo(({ disabled, onClick }: ReturnButtonProps) => {
return (
<Button
className="w-28 h-6 rounded-sm"
size="sm"
disabled={disabled}
onClick={onClick}
aria-label="Retornar para o mês atual"
>
Mês Atual
</Button>
);
return (
<Button
className="w-28 h-6 rounded-sm"
size="sm"
disabled={disabled}
onClick={onClick}
aria-label="Retornar para o mês atual"
>
Mês Atual
</Button>
);
});
ReturnButton.displayName = "ReturnButton";