Migrate all components and pages to shadcn/ui with DataTable
All checks were successful
Deploy to VPS / deploy (push) Successful in 28s

Replace custom markup across all pages and components with shadcn/ui
primitives (Dialog, Sheet, Select, Card, Tabs, etc.). Add reusable
DataTable component powered by @tanstack/react-table with sortable
column headers and client-side pagination. Introduce TransactionList
with responsive mobile cards and desktop DataTable, dashboard section
customization (DashboardSection, SectionConfigDialog), and settings
API types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-03-22 14:45:44 -06:00
parent 46f2d8679c
commit 2cd0d3b2e1
17 changed files with 1626 additions and 1109 deletions

View File

@@ -0,0 +1,41 @@
import { type Column } from '@tanstack/react-table';
import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
interface DataTableColumnHeaderProps<TData, TValue> {
column: Column<TData, TValue>;
title: string;
className?: string;
}
export function DataTableColumnHeader<TData, TValue>({
column,
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
if (!column.getCanSort()) {
return <div className={cn(className)}>{title}</div>;
}
const sorted = column.getIsSorted();
return (
<Button
variant="ghost"
size="sm"
className={cn('-ml-3 h-8', className)}
onClick={() => column.toggleSorting(sorted === 'asc')}
>
{title}
{sorted === 'desc' ? (
<ArrowDown className="ml-1 h-3.5 w-3.5" />
) : sorted === 'asc' ? (
<ArrowUp className="ml-1 h-3.5 w-3.5" />
) : (
<ArrowUpDown className="ml-1 h-3.5 w-3.5 text-muted-foreground/50" />
)}
</Button>
);
}

View File

@@ -0,0 +1,128 @@
import { useState } from 'react';
import {
type ColumnDef,
type SortingState,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
pagination?: boolean;
pageSize?: number;
emptyMessage?: React.ReactNode;
initialSorting?: SortingState;
}
export function DataTable<TData, TValue>({
columns,
data,
pagination = false,
pageSize = 25,
emptyMessage = 'No results.',
initialSorting = [],
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>(initialSorting);
const table = useReactTable({
data,
columns,
state: { sorting },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
...(pagination && {
getPaginationRowModel: getPaginationRowModel(),
initialState: { pagination: { pageSize } },
}),
});
return (
<div>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
className={(header.column.columnDef.meta as Record<string, string>)?.className}
style={{ width: header.getSize() !== 150 ? header.getSize() : undefined }}
>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={(cell.column.columnDef.meta as Record<string, string>)?.className}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center text-muted-foreground">
{emptyMessage}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
{pagination && table.getPageCount() > 1 && (
<div className="flex items-center justify-between px-4 py-3 border-t">
<p className="text-sm text-muted-foreground">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</p>
<div className="flex items-center gap-1">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
)}
</div>
);
}