@ramme-io/create-app 1.2.2 → 1.2.6

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 (27) hide show
  1. package/package.json +9 -14
  2. package/template/package.json +5 -3
  3. package/template/pkg.json +11 -7
  4. package/template/src/App.tsx +11 -1
  5. package/template/src/components/AppHeader.tsx +10 -10
  6. package/template/src/components/dashboard/ChartLine.tsx +28 -0
  7. package/template/src/components/dashboard/StatCard.tsx +61 -0
  8. package/template/src/config/component-registry.tsx +57 -44
  9. package/template/src/engine/renderers/DynamicBlock.tsx +1 -1
  10. package/template/src/engine/renderers/DynamicPage.tsx +134 -98
  11. package/template/src/engine/runtime/ManifestContext.tsx +79 -0
  12. package/template/src/engine/runtime/MqttContext.tsx +16 -21
  13. package/template/src/engine/runtime/data-seeder.ts +9 -7
  14. package/template/src/engine/runtime/useAction.ts +7 -21
  15. package/template/src/engine/runtime/useDynamicSitemap.tsx +43 -0
  16. package/template/src/engine/runtime/useJustInTimeSeeder.ts +76 -0
  17. package/template/src/engine/runtime/useLiveBridge.ts +44 -0
  18. package/template/src/engine/runtime/useSignal.ts +10 -21
  19. package/template/src/engine/runtime/useWorkflowEngine.ts +23 -78
  20. package/template/src/features/datagrid/SmartTable.tsx +38 -20
  21. package/template/src/features/developer/GhostOverlay.tsx +67 -21
  22. package/template/src/features/visualizations/SmartChart.tsx +178 -0
  23. package/template/src/main.tsx +25 -13
  24. package/template/src/templates/dashboard/DashboardLayout.tsx +19 -18
  25. package/template/src/templates/dashboard/dashboard.sitemap.ts +1 -8
  26. package/template/tailwind.config.cjs +10 -9
  27. package/template/vite.config.ts +0 -14
@@ -0,0 +1,178 @@
1
+ import React, { useMemo } from 'react';
2
+ import {
3
+ BarChart, Bar, LineChart, Line, AreaChart, Area, PieChart, Pie, Cell,
4
+ XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend
5
+ } from 'recharts';
6
+ import { Card } from '@ramme-io/ui';
7
+ import { BarChart3, Bug } from 'lucide-react';
8
+
9
+ interface SmartChartProps {
10
+ rowData?: any[];
11
+ chartType?: 'bar' | 'line' | 'area' | 'pie';
12
+ xAxis?: string;
13
+ yAxis?: string;
14
+ aggregation?: string;
15
+ title?: string;
16
+ className?: string;
17
+ }
18
+
19
+ const COLORS = ['#0ea5e9', '#22c55e', '#eab308', '#ef4444', '#8b5cf6'];
20
+
21
+ export const SmartChart: React.FC<SmartChartProps> = ({
22
+ rowData = [], chartType = 'bar', xAxis, yAxis, aggregation = 'none', title, className
23
+ }) => {
24
+
25
+ // 1. DATA PROCESSING ENGINE
26
+ const processedData = useMemo(() => {
27
+ if (!rowData || rowData.length === 0) return [];
28
+
29
+ // Fallback: If no xAxis provided, try to find a string key, else use 'id'
30
+ const activeX = xAxis || Object.keys(rowData[0]).find(k => typeof rowData[0][k] === 'string') || 'id';
31
+
32
+ // Normalize mode
33
+ const mode = aggregation?.toLowerCase() || 'none';
34
+
35
+ // SCENARIO A: COUNT
36
+ if (mode === 'count') {
37
+ const counts: Record<string, number> = {};
38
+ rowData.forEach(row => {
39
+ const rawVal = row[activeX];
40
+ // Handle Booleans (true/false) explicitly
41
+ const key = String(rawVal !== undefined && rawVal !== null ? rawVal : 'Unknown');
42
+ counts[key] = (counts[key] || 0) + 1;
43
+ });
44
+ return Object.entries(counts).map(([name, value]) => ({ name, value }));
45
+ }
46
+
47
+ // SCENARIO B: SUM
48
+ if (mode === 'sum' && yAxis) {
49
+ const sums: Record<string, number> = {};
50
+ rowData.forEach(row => {
51
+ const key = String(row[activeX] ?? 'Unknown');
52
+ const val = Number(row[yAxis]) || 0;
53
+ sums[key] = (sums[key] || 0) + val;
54
+ });
55
+ return Object.entries(sums).map(([name, value]) => ({ name, value }));
56
+ }
57
+
58
+ // SCENARIO C: RAW
59
+ return rowData.map(row => ({
60
+ name: String(row[activeX] ?? ''),
61
+ value: yAxis ? Number(row[yAxis]) : 0
62
+ }));
63
+
64
+ }, [rowData, xAxis, yAxis, aggregation]);
65
+
66
+ // 2. RENDERERS
67
+ const renderChart = () => {
68
+ // If no data to render, show placeholder
69
+ if (processedData.length === 0) {
70
+ return (
71
+ <div className="flex flex-col items-center justify-center h-full text-muted-foreground opacity-50">
72
+ <BarChart3 size={32} />
73
+ <span className="text-xs mt-2">No Data Available</span>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ const type = chartType?.toLowerCase() || 'bar';
79
+ switch (type) {
80
+ case 'pie':
81
+ return (
82
+ <PieChart>
83
+ <Pie data={processedData} cx="50%" cy="50%" innerRadius={60} outerRadius={80} paddingAngle={5} dataKey="value">
84
+ {processedData.map((_, index) => <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />)}
85
+ </Pie>
86
+ <Tooltip />
87
+ <Legend verticalAlign="bottom" height={36}/>
88
+ </PieChart>
89
+ );
90
+ case 'line':
91
+ return (
92
+ <LineChart data={processedData}>
93
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
94
+ <XAxis dataKey="name" fontSize={12} tickLine={false} axisLine={false} />
95
+ <YAxis fontSize={12} tickLine={false} axisLine={false} />
96
+ <Tooltip />
97
+ <Line type="monotone" dataKey="value" stroke="#0ea5e9" strokeWidth={3} dot={{r:4}} />
98
+ </LineChart>
99
+ );
100
+ case 'area':
101
+ return (
102
+ <AreaChart data={processedData}>
103
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
104
+ <XAxis dataKey="name" fontSize={12} tickLine={false} axisLine={false} />
105
+ <YAxis fontSize={12} tickLine={false} axisLine={false} />
106
+ <Tooltip />
107
+ <Area type="monotone" dataKey="value" stroke="#0ea5e9" fill="#0ea5e9" fillOpacity={0.2} />
108
+ </AreaChart>
109
+ );
110
+ case 'bar': default:
111
+ return (
112
+ <BarChart data={processedData}>
113
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
114
+ <XAxis dataKey="name" fontSize={12} tickLine={false} axisLine={false} />
115
+ <YAxis fontSize={12} tickLine={false} axisLine={false} />
116
+ <Tooltip cursor={{ fill: '#f1f5f9' }} />
117
+ <Bar dataKey="value" fill="#0ea5e9" radius={[4, 4, 0, 0]} />
118
+ </BarChart>
119
+ );
120
+ }
121
+ };
122
+
123
+ return (
124
+ <Card className={`p-4 flex flex-col h-full bg-white border border-border shadow-sm ${className}`}>
125
+ {/* Title Header */}
126
+ <div className="flex items-center justify-between mb-4">
127
+ <div className="flex flex-col">
128
+ {title && <h3 className="text-sm font-bold text-foreground uppercase tracking-wider">{title}</h3>}
129
+ {/* Subtitle with Aggregation info */}
130
+ {aggregation && aggregation !== 'none' && (
131
+ <span className="text-[10px] text-muted-foreground font-mono">
132
+ BY {aggregation.toUpperCase()} ({xAxis})
133
+ </span>
134
+ )}
135
+ </div>
136
+ </div>
137
+
138
+ {/* Chart Canvas */}
139
+ <div className="flex-1 w-full min-h-[200px]">
140
+ <ResponsiveContainer width="100%" height="100%">{renderChart()}</ResponsiveContainer>
141
+ </div>
142
+
143
+ {/* ✅ DEBUG CONSOLE (Collapsible) */}
144
+ <div className="mt-4 border-t border-border pt-2">
145
+ <details className="text-[10px] text-muted-foreground group">
146
+ <summary className="cursor-pointer hover:text-primary flex items-center gap-2 select-none font-mono">
147
+ <Bug size={12} />
148
+ <span>Debug Configuration</span>
149
+ <span className="ml-auto opacity-50">{rowData?.length || 0} Rows</span>
150
+ </summary>
151
+ <div className="mt-2 p-3 bg-slate-950 text-green-400 rounded-md font-mono overflow-x-auto text-[10px] leading-relaxed shadow-inner">
152
+ <div className="mb-2 pb-2 border-b border-white/10">
153
+ <span className="text-white font-bold block mb-1">INCOMING PROPS:</span>
154
+ <div>chartType: <span className="text-yellow-300">"{chartType}"</span></div>
155
+ <div>aggregation: <span className="text-yellow-300">"{aggregation}"</span></div>
156
+ <div>xAxis (Key): <span className="text-yellow-300">"{xAxis}"</span></div>
157
+ <div>yAxis (Val): <span className="text-yellow-300">"{yAxis}"</span></div>
158
+ </div>
159
+
160
+ <div className="mb-2 pb-2 border-b border-white/10">
161
+ <span className="text-white font-bold block mb-1">DATA SNAPSHOT (Row 0):</span>
162
+ {rowData && rowData.length > 0 ? (
163
+ <pre>{JSON.stringify(rowData[0], null, 2)}</pre>
164
+ ) : (
165
+ <span className="text-red-400">No Data Rows Received</span>
166
+ )}
167
+ </div>
168
+
169
+ <div>
170
+ <span className="text-white font-bold block mb-1">PROCESSED OUTPUT (Top 3):</span>
171
+ <pre>{JSON.stringify(processedData.slice(0, 3), null, 2)}</pre>
172
+ </div>
173
+ </div>
174
+ </details>
175
+ </div>
176
+ </Card>
177
+ );
178
+ };
@@ -5,24 +5,36 @@ import App from './App.tsx';
5
5
  import { ThemeProvider, ToastProvider } from '@ramme-io/ui';
6
6
  import { AuthProvider } from './features/auth/AuthContext.tsx';
7
7
  import { MqttProvider } from './engine/runtime/MqttContext';
8
- import "@ramme-io/ui/index.css";
8
+ import "@ramme-io/ui/index.css";
9
9
  import './index.css';
10
10
 
11
- // This import activates all AG Grid Enterprise features
11
+ // 1. Data Seeder (Critical for Auth)
12
+ import { initializeDataLake } from './engine/runtime/data-seeder';
13
+
14
+ // 2. Manifest Provider (Critical for Builder Preview)
15
+ import { ManifestProvider } from './engine/runtime/ManifestContext';
16
+
17
+ // 3. AG Grid Enterprise
12
18
  import 'ag-grid-enterprise';
13
19
 
20
+ // Initialize mock data immediately on boot
21
+ initializeDataLake();
22
+
14
23
  ReactDOM.createRoot(document.getElementById('root')!).render(
15
24
  <React.StrictMode>
16
- <BrowserRouter>
17
- <ThemeProvider>
18
- <ToastProvider>
19
- <AuthProvider>
20
- <MqttProvider>
21
- <App />
22
- </MqttProvider>
23
- </AuthProvider>
24
- </ToastProvider>
25
- </ThemeProvider>
26
- </BrowserRouter>
25
+ {/* 🛡️ KEEPS THE BRIDGE ALIVE: Wraps the entire app */}
26
+ <ManifestProvider>
27
+ <BrowserRouter>
28
+ <ThemeProvider>
29
+ <ToastProvider>
30
+ <AuthProvider>
31
+ <MqttProvider>
32
+ <App />
33
+ </MqttProvider>
34
+ </AuthProvider>
35
+ </ToastProvider>
36
+ </ThemeProvider>
37
+ </BrowserRouter>
38
+ </ManifestProvider>
27
39
  </React.StrictMode>,
28
40
  );
@@ -4,8 +4,8 @@ import {
4
4
  Sidebar,
5
5
  type SidebarItem,
6
6
  ChatFAB,
7
- type IconName, // Type for safety
8
- } from '@ramme-io/ui'; // ✅ Import only what exists in v1.2.0
7
+ type IconName,
8
+ } from '@ramme-io/ui';
9
9
 
10
10
  import { dashboardSitemap } from './dashboard.sitemap';
11
11
  import { SitemapProvider } from '../../engine/runtime/SitemapContext';
@@ -14,7 +14,9 @@ import AppHeader from '../../components/AppHeader';
14
14
  import { AIChatWidget } from '../../components/AIChatWidget';
15
15
  import { useWorkflowEngine } from '../../engine/runtime/useWorkflowEngine';
16
16
 
17
- // Main Layout Component
17
+ // 1. IMPORT THE HOOK
18
+ import { useDynamicSitemap } from '../../engine/runtime/useDynamicSitemap';
19
+
18
20
  const DashboardLayout: React.FC = () => {
19
21
  const navigate = useNavigate();
20
22
  const location = useLocation();
@@ -22,39 +24,39 @@ const DashboardLayout: React.FC = () => {
22
24
 
23
25
  useWorkflowEngine();
24
26
 
25
- // 1. Transform Sitemap to Sidebar Items
26
- // The new Sidebar expects a simple array of items.
27
+ // ✅ 2. GET LIVE DATA (Merges Static + Builder Data)
28
+ const liveSitemap = useDynamicSitemap(dashboardSitemap);
29
+
30
+ // ✅ 3. BUILD SIDEBAR FROM LIVE DATA
27
31
  const sidebarItems: SidebarItem[] = useMemo(() => {
28
- return dashboardSitemap.map((route) => ({
32
+ // We map over 'liveSitemap' instead of 'dashboardSitemap'
33
+ return liveSitemap.map((route) => ({
29
34
  id: route.id,
30
35
  label: route.title,
31
- icon: route.icon as IconName, // Ensure your sitemap strings match valid icon names
32
- href: route.path ? `/dashboard/${route.path}` : '/dashboard',
33
- // Note: v1.2.0 Sidebar currently handles top-level items.
34
- // If you need nested items, we would flatten them here or update Sidebar.tsx later.
36
+ icon: route.icon as IconName,
37
+ // Handle the path logic safely
38
+ href: route.path.startsWith('/') ? route.path : `/dashboard/${route.path}`,
35
39
  }));
36
- }, []);
40
+ }, [liveSitemap]); // Re-run whenever the Builder sends an update
37
41
 
38
- // 2. Determine Active Item based on URL
42
+ // 4. Determine Active Item
39
43
  const activeItemId = useMemo(() => {
40
- // Find the item whose href matches the start of the current path
41
44
  const active = sidebarItems.find(item =>
42
45
  item.href !== '/dashboard' && location.pathname.startsWith(item.href!)
43
46
  );
44
- // Default to dashboard (first item) if no specific match
45
47
  return active?.id || sidebarItems[0]?.id;
46
48
  }, [location.pathname, sidebarItems]);
47
49
 
48
50
  return (
49
- <SitemapProvider value={dashboardSitemap}>
51
+ // ✅ 5. PASS LIVE SITEMAP TO CONTEXT (For Breadcrumbs/Titles)
52
+ <SitemapProvider value={liveSitemap}>
50
53
  <PageTitleUpdater />
51
54
 
52
- {/* 3. New Layout Structure (No Provider needed) */}
53
55
  <div className="flex h-screen bg-background text-foreground relative">
54
56
 
55
57
  <Sidebar
56
58
  className="relative border-border"
57
- items={sidebarItems}
59
+ items={sidebarItems} // Now contains your new page!
58
60
  activeItemId={activeItemId}
59
61
  onNavigate={(item) => {
60
62
  if (item.href) navigate(item.href);
@@ -78,7 +80,6 @@ const DashboardLayout: React.FC = () => {
78
80
  </main>
79
81
  </div>
80
82
 
81
- {/* --- AI COPILOT SECTION --- */}
82
83
  {isChatOpen && (
83
84
  <AIChatWidget onClose={() => setIsChatOpen(false)} />
84
85
  )}
@@ -18,14 +18,7 @@ export const dashboardSitemap: SitemapEntry[] = [
18
18
  icon: 'rocket',
19
19
  component: Welcome,
20
20
  },
21
- // 2. The "Mission Control" Charts Page
22
- {
23
- id: 'overview',
24
- path: 'overview',
25
- title: 'Overview',
26
- icon: 'layout-dashboard',
27
- component: OverviewPage,
28
- },
21
+
29
22
  // 3. ✅ The User Management Module
30
23
  {
31
24
  id: 'users',
@@ -1,18 +1,19 @@
1
- const path = require('path');
2
-
3
- // This finds the absolute path to the @ramme-io/ui package's directory
4
- const rammeUiPath = path.dirname(require.resolve('@ramme-io/ui/package.json'));
5
-
6
1
  /** @type {import('tailwindcss').Config} */
7
2
  module.exports = {
8
3
  presets: [
9
- // Load the preset from the absolute path we found
10
- require(path.join(rammeUiPath, 'tailwind.preset.js'))
4
+ // STANDARD: Directly require the preset from the package
5
+ require('@ramme-io/ui/tailwind.preset.js')
11
6
  ],
12
7
  content: [
13
8
  "./index.html",
14
9
  "./src/**/*.{js,ts,jsx,tsx}",
15
- // Scan the library's files for classes using the absolute path
16
- path.join(rammeUiPath, 'dist/**/*.js'),
10
+
11
+ // ✅ STANDARD: Point directly to node_modules relative to this config file
12
+ // This is the industry standard way (like how Flowbite or Shadcn work)
13
+ "./node_modules/@ramme-io/ui/dist/**/*.{js,ts,jsx,tsx}"
17
14
  ],
15
+ theme: {
16
+ extend: {},
17
+ },
18
+ plugins: [],
18
19
  }
@@ -7,20 +7,6 @@ export default defineConfig({
7
7
  resolve: {
8
8
  alias: {
9
9
  '@': path.resolve(__dirname, './src'),
10
-
11
- // ✅ FIX: Go up 2 levels to find the sibling 'ramme-ui' folder
12
- '@ramme-io/ui': path.resolve(__dirname, '../../ramme-ui/src'),
13
- },
14
- },
15
- // ✅ CRITICAL: Allow Vite to serve files outside the current project folder
16
- server: {
17
- fs: {
18
- allow: [
19
- // Allow serving files from the project root
20
- '.',
21
- // Allow serving files from the sibling UI library
22
- '../../ramme-ui',
23
- ],
24
10
  },
25
11
  },
26
12
  });