4ec62f5ed6
- 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.
70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
import { useState } from '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'
|
|
|
|
interface SectionProps {
|
|
section: SectionDef
|
|
queries: Record<string, QueryDef>
|
|
filters: FilterValues
|
|
globalColumns: number
|
|
getData: (widgetID: string, filters: FilterValues) => Promise<Record<string, unknown>[]>
|
|
fillHeight?: boolean
|
|
}
|
|
|
|
export function Section({ section, queries, filters, globalColumns, getData, fillHeight }: SectionProps) {
|
|
const [collapsed, setCollapsed] = useState(false)
|
|
const columns = section.columns || globalColumns
|
|
|
|
return (
|
|
<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)}
|
|
style={{ flexShrink: 0, cursor: section.collapsible ? 'pointer' : 'default', userSelect: 'none' }}
|
|
>
|
|
<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 && (
|
|
<Box
|
|
style={{
|
|
display: 'grid',
|
|
gap: 'var(--mantine-spacing-xs)',
|
|
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
...(fillHeight ? { flex: 1, minHeight: 0 } : {}),
|
|
}}
|
|
>
|
|
{section.widgets.map(widget => (
|
|
<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
|
|
widget={widget}
|
|
queryDef={queries[widget.query]}
|
|
filters={filters}
|
|
getData={getData}
|
|
/>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|