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

@@ -1,194 +1,193 @@
"use client";
import {
RiFilter3Line,
RiLineChartLine,
RiPieChartLine,
RiTable2,
} from "@remixicon/react";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useMemo, useState, useTransition } from "react";
import { EmptyState } from "@/components/empty-state";
import { CategoryReportSkeleton } from "@/components/skeletons/category-report-skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import type { CategoryChartData } from "@/lib/relatorios/fetch-category-chart-data";
import type { CategoryReportData } from "@/lib/relatorios/types";
import type { CategoryOption, FilterState } from "./types";
import { CategoryReportCards } from "./category-report-cards";
import { CategoryReportChart } from "./category-report-chart";
import { CategoryReportExport } from "./category-report-export";
import { CategoryReportFilters } from "./category-report-filters";
import { CategoryReportTable } from "./category-report-table";
import { CategoryReportCards } from "./category-report-cards";
import { CategoryReportExport } from "./category-report-export";
import { Skeleton } from "@/components/ui/skeleton";
import { EmptyState } from "@/components/empty-state";
import {
RiFilter3Line,
RiPieChartLine,
RiTable2,
RiLineChartLine,
} from "@remixicon/react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { CategoryReportChart } from "./category-report-chart";
import type { CategoryChartData } from "@/lib/relatorios/fetch-category-chart-data";
import { CategoryReportSkeleton } from "@/components/skeletons/category-report-skeleton";
import type { CategoryOption, FilterState } from "./types";
interface CategoryReportPageProps {
initialData: CategoryReportData;
categories: CategoryOption[];
initialFilters: FilterState;
chartData: CategoryChartData;
initialData: CategoryReportData;
categories: CategoryOption[];
initialFilters: FilterState;
chartData: CategoryChartData;
}
export function CategoryReportPage({
initialData,
categories,
initialFilters,
chartData,
initialData,
categories,
initialFilters,
chartData,
}: CategoryReportPageProps) {
const router = useRouter();
const searchParams = useSearchParams();
const [isPending, startTransition] = useTransition();
const router = useRouter();
const searchParams = useSearchParams();
const [isPending, startTransition] = useTransition();
const [filters, setFilters] = useState<FilterState>(initialFilters);
const [data, setData] = useState<CategoryReportData>(initialData);
const [filters, setFilters] = useState<FilterState>(initialFilters);
const [data, setData] = useState<CategoryReportData>(initialData);
// Get active tab from URL or default to "table"
const activeTab = searchParams.get("aba") || "table";
// Get active tab from URL or default to "table"
const activeTab = searchParams.get("aba") || "table";
// Debounce timer
const [debounceTimer, setDebounceTimer] = useState<NodeJS.Timeout | null>(
null
);
// Debounce timer
const [debounceTimer, setDebounceTimer] = useState<NodeJS.Timeout | null>(
null,
);
const handleFiltersChange = useCallback(
(newFilters: FilterState) => {
setFilters(newFilters);
const handleFiltersChange = useCallback(
(newFilters: FilterState) => {
setFilters(newFilters);
// Clear existing timer
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// Clear existing timer
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// Set new debounced timer (300ms)
const timer = setTimeout(() => {
startTransition(() => {
// Build new URL with query params
const params = new URLSearchParams(searchParams.toString());
// Set new debounced timer (300ms)
const timer = setTimeout(() => {
startTransition(() => {
// Build new URL with query params
const params = new URLSearchParams(searchParams.toString());
params.set("inicio", newFilters.startPeriod);
params.set("fim", newFilters.endPeriod);
params.set("inicio", newFilters.startPeriod);
params.set("fim", newFilters.endPeriod);
if (newFilters.selectedCategories.length > 0) {
params.set("categorias", newFilters.selectedCategories.join(","));
} else {
params.delete("categorias");
}
if (newFilters.selectedCategories.length > 0) {
params.set("categorias", newFilters.selectedCategories.join(","));
} else {
params.delete("categorias");
}
// Preserve current tab
const currentTab = searchParams.get("aba");
if (currentTab) {
params.set("aba", currentTab);
}
// Preserve current tab
const currentTab = searchParams.get("aba");
if (currentTab) {
params.set("aba", currentTab);
}
// Navigate with new params (this will trigger server component re-render)
router.push(`?${params.toString()}`, { scroll: false });
});
}, 300);
// Navigate with new params (this will trigger server component re-render)
router.push(`?${params.toString()}`, { scroll: false });
});
}, 300);
setDebounceTimer(timer);
},
[debounceTimer, router, searchParams]
);
setDebounceTimer(timer);
},
[debounceTimer, router, searchParams],
);
// Handle tab change
const handleTabChange = useCallback(
(value: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set("aba", value);
router.push(`?${params.toString()}`, { scroll: false });
},
[router, searchParams]
);
// Handle tab change
const handleTabChange = useCallback(
(value: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set("aba", value);
router.push(`?${params.toString()}`, { scroll: false });
},
[router, searchParams],
);
// Update data when initialData changes (from server)
useMemo(() => {
setData(initialData);
}, [initialData]);
// Update data when initialData changes (from server)
useMemo(() => {
setData(initialData);
}, [initialData]);
// Check if no categories are available
const hasNoCategories = categories.length === 0;
// Check if no categories are available
const hasNoCategories = categories.length === 0;
// Check if no data in period
const hasNoData = data.categories.length === 0 && !hasNoCategories;
// Check if no data in period
const hasNoData = data.categories.length === 0 && !hasNoCategories;
return (
<div className="flex flex-col gap-6">
{/* Filters */}
<CategoryReportFilters
categories={categories}
filters={filters}
onFiltersChange={handleFiltersChange}
exportButton={<CategoryReportExport data={data} filters={filters} />}
/>
return (
<div className="flex flex-col gap-6">
{/* Filters */}
<CategoryReportFilters
categories={categories}
filters={filters}
onFiltersChange={handleFiltersChange}
exportButton={<CategoryReportExport data={data} filters={filters} />}
/>
{/* Loading State */}
{isPending && <CategoryReportSkeleton />}
{/* Loading State */}
{isPending && <CategoryReportSkeleton />}
{/* Empty States */}
{!isPending && hasNoCategories && (
<EmptyState
title="Nenhuma categoria cadastrada"
description="Você precisa cadastrar categorias antes de visualizar o relatório."
media={<RiPieChartLine className="h-12 w-12" />}
mediaVariant="icon"
/>
)}
{/* Empty States */}
{!isPending && hasNoCategories && (
<EmptyState
title="Nenhuma categoria cadastrada"
description="Você precisa cadastrar categorias antes de visualizar o relatório."
media={<RiPieChartLine className="h-12 w-12" />}
mediaVariant="icon"
/>
)}
{!isPending &&
!hasNoCategories &&
hasNoData &&
filters.selectedCategories.length === 0 && (
<EmptyState
title="Selecione pelo menos uma categoria"
description="Use o filtro acima para selecionar as categorias que deseja visualizar no relatório."
media={<RiFilter3Line className="h-12 w-12" />}
mediaVariant="icon"
/>
)}
{!isPending &&
!hasNoCategories &&
hasNoData &&
filters.selectedCategories.length === 0 && (
<EmptyState
title="Selecione pelo menos uma categoria"
description="Use o filtro acima para selecionar as categorias que deseja visualizar no relatório."
media={<RiFilter3Line className="h-12 w-12" />}
mediaVariant="icon"
/>
)}
{!isPending &&
!hasNoCategories &&
hasNoData &&
filters.selectedCategories.length > 0 && (
<EmptyState
title="Nenhum lançamento encontrado"
description="Não há transações no período selecionado para as categorias filtradas."
media={<RiPieChartLine className="h-12 w-12" />}
mediaVariant="icon"
/>
)}
{!isPending &&
!hasNoCategories &&
hasNoData &&
filters.selectedCategories.length > 0 && (
<EmptyState
title="Nenhum lançamento encontrado"
description="Não há transações no período selecionado para as categorias filtradas."
media={<RiPieChartLine className="h-12 w-12" />}
mediaVariant="icon"
/>
)}
{/* Tabs: Table and Chart */}
{!isPending && !hasNoCategories && !hasNoData && (
<Tabs
value={activeTab}
onValueChange={handleTabChange}
className="w-full"
>
<TabsList>
<TabsTrigger value="table">
<RiTable2 className="h-4 w-4 mr-2" />
Tabela
</TabsTrigger>
<TabsTrigger value="chart">
<RiLineChartLine className="h-4 w-4 mr-2" />
Gráfico
</TabsTrigger>
</TabsList>
{/* Tabs: Table and Chart */}
{!isPending && !hasNoCategories && !hasNoData && (
<Tabs
value={activeTab}
onValueChange={handleTabChange}
className="w-full"
>
<TabsList>
<TabsTrigger value="table">
<RiTable2 className="h-4 w-4 mr-2" />
Tabela
</TabsTrigger>
<TabsTrigger value="chart">
<RiLineChartLine className="h-4 w-4 mr-2" />
Gráfico
</TabsTrigger>
</TabsList>
<TabsContent value="table" className="mt-4">
{/* Desktop Table */}
<div className="hidden md:block">
<CategoryReportTable data={data} />
</div>
<TabsContent value="table" className="mt-4">
{/* Desktop Table */}
<div className="hidden md:block">
<CategoryReportTable data={data} />
</div>
{/* Mobile Cards */}
<CategoryReportCards data={data} />
</TabsContent>
{/* Mobile Cards */}
<CategoryReportCards data={data} />
</TabsContent>
<TabsContent value="chart" className="mt-4">
<CategoryReportChart data={chartData} />
</TabsContent>
</Tabs>
)}
</div>
);
<TabsContent value="chart" className="mt-4">
<CategoryReportChart data={chartData} />
</TabsContent>
</Tabs>
)}
</div>
);
}