@flowselections/dashboard-erp-kwekers 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/_core-safelist.d.ts +2 -0
- package/dist-lib/_core-safelist.d.ts.map +1 -0
- package/dist-lib/_core-safelist.js +15 -0
- package/dist-lib/components/DashboardErpLogo.d.ts +6 -0
- package/dist-lib/components/DashboardErpLogo.d.ts.map +1 -0
- package/dist-lib/components/DashboardErpLogo.js +4 -0
- package/dist-lib/components/dashboard/AiSettingsCard.d.ts +2 -0
- package/dist-lib/components/dashboard/AiSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/dashboard/AiSettingsCard.js +37 -0
- package/dist-lib/components/dashboard/AlertHistoryPanel.d.ts +4 -0
- package/dist-lib/components/dashboard/AlertHistoryPanel.d.ts.map +1 -0
- package/dist-lib/components/dashboard/AlertHistoryPanel.js +18 -0
- package/dist-lib/components/dashboard/BlockEditorDialog.d.ts +15 -0
- package/dist-lib/components/dashboard/BlockEditorDialog.d.ts.map +1 -0
- package/dist-lib/components/dashboard/BlockEditorDialog.js +89 -0
- package/dist-lib/components/dashboard/BlockRenderer.d.ts +5 -0
- package/dist-lib/components/dashboard/BlockRenderer.d.ts.map +1 -0
- package/dist-lib/components/dashboard/BlockRenderer.js +25 -0
- package/dist-lib/components/dashboard/DashboardBuilderPage.d.ts +4 -0
- package/dist-lib/components/dashboard/DashboardBuilderPage.d.ts.map +1 -0
- package/dist-lib/components/dashboard/DashboardBuilderPage.js +49 -0
- package/dist-lib/components/dashboard/DashboardErpLayoutPage.d.ts +2 -0
- package/dist-lib/components/dashboard/DashboardErpLayoutPage.d.ts.map +1 -0
- package/dist-lib/components/dashboard/DashboardErpLayoutPage.js +30 -0
- package/dist-lib/components/dashboard/DashboardSummary.d.ts +9 -0
- package/dist-lib/components/dashboard/DashboardSummary.d.ts.map +1 -0
- package/dist-lib/components/dashboard/DashboardSummary.js +136 -0
- package/dist-lib/components/dashboard/DashboardViewPage.d.ts +3 -0
- package/dist-lib/components/dashboard/DashboardViewPage.d.ts.map +1 -0
- package/dist-lib/components/dashboard/DashboardViewPage.js +37 -0
- package/dist-lib/components/dashboard/FilterBar.d.ts +17 -0
- package/dist-lib/components/dashboard/FilterBar.d.ts.map +1 -0
- package/dist-lib/components/dashboard/FilterBar.js +66 -0
- package/dist-lib/components/dashboard/QuickAddDialog.d.ts +15 -0
- package/dist-lib/components/dashboard/QuickAddDialog.d.ts.map +1 -0
- package/dist-lib/components/dashboard/QuickAddDialog.js +168 -0
- package/dist-lib/components/dashboard/SuggestionBanner.d.ts +15 -0
- package/dist-lib/components/dashboard/SuggestionBanner.d.ts.map +1 -0
- package/dist-lib/components/dashboard/SuggestionBanner.js +13 -0
- package/dist-lib/components/dashboard/TileGallery.d.ts +17 -0
- package/dist-lib/components/dashboard/TileGallery.d.ts.map +1 -0
- package/dist-lib/components/dashboard/TileGallery.js +60 -0
- package/dist-lib/components/dashboard/ai/AiChatPanel.d.ts +6 -0
- package/dist-lib/components/dashboard/ai/AiChatPanel.d.ts.map +1 -0
- package/dist-lib/components/dashboard/ai/AiChatPanel.js +57 -0
- package/dist-lib/components/dashboard/ai/BlockGeneratorDialog.d.ts +14 -0
- package/dist-lib/components/dashboard/ai/BlockGeneratorDialog.d.ts.map +1 -0
- package/dist-lib/components/dashboard/ai/BlockGeneratorDialog.js +61 -0
- package/dist-lib/components/dashboard/blocks/AiAnalysisBlock.d.ts +5 -0
- package/dist-lib/components/dashboard/blocks/AiAnalysisBlock.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/AiAnalysisBlock.js +58 -0
- package/dist-lib/components/dashboard/blocks/AlertBlock.d.ts +5 -0
- package/dist-lib/components/dashboard/blocks/AlertBlock.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/AlertBlock.js +107 -0
- package/dist-lib/components/dashboard/blocks/BlockShell.d.ts +10 -0
- package/dist-lib/components/dashboard/blocks/BlockShell.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/BlockShell.js +39 -0
- package/dist-lib/components/dashboard/blocks/ChartBlock.d.ts +5 -0
- package/dist-lib/components/dashboard/blocks/ChartBlock.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/ChartBlock.js +31 -0
- package/dist-lib/components/dashboard/blocks/ForecastBlock.d.ts +5 -0
- package/dist-lib/components/dashboard/blocks/ForecastBlock.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/ForecastBlock.js +86 -0
- package/dist-lib/components/dashboard/blocks/KpiBlock.d.ts +5 -0
- package/dist-lib/components/dashboard/blocks/KpiBlock.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/KpiBlock.js +17 -0
- package/dist-lib/components/dashboard/blocks/TableBlock.d.ts +5 -0
- package/dist-lib/components/dashboard/blocks/TableBlock.d.ts.map +1 -0
- package/dist-lib/components/dashboard/blocks/TableBlock.js +12 -0
- package/dist-lib/components/dashboard/types.d.ts +129 -0
- package/dist-lib/components/dashboard/types.d.ts.map +1 -0
- package/dist-lib/components/dashboard/types.js +1 -0
- package/dist-lib/hooks/useAlertEvents.d.ts +9 -0
- package/dist-lib/hooks/useAlertEvents.d.ts.map +1 -0
- package/dist-lib/hooks/useAlertEvents.js +32 -0
- package/dist-lib/hooks/useBlockData.d.ts +10 -0
- package/dist-lib/hooks/useBlockData.d.ts.map +1 -0
- package/dist-lib/hooks/useBlockData.js +130 -0
- package/dist-lib/hooks/useDashboardBlocks.d.ts +25 -0
- package/dist-lib/hooks/useDashboardBlocks.d.ts.map +1 -0
- package/dist-lib/hooks/useDashboardBlocks.js +66 -0
- package/dist-lib/hooks/useDashboardPages.d.ts +10 -0
- package/dist-lib/hooks/useDashboardPages.d.ts.map +1 -0
- package/dist-lib/hooks/useDashboardPages.js +56 -0
- package/dist-lib/hooks/useSchema.d.ts +13 -0
- package/dist-lib/hooks/useSchema.d.ts.map +1 -0
- package/dist-lib/hooks/useSchema.js +23 -0
- package/dist-lib/hooks/useUserRoles.d.ts +5 -0
- package/dist-lib/hooks/useUserRoles.d.ts.map +1 -0
- package/dist-lib/hooks/useUserRoles.js +23 -0
- package/dist-lib/index.d.ts +17 -0
- package/dist-lib/index.d.ts.map +1 -0
- package/dist-lib/index.js +47 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2555 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
- package/dist-lib/integrations/supabase/client.d.ts +2551 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.js +13 -0
- package/dist-lib/integrations/supabase/client.server.d.ts +2551 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.server.js +30 -0
- package/dist-lib/integrations/supabase/types.d.ts +2685 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/types.js +8 -0
- package/dist-lib/lib/ai-gateway.server.d.ts +2 -0
- package/dist-lib/lib/ai-gateway.server.d.ts.map +1 -0
- package/dist-lib/lib/ai-gateway.server.js +11 -0
- package/dist-lib/lib/ai-insights.functions.d.ts +10271 -0
- package/dist-lib/lib/ai-insights.functions.d.ts.map +1 -0
- package/dist-lib/lib/ai-insights.functions.js +118 -0
- package/dist-lib/lib/aiSettings.d.ts +22 -0
- package/dist-lib/lib/aiSettings.d.ts.map +1 -0
- package/dist-lib/lib/aiSettings.js +107 -0
- package/dist-lib/lib/dashboard-eval.functions.d.ts +5130 -0
- package/dist-lib/lib/dashboard-eval.functions.d.ts.map +1 -0
- package/dist-lib/lib/dashboard-eval.functions.js +87 -0
- package/dist-lib/lib/dashboard.functions.d.ts +2562 -0
- package/dist-lib/lib/dashboard.functions.d.ts.map +1 -0
- package/dist-lib/lib/dashboard.functions.js +21 -0
- package/dist-lib/lib/dashboardTemplates.d.ts +11 -0
- package/dist-lib/lib/dashboardTemplates.d.ts.map +1 -0
- package/dist-lib/lib/dashboardTemplates.js +44 -0
- package/dist-lib/lib/format.d.ts +20 -0
- package/dist-lib/lib/format.d.ts.map +1 -0
- package/dist-lib/lib/format.js +121 -0
- package/dist-lib/lib/query.functions.d.ts +2583 -0
- package/dist-lib/lib/query.functions.d.ts.map +1 -0
- package/dist-lib/lib/query.functions.js +85 -0
- package/dist-lib/lib/quickBlocks.d.ts +28 -0
- package/dist-lib/lib/quickBlocks.d.ts.map +1 -0
- package/dist-lib/lib/quickBlocks.js +297 -0
- package/dist-lib/lib/roles.functions.d.ts +2556 -0
- package/dist-lib/lib/roles.functions.d.ts.map +1 -0
- package/dist-lib/lib/roles.functions.js +14 -0
- package/dist-lib/lib/suggestions.d.ts +8 -0
- package/dist-lib/lib/suggestions.d.ts.map +1 -0
- package/dist-lib/lib/suggestions.js +41 -0
- package/dist-lib/lib/utils.d.ts +3 -0
- package/dist-lib/lib/utils.d.ts.map +1 -0
- package/dist-lib/lib/utils.js +5 -0
- package/dist-lib/lib/validationSchemas.d.ts +15 -0
- package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
- package/dist-lib/lib/validationSchemas.js +25 -0
- package/dist-lib/styles.css +1 -0
- package/package.json +95 -0
- package/public/flowselections-assets/template-module/README.md +15 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_core-safelist.d.ts","sourceRoot":"","sources":["../src/_core-safelist.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Forceert Tailwind classes die uit @flowselections/core komen maar niet
|
|
2
|
+
// voorkomen in de eigen broncode van deze module.
|
|
3
|
+
// Zonder dit bestand worden deze classes weggeoptimaliseerd op de
|
|
4
|
+
// Lovable productie build.
|
|
5
|
+
const _safelist = [
|
|
6
|
+
// Layout — Sidebar en shell structuur
|
|
7
|
+
'h-full', 'flex-col', 'flex-1', 'min-h-screen', 'ml-64', 'pt-16',
|
|
8
|
+
'items-start', 'items-center', 'justify-between', 'border-t',
|
|
9
|
+
'w-64', 'h-screen', 'h-20', 'overflow-y-auto',
|
|
10
|
+
// Grid — InstellingenPage
|
|
11
|
+
'grid', 'lg:grid-cols-2', 'gap-6', 'p-6', 'space-y-6',
|
|
12
|
+
// Toggle/Switch — aan/uit zichtbaarheid
|
|
13
|
+
'data-[state=checked]:bg-primary', 'data-[state=unchecked]:bg-input',
|
|
14
|
+
];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardErpLogo.d.ts","sourceRoot":"","sources":["../../src/components/DashboardErpLogo.tsx"],"names":[],"mappings":"AAAA,UAAU,KAAK;IAAG,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;AAEtC,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BASpD"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function DashboardErpLogo({ className }) {
|
|
3
|
+
return (_jsxs("svg", { viewBox: "0 0 40 40", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className, "aria-hidden": "true", children: [_jsx("rect", { width: "40", height: "40", rx: "8", fill: "currentColor", opacity: "0.12" }), _jsx("path", { d: "M10 28V18l5-4 5 4v10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M22 28V12l4-3 4 3v16", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M6 30h28", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })] }));
|
|
4
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/AiSettingsCard.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,cAAc,gCAuG7B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, CardContent, CardHeader, CardTitle, Switch, Checkbox, Button } from '@flowselections/core';
|
|
3
|
+
import { Sparkles } from 'lucide-react';
|
|
4
|
+
import { useAiSettings, } from '../../lib/aiSettings';
|
|
5
|
+
const TYPE_LABELS = {
|
|
6
|
+
dashboard: { label: 'Dashboard-samenvattingen', example: '"De omzet stijgt met 12% t.o.v. de vorige periode."' },
|
|
7
|
+
voorraad: { label: 'Voorraad-samenvattingen', example: '"5 producten dreigen binnen 7 dagen uitverkocht te raken."' },
|
|
8
|
+
verkoop: { label: 'Verkoop-samenvattingen', example: '"Productgroep Kamerplanten laat de grootste groei zien."' },
|
|
9
|
+
klanten: { label: 'Klant-samenvattingen', example: '"3 klanten hebben deze maand aanzienlijk minder besteld."' },
|
|
10
|
+
forecast: { label: 'Forecast-samenvattingen', example: '"Op basis van de huidige trend wordt 8% omzetgroei verwacht."' },
|
|
11
|
+
risico: { label: 'Risico-meldingen', example: '"Voorraad van productgroep X raakt binnen 14 dagen op."' },
|
|
12
|
+
dagelijks: { label: 'Dagelijkse samenvatting', example: 'Elke dag een korte stand van zaken.' },
|
|
13
|
+
wekelijks: { label: 'Wekelijkse samenvatting', example: 'Elke week een overzicht van trends.' },
|
|
14
|
+
maandelijks: { label: 'Maandelijkse samenvatting', example: 'Elke maand een uitgebreide analyse.' },
|
|
15
|
+
};
|
|
16
|
+
const FOCUS_LABELS = {
|
|
17
|
+
omzet: 'Omzet', voorraad: 'Voorraad', orders: 'Orders', klanten: 'Klanten',
|
|
18
|
+
producten: 'Producten', inkoop: 'Inkoop', leveranciers: 'Leveranciers',
|
|
19
|
+
forecasts: 'Forecasts', risicos: "Risico's", marge: 'Winst & marge',
|
|
20
|
+
};
|
|
21
|
+
const DETAILS = [
|
|
22
|
+
{ id: 'kort', label: 'Kort', hint: '1 tot 3 regels' },
|
|
23
|
+
{ id: 'normaal', label: 'Normaal', hint: 'Korte managementsamenvatting' },
|
|
24
|
+
{ id: 'uitgebreid', label: 'Uitgebreid', hint: 'Volledige analyse met aanbevelingen' },
|
|
25
|
+
];
|
|
26
|
+
const PERSONALITIES = [
|
|
27
|
+
{ id: 'zakelijk', label: 'Zakelijk' },
|
|
28
|
+
{ id: 'management', label: 'Management' },
|
|
29
|
+
{ id: 'operationeel', label: 'Operationeel' },
|
|
30
|
+
{ id: 'kort', label: 'Kort & bondig' },
|
|
31
|
+
{ id: 'advies', label: 'Adviesgericht' },
|
|
32
|
+
{ id: 'analytisch', label: 'Analytisch' },
|
|
33
|
+
];
|
|
34
|
+
export function AiSettingsCard() {
|
|
35
|
+
const [s, set] = useAiSettings();
|
|
36
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Sparkles, { className: "w-4 h-4 text-primary" }), " AI-samenvattingen"] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium text-sm", children: "AI-samenvattingen inschakelen" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wanneer uit: geen samenvattingen, analyses of automatische inzichten." })] }), _jsx(Switch, { checked: s.enabled, onCheckedChange: (v) => set({ ...s, enabled: !!v }) })] }), _jsxs("div", { className: s.enabled ? '' : 'opacity-50 pointer-events-none', children: [_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Soorten samenvattingen" }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-2", children: Object.keys(TYPE_LABELS).map((k) => (_jsxs("label", { className: "flex items-start gap-3 rounded-md border p-3 cursor-pointer", children: [_jsx(Switch, { checked: s.types[k], onCheckedChange: (v) => set({ ...s, types: { ...s.types, [k]: !!v } }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-sm font-medium", children: TYPE_LABELS[k].label }), _jsx("div", { className: "text-xs text-muted-foreground italic", children: TYPE_LABELS[k].example })] })] }, k))) })] }), _jsxs("div", { className: "space-y-2 mt-6", children: [_jsx("div", { className: "text-sm font-medium", children: "Detailniveau" }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-2", children: DETAILS.map((d) => (_jsxs(Button, { type: "button", variant: s.detail === d.id ? 'default' : 'outline', onClick: () => set({ ...s, detail: d.id }), className: "h-auto py-3 flex flex-col items-start", children: [_jsx("span", { className: "font-medium", children: d.label }), _jsx("span", { className: "text-xs opacity-80", children: d.hint })] }, d.id))) })] }), _jsxs("div", { className: "space-y-2 mt-6", children: [_jsx("div", { className: "text-sm font-medium", children: "Focusgebieden" }), _jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2", children: Object.keys(FOCUS_LABELS).map((k) => (_jsxs("label", { className: "flex items-center gap-2 rounded-md border p-2 cursor-pointer", children: [_jsx(Checkbox, { checked: s.focus[k], onCheckedChange: (v) => set({ ...s, focus: { ...s.focus, [k]: !!v } }) }), _jsx("span", { className: "text-sm", children: FOCUS_LABELS[k] })] }, k))) })] }), _jsxs("div", { className: "space-y-2 mt-6", children: [_jsx("div", { className: "text-sm font-medium", children: "AI-persoonlijkheid" }), _jsx("div", { className: "flex flex-wrap gap-2", children: PERSONALITIES.map((p) => (_jsx(Button, { type: "button", size: "sm", variant: s.personality === p.id ? 'default' : 'outline', onClick: () => set({ ...s, personality: p.id }), children: p.label }, p.id))) })] }), _jsx("p", { className: "text-xs text-muted-foreground mt-6", children: "Tip: per dashboard kun je de AI-samenvatting tonen of verbergen via het oogje rechtsboven de samenvatting." })] })] })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AlertHistoryPanel.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/AlertHistoryPanel.tsx"],"names":[],"mappings":"AAUA,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,sCAuC/D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '@flowselections/core';
|
|
3
|
+
import { Bell, Check, Trash2 } from 'lucide-react';
|
|
4
|
+
import { useAlertEvents } from '../../hooks/useAlertEvents';
|
|
5
|
+
const SEV_COLOR = {
|
|
6
|
+
info: 'bg-blue-500/15 text-blue-700 dark:text-blue-300 border-blue-500/30',
|
|
7
|
+
warning: 'bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-500/30',
|
|
8
|
+
critical: 'bg-destructive/15 text-destructive border-destructive/30',
|
|
9
|
+
};
|
|
10
|
+
export function AlertHistoryPanel({ pageId }) {
|
|
11
|
+
const { events, loading, acknowledge, remove } = useAlertEvents(pageId);
|
|
12
|
+
const open = events.filter(e => !e.acknowledged);
|
|
13
|
+
if (loading)
|
|
14
|
+
return null;
|
|
15
|
+
if (!events.length)
|
|
16
|
+
return null;
|
|
17
|
+
return (_jsxs(Card, { className: "mb-4 mx-2", children: [_jsx(CardHeader, { className: "pb-2 flex flex-row items-center justify-between space-y-0", children: _jsxs(CardTitle, { className: "text-sm font-medium flex items-center gap-2", children: [_jsx(Bell, { className: "w-4 h-4" }), " Alert-historie", open.length > 0 && _jsxs(Badge, { variant: "destructive", className: "ml-1", children: [open.length, " open"] })] }) }), _jsx(CardContent, { className: "space-y-2 max-h-64 overflow-auto", children: events.map(ev => (_jsxs("div", { className: `flex items-center justify-between gap-2 rounded border p-2 text-xs ${SEV_COLOR[ev.severity] ?? ''} ${ev.acknowledged ? 'opacity-60' : ''}`, children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: ev.message || 'Drempel overschreden' }), _jsxs("div", { className: "opacity-70", children: ["Waarde ", ev.value ?? '—', " \u00B7 drempel ", ev.threshold ?? '—', " \u00B7 ", new Date(ev.created_at).toLocaleString('nl-NL')] })] }), _jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [!ev.acknowledged && (_jsx(Button, { size: "icon", variant: "ghost", className: "h-6 w-6", onClick: () => acknowledge(ev.id), title: "Markeer als gezien", children: _jsx(Check, { className: "w-3.5 h-3.5" }) })), _jsx(Button, { size: "icon", variant: "ghost", className: "h-6 w-6", onClick: () => remove(ev.id), title: "Verwijderen", children: _jsx(Trash2, { className: "w-3.5 h-3.5" }) })] })] }, ev.id))) })] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AnyBlockConfig, BlockRow, BlockType, BlockPermissions } from './types';
|
|
2
|
+
interface Props {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (v: boolean) => void;
|
|
5
|
+
initial?: BlockRow | null;
|
|
6
|
+
onSave: (input: {
|
|
7
|
+
type: BlockType;
|
|
8
|
+
title: string;
|
|
9
|
+
config: AnyBlockConfig;
|
|
10
|
+
permissions: BlockPermissions;
|
|
11
|
+
}) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function BlockEditorDialog({ open, onOpenChange, initial, onSave }: Props): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=BlockEditorDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockEditorDialog.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/BlockEditorDialog.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAsF,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEzK,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,cAAc,CAAC;QAAC,WAAW,EAAE,gBAAgB,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpI;AAcD,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,+BA8X/E"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, toast, Switch, } from '@flowselections/core';
|
|
4
|
+
import { Sparkles, Loader2 } from 'lucide-react';
|
|
5
|
+
import { useServerFn } from '@tanstack/react-start';
|
|
6
|
+
import { useSchema } from '../../hooks/useSchema';
|
|
7
|
+
import { proposeBlockConfig } from '../../lib/ai-insights.functions';
|
|
8
|
+
const AGGS = ['count', 'sum', 'avg', 'min', 'max'];
|
|
9
|
+
const TYPES = [
|
|
10
|
+
{ value: 'kpi', label: 'KPI' },
|
|
11
|
+
{ value: 'table', label: 'Tabel' },
|
|
12
|
+
{ value: 'chart', label: 'Grafiek' },
|
|
13
|
+
{ value: 'ai_analysis', label: 'AI Analyse' },
|
|
14
|
+
{ value: 'forecast', label: 'Forecast' },
|
|
15
|
+
{ value: 'alert', label: 'Alert' },
|
|
16
|
+
];
|
|
17
|
+
const OPERATORS = ['>', '>=', '<', '<=', '==', '!='];
|
|
18
|
+
export function BlockEditorDialog({ open, onOpenChange, initial, onSave }) {
|
|
19
|
+
const { tables, loading } = useSchema();
|
|
20
|
+
const [type, setType] = useState(initial?.type ?? 'kpi');
|
|
21
|
+
const [title, setTitle] = useState(initial?.title ?? '');
|
|
22
|
+
const [config, setConfig] = useState(initial?.config ?? { table: '', agg: 'count' });
|
|
23
|
+
const [aiPrompt, setAiPrompt] = useState('');
|
|
24
|
+
const [aiBusy, setAiBusy] = useState(false);
|
|
25
|
+
const [permissions, setPermissions] = useState(initial?.permissions ?? { can_view_ai: true, can_edit: true });
|
|
26
|
+
const propose = useServerFn(proposeBlockConfig);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (open) {
|
|
29
|
+
setType(initial?.type ?? 'kpi');
|
|
30
|
+
setTitle(initial?.title ?? '');
|
|
31
|
+
setConfig(initial?.config ?? { table: '', agg: 'count' });
|
|
32
|
+
setPermissions(initial?.permissions ?? { can_view_ai: true, can_edit: true });
|
|
33
|
+
}
|
|
34
|
+
}, [open, initial]);
|
|
35
|
+
const tableNames = useMemo(() => tables.map(t => t.name), [tables]);
|
|
36
|
+
const currentCols = useMemo(() => {
|
|
37
|
+
const t = tables.find(t => t.name === config.table);
|
|
38
|
+
return t?.columns ?? [];
|
|
39
|
+
}, [tables, config]);
|
|
40
|
+
function patchCfg(p) {
|
|
41
|
+
setConfig(prev => ({ ...prev, ...p }));
|
|
42
|
+
}
|
|
43
|
+
function handleTypeChange(v) {
|
|
44
|
+
setType(v);
|
|
45
|
+
const t = config.table ?? '';
|
|
46
|
+
if (v === 'kpi')
|
|
47
|
+
setConfig({ table: t, agg: 'count' });
|
|
48
|
+
if (v === 'table')
|
|
49
|
+
setConfig({ table: t, columns: [], limit: 50 });
|
|
50
|
+
if (v === 'chart')
|
|
51
|
+
setConfig({ kind: 'line', table: t, xColumn: '', yColumn: '', agg: 'sum', limit: 100 });
|
|
52
|
+
if (v === 'ai_analysis')
|
|
53
|
+
setConfig({ table: t, columns: [], limit: 100 });
|
|
54
|
+
if (v === 'forecast')
|
|
55
|
+
setConfig({ table: t, xColumn: '', yColumn: '', agg: 'sum', horizon: 6, history: 200 });
|
|
56
|
+
if (v === 'alert')
|
|
57
|
+
setConfig({ table: t, agg: 'count', operator: '>', threshold: 0, severity: 'warning' });
|
|
58
|
+
}
|
|
59
|
+
async function generateWithAi() {
|
|
60
|
+
if (!aiPrompt.trim())
|
|
61
|
+
return;
|
|
62
|
+
setAiBusy(true);
|
|
63
|
+
try {
|
|
64
|
+
const res = await propose({ data: { prompt: aiPrompt, schema: tables } });
|
|
65
|
+
if (!res.proposal) {
|
|
66
|
+
toast.error(res.note || 'Geen voorstel');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const p = res.proposal;
|
|
70
|
+
if (p.type)
|
|
71
|
+
setType(p.type);
|
|
72
|
+
if (p.title)
|
|
73
|
+
setTitle(p.title);
|
|
74
|
+
if (p.config)
|
|
75
|
+
setConfig(p.config);
|
|
76
|
+
toast.success('Voorstel ingevuld — pas aan en sla op');
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
toast.error('Fout: ' + (e?.message ?? 'onbekend'));
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
setAiBusy(false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: initial ? 'Blok bewerken' : 'Nieuw blok' }) }), !initial && (_jsxs("div", { className: "rounded-lg border border-primary/30 bg-primary/5 p-3 space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-primary", children: [_jsx(Sparkles, { className: "w-4 h-4" }), " Genereer met AI"] }), _jsx(Textarea, { value: aiPrompt, onChange: (e) => setAiPrompt(e.target.value), placeholder: "bijv. Toon een staafgrafiek van de omzet per maand uit tabel orders", rows: 2 }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { size: "sm", onClick: generateWithAi, disabled: aiBusy || !aiPrompt.trim(), children: [aiBusy ? _jsx(Loader2, { className: "w-3.5 h-3.5 mr-2 animate-spin" }) : _jsx(Sparkles, { className: "w-3.5 h-3.5 mr-2" }), "Voorstel maken"] }) })] })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Type" }), _jsxs(Select, { value: type, onValueChange: (v) => handleTypeChange(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: TYPES.map(t => _jsx(SelectItem, { value: t.value, children: t.label }, t.value)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Titel" }), _jsx(Input, { value: title, onChange: (e) => setTitle(e.target.value), placeholder: "bijv. Aantal orders" })] }), _jsxs("div", { className: "space-y-2 col-span-2", children: [_jsx(Label, { children: "Tabel (databron)" }), _jsxs(Select, { value: config.table ?? '', onValueChange: (v) => patchCfg({ table: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? 'Laden…' : 'Kies een tabel' }) }), _jsx(SelectContent, { className: "max-h-72", children: tableNames.map(n => _jsx(SelectItem, { value: n, children: n }, n)) })] })] }), type === 'kpi' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aggregatie" }), _jsxs(Select, { value: config.agg, onValueChange: (v) => patchCfg({ agg: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: AGGS.map(a => _jsx(SelectItem, { value: a, children: a.toUpperCase() }, a)) })] })] }), config.agg !== 'count' && (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Kolom" }), _jsxs(Select, { value: config.column ?? '', onValueChange: (v) => patchCfg({ column: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies kolom" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Formaat" }), _jsxs(Select, { value: config.format ?? 'number', onValueChange: (v) => patchCfg({ format: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "number", children: "Getal" }), _jsx(SelectItem, { value: "currency", children: "\u20AC Bedrag" }), _jsx(SelectItem, { value: "percent", children: "Percentage" })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Subtitel" }), _jsx(Input, { value: config.subtitle ?? '', onChange: (e) => patchCfg({ subtitle: e.target.value }) })] })] })), type === 'table' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2 col-span-2", children: [_jsx(Label, { children: "Kolommen (komma-gescheiden, leeg = alles)" }), _jsx(Input, { value: (config.columns ?? []).join(','), onChange: (e) => patchCfg({ columns: e.target.value.split(',').map(s => s.trim()).filter(Boolean) }), placeholder: "bijv. id,name,created_at" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Sorteer op" }), _jsxs(Select, { value: config.orderBy ?? '', onValueChange: (v) => patchCfg({ orderBy: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kolom" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Limiet" }), _jsx(Input, { type: "number", value: config.limit ?? 50, onChange: (e) => patchCfg({ limit: Number(e.target.value) }) })] })] })), type === 'chart' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Soort" }), _jsxs(Select, { value: config.kind, onValueChange: (v) => patchCfg({ kind: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "line", children: "Lijn" }), _jsx(SelectItem, { value: "bar", children: "Staaf" }), _jsx(SelectItem, { value: "area", children: "Vlak" }), _jsx(SelectItem, { value: "stacked-bar", children: "Gestapelde staaf" }), _jsx(SelectItem, { value: "pie", children: "Taart" }), _jsx(SelectItem, { value: "donut", children: "Donut" }), _jsx(SelectItem, { value: "scatter", children: "Scatter" }), _jsx(SelectItem, { value: "heatmap", children: "Heatmap" }), _jsx(SelectItem, { value: "funnel", children: "Funnel" })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aggregatie (Y)" }), _jsxs(Select, { value: config.agg, onValueChange: (v) => patchCfg({ agg: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: AGGS.map(a => _jsx(SelectItem, { value: a, children: a.toUpperCase() }, a)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "X-as kolom" }), _jsxs(Select, { value: config.xColumn, onValueChange: (v) => patchCfg({ xColumn: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies X" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Y-as kolom" }), _jsxs(Select, { value: config.yColumn, onValueChange: (v) => patchCfg({ yColumn: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies Y" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] })] })), type === 'ai_analysis' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2 col-span-2", children: [_jsx(Label, { children: "Kolommen (komma-gescheiden, leeg = alles)" }), _jsx(Input, { value: (config.columns ?? []).join(','), onChange: (e) => patchCfg({ columns: e.target.value.split(',').map(s => s.trim()).filter(Boolean) }), placeholder: "bijv. id,status,created_at" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Sample-grootte" }), _jsx(Input, { type: "number", value: config.limit ?? 100, onChange: (e) => patchCfg({ limit: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-2 col-span-2", children: [_jsx(Label, { children: "Extra vraag voor de AI (optioneel)" }), _jsx(Input, { value: config.question ?? '', onChange: (e) => patchCfg({ question: e.target.value }), placeholder: "bijv. Welke artikelen blijven achter?" })] })] })), type === 'forecast' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "X-as (periode/datum)" }), _jsxs(Select, { value: config.xColumn, onValueChange: (v) => patchCfg({ xColumn: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies kolom" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Y-as (waarde)" }), _jsxs(Select, { value: config.yColumn, onValueChange: (v) => patchCfg({ yColumn: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies kolom" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aggregatie" }), _jsxs(Select, { value: config.agg, onValueChange: (v) => patchCfg({ agg: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: AGGS.map(a => _jsx(SelectItem, { value: a, children: a.toUpperCase() }, a)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Horizon (periodes)" }), _jsx(Input, { type: "number", value: config.horizon ?? 6, onChange: (e) => patchCfg({ horizon: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Historie-rijen" }), _jsx(Input, { type: "number", value: config.history ?? 200, onChange: (e) => patchCfg({ history: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-2 col-span-2", children: [_jsx(Label, { children: "Extra context (optioneel)" }), _jsx(Input, { value: config.question ?? '', onChange: (e) => patchCfg({ question: e.target.value }), placeholder: "bijv. Houd rekening met seizoenseffect" })] })] })), type === 'alert' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Aggregatie" }), _jsxs(Select, { value: config.agg, onValueChange: (v) => patchCfg({ agg: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: AGGS.map(a => _jsx(SelectItem, { value: a, children: a.toUpperCase() }, a)) })] })] }), config.agg !== 'count' && (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Kolom" }), _jsxs(Select, { value: config.column ?? '', onValueChange: (v) => patchCfg({ column: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies kolom" }) }), _jsx(SelectContent, { children: currentCols.map(c => _jsx(SelectItem, { value: c.name, children: c.name }, c.name)) })] })] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Operator" }), _jsxs(Select, { value: config.operator, onValueChange: (v) => patchCfg({ operator: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: OPERATORS.map(o => _jsx(SelectItem, { value: o, children: o }, o)) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Drempelwaarde" }), _jsx(Input, { type: "number", value: config.threshold ?? 0, onChange: (e) => patchCfg({ threshold: Number(e.target.value) }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ernst" }), _jsxs(Select, { value: config.severity ?? 'warning', onValueChange: (v) => patchCfg({ severity: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "info", children: "Info" }), _jsx(SelectItem, { value: "warning", children: "Waarschuwing" }), _jsx(SelectItem, { value: "critical", children: "Kritiek" })] })] })] }), _jsxs("div", { className: "space-y-2 col-span-2", children: [_jsx(Label, { children: "Boodschap bij trigger" }), _jsx(Input, { value: config.message ?? '', onChange: (e) => patchCfg({ message: e.target.value }), placeholder: "bijv. Voorraad onder kritieke grens" })] })] }))] }), _jsxs("div", { className: "rounded-lg border bg-muted/30 p-3 space-y-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Rechten" }), _jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsxs(Label, { className: "flex flex-col gap-0.5", children: [_jsx("span", { children: "AI-analyse zichtbaar" }), _jsx("span", { className: "text-xs text-muted-foreground font-normal", children: "Toon AI-inzichten op dit blok" })] }), _jsx(Switch, { checked: permissions.can_view_ai !== false, onCheckedChange: (v) => setPermissions(p => ({ ...p, can_view_ai: v })) })] }), _jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsxs(Label, { className: "flex flex-col gap-0.5", children: [_jsx("span", { children: "Bewerkbaar" }), _jsx("span", { className: "text-xs text-muted-foreground font-normal", children: "Mag in de builder worden aangepast" })] }), _jsx(Switch, { checked: permissions.can_edit !== false, onCheckedChange: (v) => setPermissions(p => ({ ...p, can_edit: v })) })] }), _jsxs("div", { className: "space-y-1 pt-2 border-t", children: [_jsx(Label, { className: "text-sm", children: "Zichtbaar voor rollen (komma-gescheiden, leeg = iedereen)" }), _jsx(Input, { placeholder: "bijv. admin, manager, sales", value: (permissions.viewer_roles ?? []).join(', '), onChange: (e) => setPermissions(p => ({
|
|
86
|
+
...p,
|
|
87
|
+
viewer_roles: e.target.value.split(',').map(s => s.trim()).filter(Boolean),
|
|
88
|
+
})) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Server-side controle: alleen gebruikers met \u00E9\u00E9n van deze rollen (via has_role) zien dit blok." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { onClick: async () => { await onSave({ type, title, config, permissions }); onOpenChange(false); }, children: "Opslaan" })] })] }) }));
|
|
89
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockRenderer.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/BlockRenderer.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,+BAoB3D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Lock } from 'lucide-react';
|
|
3
|
+
import { KpiBlock } from './blocks/KpiBlock';
|
|
4
|
+
import { TableBlock } from './blocks/TableBlock';
|
|
5
|
+
import { ChartBlock } from './blocks/ChartBlock';
|
|
6
|
+
import { AiAnalysisBlock } from './blocks/AiAnalysisBlock';
|
|
7
|
+
import { ForecastBlock } from './blocks/ForecastBlock';
|
|
8
|
+
import { AlertBlock } from './blocks/AlertBlock';
|
|
9
|
+
import { useUserRoles } from '../../hooks/useUserRoles';
|
|
10
|
+
export function BlockRenderer({ block }) {
|
|
11
|
+
const { roles, loading } = useUserRoles();
|
|
12
|
+
const required = block.permissions?.viewer_roles ?? [];
|
|
13
|
+
if (required.length > 0 && !loading && !required.some(r => roles.includes(r))) {
|
|
14
|
+
return (_jsxs("div", { className: "h-full flex flex-col items-center justify-center text-xs text-muted-foreground border rounded-lg bg-muted/30 p-4 gap-2", children: [_jsx(Lock, { className: "w-4 h-4" }), "Geen toegang tot dit blok"] }));
|
|
15
|
+
}
|
|
16
|
+
switch (block.type) {
|
|
17
|
+
case 'kpi': return _jsx(KpiBlock, { block: block });
|
|
18
|
+
case 'table': return _jsx(TableBlock, { block: block });
|
|
19
|
+
case 'chart': return _jsx(ChartBlock, { block: block });
|
|
20
|
+
case 'ai_analysis': return _jsx(AiAnalysisBlock, { block: block });
|
|
21
|
+
case 'forecast': return _jsx(ForecastBlock, { block: block });
|
|
22
|
+
case 'alert': return _jsx(AlertBlock, { block: block });
|
|
23
|
+
default: return _jsxs("div", { className: "p-4 text-sm text-muted-foreground", children: ["Onbekend bloktype: ", block.type] });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardBuilderPage.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/DashboardBuilderPage.tsx"],"names":[],"mappings":"AAeA,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAExC,wBAAgB,oBAAoB,gCA2InC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useParams, useNavigate } from '@tanstack/react-router';
|
|
4
|
+
import { Button } from '@flowselections/core';
|
|
5
|
+
import { Save, Trash2, Pencil, Sparkles, Settings, MessageSquare } from 'lucide-react';
|
|
6
|
+
import GridLayout from 'react-grid-layout/legacy';
|
|
7
|
+
import { useDashboardPages } from '../../hooks/useDashboardPages';
|
|
8
|
+
import { useDashboardBlocks } from '../../hooks/useDashboardBlocks';
|
|
9
|
+
import { BlockRenderer } from './BlockRenderer';
|
|
10
|
+
import { BlockEditorDialog } from './BlockEditorDialog';
|
|
11
|
+
import { BlockGeneratorDialog } from './ai/BlockGeneratorDialog';
|
|
12
|
+
import { QuickAddDialog } from './QuickAddDialog';
|
|
13
|
+
import { SuggestionBanner } from './SuggestionBanner';
|
|
14
|
+
import { TileGallery } from './TileGallery';
|
|
15
|
+
import 'react-grid-layout/css/styles.css';
|
|
16
|
+
import 'react-resizable/css/styles.css';
|
|
17
|
+
export function DashboardBuilderPage() {
|
|
18
|
+
const params = useParams({ strict: false });
|
|
19
|
+
const pageId = params.pageId ?? null;
|
|
20
|
+
const navigate = useNavigate();
|
|
21
|
+
const { pages } = useDashboardPages();
|
|
22
|
+
const page = pages.find(p => p.id === pageId);
|
|
23
|
+
const { blocks, create, update, remove, updateLayouts } = useDashboardBlocks(pageId);
|
|
24
|
+
const [editorOpen, setEditorOpen] = useState(false);
|
|
25
|
+
const [editing, setEditing] = useState(null);
|
|
26
|
+
const [genOpen, setGenOpen] = useState(false);
|
|
27
|
+
const [quickOpen, setQuickOpen] = useState(false);
|
|
28
|
+
const layout = blocks.map(b => ({
|
|
29
|
+
i: b.id,
|
|
30
|
+
x: b.layout?.x ?? 0,
|
|
31
|
+
y: b.layout?.y ?? 0,
|
|
32
|
+
w: b.layout?.w ?? 4,
|
|
33
|
+
h: b.layout?.h ?? 4,
|
|
34
|
+
}));
|
|
35
|
+
async function handleLayoutChange(next) {
|
|
36
|
+
const patch = next.map(l => ({ id: l.i, layout: { x: l.x, y: l.y, w: l.w, h: l.h } }));
|
|
37
|
+
await updateLayouts(patch);
|
|
38
|
+
}
|
|
39
|
+
return (_jsxs("div", { className: "p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4 px-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Bewerken" }), _jsx("h1", { className: "text-xl font-semibold", children: page?.name ?? 'Dashboard' })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => { setEditing(null); setEditorOpen(true); }, title: "Geavanceerd: zelf instellen", children: _jsx(Settings, { className: "w-4 h-4" }) }), _jsxs(Button, { variant: "outline", onClick: () => pageId && navigate({ to: '/dashboard-erp-kwekers/$pageId', params: { pageId } }), children: [_jsx(Save, { className: "w-4 h-4 mr-2" }), " Klaar"] })] })] }), _jsx("div", { className: "px-2", children: _jsx(TileGallery, { onAdd: async ({ type, title, config }) => { await create({ type, title, config }); } }) }), blocks.length === 0 ? (_jsx("div", { className: "p-8 text-center border-2 border-dashed rounded-lg mx-2 text-muted-foreground", children: "Nog geen blokken. Kies hierboven een kant-en-klare tegel, of vraag de AI rechtsonder om een voorstel." })) : (_jsx(GridLayout, { className: "layout", layout: layout, cols: 12, rowHeight: 60, width: 1200, onLayoutChange: handleLayoutChange, draggableHandle: ".drag-handle", children: blocks.map(b => (_jsxs("div", { className: "relative group", children: [_jsx("div", { className: "drag-handle absolute top-1 left-1 right-1 h-6 cursor-move opacity-0 group-hover:opacity-100 transition bg-muted/50 rounded text-xs flex items-center justify-center text-muted-foreground z-10", children: "\u22EE\u22EE slepen" }), _jsxs("div", { className: "absolute top-1 right-1 flex gap-1 opacity-0 group-hover:opacity-100 transition z-10", children: [_jsx(Button, { size: "icon", variant: "secondary", className: "h-7 w-7", onClick: () => { setEditing(b); setEditorOpen(true); }, children: _jsx(Pencil, { className: "w-3.5 h-3.5" }) }), _jsx(Button, { size: "icon", variant: "destructive", className: "h-7 w-7", onClick: () => remove(b.id), children: _jsx(Trash2, { className: "w-3.5 h-3.5" }) })] }), _jsx(BlockRenderer, { block: b })] }, b.id))) })), blocks.length > 0 && (_jsx("div", { className: "px-2 mt-4", children: _jsx(SuggestionBanner, { block: blocks[blocks.length - 1], onAdd: async ({ type, title, config }) => { await create({ type, title, config }); } }) })), _jsxs("div", { className: "fixed bottom-6 right-6 z-40 flex flex-col items-end gap-2", children: [_jsxs(Button, { onClick: () => setQuickOpen(true), className: "shadow-lg rounded-full h-14 pl-4 pr-5", children: [_jsx(Sparkles, { className: "w-5 h-5 mr-2" }), "AI-voorstel"] }), _jsxs(Button, { variant: "secondary", size: "sm", onClick: () => setGenOpen(true), className: "shadow-md rounded-full", children: [_jsx(MessageSquare, { className: "w-4 h-4 mr-2" }), "AI-chat"] })] }), _jsx(QuickAddDialog, { open: quickOpen, onOpenChange: setQuickOpen, onAdd: async ({ type, title, config }) => { await create({ type, title, config }); }, onAdvanced: () => { setEditing(null); setEditorOpen(true); } }), _jsx(BlockEditorDialog, { open: editorOpen, onOpenChange: setEditorOpen, initial: editing, onSave: async ({ type, title, config, permissions }) => {
|
|
40
|
+
if (editing) {
|
|
41
|
+
await update(editing.id, { title, config, permissions });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await create({ type, title, config, permissions });
|
|
45
|
+
}
|
|
46
|
+
} }), _jsx(BlockGeneratorDialog, { open: genOpen, onOpenChange: setGenOpen, onSave: async ({ type, title, config, permissions }) => {
|
|
47
|
+
await create({ type, title, config, permissions });
|
|
48
|
+
} })] }));
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardErpLayoutPage.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/DashboardErpLayoutPage.tsx"],"names":[],"mappings":"AAOA,wBAAgB,sBAAsB,gCAkErC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Outlet, useParams, useNavigate, Link } from '@tanstack/react-router';
|
|
4
|
+
import { Button, Input, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@flowselections/core';
|
|
5
|
+
import { Plus, LayoutGrid } from 'lucide-react';
|
|
6
|
+
import { useDashboardPages } from '../../hooks/useDashboardPages';
|
|
7
|
+
import { AiChatPanel } from './ai/AiChatPanel';
|
|
8
|
+
export function DashboardErpLayoutPage() {
|
|
9
|
+
const { pages, loading, create } = useDashboardPages();
|
|
10
|
+
const params = useParams({ strict: false });
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const [openNew, setOpenNew] = useState(false);
|
|
13
|
+
const [name, setName] = useState('');
|
|
14
|
+
const activeId = params.pageId ?? pages[0]?.id;
|
|
15
|
+
async function handleCreate() {
|
|
16
|
+
if (!name.trim())
|
|
17
|
+
return;
|
|
18
|
+
const page = await create(name.trim());
|
|
19
|
+
setOpenNew(false);
|
|
20
|
+
setName('');
|
|
21
|
+
if (page)
|
|
22
|
+
navigate({ to: '/dashboard-erp-kwekers/$pageId', params: { pageId: page.id } });
|
|
23
|
+
}
|
|
24
|
+
return (_jsxs("div", { className: "flex flex-col h-full", children: [_jsx("div", { className: "border-b bg-card", children: _jsxs("div", { className: "px-6 pt-3 flex items-end gap-1 overflow-x-auto", children: [loading && _jsx("div", { className: "text-sm text-muted-foreground pb-3", children: "Dashboards laden\u2026" }), !loading && pages.length === 0 && (_jsx("div", { className: "text-sm text-muted-foreground pb-3", children: "Nog geen dashboards." })), pages.map(p => {
|
|
25
|
+
const isActive = p.id === activeId;
|
|
26
|
+
return (_jsxs(Link, { to: '/dashboard-erp-kwekers/$pageId', params: { pageId: p.id }, className: `px-4 py-2 text-sm rounded-t-md border-b-2 -mb-px whitespace-nowrap ${isActive
|
|
27
|
+
? 'border-primary text-foreground font-medium bg-background'
|
|
28
|
+
: 'border-transparent text-muted-foreground hover:text-foreground'}`, children: [_jsx(LayoutGrid, { className: "inline w-3.5 h-3.5 mr-1.5 -mt-0.5" }), p.name] }, p.id));
|
|
29
|
+
}), _jsxs(Button, { variant: "ghost", size: "sm", className: "ml-2 mb-1", onClick: () => setOpenNew(true), children: [_jsx(Plus, { className: "w-4 h-4 mr-1" }), " Nieuw dashboard"] })] }) }), _jsx("div", { className: "flex-1 overflow-auto", children: _jsx(Outlet, {}) }), _jsx(AiChatPanel, { pageId: activeId ?? null }), _jsx(Dialog, { open: openNew, onOpenChange: setOpenNew, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Nieuw dashboard" }) }), _jsx(Input, { value: name, onChange: (e) => setName(e.target.value), placeholder: "Bijv. Voorraad overzicht", autoFocus: true }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setOpenNew(false), children: "Annuleren" }), _jsx(Button, { onClick: handleCreate, children: "Aanmaken" })] })] }) })] }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlockRow } from './types';
|
|
2
|
+
interface Props {
|
|
3
|
+
pageId?: string;
|
|
4
|
+
pageName: string;
|
|
5
|
+
blocks: BlockRow[];
|
|
6
|
+
}
|
|
7
|
+
export declare function DashboardSummary({ pageId, pageName, blocks }: Props): import("react").JSX.Element | null;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=DashboardSummary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardSummary.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/DashboardSummary.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAuC,MAAM,SAAS,CAAC;AAE7E,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AA+CD,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,sCAqHnE"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { Card, CardContent, Button, supabase, toast } from '@flowselections/core';
|
|
4
|
+
import { Sparkles, RefreshCw, Loader2, FileText, Eye, EyeOff } from 'lucide-react';
|
|
5
|
+
import { useServerFn } from '@tanstack/react-start';
|
|
6
|
+
import ReactMarkdown from 'react-markdown';
|
|
7
|
+
import { summarizeDashboard } from '../../lib/ai-insights.functions';
|
|
8
|
+
import { saveDailyReport, listDailyReports } from '../../lib/dashboard-eval.functions';
|
|
9
|
+
import { useAiSettings, useDashboardAiVisible, buildAiInstruction } from '../../lib/aiSettings';
|
|
10
|
+
async function snapshotBlock(b) {
|
|
11
|
+
const title = b.title || b.type;
|
|
12
|
+
try {
|
|
13
|
+
if (b.type === 'kpi') {
|
|
14
|
+
const c = b.config;
|
|
15
|
+
if (!c.table)
|
|
16
|
+
return { title, type: b.type };
|
|
17
|
+
if (c.agg === 'count') {
|
|
18
|
+
const { count } = await supabase.from(c.table).select('*', { count: 'exact', head: true });
|
|
19
|
+
return { title, type: b.type, table: c.table, value: count ?? 0 };
|
|
20
|
+
}
|
|
21
|
+
if (!c.column)
|
|
22
|
+
return { title, type: b.type, table: c.table, value: 0 };
|
|
23
|
+
const { data } = await supabase.from(c.table).select(c.column).limit(5000);
|
|
24
|
+
const nums = (data ?? []).map(r => Number(r?.[c.column])).filter(n => !Number.isNaN(n));
|
|
25
|
+
let value = 0;
|
|
26
|
+
if (nums.length) {
|
|
27
|
+
switch (c.agg) {
|
|
28
|
+
case 'sum':
|
|
29
|
+
value = nums.reduce((a, b) => a + b, 0);
|
|
30
|
+
break;
|
|
31
|
+
case 'avg':
|
|
32
|
+
value = nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
33
|
+
break;
|
|
34
|
+
case 'min':
|
|
35
|
+
value = Math.min(...nums);
|
|
36
|
+
break;
|
|
37
|
+
case 'max':
|
|
38
|
+
value = Math.max(...nums);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { title, type: b.type, table: c.table, value };
|
|
43
|
+
}
|
|
44
|
+
if (b.type === 'table') {
|
|
45
|
+
const c = b.config;
|
|
46
|
+
if (!c.table)
|
|
47
|
+
return { title, type: b.type };
|
|
48
|
+
const cols = c.columns && c.columns.length ? c.columns.join(',') : '*';
|
|
49
|
+
const { data } = await supabase.from(c.table).select(cols).limit(Math.min(c.limit ?? 20, 20));
|
|
50
|
+
return { title, type: b.type, table: c.table, rows: Array.isArray(data) ? data : [] };
|
|
51
|
+
}
|
|
52
|
+
if (b.type === 'chart') {
|
|
53
|
+
const c = b.config;
|
|
54
|
+
if (!c.table)
|
|
55
|
+
return { title, type: b.type };
|
|
56
|
+
const { data } = await supabase.from(c.table).select(`${c.xColumn},${c.yColumn}`).limit(100);
|
|
57
|
+
return { title, type: b.type, table: c.table, rows: Array.isArray(data) ? data.slice(0, 20) : [] };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore
|
|
62
|
+
}
|
|
63
|
+
return { title, type: b.type };
|
|
64
|
+
}
|
|
65
|
+
export function DashboardSummary({ pageId, pageName, blocks }) {
|
|
66
|
+
const [settings] = useAiSettings();
|
|
67
|
+
const [visible, setVisible] = useDashboardAiVisible(pageId);
|
|
68
|
+
const run = useServerFn(summarizeDashboard);
|
|
69
|
+
const save = useServerFn(saveDailyReport);
|
|
70
|
+
const listReports = useServerFn(listDailyReports);
|
|
71
|
+
const [text, setText] = useState('');
|
|
72
|
+
const [loading, setLoading] = useState(false);
|
|
73
|
+
const [error, setError] = useState(null);
|
|
74
|
+
const [saving, setSaving] = useState(false);
|
|
75
|
+
const [reports, setReports] = useState([]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!pageId)
|
|
78
|
+
return;
|
|
79
|
+
(async () => {
|
|
80
|
+
try {
|
|
81
|
+
const res = await listReports({ data: { pageId } });
|
|
82
|
+
setReports(Array.isArray(res?.reports) ? res.reports.slice(0, 3) : []);
|
|
83
|
+
}
|
|
84
|
+
catch { /* niet-fataal */ }
|
|
85
|
+
})();
|
|
86
|
+
}, [pageId, listReports]);
|
|
87
|
+
const summarize = useCallback(async () => {
|
|
88
|
+
if (!settings.enabled || !settings.types.dashboard)
|
|
89
|
+
return;
|
|
90
|
+
const dataBlocks = blocks.filter(b => b.type !== 'ai_analysis');
|
|
91
|
+
if (!dataBlocks.length)
|
|
92
|
+
return;
|
|
93
|
+
setLoading(true);
|
|
94
|
+
setError(null);
|
|
95
|
+
try {
|
|
96
|
+
const snapshots = await Promise.all(dataBlocks.map(snapshotBlock));
|
|
97
|
+
const res = await run({
|
|
98
|
+
data: { pageName, blocks: snapshots, instruction: buildAiInstruction(settings) },
|
|
99
|
+
});
|
|
100
|
+
setText(res.text);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
setError(e?.message ?? 'Fout bij samenvatting');
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
}
|
|
108
|
+
}, [run, pageName, blocks, settings]);
|
|
109
|
+
useEffect(() => { if (visible)
|
|
110
|
+
summarize(); /* eslint-disable-next-line */ }, [pageName, blocks.length, settings.enabled, settings.detail, settings.personality, visible]);
|
|
111
|
+
if (!blocks.length)
|
|
112
|
+
return null;
|
|
113
|
+
if (!settings.enabled || !settings.types.dashboard)
|
|
114
|
+
return null;
|
|
115
|
+
if (!visible) {
|
|
116
|
+
return (_jsx("div", { className: "mb-2 mx-2 flex justify-end", children: _jsxs(Button, { size: "sm", variant: "ghost", onClick: () => setVisible(true), className: "h-7 text-xs gap-1", children: [_jsx(Eye, { className: "w-3.5 h-3.5" }), " AI-samenvatting tonen"] }) }));
|
|
117
|
+
}
|
|
118
|
+
async function handleSave() {
|
|
119
|
+
if (!pageId || !text)
|
|
120
|
+
return;
|
|
121
|
+
setSaving(true);
|
|
122
|
+
try {
|
|
123
|
+
await save({ data: { pageId, summary: text } });
|
|
124
|
+
toast.success('Rapport opgeslagen');
|
|
125
|
+
const res = await listReports({ data: { pageId } });
|
|
126
|
+
setReports(Array.isArray(res?.reports) ? res.reports.slice(0, 3) : []);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
toast.error('Opslaan mislukt: ' + (e?.message ?? ''));
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
setSaving(false);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return (_jsx(Card, { className: "mb-4 mx-2 border-primary/20 bg-gradient-to-br from-primary/5 to-transparent", children: _jsxs(CardContent, { className: "py-4 px-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-primary", children: [_jsx(Sparkles, { className: "w-4 h-4" }), " AI-samenvatting"] }), _jsxs("div", { className: "flex items-center gap-1 -mt-1", children: [_jsx(Button, { size: "icon", variant: "ghost", onClick: () => setVisible(false), className: "h-7 w-7", title: "Verbergen op dit dashboard", children: _jsx(EyeOff, { className: "w-3.5 h-3.5" }) }), pageId && text && (_jsx(Button, { size: "sm", variant: "ghost", onClick: handleSave, disabled: saving, className: "h-7 text-xs", children: saving ? 'Opslaan…' : 'Bewaar als dagrapport' })), _jsx(Button, { size: "icon", variant: "ghost", onClick: summarize, disabled: loading, className: "h-7 w-7", children: loading ? _jsx(Loader2, { className: "w-3.5 h-3.5 animate-spin" }) : _jsx(RefreshCw, { className: "w-3.5 h-3.5" }) })] })] }), _jsxs("div", { className: "mt-2 text-sm", children: [error && _jsx("div", { className: "text-destructive", children: error }), loading && !text && _jsx("div", { className: "text-muted-foreground", children: "AI leest het dashboard\u2026" }), text && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none", children: _jsx(ReactMarkdown, { children: text }) }))] }), reports.length > 0 && (_jsxs("div", { className: "mt-3 pt-3 border-t border-primary/10", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-muted-foreground mb-2", children: [_jsx(FileText, { className: "w-3.5 h-3.5" }), " Recente dagrapporten"] }), _jsx("div", { className: "space-y-2", children: reports.map(r => (_jsxs("details", { className: "text-xs", children: [_jsx("summary", { className: "cursor-pointer text-muted-foreground hover:text-foreground", children: new Date(r.created_at).toLocaleString('nl-NL') }), _jsx("div", { className: "mt-1 pl-3 text-foreground/80", children: r.summary })] }, r.id))) })] }))] }) }));
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardViewPage.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/DashboardViewPage.tsx"],"names":[],"mappings":"AAUA,OAAO,kCAAkC,CAAC;AAE1C,wBAAgB,iBAAiB,gCAMhC"}
|