refactor: migrate frontend from shadcn/Tailwind to Mantine v9

Reescribe todos los componentes UI para usar Mantine v9 en lugar de shadcn/Tailwind.
Elimina cn(), CVA, components.json, theme_provider custom y globals.css con Tailwind.
Añade 25+ componentes nuevos (AppShell, AuthForm, DatePickerInput, Dropzone, etc.)
y MantineProvider como wrapper estándar del sistema de temas.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 23:46:44 +02:00
parent 4b2bb6998a
commit 97a3c84625
163 changed files with 6008 additions and 6310 deletions
+39 -40
View File
@@ -1,5 +1,5 @@
import * as React from 'react'
import { cn } from '../core/cn'
import { Table, Text, Center, Loader } from '@mantine/core'
interface ColumnDef {
key: string
@@ -16,7 +16,6 @@ interface DataTableProps {
/** Column keys that should be colored by value intensity (heatmap). */
heatmapColumns?: string[]
maxHeight?: number | string
className?: string
loading?: boolean
error?: Error | null
}
@@ -33,7 +32,7 @@ function formatCell(value: unknown, format?: string): string {
if (!isNaN(num)) {
if (format.includes('f')) {
const match = format.match(/\.(\d+)f/)
const d = match ? parseInt(match[1]) : 0
const d = match ? parseInt(match[1]!) : 0
let str = num.toFixed(d)
if (format.includes(',')) {
str = Number(str).toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d })
@@ -51,7 +50,6 @@ function DataTableComponent({
columns,
heatmapColumns = [],
maxHeight = 500,
className,
loading = false,
error = null,
}: DataTableProps) {
@@ -59,7 +57,7 @@ function DataTableComponent({
const effectiveColumns: ColumnDef[] = (columns && columns.length > 0)
? columns
: (data && data.length > 0)
? Object.keys(data[0]).map(k => ({ key: k, label: k }))
? Object.keys(data[0]!).map(k => ({ key: k, label: k }))
: []
// Compute heatmap ranges per column
@@ -82,73 +80,74 @@ function DataTableComponent({
const num = Number(value)
if (isNaN(num)) return undefined
const t = (num - range.min) / (range.max - range.min)
// Dark blue (low) → bright blue (high)
const alpha = 0.1 + t * 0.55
return { backgroundColor: `rgba(59, 130, 246, ${alpha})` }
}
const maxHeightStyle = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight
if (loading && (!data || data.length === 0)) {
return (
<div className={cn('flex items-center justify-center text-muted-foreground text-sm', className)}
style={{ height: 200 }}>
Loading...
</div>
<Center h={200}>
<Loader size="sm" />
</Center>
)
}
if (error) {
return (
<div className={cn('flex items-center justify-center text-destructive text-sm', className)}
style={{ height: 200 }}>
{error.message}
</div>
<Center h={200}>
<Text size="sm" c="red">{error.message}</Text>
</Center>
)
}
return (
<div className={cn('overflow-auto', className)} style={{ maxHeight: maxHeightStyle }}>
<table className="w-full text-sm">
<thead className="sticky top-0 bg-card z-10">
<tr className="border-b border-border">
<Table.ScrollContainer minWidth={0} mah={maxHeight} type="scrollarea">
<Table striped={false} highlightOnHover withTableBorder={false} withColumnBorders={false}>
<Table.Thead style={{ position: 'sticky', top: 0, zIndex: 10, backgroundColor: 'var(--mantine-color-body)' }}>
<Table.Tr>
{effectiveColumns.map(col => (
<th
<Table.Th
key={col.key}
className="text-left py-1.5 px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider whitespace-nowrap"
style={{ whiteSpace: 'nowrap' }}
fz="xs"
fw={500}
c="dimmed"
tt="uppercase"
py={6}
px="sm"
>
{col.label}
</th>
</Table.Th>
))}
</tr>
</thead>
<tbody>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{(data ?? []).map((row, i) => (
<tr key={i} className="border-b border-border hover:bg-accent/50 transition-colors">
<Table.Tr key={i}>
{effectiveColumns.map(col => {
const align = col.align ?? (typeof row[col.key] === 'number' ? 'right' : 'left')
return (
<td
<Table.Td
key={col.key}
className={cn(
'py-1.5 px-3 font-mono text-xs',
align === 'right' && 'text-right',
align === 'center' && 'text-center',
)}
style={heatmapStyle(col.key, row[col.key])}
style={{ textAlign: align, fontFamily: 'var(--mantine-font-family-monospace)', ...heatmapStyle(col.key, row[col.key]) }}
fz="xs"
py={6}
px="sm"
>
{formatCell(row[col.key], col.format)}
</td>
</Table.Td>
)
})}
</tr>
</Table.Tr>
))}
</tbody>
</table>
</Table.Tbody>
</Table>
{(!data || data.length === 0) && (
<p className="text-center text-muted-foreground text-sm py-8">No data</p>
<Center py="xl">
<Text size="sm" c="dimmed">No data</Text>
</Center>
)}
</div>
</Table.ScrollContainer>
)
}