@ramme-io/create-app 1.2.1 → 1.2.5
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/package.json +9 -15
- package/template/package.json +41 -0
- package/template/pkg.json +9 -7
- package/template/src/App.tsx +72 -31
- package/template/src/components/AIChatWidget.tsx +2 -2
- package/template/src/components/AppHeader.tsx +12 -12
- package/template/src/components/AutoForm.tsx +13 -0
- package/template/src/{pages/styleguide → components}/NotFound.tsx +1 -1
- package/template/src/components/PageTitleUpdater.tsx +2 -2
- package/template/src/components/ProtectedRoute.tsx +18 -1
- package/template/src/components/ScrollToTop.tsx +19 -0
- package/template/src/components/dashboard/ChartLine.tsx +28 -0
- package/template/src/components/dashboard/StatCard.tsx +61 -0
- package/template/src/config/app.manifest.ts +3 -1
- package/template/src/config/component-registry.tsx +69 -0
- package/template/src/config/navigation.ts +1 -1
- package/template/src/data/mock-charts.ts +32 -28
- package/template/src/{components → engine/renderers}/DynamicBlock.tsx +28 -8
- package/template/src/engine/renderers/DynamicPage.tsx +150 -0
- package/template/src/engine/runtime/ManifestContext.tsx +79 -0
- package/template/src/{contexts → engine/runtime}/MqttContext.tsx +23 -14
- package/template/src/{contexts → engine/runtime}/SitemapContext.tsx +1 -1
- package/template/src/engine/runtime/data-seeder.ts +47 -0
- package/template/src/{hooks → engine/runtime}/useAction.ts +11 -14
- package/template/src/{hooks → engine/runtime}/useCrudLocalStorage.ts +27 -8
- package/template/src/{hooks → engine/runtime}/useDataQuery.ts +15 -1
- package/template/src/engine/runtime/useDynamicSitemap.tsx +43 -0
- package/template/src/engine/runtime/useJustInTimeSeeder.ts +76 -0
- package/template/src/engine/runtime/useLiveBridge.ts +44 -0
- package/template/src/engine/runtime/useSignal.ts +40 -0
- package/template/src/{generated/hooks.ts → engine/runtime/useSignalStore.ts} +35 -8
- package/template/src/engine/runtime/useWorkflowEngine.ts +89 -0
- package/template/src/{core → engine/types}/manifest-types.ts +35 -3
- package/template/src/{types → engine/validation}/schema.ts +17 -0
- package/template/src/{pages → features/ai/pages}/AiChat.tsx +1 -1
- package/template/src/features/auth/AuthContext.tsx +118 -0
- package/template/src/features/auth/pages/AuthLayout.tsx +55 -0
- package/template/src/features/auth/pages/LoginPage.tsx +106 -0
- package/template/src/features/auth/pages/SignupPage.tsx +96 -0
- package/template/src/{blocks → features/datagrid}/SmartTable.tsx +41 -25
- package/template/src/features/developer/GhostOverlay.tsx +114 -0
- package/template/src/{pages → features/onboarding/pages}/Welcome.tsx +0 -1
- package/template/src/features/overview/index.ts +1 -0
- package/template/src/features/overview/pages/OverviewPage.tsx +127 -0
- package/template/src/{pages → features/playground/pages}/AccountingLedgerPage.tsx +1 -1
- package/template/src/{pages/prototypes → features/playground/pages}/ItemSelectorPage.tsx +1 -1
- package/template/src/{pages/settings → features/settings/pages}/BillingPage.tsx +1 -1
- package/template/src/features/settings/pages/ProfilePage.tsx +153 -0
- package/template/src/{pages/settings → features/settings/pages}/TeamPage.tsx +1 -1
- package/template/src/features/styleguide/Styleguide.tsx +75 -0
- package/template/src/features/users/components/UserDrawer.tsx +138 -0
- package/template/src/features/users/index.ts +2 -0
- package/template/src/features/users/pages/UsersPage.tsx +151 -0
- package/template/src/features/visualizations/SmartChart.tsx +178 -0
- package/template/src/index.css +1 -1
- package/template/src/main.tsx +27 -15
- package/template/src/templates/dashboard/DashboardLayout.tsx +77 -107
- package/template/src/templates/dashboard/dashboard.sitemap.ts +19 -22
- package/template/src/templates/docs/DocsLayout.tsx +49 -38
- package/template/src/templates/docs/docs.sitemap.ts +22 -34
- package/template/src/templates/settings/SettingsLayout.tsx +83 -143
- package/template/src/templates/settings/settings.sitemap.ts +6 -6
- package/template/tailwind.config.cjs +10 -9
- package/template/vite.config.ts +0 -11
- package/template/src/adaptors/.gitkeep +0 -0
- package/template/src/components/LocalSideNav.tsx +0 -120
- package/template/src/components/PageWithSideNav.tsx +0 -69
- package/template/src/components/dev/GhostOverlay.tsx +0 -68
- package/template/src/config/dashboard.layout.ts +0 -110
- package/template/src/contexts/AuthContext.tsx +0 -64
- package/template/src/core/component-registry.tsx +0 -56
- package/template/src/core/data-seeder.ts +0 -35
- package/template/src/data/mockUsers.ts +0 -18
- package/template/src/hooks/useSignal.ts +0 -83
- package/template/src/hooks/useWorkflowEngine.ts +0 -123
- package/template/src/layouts/DataLayout.tsx +0 -37
- package/template/src/layouts/SideNavLayout.tsx +0 -28
- package/template/src/pages/Dashboard.tsx +0 -60
- package/template/src/pages/DataGridPage.tsx +0 -184
- package/template/src/pages/DynamicPage.tsx +0 -95
- package/template/src/pages/LoginPage.tsx +0 -58
- package/template/src/pages/settings/ProfilePage.tsx +0 -10
- package/template/src/pages/styleguide/Styleguide.tsx +0 -40
- package/template/src/templates/docs/pages/Introduction.tsx +0 -13
- package/template/src/types/signal.ts +0 -23
- /package/template/src/{core → engine/renderers}/route-generator.tsx +0 -0
- /package/template/src/{core → engine/types}/sitemap-entry.ts +0 -0
- /package/template/src/{pages → features}/GenericContentPage.tsx +0 -0
- /package/template/src/{hooks → features/assistant}/useMockChat.ts +0 -0
- /package/template/src/{hooks → features/developer}/useDevTools.ts +0 -0
- /package/template/src/{pages → features}/styleguide/sections/charts/ChartsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/colors/ColorsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/elements/ElementsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/feedback/FeedbackSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/forms/FormsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/icons/IconsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/layout/LayoutSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/navigation/NavigationSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/tables/TablesSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/templates/TemplatesSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/theming/ThemingSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/utilities/UtilitiesSection.tsx +0 -0
|
@@ -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
|
+
};
|
package/template/src/index.css
CHANGED
package/template/src/main.tsx
CHANGED
|
@@ -3,26 +3,38 @@ import ReactDOM from 'react-dom/client';
|
|
|
3
3
|
import { BrowserRouter } from 'react-router-dom';
|
|
4
4
|
import App from './App.tsx';
|
|
5
5
|
import { ThemeProvider, ToastProvider } from '@ramme-io/ui';
|
|
6
|
-
import { AuthProvider } from './
|
|
7
|
-
import { MqttProvider } from './
|
|
8
|
-
import
|
|
6
|
+
import { AuthProvider } from './features/auth/AuthContext.tsx';
|
|
7
|
+
import { MqttProvider } from './engine/runtime/MqttContext';
|
|
8
|
+
import "@ramme-io/ui/index.css";
|
|
9
9
|
import './index.css';
|
|
10
10
|
|
|
11
|
-
//
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
);
|
|
@@ -1,127 +1,97 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Outlet,
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
|
3
3
|
import {
|
|
4
4
|
Sidebar,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
SidebarMenuItem,
|
|
11
|
-
useSidebar,
|
|
12
|
-
Button,
|
|
13
|
-
Icon,
|
|
14
|
-
ChatFAB, // <-- NEW: Import FAB
|
|
15
|
-
} from '@ramme-io/ui';
|
|
5
|
+
type SidebarItem,
|
|
6
|
+
ChatFAB,
|
|
7
|
+
type IconName,
|
|
8
|
+
} from '@ramme-io/ui';
|
|
9
|
+
|
|
16
10
|
import { dashboardSitemap } from './dashboard.sitemap';
|
|
17
|
-
import { SitemapProvider } from '../../
|
|
11
|
+
import { SitemapProvider } from '../../engine/runtime/SitemapContext';
|
|
18
12
|
import PageTitleUpdater from '../../components/PageTitleUpdater';
|
|
19
13
|
import AppHeader from '../../components/AppHeader';
|
|
20
|
-
import { AIChatWidget } from '../../components/AIChatWidget';
|
|
21
|
-
import { useWorkflowEngine } from '../../
|
|
22
|
-
|
|
23
|
-
// NavLink wrapper - Correct
|
|
24
|
-
const SidebarNavLink = React.forwardRef<HTMLAnchorElement, any>(
|
|
25
|
-
({ end, href, ...props }, ref) => {
|
|
26
|
-
return <NavLink ref={ref} to={href || ''} {...props} end={end} />;
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
SidebarNavLink.displayName = 'SidebarNavLink';
|
|
14
|
+
import { AIChatWidget } from '../../components/AIChatWidget';
|
|
15
|
+
import { useWorkflowEngine } from '../../engine/runtime/useWorkflowEngine';
|
|
30
16
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
const { isOpen, toggle } = useSidebar();
|
|
34
|
-
return (
|
|
35
|
-
<>
|
|
36
|
-
<SidebarHeader>
|
|
37
|
-
<div className="flex items-center justify-end w-full h-full">
|
|
38
|
-
<Button
|
|
39
|
-
variant="ghost"
|
|
40
|
-
size="icon"
|
|
41
|
-
onClick={toggle}
|
|
42
|
-
className="hidden md:flex mr-1"
|
|
43
|
-
>
|
|
44
|
-
<Icon name={isOpen ? 'panel-left-close' : 'panel-left-open'} />
|
|
45
|
-
</Button>
|
|
46
|
-
</div>
|
|
47
|
-
</SidebarHeader>
|
|
17
|
+
// ✅ 1. IMPORT THE HOOK
|
|
18
|
+
import { useDynamicSitemap } from '../../engine/runtime/useDynamicSitemap';
|
|
48
19
|
|
|
49
|
-
<SidebarContent>
|
|
50
|
-
<SidebarMenu>
|
|
51
|
-
{dashboardSitemap.map((item) => (
|
|
52
|
-
<React.Fragment key={item.id}>
|
|
53
|
-
<SidebarMenuItem
|
|
54
|
-
as={SidebarNavLink}
|
|
55
|
-
href={item.path ? `/dashboard/${item.path}` : '/dashboard'}
|
|
56
|
-
end
|
|
57
|
-
icon={item.icon ? <Icon name={item.icon} /> : undefined}
|
|
58
|
-
tooltip={item.title}
|
|
59
|
-
>
|
|
60
|
-
{item.title}
|
|
61
|
-
</SidebarMenuItem>
|
|
62
|
-
{item.children &&
|
|
63
|
-
isOpen &&
|
|
64
|
-
item.children.map((child) => (
|
|
65
|
-
<SidebarMenuItem
|
|
66
|
-
key={child.id}
|
|
67
|
-
as={SidebarNavLink}
|
|
68
|
-
href={`/dashboard/${item.path}/${child.path}`}
|
|
69
|
-
end
|
|
70
|
-
icon={child.icon ? <Icon name={child.icon} /> : undefined}
|
|
71
|
-
tooltip={child.title}
|
|
72
|
-
className="pl-10"
|
|
73
|
-
>
|
|
74
|
-
{child.title}
|
|
75
|
-
</SidebarMenuItem>
|
|
76
|
-
))}
|
|
77
|
-
</React.Fragment>
|
|
78
|
-
))}
|
|
79
|
-
</SidebarMenu>
|
|
80
|
-
</SidebarContent>
|
|
81
|
-
|
|
82
|
-
<SidebarFooter />
|
|
83
|
-
</>
|
|
84
|
-
);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Main Layout Component
|
|
88
20
|
const DashboardLayout: React.FC = () => {
|
|
89
|
-
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
const location = useLocation();
|
|
90
23
|
const [isChatOpen, setIsChatOpen] = useState(false);
|
|
24
|
+
|
|
91
25
|
useWorkflowEngine();
|
|
92
26
|
|
|
27
|
+
// ✅ 2. GET LIVE DATA (Merges Static + Builder Data)
|
|
28
|
+
const liveSitemap = useDynamicSitemap(dashboardSitemap);
|
|
29
|
+
|
|
30
|
+
// ✅ 3. BUILD SIDEBAR FROM LIVE DATA
|
|
31
|
+
const sidebarItems: SidebarItem[] = useMemo(() => {
|
|
32
|
+
// We map over 'liveSitemap' instead of 'dashboardSitemap'
|
|
33
|
+
return liveSitemap.map((route) => ({
|
|
34
|
+
id: route.id,
|
|
35
|
+
label: route.title,
|
|
36
|
+
icon: route.icon as IconName,
|
|
37
|
+
// Handle the path logic safely
|
|
38
|
+
href: route.path.startsWith('/') ? route.path : `/dashboard/${route.path}`,
|
|
39
|
+
}));
|
|
40
|
+
}, [liveSitemap]); // Re-run whenever the Builder sends an update
|
|
41
|
+
|
|
42
|
+
// 4. Determine Active Item
|
|
43
|
+
const activeItemId = useMemo(() => {
|
|
44
|
+
const active = sidebarItems.find(item =>
|
|
45
|
+
item.href !== '/dashboard' && location.pathname.startsWith(item.href!)
|
|
46
|
+
);
|
|
47
|
+
return active?.id || sidebarItems[0]?.id;
|
|
48
|
+
}, [location.pathname, sidebarItems]);
|
|
49
|
+
|
|
93
50
|
return (
|
|
94
|
-
|
|
51
|
+
// ✅ 5. PASS LIVE SITEMAP TO CONTEXT (For Breadcrumbs/Titles)
|
|
52
|
+
<SitemapProvider value={liveSitemap}>
|
|
95
53
|
<PageTitleUpdater />
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
54
|
+
|
|
55
|
+
<div className="flex h-screen bg-background text-foreground relative">
|
|
56
|
+
|
|
57
|
+
<Sidebar
|
|
58
|
+
className="relative border-border"
|
|
59
|
+
items={sidebarItems} // Now contains your new page!
|
|
60
|
+
activeItemId={activeItemId}
|
|
61
|
+
onNavigate={(item) => {
|
|
62
|
+
if (item.href) navigate(item.href);
|
|
63
|
+
}}
|
|
64
|
+
user={{
|
|
65
|
+
name: "Demo User",
|
|
66
|
+
email: "user@example.com",
|
|
67
|
+
avatarUrl: "https://i.pravatar.cc/150?u=ramme"
|
|
68
|
+
}}
|
|
69
|
+
logo={
|
|
70
|
+
<div className="flex items-center gap-2 px-2 font-bold text-xl tracking-tight">
|
|
71
|
+
<span className="text-primary">Ramme</span>App
|
|
72
|
+
</div>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
107
75
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
76
|
+
<div className="flex flex-col flex-1 overflow-hidden">
|
|
77
|
+
<AppHeader />
|
|
78
|
+
<main className="flex-1 overflow-y-auto p-8 bg-muted/20">
|
|
79
|
+
<Outlet />
|
|
80
|
+
</main>
|
|
81
|
+
</div>
|
|
114
82
|
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
onClick={() => setIsChatOpen(!isChatOpen)}
|
|
119
|
-
tooltipContent={isChatOpen ? "Close Assistant" : "Open Bodewell AI"}
|
|
120
|
-
/>
|
|
121
|
-
</div>
|
|
83
|
+
{isChatOpen && (
|
|
84
|
+
<AIChatWidget onClose={() => setIsChatOpen(false)} />
|
|
85
|
+
)}
|
|
122
86
|
|
|
87
|
+
<div className="fixed bottom-6 right-6 z-50">
|
|
88
|
+
<ChatFAB
|
|
89
|
+
onClick={() => setIsChatOpen(!isChatOpen)}
|
|
90
|
+
tooltipContent={isChatOpen ? "Close Assistant" : "Open Bodewell AI"}
|
|
91
|
+
/>
|
|
123
92
|
</div>
|
|
124
|
-
|
|
93
|
+
|
|
94
|
+
</div>
|
|
125
95
|
</SitemapProvider>
|
|
126
96
|
);
|
|
127
97
|
};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { type SitemapEntry } from '../../
|
|
1
|
+
import { type SitemapEntry } from '../../engine/types/sitemap-entry';
|
|
2
2
|
import { appManifest } from '../../config/app.manifest';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import Welcome from '../../pages/Welcome';
|
|
3
|
+
|
|
4
|
+
// Existing pages (Legacy location)
|
|
5
|
+
import Welcome from '../../features/onboarding/pages/Welcome';
|
|
6
|
+
import { OverviewPage } from '../../features/overview';
|
|
7
|
+
import AiChat from '../../features/ai/pages/AiChat';
|
|
8
|
+
|
|
9
|
+
// ✅ NEW: Import from the Feature Domain (Clean API)
|
|
10
|
+
import { UsersPage } from '../../features/users';
|
|
6
11
|
|
|
7
12
|
export const dashboardSitemap: SitemapEntry[] = [
|
|
8
|
-
//
|
|
13
|
+
// 1. The New Landing Page
|
|
9
14
|
{
|
|
10
15
|
id: 'welcome',
|
|
11
16
|
path: 'welcome',
|
|
@@ -13,24 +18,16 @@ export const dashboardSitemap: SitemapEntry[] = [
|
|
|
13
18
|
icon: 'rocket',
|
|
14
19
|
component: Welcome,
|
|
15
20
|
},
|
|
16
|
-
];
|
|
17
21
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// This prevents conflict with the layout root
|
|
28
|
-
path: isDashboard ? 'app' : page.slug,
|
|
29
|
-
icon: isDashboard ? 'layout-dashboard' : 'file-text',
|
|
30
|
-
component: Dashboard,
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
22
|
+
// 3. ✅ The User Management Module
|
|
23
|
+
{
|
|
24
|
+
id: 'users',
|
|
25
|
+
path: 'users',
|
|
26
|
+
title: 'Users',
|
|
27
|
+
icon: 'users',
|
|
28
|
+
component: UsersPage,
|
|
29
|
+
}
|
|
30
|
+
];
|
|
34
31
|
|
|
35
32
|
// B. Dynamic Modules
|
|
36
33
|
if (appManifest.modules?.includes('ai-chat')) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Outlet, NavLink } from 'react-router-dom';
|
|
3
3
|
import { docsSitemap } from './docs.sitemap';
|
|
4
|
-
import { SitemapProvider } from '../../
|
|
4
|
+
import { SitemapProvider } from '../../engine/runtime/SitemapContext';
|
|
5
5
|
import AppHeader from '../../components/AppHeader';
|
|
6
6
|
import AppFooter from '../../components/AppFooter';
|
|
7
7
|
import { Icon } from '@ramme-io/ui';
|
|
@@ -9,44 +9,55 @@ import PageTitleUpdater from '../../components/PageTitleUpdater';
|
|
|
9
9
|
|
|
10
10
|
const DocsLayout: React.FC = () => {
|
|
11
11
|
return (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
12
|
+
<div className="bg-background text-foreground min-h-screen flex flex-col font-sans">
|
|
13
|
+
<AppHeader />
|
|
14
|
+
|
|
15
|
+
<main className="flex-grow flex flex-col">
|
|
16
|
+
{/* --- TOP NAVIGATION BAR --- */}
|
|
17
|
+
<div className="bg-card border-b border-border sticky top-16 z-30 w-full">
|
|
18
|
+
<nav className="w-full px-6">
|
|
19
|
+
<ul className="flex items-center gap-6 h-12">
|
|
20
|
+
{docsSitemap.map((item) => (
|
|
21
|
+
<li key={item.id}>
|
|
22
|
+
<NavLink
|
|
23
|
+
to={item.path || ''}
|
|
24
|
+
end={!item.children || item.children.length === 0}
|
|
25
|
+
className={({ isActive }) =>
|
|
26
|
+
`flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
27
|
+
isActive
|
|
28
|
+
? 'bg-primary/10 text-primary'
|
|
29
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
|
30
|
+
}`
|
|
31
|
+
}
|
|
32
|
+
>
|
|
33
|
+
{item.icon && <Icon name={item.icon} className="h-4 w-4" />}
|
|
34
|
+
<span>{item.title}</span>
|
|
35
|
+
</NavLink>
|
|
36
|
+
</li>
|
|
37
|
+
))}
|
|
38
|
+
</ul>
|
|
39
|
+
</nav>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{/* --- MAIN CONTENT WRAPPER --- */}
|
|
43
|
+
<div className="w-full flex-1 flex flex-col">
|
|
44
|
+
<SitemapProvider value={docsSitemap}>
|
|
45
|
+
<PageTitleUpdater />
|
|
46
|
+
|
|
47
|
+
{/* Page Content */}
|
|
48
|
+
<div className="flex-1">
|
|
44
49
|
<Outlet />
|
|
45
|
-
</
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Footer - Now part of the content flow */}
|
|
53
|
+
<div className="border-t border-border mt-auto w-full bg-background">
|
|
54
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
55
|
+
<AppFooter />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</SitemapProvider>
|
|
59
|
+
</div>
|
|
60
|
+
</main>
|
|
50
61
|
</div>
|
|
51
62
|
);
|
|
52
63
|
};
|