refactor(frontend): migracion a Mantine y limpieza de widgets
- Migra el frontend a Mantine v9 siguiendo la regla de theming del registry (@fn_library, sin Tailwind/cn/CVA). - Reescribe DashboardShell, FilterBar, Section, WidgetRenderer y todos los widgets (Area/Bar/Line/Pie/KPI/Sparkline/Table) con componentes y props de Mantine. - Ajusta vite.config, main.tsx, App.tsx, app.css y env.d.ts. - Añade postcss.config.cjs requerido por Mantine. - Actualiza package.json y pnpm-lock. - Ajusta config.go, main.go y los ejemplos (fn_registry_apps/overview) para el nuevo esquema de tipos en frontend/src/types.ts.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import { Box, UnstyledButton, Group, Text } from '@mantine/core'
|
||||
import { IconChevronDown, IconChevronRight } from '@tabler/icons-react'
|
||||
import { WidgetRenderer } from './WidgetRenderer'
|
||||
import type { SectionDef, QueryDef } from '../types'
|
||||
import type { FilterValues } from '../hooks/useFilterState'
|
||||
@@ -10,42 +11,47 @@ interface SectionProps {
|
||||
filters: FilterValues
|
||||
globalColumns: number
|
||||
getData: (widgetID: string, filters: FilterValues) => Promise<Record<string, unknown>[]>
|
||||
fillHeight?: boolean
|
||||
}
|
||||
|
||||
export function Section({ section, queries, filters, globalColumns, getData }: SectionProps) {
|
||||
export function Section({ section, queries, filters, globalColumns, getData, fillHeight }: SectionProps) {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const columns = section.columns || globalColumns
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<button
|
||||
className="flex items-center gap-2 cursor-pointer select-none bg-transparent border-none p-0"
|
||||
<Box style={fillHeight ? { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', gap: 'var(--mantine-spacing-xs)' } : { display: 'flex', flexDirection: 'column', gap: 'var(--mantine-spacing-xs)' }}>
|
||||
<UnstyledButton
|
||||
onClick={() => section.collapsible && setCollapsed(c => !c)}
|
||||
type="button"
|
||||
style={{ flexShrink: 0, cursor: section.collapsible ? 'pointer' : 'default', userSelect: 'none' }}
|
||||
>
|
||||
{section.collapsible && (
|
||||
collapsed
|
||||
? <ChevronRight className="w-4 h-4 text-[var(--muted-foreground)]" />
|
||||
: <ChevronDown className="w-4 h-4 text-[var(--muted-foreground)]" />
|
||||
)}
|
||||
<h2 className="text-sm font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
|
||||
{section.title}
|
||||
</h2>
|
||||
</button>
|
||||
<Group gap="xs">
|
||||
{section.collapsible && (
|
||||
collapsed
|
||||
? <IconChevronRight size={16} style={{ color: 'var(--mantine-color-dimmed)' }} />
|
||||
: <IconChevronDown size={16} style={{ color: 'var(--mantine-color-dimmed)' }} />
|
||||
)}
|
||||
<Text size="xs" fw={500} c="dimmed" tt="uppercase" style={{ letterSpacing: '0.05em' }}>
|
||||
{section.title}
|
||||
</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
|
||||
{!collapsed && (
|
||||
<div
|
||||
className="grid gap-2"
|
||||
<Box
|
||||
style={{
|
||||
display: 'grid',
|
||||
gap: 'var(--mantine-spacing-xs)',
|
||||
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
||||
...(fillHeight ? { flex: 1, minHeight: 0 } : {}),
|
||||
}}
|
||||
>
|
||||
{section.widgets.map(widget => (
|
||||
<div
|
||||
<Box
|
||||
key={widget.id}
|
||||
style={{
|
||||
gridColumn: `span ${Math.min(widget.span || 1, columns)}`,
|
||||
gridRow: widget.rowSpan ? `span ${widget.rowSpan}` : undefined,
|
||||
...(fillHeight ? { minHeight: 0, overflow: 'hidden' } : {}),
|
||||
}}
|
||||
>
|
||||
<WidgetRenderer
|
||||
@@ -54,10 +60,10 @@ export function Section({ section, queries, filters, globalColumns, getData }: S
|
||||
filters={filters}
|
||||
getData={getData}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user