/** * cli:scaffold-component — render/dashboard.ts * Extracted VERBATIM from the historical generate.ts (pure move — frozen by * the 1.0 corpus hash). Renders nothing when the view is absent from * spec.views. */ import type { GeneratedFile } from '../types.js' import { extensionsModuleId } from '../../../../../../lib/app-classification.js' import { GENERATED_MARKER, resolveScreenNav } from './shared.js' import type { RenderContext } from './context.js' export function renderDashboard(rc: RenderContext): GeneratedFile[] { const files: GeneratedFile[] = [] const { spec, ctx, e, eLower, featurePath, pathFor, permKey, sectionCamel } = rc // ─── DashboardPage ────────────────────────────────────────────────────── if (spec.views.includes('dashboard')) { const dashWidgets = spec.pageSpec?.widgets ?? [] const widgetsLiteral = dashWidgets.length === 0 ? '[]' : '[\n' + dashWidgets .map((w) => { const parts = [ `key: ${JSON.stringify(w.key)}`, `label: ${JSON.stringify(w.label ?? w.key)}`, `type: ${JSON.stringify(w.type ?? 'kpi')}`, ] if (typeof w.col === 'number') parts.push(`col: ${w.col}`) if (w.permission) parts.push(`permission: ${JSON.stringify(w.permission)}`) if (w.stacked) parts.push('stacked: true') if (w.sparkline) parts.push('sparkline: true') return ` { ${parts.join(', ')} }` }) .join(',\n') + ',\n ]' // ─── Drill-down (widgets[].drillTo — plan UI 3.4) ──────────────────────── // A widget with a resolvable drillTo becomes a click/keyboard affordance // opening the backing list, with optional preset URL-state params // (the useListState contract — `f.`, `seg`, `view`…). Unresolvable // → no affordance + a generation warning (never a dead click). const ownFamilies = ctx.routesFamilies?.[extensionsModuleId(spec.appCode, spec.module)] const drillEntries = dashWidgets.flatMap((w) => { if (!w.drillTo) return [] const nav = resolveScreenNav(spec.module, ownFamilies, w.drillTo) if (!nav) { ctx.warnings?.push( `scaffold-component: ${e}DashboardPage widget '${w.key}' has no resolvable drillTo ` + `('${w.drillTo}') — drill-down omitted.`, ) return [] } const params = w.drillParams && Object.keys(w.drillParams).length > 0 ? ` + '?' + new URLSearchParams(${JSON.stringify(w.drillParams)}).toString()` : '' return [` ${JSON.stringify(w.key)}: () => navigate(${nav}${params}),`] }) const hasDrill = drillEntries.length > 0 const drillNavDecl = hasDrill ? `\n // Drill-down targets — widget key → the list view it opens (preset URL state). const useWidgetNav = () => { const navigate = useNavigate() const widgetNav: Record void) | undefined> = { ${drillEntries.join('\n')} } return widgetNav }` : '' const drillImport = hasDrill ? `import { useNavigate } from 'react-router-dom'\n` : '' const drillHookDecl = hasDrill ? `\n const widgetNav = useWidgetNav()` : '' const drillProp = hasDrill ? `\n onOpen={widgetNav[w.key]}` : '' files.push({ path: pathFor('dashboard', `${e}DashboardPage.tsx`), content: `${GENERATED_MARKER}${drillImport}import { useState } from 'react' import { useTranslation } from 'react-i18next' import { LayoutDashboard, AlertTriangle } from 'lucide-react' import { Slot } from '@atlashub/smartstack' import { PermissionGuard } from '@/components/auth/PermissionGuard' import { PageTemplate } from '@/components/ui/PageTemplate' import { routes } from '@/extensions/${extensionsModuleId(spec.appCode, spec.module)}Routes' import { DateInput } from '@/components/ui/DateInput' import { DashboardGrid } from '@/components/dashboard/DashboardGrid' import { WidgetRenderer } from '@/components/dashboard/WidgetRenderer' import type { DashboardWidget } from '@/components/dashboard/types' import { useDashboard${e} } from '${featurePath}/hooks/use${e}' // Widgets from the BA SmartDashboard / pageSpec. Editable — add, reorder, retune \`col\`. const WIDGETS: DashboardWidget[] = ${widgetsLiteral}${drillNavDecl} export function ${e}DashboardPage() { const { t } = useTranslation('${spec.module}')${drillHookDecl} const [period, setPeriod] = useState<{ startDate: string | null; endDate: string | null }>({ startDate: null, endDate: null }) const { data, isLoading, error } = useDashboard${e}({ startDate: period.startDate ?? undefined, endDate: period.endDate ?? undefined }) return ( } breadcrumbs={[ { label: t('${eLower}.breadcrumb.section'), href: routes.${sectionCamel}.list() }, { label: t('${eLower}.dashboard.title') }, ]} >
setPeriod((p) => ({ ...p, startDate: v }))} label={t('${eLower}.dashboard.startDate')} /> setPeriod((p) => ({ ...p, endDate: v }))} label={t('${eLower}.dashboard.endDate')} />
{error && (
{t('${eLower}.dashboard.error')}
)} {WIDGETS.map((w) => { const widget = { ...w, label: t(\`${eLower}.dashboard.widget.\${w.key}\`, { defaultValue: w.label }) } const node = ( ) return w.permission ? {node} : node })}
) } export default ${e}DashboardPage `, }) } return files }