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
+34 -42
View File
@@ -1,40 +1,27 @@
import * as React from 'react'
import { cn } from '../core/cn'
import { Sparkline as MantineSparkline } from '@mantine/charts'
type SparklineVariant = 'line' | 'area' | 'bar'
interface SparklineProps extends React.SVGAttributes<SVGSVGElement> {
interface SparklineProps {
data: number[]
variant?: SparklineVariant
color?: string
/** Per-bar colors for 'bar' variant. Cycles if shorter than data. */
colors?: string[]
width?: number
height?: number
strokeWidth?: number
showLastPoint?: boolean
className?: string
}
function getPath(data: number[], width: number, height: number, padding: number = 2) {
if (data.length === 0) return { linePath: '', areaPath: '' }
const min = Math.min(...data)
const max = Math.max(...data)
const range = max - min || 1
const ew = width - padding * 2
const eh = height - padding * 2
const points = data.map((value, index) => ({
x: padding + (index / (data.length - 1)) * ew,
y: padding + eh - ((value - min) / range) * eh,
}))
const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ')
const areaPath = `${linePath} L ${points[points.length - 1].x} ${height - padding} L ${padding} ${height - padding} Z`
return { linePath, areaPath }
}
const Sparkline = React.forwardRef<SVGSVGElement, SparklineProps>(
({ data, variant = 'line', color = 'currentColor', colors, width = 80, height = 24, strokeWidth = 1.5, showLastPoint = true, className, ...props }, ref) => {
if (data.length === 0) return <svg ref={ref} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={cn('text-primary', className)} {...props} />
const Sparkline = React.forwardRef<HTMLDivElement, SparklineProps>(
({ data, variant = 'line', color, colors, width = 80, height = 24, strokeWidth = 1.5, className, ...props }, ref) => {
if (data.length === 0) {
return <div ref={ref} style={{ width, height }} className={className} />
}
// Bar variant: use custom SVG (Mantine Sparkline only supports area)
if (variant === 'bar') {
const min = Math.min(...data, 0)
const max = Math.max(...data)
@@ -42,31 +29,36 @@ const Sparkline = React.forwardRef<SVGSVGElement, SparklineProps>(
const p = 2
const eh = height - p * 2
const bw = (width - p * 2) / data.length - 1
const barColor = color ?? 'currentColor'
return (
<svg ref={ref} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={cn('text-primary', className)} {...props}>
{data.map((value, index) => {
const bh = ((value - min) / range) * eh
const x = p + index * ((width - p * 2) / data.length) + 0.5
const y = p + eh - bh
const barColor = colors ? colors[index % colors.length] : color
return <rect key={index} x={x} y={y} width={Math.max(bw, 1)} height={Math.max(bh, 1)} fill={barColor} rx={1} opacity={0.85} />
})}
</svg>
<div ref={ref} className={className} {...props}>
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
{data.map((value, index) => {
const bh = ((value - min) / range) * eh
const x = p + index * ((width - p * 2) / data.length) + 0.5
const y = p + eh - bh
const fill = colors ? colors[index % colors.length] : barColor
return <rect key={index} x={x} y={y} width={Math.max(bw, 1)} height={Math.max(bh, 1)} fill={fill} rx={1} opacity={0.85} />
})}
</svg>
</div>
)
}
const { linePath, areaPath } = getPath(data, width, height)
const lastPoint = {
x: width - 2,
y: 2 + (height - 4) - ((data[data.length - 1] - Math.min(...data)) / (Math.max(...data) - Math.min(...data) || 1)) * (height - 4)
}
// Line/area: use Mantine Sparkline
return (
<svg ref={ref} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={cn('text-primary', className)} {...props}>
{variant === 'area' && <path d={areaPath} fill={color} opacity={0.2} />}
<path d={linePath} fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" />
{showLastPoint && <circle cx={lastPoint.x} cy={lastPoint.y} r={2.5} fill={color} />}
</svg>
<div ref={ref} className={className} {...props}>
<MantineSparkline
data={data}
color={color ?? 'blue'}
w={width}
h={height}
strokeWidth={strokeWidth}
curveType="natural"
withGradient={variant === 'area'}
fillOpacity={variant === 'area' ? 0.3 : 0}
/>
</div>
)
}
)