@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.
Files changed (150) hide show
  1. package/dist-lib/_core-safelist.d.ts +2 -0
  2. package/dist-lib/_core-safelist.d.ts.map +1 -0
  3. package/dist-lib/_core-safelist.js +15 -0
  4. package/dist-lib/components/DashboardErpLogo.d.ts +6 -0
  5. package/dist-lib/components/DashboardErpLogo.d.ts.map +1 -0
  6. package/dist-lib/components/DashboardErpLogo.js +4 -0
  7. package/dist-lib/components/dashboard/AiSettingsCard.d.ts +2 -0
  8. package/dist-lib/components/dashboard/AiSettingsCard.d.ts.map +1 -0
  9. package/dist-lib/components/dashboard/AiSettingsCard.js +37 -0
  10. package/dist-lib/components/dashboard/AlertHistoryPanel.d.ts +4 -0
  11. package/dist-lib/components/dashboard/AlertHistoryPanel.d.ts.map +1 -0
  12. package/dist-lib/components/dashboard/AlertHistoryPanel.js +18 -0
  13. package/dist-lib/components/dashboard/BlockEditorDialog.d.ts +15 -0
  14. package/dist-lib/components/dashboard/BlockEditorDialog.d.ts.map +1 -0
  15. package/dist-lib/components/dashboard/BlockEditorDialog.js +89 -0
  16. package/dist-lib/components/dashboard/BlockRenderer.d.ts +5 -0
  17. package/dist-lib/components/dashboard/BlockRenderer.d.ts.map +1 -0
  18. package/dist-lib/components/dashboard/BlockRenderer.js +25 -0
  19. package/dist-lib/components/dashboard/DashboardBuilderPage.d.ts +4 -0
  20. package/dist-lib/components/dashboard/DashboardBuilderPage.d.ts.map +1 -0
  21. package/dist-lib/components/dashboard/DashboardBuilderPage.js +49 -0
  22. package/dist-lib/components/dashboard/DashboardErpLayoutPage.d.ts +2 -0
  23. package/dist-lib/components/dashboard/DashboardErpLayoutPage.d.ts.map +1 -0
  24. package/dist-lib/components/dashboard/DashboardErpLayoutPage.js +30 -0
  25. package/dist-lib/components/dashboard/DashboardSummary.d.ts +9 -0
  26. package/dist-lib/components/dashboard/DashboardSummary.d.ts.map +1 -0
  27. package/dist-lib/components/dashboard/DashboardSummary.js +136 -0
  28. package/dist-lib/components/dashboard/DashboardViewPage.d.ts +3 -0
  29. package/dist-lib/components/dashboard/DashboardViewPage.d.ts.map +1 -0
  30. package/dist-lib/components/dashboard/DashboardViewPage.js +37 -0
  31. package/dist-lib/components/dashboard/FilterBar.d.ts +17 -0
  32. package/dist-lib/components/dashboard/FilterBar.d.ts.map +1 -0
  33. package/dist-lib/components/dashboard/FilterBar.js +66 -0
  34. package/dist-lib/components/dashboard/QuickAddDialog.d.ts +15 -0
  35. package/dist-lib/components/dashboard/QuickAddDialog.d.ts.map +1 -0
  36. package/dist-lib/components/dashboard/QuickAddDialog.js +168 -0
  37. package/dist-lib/components/dashboard/SuggestionBanner.d.ts +15 -0
  38. package/dist-lib/components/dashboard/SuggestionBanner.d.ts.map +1 -0
  39. package/dist-lib/components/dashboard/SuggestionBanner.js +13 -0
  40. package/dist-lib/components/dashboard/TileGallery.d.ts +17 -0
  41. package/dist-lib/components/dashboard/TileGallery.d.ts.map +1 -0
  42. package/dist-lib/components/dashboard/TileGallery.js +60 -0
  43. package/dist-lib/components/dashboard/ai/AiChatPanel.d.ts +6 -0
  44. package/dist-lib/components/dashboard/ai/AiChatPanel.d.ts.map +1 -0
  45. package/dist-lib/components/dashboard/ai/AiChatPanel.js +57 -0
  46. package/dist-lib/components/dashboard/ai/BlockGeneratorDialog.d.ts +14 -0
  47. package/dist-lib/components/dashboard/ai/BlockGeneratorDialog.d.ts.map +1 -0
  48. package/dist-lib/components/dashboard/ai/BlockGeneratorDialog.js +61 -0
  49. package/dist-lib/components/dashboard/blocks/AiAnalysisBlock.d.ts +5 -0
  50. package/dist-lib/components/dashboard/blocks/AiAnalysisBlock.d.ts.map +1 -0
  51. package/dist-lib/components/dashboard/blocks/AiAnalysisBlock.js +58 -0
  52. package/dist-lib/components/dashboard/blocks/AlertBlock.d.ts +5 -0
  53. package/dist-lib/components/dashboard/blocks/AlertBlock.d.ts.map +1 -0
  54. package/dist-lib/components/dashboard/blocks/AlertBlock.js +107 -0
  55. package/dist-lib/components/dashboard/blocks/BlockShell.d.ts +10 -0
  56. package/dist-lib/components/dashboard/blocks/BlockShell.d.ts.map +1 -0
  57. package/dist-lib/components/dashboard/blocks/BlockShell.js +39 -0
  58. package/dist-lib/components/dashboard/blocks/ChartBlock.d.ts +5 -0
  59. package/dist-lib/components/dashboard/blocks/ChartBlock.d.ts.map +1 -0
  60. package/dist-lib/components/dashboard/blocks/ChartBlock.js +31 -0
  61. package/dist-lib/components/dashboard/blocks/ForecastBlock.d.ts +5 -0
  62. package/dist-lib/components/dashboard/blocks/ForecastBlock.d.ts.map +1 -0
  63. package/dist-lib/components/dashboard/blocks/ForecastBlock.js +86 -0
  64. package/dist-lib/components/dashboard/blocks/KpiBlock.d.ts +5 -0
  65. package/dist-lib/components/dashboard/blocks/KpiBlock.d.ts.map +1 -0
  66. package/dist-lib/components/dashboard/blocks/KpiBlock.js +17 -0
  67. package/dist-lib/components/dashboard/blocks/TableBlock.d.ts +5 -0
  68. package/dist-lib/components/dashboard/blocks/TableBlock.d.ts.map +1 -0
  69. package/dist-lib/components/dashboard/blocks/TableBlock.js +12 -0
  70. package/dist-lib/components/dashboard/types.d.ts +129 -0
  71. package/dist-lib/components/dashboard/types.d.ts.map +1 -0
  72. package/dist-lib/components/dashboard/types.js +1 -0
  73. package/dist-lib/hooks/useAlertEvents.d.ts +9 -0
  74. package/dist-lib/hooks/useAlertEvents.d.ts.map +1 -0
  75. package/dist-lib/hooks/useAlertEvents.js +32 -0
  76. package/dist-lib/hooks/useBlockData.d.ts +10 -0
  77. package/dist-lib/hooks/useBlockData.d.ts.map +1 -0
  78. package/dist-lib/hooks/useBlockData.js +130 -0
  79. package/dist-lib/hooks/useDashboardBlocks.d.ts +25 -0
  80. package/dist-lib/hooks/useDashboardBlocks.d.ts.map +1 -0
  81. package/dist-lib/hooks/useDashboardBlocks.js +66 -0
  82. package/dist-lib/hooks/useDashboardPages.d.ts +10 -0
  83. package/dist-lib/hooks/useDashboardPages.d.ts.map +1 -0
  84. package/dist-lib/hooks/useDashboardPages.js +56 -0
  85. package/dist-lib/hooks/useSchema.d.ts +13 -0
  86. package/dist-lib/hooks/useSchema.d.ts.map +1 -0
  87. package/dist-lib/hooks/useSchema.js +23 -0
  88. package/dist-lib/hooks/useUserRoles.d.ts +5 -0
  89. package/dist-lib/hooks/useUserRoles.d.ts.map +1 -0
  90. package/dist-lib/hooks/useUserRoles.js +23 -0
  91. package/dist-lib/index.d.ts +17 -0
  92. package/dist-lib/index.d.ts.map +1 -0
  93. package/dist-lib/index.js +47 -0
  94. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  95. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  96. package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
  97. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2555 -0
  98. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  99. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  100. package/dist-lib/integrations/supabase/client.d.ts +2551 -0
  101. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  102. package/dist-lib/integrations/supabase/client.js +13 -0
  103. package/dist-lib/integrations/supabase/client.server.d.ts +2551 -0
  104. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  105. package/dist-lib/integrations/supabase/client.server.js +30 -0
  106. package/dist-lib/integrations/supabase/types.d.ts +2685 -0
  107. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  108. package/dist-lib/integrations/supabase/types.js +8 -0
  109. package/dist-lib/lib/ai-gateway.server.d.ts +2 -0
  110. package/dist-lib/lib/ai-gateway.server.d.ts.map +1 -0
  111. package/dist-lib/lib/ai-gateway.server.js +11 -0
  112. package/dist-lib/lib/ai-insights.functions.d.ts +10271 -0
  113. package/dist-lib/lib/ai-insights.functions.d.ts.map +1 -0
  114. package/dist-lib/lib/ai-insights.functions.js +118 -0
  115. package/dist-lib/lib/aiSettings.d.ts +22 -0
  116. package/dist-lib/lib/aiSettings.d.ts.map +1 -0
  117. package/dist-lib/lib/aiSettings.js +107 -0
  118. package/dist-lib/lib/dashboard-eval.functions.d.ts +5130 -0
  119. package/dist-lib/lib/dashboard-eval.functions.d.ts.map +1 -0
  120. package/dist-lib/lib/dashboard-eval.functions.js +87 -0
  121. package/dist-lib/lib/dashboard.functions.d.ts +2562 -0
  122. package/dist-lib/lib/dashboard.functions.d.ts.map +1 -0
  123. package/dist-lib/lib/dashboard.functions.js +21 -0
  124. package/dist-lib/lib/dashboardTemplates.d.ts +11 -0
  125. package/dist-lib/lib/dashboardTemplates.d.ts.map +1 -0
  126. package/dist-lib/lib/dashboardTemplates.js +44 -0
  127. package/dist-lib/lib/format.d.ts +20 -0
  128. package/dist-lib/lib/format.d.ts.map +1 -0
  129. package/dist-lib/lib/format.js +121 -0
  130. package/dist-lib/lib/query.functions.d.ts +2583 -0
  131. package/dist-lib/lib/query.functions.d.ts.map +1 -0
  132. package/dist-lib/lib/query.functions.js +85 -0
  133. package/dist-lib/lib/quickBlocks.d.ts +28 -0
  134. package/dist-lib/lib/quickBlocks.d.ts.map +1 -0
  135. package/dist-lib/lib/quickBlocks.js +297 -0
  136. package/dist-lib/lib/roles.functions.d.ts +2556 -0
  137. package/dist-lib/lib/roles.functions.d.ts.map +1 -0
  138. package/dist-lib/lib/roles.functions.js +14 -0
  139. package/dist-lib/lib/suggestions.d.ts +8 -0
  140. package/dist-lib/lib/suggestions.d.ts.map +1 -0
  141. package/dist-lib/lib/suggestions.js +41 -0
  142. package/dist-lib/lib/utils.d.ts +3 -0
  143. package/dist-lib/lib/utils.d.ts.map +1 -0
  144. package/dist-lib/lib/utils.js +5 -0
  145. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  146. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  147. package/dist-lib/lib/validationSchemas.js +25 -0
  148. package/dist-lib/styles.css +1 -0
  149. package/package.json +95 -0
  150. package/public/flowselections-assets/template-module/README.md +15 -0
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useParams, useNavigate, Link } from '@tanstack/react-router';
3
+ import { Button } from '@flowselections/core';
4
+ import { Pencil, LayoutGrid } from 'lucide-react';
5
+ import GridLayout from 'react-grid-layout/legacy';
6
+ import { useDashboardPages } from '../../hooks/useDashboardPages';
7
+ import { useDashboardBlocks } from '../../hooks/useDashboardBlocks';
8
+ import { BlockRenderer } from './BlockRenderer';
9
+ import { DashboardSummary } from './DashboardSummary';
10
+ import { AlertHistoryPanel } from './AlertHistoryPanel';
11
+ import { FilterBar, FilterProvider } from './FilterBar';
12
+ import 'react-grid-layout/css/styles.css';
13
+ export function DashboardViewPage() {
14
+ return (_jsx(FilterProvider, { children: _jsx(DashboardViewInner, {}) }));
15
+ }
16
+ function DashboardViewInner() {
17
+ const { pages, loading: pagesLoading } = useDashboardPages();
18
+ const params = useParams({ strict: false });
19
+ const navigate = useNavigate();
20
+ const pageId = params.pageId ?? pages[0]?.id ?? null;
21
+ const page = pages.find(p => p.id === pageId);
22
+ const { blocks, loading } = useDashboardBlocks(pageId);
23
+ if (pagesLoading)
24
+ return _jsx("div", { className: "p-6 text-muted-foreground", children: "Laden\u2026" });
25
+ if (!page) {
26
+ return (_jsxs("div", { className: "p-12 text-center", children: [_jsx(LayoutGrid, { className: "w-12 h-12 mx-auto text-muted-foreground/40" }), _jsx("h2", { className: "mt-4 text-lg font-medium", children: "Nog geen dashboard" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Maak je eerste dashboard aan via de tabbalk hierboven." })] }));
27
+ }
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
+ static: true,
35
+ }));
36
+ return (_jsxs("div", { className: "p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4 px-2", children: [_jsx("h1", { className: "text-xl font-semibold", children: page.name }), _jsx(Button, { asChild: true, size: "sm", children: _jsxs(Link, { to: '/dashboard-erp-kwekers/builder/$pageId', params: { pageId: page.id }, children: [_jsx(Pencil, { className: "w-4 h-4 mr-2" }), " Bewerken"] }) })] }), loading ? (_jsx("div", { className: "text-sm text-muted-foreground px-2", children: "Blokken laden\u2026" })) : blocks.length === 0 ? (_jsxs("div", { className: "p-12 text-center border-2 border-dashed rounded-lg mx-2", children: [_jsx("p", { className: "text-muted-foreground", children: "Dit dashboard is nog leeg." }), _jsxs(Button, { className: "mt-4", onClick: () => navigate({ to: '/dashboard-erp-kwekers/builder/$pageId', params: { pageId: page.id } }), children: [_jsx(Pencil, { className: "w-4 h-4 mr-2" }), " Blokken toevoegen"] })] })) : (_jsxs(_Fragment, { children: [_jsx(FilterBar, {}), _jsx(DashboardSummary, { pageId: page.id, pageName: page.name, blocks: blocks }), _jsx(AlertHistoryPanel, { pageId: page.id }), _jsx(GridLayout, { className: "layout", layout: layout, cols: 12, rowHeight: 60, width: 1200, isDraggable: false, isResizable: false, children: blocks.map(b => (_jsx("div", { children: _jsx(BlockRenderer, { block: b }) }, b.id))) })] }))] }));
37
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface GlobalFilter {
3
+ from?: string;
4
+ to?: string;
5
+ search?: string;
6
+ }
7
+ interface Ctx {
8
+ filter: GlobalFilter;
9
+ setFilter: (f: GlobalFilter) => void;
10
+ }
11
+ export declare function FilterProvider({ children }: {
12
+ children: ReactNode;
13
+ }): import("react").JSX.Element;
14
+ export declare function useGlobalFilter(): Ctx;
15
+ export declare function FilterBar(): import("react").JSX.Element;
16
+ export {};
17
+ //# sourceMappingURL=FilterBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterBar.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/FilterBar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,GAAG;IACX,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;CACtC;AAID,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAInE;AAED,wBAAgB,eAAe,QAE9B;AAED,wBAAgB,SAAS,gCA4GxB"}
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo, useState } from 'react';
3
+ import { Input, Label, Button, Popover, PopoverContent, PopoverTrigger, Separator } from '@flowselections/core';
4
+ import { Filter, X, Calendar, Check } from 'lucide-react';
5
+ import { formatDate } from '../../lib/format';
6
+ const FilterCtx = createContext({ filter: {}, setFilter: () => { } });
7
+ export function FilterProvider({ children }) {
8
+ const [filter, setFilter] = useState({});
9
+ const value = useMemo(() => ({ filter, setFilter }), [filter]);
10
+ return _jsx(FilterCtx.Provider, { value: value, children: children });
11
+ }
12
+ export function useGlobalFilter() {
13
+ return useContext(FilterCtx);
14
+ }
15
+ export function FilterBar() {
16
+ const { filter, setFilter } = useGlobalFilter();
17
+ const active = !!(filter.from || filter.to || filter.search);
18
+ const presets = [
19
+ { id: 'today', label: 'Vandaag', days: 0 },
20
+ { id: '7d', label: 'Laatste 7 dagen', days: 7 },
21
+ { id: '30d', label: 'Laatste 30 dagen', days: 30 },
22
+ { id: '90d', label: 'Laatste 90 dagen', days: 90 },
23
+ { id: '6m', label: 'Laatste 6 maanden', days: 180 },
24
+ { id: '12m', label: 'Laatste 12 maanden', days: 365 },
25
+ { id: 'all', label: 'Alle tijd', days: null },
26
+ ];
27
+ function applyPreset(days) {
28
+ if (days === null) {
29
+ setFilter({ ...filter, from: undefined, to: undefined });
30
+ return;
31
+ }
32
+ const today = new Date();
33
+ const to = today.toISOString().slice(0, 10);
34
+ const fromDate = new Date(today.getTime() - days * 86400000);
35
+ const from = fromDate.toISOString().slice(0, 10);
36
+ setFilter({ ...filter, from, to });
37
+ }
38
+ function activePresetId() {
39
+ if (!filter.from && !filter.to)
40
+ return 'all';
41
+ if (!filter.from || !filter.to)
42
+ return null;
43
+ const diff = Math.round((new Date(filter.to).getTime() - new Date(filter.from).getTime()) / 86400000);
44
+ const today = new Date().toISOString().slice(0, 10);
45
+ if (filter.to !== today)
46
+ return null;
47
+ const match = presets.find((p) => p.days === diff);
48
+ return match?.id ?? null;
49
+ }
50
+ const activeId = activePresetId();
51
+ const rangeLabel = (() => {
52
+ if (activeId)
53
+ return presets.find((p) => p.id === activeId)?.label ?? 'Periode';
54
+ if (filter.from && filter.to)
55
+ return `${formatDate(filter.from)} – ${formatDate(filter.to)}`;
56
+ if (filter.from)
57
+ return `Vanaf ${formatDate(filter.from)}`;
58
+ if (filter.to)
59
+ return `T/m ${formatDate(filter.to)}`;
60
+ return 'Periode';
61
+ })();
62
+ return (_jsxs("div", { className: "flex items-center gap-3 flex-wrap mb-4 mx-2 p-3 rounded-lg border bg-muted/30", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-muted-foreground", children: [_jsx(Filter, { className: "w-4 h-4" }), " Filters"] }), _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9 gap-2", children: [_jsx(Calendar, { className: "w-4 h-4" }), _jsx("span", { className: "font-medium", children: rangeLabel })] }) }), _jsxs(PopoverContent, { align: "start", className: "w-72 p-2", children: [_jsx("div", { className: "text-xs font-semibold text-muted-foreground px-2 py-1.5", children: "Snelle keuze" }), _jsx("div", { className: "flex flex-col", children: presets.map((p) => {
63
+ const isActive = activeId === p.id;
64
+ return (_jsxs("button", { type: "button", onClick: () => applyPreset(p.days), className: `flex items-center justify-between text-sm rounded-md px-2 py-1.5 hover:bg-accent text-left ${isActive ? 'bg-accent font-medium' : ''}`, children: [_jsx("span", { children: p.label }), isActive && _jsx(Check, { className: "w-3.5 h-3.5" })] }, p.id));
65
+ }) }), _jsx(Separator, { className: "my-2" }), _jsx("div", { className: "text-xs font-semibold text-muted-foreground px-2 py-1", children: "Eigen periode" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 px-1 pb-1", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: "Van" }), _jsx(Input, { type: "date", value: filter.from ?? '', onChange: (e) => setFilter({ ...filter, from: e.target.value || undefined }), className: "h-8" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: "Tot" }), _jsx(Input, { type: "date", value: filter.to ?? '', onChange: (e) => setFilter({ ...filter, to: e.target.value || undefined }), className: "h-8" })] })] })] })] }), active && (_jsxs(Button, { size: "sm", variant: "ghost", onClick: () => setFilter({}), className: "h-9", children: [_jsx(X, { className: "w-3.5 h-3.5 mr-1" }), " Wissen"] }))] }));
66
+ }
@@ -0,0 +1,15 @@
1
+ import type { BlockType, AnyBlockConfig } from './types';
2
+ export interface QuickAddSubmit {
3
+ type: BlockType;
4
+ title: string;
5
+ config: AnyBlockConfig;
6
+ }
7
+ interface Props {
8
+ open: boolean;
9
+ onOpenChange: (v: boolean) => void;
10
+ onAdd: (input: QuickAddSubmit) => Promise<void> | void;
11
+ onAdvanced?: () => void;
12
+ }
13
+ export declare function QuickAddDialog({ open, onOpenChange, onAdd, onAdvanced }: Props): import("react").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=QuickAddDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuickAddDialog.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/QuickAddDialog.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAYzD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,+BAsD9E"}
@@ -0,0 +1,168 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Tabs, TabsContent, TabsList, TabsTrigger, Button, Card, CardContent, Textarea, toast, } from '@flowselections/core';
4
+ import { AlertTriangle, CalendarDays, ClipboardList, Euro, LayoutDashboard, LineChart, List, Loader2, Package, ShoppingCart, Sparkles, TrendingUp, Trophy, Truck, UserPlus, Users, Wallet, Wand2, ArrowRight, ArrowLeft, BarChart3, MessageSquare, } from 'lucide-react';
5
+ import { useServerFn } from '@tanstack/react-start';
6
+ import { proposeBlockConfig } from '../../lib/ai-insights.functions';
7
+ import { useSchema } from '../../hooks/useSchema';
8
+ import { QUICK_BLOCKS, QUICK_CATEGORIES } from '../../lib/quickBlocks';
9
+ const ICONS = {
10
+ AlertTriangle, CalendarDays, ClipboardList, Euro, LayoutDashboard, LineChart,
11
+ List, Package, ShoppingCart, Sparkles, TrendingUp, Trophy, Truck, UserPlus,
12
+ Users, Wallet, Wand2, BarChart3, MessageSquare,
13
+ };
14
+ function Icon({ name, className }) {
15
+ const C = ICONS[name] ?? Sparkles;
16
+ return _jsx(C, { className: className });
17
+ }
18
+ export function QuickAddDialog({ open, onOpenChange, onAdd, onAdvanced }) {
19
+ const [tab, setTab] = useState('kant');
20
+ async function addQuickBlock(q) {
21
+ await onAdd({ type: q.type, title: q.title, config: q.config });
22
+ toast.success(`"${q.label}" toegevoegd`);
23
+ onOpenChange(false);
24
+ }
25
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-4xl max-h-[85vh] overflow-y-auto", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { className: "text-2xl", children: "Wat wil je weten?" }), _jsx(DialogDescription, { children: "Kies een kant-en-klaar blok, beantwoord een paar vragen, of vraag het de AI." })] }), _jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(v), className: "w-full", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-3", children: [_jsxs(TabsTrigger, { value: "kant", children: [_jsx(Sparkles, { className: "w-4 h-4 mr-2" }), "Kant-en-klaar"] }), _jsxs(TabsTrigger, { value: "wizard", children: [_jsx(Wand2, { className: "w-4 h-4 mr-2" }), "Stap voor stap"] }), _jsxs(TabsTrigger, { value: "ai", children: [_jsx(MessageSquare, { className: "w-4 h-4 mr-2" }), "Vraag het de AI"] })] }), _jsx(TabsContent, { value: "kant", className: "mt-4", children: _jsx(KantEnKlaarTab, { onPick: addQuickBlock }) }), _jsx(TabsContent, { value: "wizard", className: "mt-4", children: _jsx(WizardTab, { onFinish: async (q) => { await addQuickBlock(q); } }) }), _jsx(TabsContent, { value: "ai", className: "mt-4", children: _jsx(AiTab, { onAdd: async (sub) => { await onAdd(sub); onOpenChange(false); } }) })] }), onAdvanced && (_jsxs("div", { className: "pt-3 border-t mt-2 text-xs text-muted-foreground flex items-center justify-between", children: [_jsx("span", { children: "Liever zelf alles instellen?" }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => { onOpenChange(false); onAdvanced(); }, children: ["Geavanceerd ", _jsx(ArrowRight, { className: "w-3.5 h-3.5 ml-1" })] })] }))] }) }));
26
+ }
27
+ // ------------------------- Tab 1: Kant-en-klaar -------------------------
28
+ function KantEnKlaarTab({ onPick }) {
29
+ const [cat, setCat] = useState('verkoop');
30
+ const items = QUICK_BLOCKS.filter((b) => b.category === cat);
31
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex flex-wrap gap-2", children: QUICK_CATEGORIES.map((c) => (_jsxs(Button, { size: "sm", variant: cat === c.id ? 'default' : 'outline', onClick: () => setCat(c.id), children: [_jsx(Icon, { name: c.icon, className: "w-4 h-4 mr-2" }), c.label] }, c.id))) }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3", children: items.map((q) => (_jsx(Card, { className: "cursor-pointer hover:border-primary hover:shadow-md transition group", onClick: () => onPick(q), children: _jsx(CardContent, { className: "p-4", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition", children: _jsx(Icon, { name: q.icon, className: "w-5 h-5" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm", children: q.label }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5 line-clamp-2", children: q.description })] })] }) }) }, q.id))) })] }));
32
+ }
33
+ const TOPICS = [
34
+ { id: 'omzet', label: 'Omzet', icon: 'Euro', description: 'Verkoopopbrengsten' },
35
+ { id: 'orders', label: 'Orders', icon: 'ShoppingCart', description: 'Verkooporders' },
36
+ { id: 'voorraad', label: 'Voorraad', icon: 'Package', description: 'Producten op voorraad' },
37
+ { id: 'klanten', label: 'Klanten', icon: 'Users', description: 'Klantgegevens' },
38
+ { id: 'inkoop', label: 'Inkoop', icon: 'Truck', description: 'Inkooporders en leveranciers' },
39
+ ];
40
+ const PERIODS = [
41
+ { id: 'today', label: 'Vandaag', days: 1 },
42
+ { id: 'week', label: 'Deze week', days: 7 },
43
+ { id: 'month', label: 'Deze maand', days: 30 },
44
+ { id: 'year', label: 'Dit jaar', days: 365 },
45
+ { id: 'all', label: 'Alle tijd', days: 0 },
46
+ ];
47
+ const VIEWS = [
48
+ { id: 'kpi', label: 'Groot cijfer', icon: 'BarChart3', description: 'Eén belangrijk getal' },
49
+ { id: 'chart', label: 'Grafiek', icon: 'TrendingUp', description: 'Lijn- of staafgrafiek' },
50
+ { id: 'table', label: 'Tabel', icon: 'List', description: 'Overzichtelijke lijst' },
51
+ { id: 'ai_analysis', label: 'AI-inzicht', icon: 'Sparkles', description: 'AI vat de data samen' },
52
+ { id: 'forecast', label: 'Voorspelling', icon: 'LineChart', description: 'AI-prognose' },
53
+ ];
54
+ function WizardTab({ onFinish }) {
55
+ const [step, setStep] = useState(1);
56
+ const [topic, setTopic] = useState(null);
57
+ const [period, setPeriod] = useState(null);
58
+ const [view, setView] = useState(null);
59
+ const preview = useMemo(() => {
60
+ if (!topic || !period || !view)
61
+ return null;
62
+ return buildWizardBlock(topic, period, view);
63
+ }, [topic, period, view]);
64
+ function reset() { setStep(1); setTopic(null); setPeriod(null); setView(null); }
65
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx("span", { className: step >= 1 ? 'text-primary font-medium' : '', children: "1. Onderwerp" }), _jsx(ArrowRight, { className: "w-3 h-3" }), _jsx("span", { className: step >= 2 ? 'text-primary font-medium' : '', children: "2. Periode" }), _jsx(ArrowRight, { className: "w-3 h-3" }), _jsx("span", { className: step >= 3 ? 'text-primary font-medium' : '', children: "3. Weergave" }), _jsx(ArrowRight, { className: "w-3 h-3" }), _jsx("span", { className: step >= 4 ? 'text-primary font-medium' : '', children: "4. Klaar" })] }), step === 1 && (_jsx(StepGrid, { title: "Wat wil je weten?", items: TOPICS, selected: topic, onPick: (id) => { setTopic(id); setStep(2); } })), step === 2 && (_jsx(StepGrid, { title: "Over welke periode?", items: PERIODS.map((p) => ({ ...p, icon: 'CalendarDays', description: '' })), selected: period, onPick: (id) => { setPeriod(id); setStep(3); } })), step === 3 && (_jsx(StepGrid, { title: "Hoe wil je dit zien?", items: VIEWS, selected: view, onPick: (id) => { setView(id); setStep(4); } })), step === 4 && preview && (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "text-sm font-medium", children: "Klaar om toe te voegen:" }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4 flex items-start gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2 text-primary", children: _jsx(Icon, { name: preview.icon, className: "w-5 h-5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: preview.label }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: preview.description })] })] }) }), _jsx(Button, { className: "w-full", onClick: async () => { await onFinish(preview); reset(); }, children: "Toevoegen aan dashboard" })] })), _jsxs("div", { className: "flex justify-between pt-2 border-t", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setStep(Math.max(1, step - 1)), disabled: step === 1, children: [_jsx(ArrowLeft, { className: "w-4 h-4 mr-1" }), " Terug"] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: reset, children: "Opnieuw beginnen" })] })] }));
66
+ }
67
+ function StepGrid({ title, items, selected, onPick, }) {
68
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "text-base font-medium", children: title }), _jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3", children: items.map((it) => (_jsx(Card, { className: `cursor-pointer hover:border-primary hover:shadow-md transition ${selected === it.id ? 'border-primary ring-2 ring-primary/30' : ''}`, onClick: () => onPick(it.id), children: _jsxs(CardContent, { className: "p-4 flex flex-col items-center text-center gap-2", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-3 text-primary", children: _jsx(Icon, { name: it.icon, className: "w-6 h-6" }) }), _jsx("div", { className: "font-medium text-sm", children: it.label }), it.description && _jsx("div", { className: "text-xs text-muted-foreground line-clamp-2", children: it.description })] }) }, it.id))) })] }));
69
+ }
70
+ function buildWizardBlock(topic, period, view) {
71
+ const days = PERIODS.find((p) => p.id === period)?.days ?? 0;
72
+ const periodLabel = PERIODS.find((p) => p.id === period)?.label ?? '';
73
+ // Mapping topic -> tabel + zinvolle kolommen
74
+ const map = {
75
+ omzet: { table: 'order_items', valueColumn: 'unit_price', xColumn: 'created_at', format: 'currency', orderColumns: ['product_name', 'quantity', 'unit_price', 'created_at'], dateColumn: 'created_at' },
76
+ orders: { table: 'orders', xColumn: 'created_at', orderColumns: ['order_number', 'customer_name', 'status', 'created_at'], dateColumn: 'created_at' },
77
+ voorraad: { table: 'products', xColumn: 'created_at', orderColumns: ['product', 'quantity', 'sale_price', 'location'], dateColumn: 'created_at' },
78
+ klanten: { table: 'customers', xColumn: 'created_at', orderColumns: ['company_name', 'city', 'email', 'phone'], dateColumn: 'created_at' },
79
+ inkoop: { table: 'purchase_orders', xColumn: 'created_at', orderColumns: ['order_number', 'supplier_name', 'status', 'expected_delivery_date'], dateColumn: 'created_at' },
80
+ };
81
+ const m = map[topic];
82
+ const dateFilter = days > 0 ? { column: m.dateColumn, days } : null;
83
+ const topicLabel = TOPICS.find((t) => t.id === topic)?.label ?? topic;
84
+ const baseTitle = days > 0 ? `${topicLabel} — ${periodLabel}` : topicLabel;
85
+ if (view === 'kpi') {
86
+ const config = m.valueColumn
87
+ ? { table: m.table, agg: 'sum', column: m.valueColumn, format: m.format ?? 'number', dateFilter }
88
+ : { table: m.table, agg: 'count', format: 'number', dateFilter };
89
+ return {
90
+ id: `wiz-${topic}-${period}-kpi`, category: 'verkoop', icon: 'BarChart3',
91
+ label: baseTitle, description: 'Eén belangrijk getal',
92
+ type: 'kpi', title: baseTitle, config,
93
+ };
94
+ }
95
+ if (view === 'chart') {
96
+ const config = {
97
+ kind: 'area', table: m.table, xColumn: m.xColumn ?? 'created_at',
98
+ yColumn: m.valueColumn ?? 'id', agg: m.valueColumn ? 'sum' : 'count',
99
+ limit: 2000, dateFilter,
100
+ };
101
+ return { id: `wiz-${topic}-${period}-chart`, category: 'verkoop', icon: 'TrendingUp',
102
+ label: `${baseTitle} (grafiek)`, description: 'Verloop over tijd',
103
+ type: 'chart', title: baseTitle, config };
104
+ }
105
+ if (view === 'table') {
106
+ const config = {
107
+ table: m.table, columns: m.orderColumns ?? [],
108
+ orderBy: m.dateColumn, orderDir: 'desc', limit: 25, dateFilter,
109
+ };
110
+ return { id: `wiz-${topic}-${period}-table`, category: 'verkoop', icon: 'List',
111
+ label: `${baseTitle} (lijst)`, description: 'Overzichtelijke tabel',
112
+ type: 'table', title: baseTitle, config };
113
+ }
114
+ if (view === 'ai_analysis') {
115
+ const config = {
116
+ table: m.table, columns: [], limit: 100,
117
+ question: `Wat valt op aan ${topicLabel.toLowerCase()} ${periodLabel.toLowerCase()}? Welke trends en uitschieters zie je?`,
118
+ };
119
+ return { id: `wiz-${topic}-${period}-ai`, category: 'verkoop', icon: 'Sparkles',
120
+ label: `AI-inzicht: ${baseTitle}`, description: 'AI vat de data voor je samen',
121
+ type: 'ai_analysis', title: `AI-inzicht: ${baseTitle}`, config };
122
+ }
123
+ // forecast
124
+ const config = {
125
+ table: m.table, xColumn: m.xColumn ?? 'created_at',
126
+ yColumn: m.valueColumn ?? 'id', agg: m.valueColumn ? 'sum' : 'count',
127
+ horizon: 6, history: 200,
128
+ };
129
+ return { id: `wiz-${topic}-${period}-forecast`, category: 'verkoop', icon: 'LineChart',
130
+ label: `Voorspelling: ${topicLabel}`, description: 'AI-prognose voor de komende periode',
131
+ type: 'forecast', title: `Voorspelling: ${topicLabel}`, config };
132
+ }
133
+ // ------------------------- Tab 3: AI -------------------------
134
+ function AiTab({ onAdd }) {
135
+ const [prompt, setPrompt] = useState('');
136
+ const [busy, setBusy] = useState(false);
137
+ const [proposal, setProposal] = useState(null);
138
+ const propose = useServerFn(proposeBlockConfig);
139
+ const { tables } = useSchema();
140
+ async function ask() {
141
+ if (!prompt.trim())
142
+ return;
143
+ setBusy(true);
144
+ setProposal(null);
145
+ try {
146
+ const res = await propose({ data: { prompt, schema: tables } });
147
+ if (!res?.proposal) {
148
+ toast.error(res?.note || 'Geen voorstel');
149
+ return;
150
+ }
151
+ const p = res.proposal;
152
+ setProposal({ type: p.type, title: p.title, config: p.config });
153
+ }
154
+ catch (e) {
155
+ toast.error('Fout: ' + (e?.message ?? 'onbekend'));
156
+ }
157
+ finally {
158
+ setBusy(false);
159
+ }
160
+ }
161
+ const examples = [
162
+ 'Laat mijn omzet van deze maand zien',
163
+ 'Welke producten verkopen het best?',
164
+ 'Welke producten zijn bijna uitverkocht?',
165
+ 'Maak een voorspelling van de omzet',
166
+ ];
167
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "text-sm text-muted-foreground", children: "Beschrijf in eigen woorden wat je wilt zien. De AI maakt het juiste blok voor je." }), _jsx(Textarea, { value: prompt, onChange: (e) => setPrompt(e.target.value), placeholder: "bijv. Laat mijn omzet van deze maand zien", rows: 3 }), _jsx("div", { className: "flex flex-wrap gap-2", children: examples.map((ex) => (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setPrompt(ex), children: ex }, ex))) }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: ask, disabled: busy || !prompt.trim(), children: [busy ? _jsx(Loader2, { className: "w-4 h-4 mr-2 animate-spin" }) : _jsx(Sparkles, { className: "w-4 h-4 mr-2" }), "Voorstel maken"] }) }), proposal && (_jsx(Card, { className: "border-primary/40", children: _jsxs(CardContent, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2 text-primary", children: _jsx(Sparkles, { className: "w-5 h-5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Voorstel" }), _jsx("div", { className: "font-medium", children: proposal.title }), _jsxs("div", { className: "text-xs text-muted-foreground mt-1", children: ["Type: ", proposal.type] })] })] }), _jsx(Button, { className: "w-full", onClick: async () => { await onAdd(proposal); }, children: "Toevoegen aan dashboard" })] }) }))] }));
168
+ }
@@ -0,0 +1,15 @@
1
+ import type { BlockRow } from './types';
2
+ interface Props {
3
+ block: BlockRow;
4
+ onAdd: (input: {
5
+ type: BlockRow['type'];
6
+ title: string;
7
+ config: BlockRow['config'];
8
+ }) => Promise<void> | void;
9
+ }
10
+ /**
11
+ * Toont 2-3 slimme vervolg-voorstellen onder een blok in bewerk-modus.
12
+ */
13
+ export declare function SuggestionBanner({ block, onAdd }: Props): import("react").JSX.Element | null;
14
+ export {};
15
+ //# sourceMappingURL=SuggestionBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SuggestionBanner.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/SuggestionBanner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,UAAU,KAAK;IACb,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC/G;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,sCA0BvD"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Card, CardContent } from '@flowselections/core';
3
+ import { Sparkles, Plus } from 'lucide-react';
4
+ import { getSuggestions } from '../../lib/suggestions';
5
+ /**
6
+ * Toont 2-3 slimme vervolg-voorstellen onder een blok in bewerk-modus.
7
+ */
8
+ export function SuggestionBanner({ block, onAdd }) {
9
+ const suggestions = getSuggestions(block);
10
+ if (suggestions.length === 0)
11
+ return null;
12
+ return (_jsx(Card, { className: "mt-2 border-dashed border-primary/30 bg-primary/5", children: _jsxs(CardContent, { className: "p-2 flex items-center gap-2 flex-wrap", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-xs text-primary font-medium pl-1", children: [_jsx(Sparkles, { className: "w-3.5 h-3.5" }), "Wil je ook?"] }), suggestions.map((s) => (_jsxs(Button, { size: "sm", variant: "outline", className: "h-7 text-xs", onClick: () => onAdd({ type: s.type, title: s.title, config: s.config }), children: [_jsx(Plus, { className: "w-3 h-3 mr-1" }), s.label] }, s.id)))] }) }));
13
+ }
@@ -0,0 +1,17 @@
1
+ import type { BlockType, AnyBlockConfig } from './types';
2
+ interface Props {
3
+ onAdd: (input: {
4
+ type: BlockType;
5
+ title: string;
6
+ config: AnyBlockConfig;
7
+ }) => Promise<void> | void;
8
+ }
9
+ /**
10
+ * Galerij van kant-en-klare tegels. Wordt bovenaan de builder getoond zodat
11
+ * de gebruiker direct ziet wat hij kan toevoegen — één klik = tegel toevoegen.
12
+ * Bevat ook sjablonen (meerdere tegels in één klik) en de eigen bibliotheek met
13
+ * opgeslagen blokken.
14
+ */
15
+ export declare function TileGallery({ onAdd }: Props): import("react").JSX.Element;
16
+ export {};
17
+ //# sourceMappingURL=TileGallery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TileGallery.d.ts","sourceRoot":"","sources":["../../../src/components/dashboard/TileGallery.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAuBzD,UAAU,KAAK;IACb,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,cAAc,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACpG;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,+BAwK3C"}
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Button, Card, CardContent, Badge, supabase, toast } from '@flowselections/core';
4
+ import { AlertTriangle, CalendarDays, ClipboardList, Euro, LineChart, List, Package, Plus, ShoppingCart, Sparkles, TrendingUp, Trophy, Truck, UserPlus, Users, Wallet, Boxes, Calculator, Globe, Mail, MapPin, PieChart, TrendingDown, UserCheck, UserMinus, LayoutTemplate, LayoutDashboard, } from 'lucide-react';
5
+ import { QUICK_BLOCKS, QUICK_CATEGORIES } from '../../lib/quickBlocks';
6
+ import { DASHBOARD_TEMPLATES, getTemplateBlocks } from '../../lib/dashboardTemplates';
7
+ const ICONS = {
8
+ AlertTriangle, CalendarDays, ClipboardList, Euro, LineChart, List, Package,
9
+ ShoppingCart, Sparkles, TrendingUp, Trophy, Truck, UserPlus, Users, Wallet,
10
+ Boxes, Calculator, Globe, Mail, MapPin, PieChart, TrendingDown, UserCheck, UserMinus,
11
+ LayoutTemplate, LayoutDashboard,
12
+ };
13
+ function Icon({ name, className }) {
14
+ const C = ICONS[name] ?? Sparkles;
15
+ return _jsx(C, { className: className });
16
+ }
17
+ /**
18
+ * Galerij van kant-en-klare tegels. Wordt bovenaan de builder getoond zodat
19
+ * de gebruiker direct ziet wat hij kan toevoegen — één klik = tegel toevoegen.
20
+ * Bevat ook sjablonen (meerdere tegels in één klik) en de eigen bibliotheek met
21
+ * opgeslagen blokken.
22
+ */
23
+ export function TileGallery({ onAdd }) {
24
+ const [cat, setCat] = useState('verkoop');
25
+ const [open, setOpen] = useState(true);
26
+ const [customTemplates, setCustomTemplates] = useState([]);
27
+ const [bulkBusy, setBulkBusy] = useState(null);
28
+ useEffect(() => {
29
+ (async () => {
30
+ const { data } = await supabase
31
+ .from('dashboard_block_templates')
32
+ .select('id,name,description,type,config')
33
+ .order('name', { ascending: true });
34
+ setCustomTemplates(Array.isArray(data) ? data : []);
35
+ })();
36
+ }, []);
37
+ const items = cat !== '__templates' && cat !== '__library'
38
+ ? QUICK_BLOCKS.filter((b) => b.category === cat)
39
+ : [];
40
+ async function pick(q) {
41
+ await onAdd({ type: q.type, title: q.title, config: q.config });
42
+ }
43
+ async function applyTemplate(templateId, templateName) {
44
+ setBulkBusy(templateId);
45
+ try {
46
+ const blocks = getTemplateBlocks(templateId);
47
+ for (const b of blocks) {
48
+ await onAdd({ type: b.type, title: b.title, config: b.config });
49
+ }
50
+ toast.success(`Sjabloon "${templateName}" toegevoegd (${blocks.length} tegels)`);
51
+ }
52
+ catch (e) {
53
+ toast.error('Sjabloon toevoegen mislukt: ' + (e?.message ?? 'onbekend'));
54
+ }
55
+ finally {
56
+ setBulkBusy(null);
57
+ }
58
+ }
59
+ return (_jsx(Card, { className: "mb-4 border-dashed", children: _jsxs(CardContent, { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Sparkles, { className: "w-4 h-4 text-primary" }), _jsx("div", { className: "font-medium text-sm", children: "Kant-en-klare tegels" }), _jsx("div", { className: "text-xs text-muted-foreground hidden sm:block", children: "\u2014 klik om toe te voegen" })] }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setOpen((v) => !v), children: open ? 'Verbergen' : 'Tonen' })] }), open && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Button, { size: "sm", variant: cat === '__templates' ? 'default' : 'outline', onClick: () => setCat('__templates'), children: [_jsx(LayoutTemplate, { className: "w-4 h-4 mr-2" }), "Sjablonen"] }), _jsxs(Button, { size: "sm", variant: cat === '__library' ? 'default' : 'outline', onClick: () => setCat('__library'), children: [_jsx(LayoutDashboard, { className: "w-4 h-4 mr-2" }), "Mijn bibliotheek"] }), QUICK_CATEGORIES.map((c) => (_jsxs(Button, { size: "sm", variant: cat === c.id ? 'default' : 'outline', onClick: () => setCat(c.id), children: [_jsx(Icon, { name: c.icon, className: "w-4 h-4 mr-2" }), c.label] }, c.id)))] }), cat === '__templates' && (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2", children: DASHBOARD_TEMPLATES.map((t) => (_jsxs("button", { type: "button", disabled: bulkBusy === t.id, onClick: () => applyTemplate(t.id, t.name), className: "group text-left rounded-lg border bg-card hover:border-primary hover:shadow-md transition p-3 flex items-start gap-3 disabled:opacity-50", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition shrink-0", children: _jsx(Icon, { name: t.icon, className: "w-4 h-4" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: t.name }), _jsx("div", { className: "text-xs text-muted-foreground line-clamp-2", children: t.description }), _jsxs("div", { className: "text-xs text-muted-foreground mt-1", children: [t.blockIds.length, " tegels"] })] }), _jsx(Plus, { className: "w-4 h-4 text-muted-foreground group-hover:text-primary shrink-0" })] }, t.id))) })), cat === '__library' && (customTemplates.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 text-center border rounded-lg", children: "Nog geen eigen blokken opgeslagen. Sla een blok op als template om hem hier terug te zien." })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2", children: customTemplates.map((t) => (_jsxs("button", { type: "button", onClick: () => onAdd({ type: t.type, title: t.name, config: t.config }), className: "group text-left rounded-lg border bg-card hover:border-primary hover:shadow-md transition p-3 flex items-start gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition shrink-0", children: _jsx(LayoutDashboard, { className: "w-4 h-4" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "font-medium text-sm truncate", children: t.name }), _jsx(Badge, { variant: "outline", className: "text-[10px]", children: t.type })] }), _jsx("div", { className: "text-xs text-muted-foreground line-clamp-2", children: t.description ?? `Template voor ${t.config?.table ?? 'data'}` })] }), _jsx(Plus, { className: "w-4 h-4 text-muted-foreground group-hover:text-primary shrink-0" })] }, t.id))) }))), cat !== '__templates' && cat !== '__library' && (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2", children: items.map((q) => (_jsxs("button", { type: "button", onClick: () => pick(q), className: "group text-left rounded-lg border bg-card hover:border-primary hover:shadow-md transition p-3 flex items-start gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition shrink-0", children: _jsx(Icon, { name: q.icon, className: "w-4 h-4" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: q.label }), _jsx("div", { className: "text-xs text-muted-foreground line-clamp-2", children: q.description })] }), _jsx(Plus, { className: "w-4 h-4 text-muted-foreground group-hover:text-primary shrink-0" })] }, q.id))) }))] }))] }) }));
60
+ }
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ pageId: string | null;
3
+ }
4
+ export declare function AiChatPanel({ pageId }: Props): import("react").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=AiChatPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiChatPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/dashboard/ai/AiChatPanel.tsx"],"names":[],"mappings":"AAQA,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,+BA8E5C"}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useChat } from '@ai-sdk/react';
4
+ import { DefaultChatTransport } from 'ai';
5
+ import { Button, Input, toast } from '@flowselections/core';
6
+ import { Sparkles, X, Send, Plus, Loader2 } from 'lucide-react';
7
+ import { useDashboardBlocks } from '../../../hooks/useDashboardBlocks';
8
+ export function AiChatPanel({ pageId }) {
9
+ const [open, setOpen] = useState(false);
10
+ const [input, setInput] = useState('');
11
+ const { create } = useDashboardBlocks(pageId);
12
+ const transport = new DefaultChatTransport({ api: '/api/chat' });
13
+ const { messages, sendMessage, status } = useChat({
14
+ transport,
15
+ onError: (e) => toast.error('AI fout: ' + e.message),
16
+ });
17
+ const busy = status === 'submitted' || status === 'streaming';
18
+ async function handleSend() {
19
+ const text = input.trim();
20
+ if (!text || busy)
21
+ return;
22
+ setInput('');
23
+ await sendMessage({ text });
24
+ }
25
+ async function addProposalAsBlock(p) {
26
+ if (!pageId) {
27
+ toast.error('Open eerst een dashboardpagina');
28
+ return;
29
+ }
30
+ const block = await create({ type: p.type, title: p.title, config: p.config });
31
+ if (block)
32
+ toast.success('Blok toegevoegd');
33
+ }
34
+ return (_jsxs(_Fragment, { children: [!open && (_jsx(Button, { onClick: () => setOpen(true), className: "fixed bottom-6 right-6 shadow-lg rounded-full h-14 w-14 p-0 z-50", "aria-label": "AI Assistent", children: _jsx(Sparkles, { className: "w-6 h-6" }) })), open && (_jsxs("div", { className: "fixed bottom-6 right-6 w-[420px] h-[600px] bg-background border rounded-xl shadow-2xl flex flex-col z-50", children: [_jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-2 font-medium", children: [_jsx(Sparkles, { className: "w-4 h-4 text-primary" }), " Dashboard AI"] }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => setOpen(false), children: _jsx(X, { className: "w-4 h-4" }) })] }), _jsxs("div", { className: "flex-1 overflow-auto px-4 py-3 space-y-3", children: [messages.length === 0 && (_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Vraag bijvoorbeeld: ", _jsx("em", { children: "\"Maak een KPI met het aantal openstaande orders\"" }), " of", _jsx("em", { children: " \"Toon een grafiek van de verkoop per maand\"" }), "."] })), messages.map((m) => (_jsx(MessageView, { m: m, onAdd: addProposalAsBlock }, m.id))), busy && _jsx(Loader2, { className: "w-4 h-4 animate-spin text-muted-foreground" })] }), _jsxs("div", { className: "border-t p-3 flex gap-2", children: [_jsx(Input, { value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter' && !e.shiftKey) {
35
+ e.preventDefault();
36
+ handleSend();
37
+ } }, placeholder: "Stel een vraag of beschrijf een blok\u2026", disabled: busy }), _jsx(Button, { onClick: handleSend, disabled: busy || !input.trim(), size: "icon", children: _jsx(Send, { className: "w-4 h-4" }) })] })] }))] }));
38
+ }
39
+ function MessageView({ m, onAdd }) {
40
+ const isUser = m.role === 'user';
41
+ return (_jsx("div", { className: `flex ${isUser ? 'justify-end' : 'justify-start'}`, children: _jsx("div", { className: `max-w-[85%] rounded-lg px-3 py-2 text-sm ${isUser ? 'bg-primary text-primary-foreground' : 'bg-muted'}`, children: m.parts.map((part, i) => {
42
+ if (part.type === 'text')
43
+ return _jsx("div", { className: "whitespace-pre-wrap", children: part.text }, i);
44
+ if (typeof part.type === 'string' && part.type.startsWith('tool-')) {
45
+ const name = part.type.replace(/^tool-/, '');
46
+ const state = part.state;
47
+ if (name === 'propose_block' && (state === 'output-available' || state === 'result')) {
48
+ const proposal = part.output?.proposal ?? part.result?.proposal;
49
+ if (proposal) {
50
+ return (_jsxs("div", { className: "mt-2 p-2 border rounded bg-background text-foreground", children: [_jsx("div", { className: "text-xs text-muted-foreground mb-1", children: "Voorgesteld blok" }), _jsx("div", { className: "font-medium text-sm", children: proposal.title }), _jsx("div", { className: "text-xs text-muted-foreground capitalize", children: proposal.type }), _jsxs(Button, { size: "sm", className: "mt-2", onClick: () => onAdd(proposal), children: [_jsx(Plus, { className: "w-3.5 h-3.5 mr-1" }), " Toevoegen"] })] }, i));
51
+ }
52
+ }
53
+ return (_jsxs("div", { className: "text-xs text-muted-foreground italic mt-1", children: [state === 'output-available' || state === 'result' ? '✓' : '⋯', " ", name] }, i));
54
+ }
55
+ return null;
56
+ }) }) }));
57
+ }
@@ -0,0 +1,14 @@
1
+ import type { AnyBlockConfig, BlockType, BlockPermissions } from '../types';
2
+ interface Props {
3
+ open: boolean;
4
+ onOpenChange: (v: boolean) => void;
5
+ onSave: (input: {
6
+ type: BlockType;
7
+ title: string;
8
+ config: AnyBlockConfig;
9
+ permissions: BlockPermissions;
10
+ }) => void | Promise<void>;
11
+ }
12
+ export declare function BlockGeneratorDialog({ open, onOpenChange, onSave }: Props): import("react").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=BlockGeneratorDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockGeneratorDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/dashboard/ai/BlockGeneratorDialog.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAY,cAAc,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEtF,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,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;AAED,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,KAAK,+BAgFzE"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Button, Textarea, toast, } 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
+ import { BlockRenderer } from '../BlockRenderer';
9
+ export function BlockGeneratorDialog({ open, onOpenChange, onSave }) {
10
+ const { tables } = useSchema();
11
+ const propose = useServerFn(proposeBlockConfig);
12
+ const [prompt, setPrompt] = useState('');
13
+ const [busy, setBusy] = useState(false);
14
+ const [preview, setPreview] = useState(null);
15
+ async function generate() {
16
+ if (!prompt.trim())
17
+ return;
18
+ setBusy(true);
19
+ setPreview(null);
20
+ try {
21
+ const res = await propose({ data: { prompt, schema: tables } });
22
+ if (!res.proposal) {
23
+ toast.error(res.note || 'Geen voorstel');
24
+ return;
25
+ }
26
+ const p = res.proposal;
27
+ setPreview({
28
+ id: 'preview',
29
+ page_id: 'preview',
30
+ type: p.type ?? 'kpi',
31
+ title: p.title ?? 'Nieuw blok',
32
+ layout: { x: 0, y: 0, w: 6, h: 4 },
33
+ config: p.config ?? {},
34
+ filters: null,
35
+ permissions: { can_view_ai: true, can_edit: true },
36
+ created_at: new Date().toISOString(),
37
+ updated_at: new Date().toISOString(),
38
+ });
39
+ }
40
+ catch (e) {
41
+ toast.error('Fout: ' + (e?.message ?? ''));
42
+ }
43
+ finally {
44
+ setBusy(false);
45
+ }
46
+ }
47
+ async function handleSave() {
48
+ if (!preview)
49
+ return;
50
+ await onSave({
51
+ type: preview.type,
52
+ title: preview.title ?? '',
53
+ config: preview.config,
54
+ permissions: preview.permissions ?? { can_view_ai: true, can_edit: true },
55
+ });
56
+ setPreview(null);
57
+ setPrompt('');
58
+ onOpenChange(false);
59
+ }
60
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-4xl", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(Sparkles, { className: "w-4 h-4 text-primary" }), " Blok genereren met AI"] }) }), _jsxs("div", { className: "space-y-3", children: [_jsx(Textarea, { value: prompt, onChange: (e) => setPrompt(e.target.value), placeholder: "bijv. Laat een staafgrafiek zien van omzet per maand uit tabel orders", rows: 3 }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: generate, disabled: busy || !prompt.trim(), children: [busy ? _jsx(Loader2, { className: "w-4 h-4 mr-2 animate-spin" }) : _jsx(Sparkles, { className: "w-4 h-4 mr-2" }), "Voorstel maken"] }) }), preview && (_jsx("div", { className: "border rounded-lg p-2 bg-muted/20", style: { height: 320 }, children: _jsx(BlockRenderer, { block: preview }) }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Sluiten" }), _jsx(Button, { onClick: handleSave, disabled: !preview, children: "Opslaan als blok" })] })] }) }));
61
+ }
@@ -0,0 +1,5 @@
1
+ import type { BlockRow } from '../types';
2
+ export declare function AiAnalysisBlock({ block }: {
3
+ block: BlockRow;
4
+ }): import("react").JSX.Element;
5
+ //# sourceMappingURL=AiAnalysisBlock.d.ts.map