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
+50 -4
View File
@@ -49,6 +49,12 @@ export interface GraphTheme {
selectionColor?: string
}
export interface ContextMenuTarget {
type: "node" | "edge" | "canvas"
id?: string
data?: GraphNode | GraphEdge
}
export interface GraphContainerProps {
data: GraphData
layout?: "organic" | "random"
@@ -58,6 +64,7 @@ export interface GraphContainerProps {
nodeTypes?: NodeType[]
onNodeClick?: (node: GraphNode) => void
onNodeDoubleClick?: (node: GraphNode) => void
onContextMenu?: (event: MouseEvent, target: ContextMenuTarget) => void
enableSelection?: boolean
selectionMode?: "single" | "multiple"
theme?: GraphTheme
@@ -84,6 +91,7 @@ function GraphContainer({
nodeTypes = [],
onNodeClick,
onNodeDoubleClick,
onContextMenu,
theme: themeProp,
height = "100%",
className,
@@ -96,10 +104,30 @@ function GraphContainer({
[themeProp],
)
// Build + render
// Build + render — wait for container to have dimensions
const [ready, setReady] = React.useState(false)
React.useEffect(() => {
const el = containerRef.current
if (!el) return
if (el.clientHeight > 0 && el.clientWidth > 0) {
setReady(true)
return
}
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
if (entry.contentRect.height > 0 && entry.contentRect.width > 0) {
setReady(true)
ro.disconnect()
}
}
})
ro.observe(el)
return () => ro.disconnect()
}, [])
React.useEffect(() => {
const el = containerRef.current
if (!el || !ready) return
// Cleanup previous instance
if (sigmaRef.current) {
@@ -110,7 +138,7 @@ function GraphContainer({
const g = new Graph({ multi: true, type: "directed" })
graphRef.current = g
// Add nodes
// Add nodes — store entity type as entityType to avoid sigma interpreting it as render program
for (const n of data.nodes) {
g.addNode(n.id, {
label: n.label,
@@ -118,7 +146,7 @@ function GraphContainer({
y: n.y ?? (Math.random() - 0.5) * 10,
size: n.size ?? theme.nodeSize,
color: n.color ?? theme.nodeColor,
type: n.type,
entityType: n.type,
})
}
@@ -152,6 +180,7 @@ function GraphContainer({
// Render
const renderer = new Sigma(g, el, {
allowInvalidContainer: true,
renderEdgeLabels: false,
defaultEdgeColor: theme.edgeColor,
defaultNodeColor: theme.nodeColor,
@@ -174,13 +203,30 @@ function GraphContainer({
onNodeDoubleClick({ id: node, ...attrs } as unknown as GraphNode)
})
}
if (onContextMenu) {
renderer.on("rightClickNode", ({ node, event }) => {
const mouseEvent = event.original as MouseEvent
mouseEvent.preventDefault()
const attrs = g.getNodeAttributes(node)
onContextMenu(mouseEvent, {
type: "node",
id: node,
data: { id: node, ...attrs } as unknown as GraphNode,
})
})
renderer.on("rightClickStage", ({ event }) => {
const mouseEvent = event.original as MouseEvent
mouseEvent.preventDefault()
onContextMenu(mouseEvent, { type: "canvas" })
})
}
return () => {
renderer.kill()
sigmaRef.current = null
graphRef.current = null
}
}, [data, layout, theme, onNodeClick, onNodeDoubleClick])
}, [data, layout, theme, onNodeClick, onNodeDoubleClick, onContextMenu, ready])
// Container background
const containerStyle: React.CSSProperties = {