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
+52 -43
View File
@@ -1,78 +1,87 @@
import * as React from 'react'
import { cn } from '../core/cn'
import { Paper, Text, Group, Stack, Box } from '@mantine/core'
type KPICardSize = 'sm' | 'default' | 'lg'
interface Delta {
value: number
isPositive: boolean
/** Descriptive label before value, e.g. "Increased by" */
label?: string
/** Suffix after value, e.g. "vs yesterday" */
suffix?: string
}
interface KPICardProps extends React.HTMLAttributes<HTMLDivElement> {
label: string
value: string | number
/** Unit displayed next to value in smaller font, e.g. "k", "ms", "%" */
unit?: string
delta?: Delta
icon?: React.ReactNode
/** Action slot rendered top-right, e.g. a menu button */
action?: React.ReactNode
/** Inline chart slot rendered to the right of the value */
chart?: React.ReactNode
subtitle?: string
size?: KPICardSize
}
const sizeStyles: Record<KPICardSize, { value: string; unit: string; label: string }> = {
sm: { value: 'text-2xl font-bold', unit: 'text-base font-medium', label: 'text-xs' },
default: { value: 'text-3xl font-bold', unit: 'text-lg font-medium', label: 'text-sm' },
lg: { value: 'text-4xl font-bold', unit: 'text-xl font-medium', label: 'text-base' },
const valueSizes: Record<KPICardSize, string> = {
sm: '1.5rem',
default: '1.875rem',
lg: '2.25rem',
}
const unitSizes: Record<KPICardSize, string> = {
sm: 'md',
default: 'lg',
lg: 'xl',
}
const labelSizes: Record<KPICardSize, string> = {
sm: 'xs',
default: 'sm',
lg: 'md',
}
const KPICard = React.forwardRef<HTMLDivElement, KPICardProps>(
({ label, value, unit, delta, icon, action, chart, subtitle, size = 'default', className, ...props }, ref) => {
const styles = sizeStyles[size]
const deltaColor = delta
? delta.value === 0 ? 'text-muted-foreground'
: delta.isPositive ? 'text-green-600 dark:text-green-500'
: 'text-red-600 dark:text-red-500'
: ''
? delta.value === 0 ? 'dimmed'
: delta.isPositive ? 'teal'
: 'red'
: undefined
return (
<div ref={ref} className={cn('rounded-lg border bg-card p-4 text-card-foreground shadow-sm', className)} {...props}>
<div className="flex items-start justify-between">
<div className="flex items-center gap-2">
{icon && <div className="text-muted-foreground">{icon}</div>}
<div className="space-y-1">
<p className={cn('text-muted-foreground', styles.label)}>{label}</p>
{subtitle && <p className="text-xs text-muted-foreground/80">{subtitle}</p>}
</div>
</div>
{action && <div className="text-muted-foreground">{action}</div>}
</div>
<div className="mt-3 flex items-end justify-between gap-4">
<div className="space-y-1">
<div className="flex items-baseline gap-1">
<span className={cn('tracking-tight', styles.value)}>{value}</span>
{unit && <span className={cn('text-muted-foreground', styles.unit)}>{unit}</span>}
</div>
<Paper ref={ref} withBorder shadow="xs" radius="md" p="md" className={className} {...props}>
<Group justify="space-between" align="flex-start">
<Group gap="xs" align="center">
{icon && <Box c="dimmed">{icon}</Box>}
<Stack gap={2}>
<Text size={labelSizes[size]} c="dimmed">{label}</Text>
{subtitle && <Text size="xs" c="dimmed" opacity={0.8}>{subtitle}</Text>}
</Stack>
</Group>
{action && <Box c="dimmed">{action}</Box>}
</Group>
<Group justify="space-between" align="flex-end" mt="md" gap="lg">
<Stack gap={4}>
<Group gap={4} align="baseline">
<Text fw={700} style={{ fontSize: valueSizes[size], lineHeight: 1, letterSpacing: '-0.025em' }}>
{value}
</Text>
{unit && <Text size={unitSizes[size]} c="dimmed" fw={500}>{unit}</Text>}
</Group>
{delta && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
{delta.label && <span>{delta.label}</span>}
<span className={cn('font-medium', deltaColor)}>
{delta.isPositive ? '' : ''} {delta.value > 0 ? '+' : ''}{delta.value}{delta.label ? '' : '%'}
</span>
{delta.suffix && <span>{delta.suffix}</span>}
</div>
<Group gap={4} align="center">
{delta.label && <Text size="xs" c="dimmed">{delta.label}</Text>}
<Text size="xs" fw={500} c={deltaColor}>
{delta.isPositive ? '\u25B2' : '\u25BC'} {delta.value > 0 ? '+' : ''}{delta.value}{delta.label ? '' : '%'}
</Text>
{delta.suffix && <Text size="xs" c="dimmed">{delta.suffix}</Text>}
</Group>
)}
</div>
{chart && <div className="flex-shrink-0">{chart}</div>}
</div>
</div>
</Stack>
{chart && <Box style={{ flexShrink: 0 }}>{chart}</Box>}
</Group>
</Paper>
)
}
)