@ramme-io/create-app 1.2.0 → 1.2.2
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 +1 -2
- package/template/package.json +41 -0
- package/template/pkg.json +1 -1
- package/template/src/App.tsx +65 -35
- package/template/src/components/AIChatWidget.tsx +2 -2
- package/template/src/components/AppHeader.tsx +2 -2
- 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/config/app.manifest.ts +3 -1
- package/template/src/{core → config}/component-registry.tsx +1 -1
- 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 +27 -7
- package/template/src/{pages → engine/renderers}/DynamicPage.tsx +23 -4
- package/template/src/{contexts → engine/runtime}/MqttContext.tsx +25 -11
- package/template/src/{contexts → engine/runtime}/SitemapContext.tsx +1 -1
- package/template/src/{core → engine/runtime}/data-seeder.ts +15 -5
- package/template/src/{hooks → engine/runtime}/useAction.ts +19 -8
- 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/useSignal.ts +51 -0
- package/template/src/engine/runtime/useSignalStore.ts +94 -0
- package/template/src/engine/runtime/useWorkflowEngine.ts +144 -0
- package/template/src/{core → engine/types}/manifest-types.ts +35 -3
- package/template/src/{types → engine/validation}/schema.ts +53 -2
- 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/features/datagrid/SmartTable.tsx +222 -0
- package/template/src/features/onboarding/pages/Welcome.tsx +161 -0
- 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/index.css +1 -1
- package/template/src/main.tsx +3 -3
- package/template/src/templates/dashboard/DashboardLayout.tsx +75 -106
- package/template/src/templates/dashboard/dashboard.sitemap.ts +34 -19
- 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/vite.config.ts +12 -9
- package/template/src/adaptors/.gitkeep +0 -0
- package/template/src/blocks/SmartTable.tsx +0 -191
- package/template/src/components/LocalSideNav.tsx +0 -120
- package/template/src/components/PageWithSideNav.tsx +0 -69
- package/template/src/config/dashboard.layout.ts +0 -110
- package/template/src/contexts/AuthContext.tsx +0 -64
- package/template/src/data/mockUsers.ts +0 -18
- package/template/src/generated/hooks.ts +0 -40
- package/template/src/hooks/useSignal.ts +0 -83
- package/template/src/hooks/useWorkflowEngine.ts +0 -6
- 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/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/{components/dev → features/developer}/GhostOverlay.tsx +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,138 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Drawer, Button, Input, Select, type SelectOption } from '@ramme-io/ui';
|
|
3
|
+
import type { User } from '../../../data/mockData';
|
|
4
|
+
|
|
5
|
+
interface UserDrawerProps {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
user: User | null;
|
|
9
|
+
onSave: (id: string, data: Partial<User>) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const UserDrawer: React.FC<UserDrawerProps> = ({ isOpen, onClose, user, onSave }) => {
|
|
13
|
+
const [formData, setFormData] = useState<Partial<User>>({
|
|
14
|
+
name: '',
|
|
15
|
+
email: '',
|
|
16
|
+
role: 'viewer',
|
|
17
|
+
status: 'active',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (user) {
|
|
22
|
+
setFormData(user);
|
|
23
|
+
} else {
|
|
24
|
+
setFormData({
|
|
25
|
+
name: '',
|
|
26
|
+
email: '',
|
|
27
|
+
role: 'viewer',
|
|
28
|
+
status: 'active',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}, [user, isOpen]);
|
|
32
|
+
|
|
33
|
+
// Options Configurations
|
|
34
|
+
const roleOptions: SelectOption[] = [
|
|
35
|
+
{ value: 'admin', label: 'Admin' },
|
|
36
|
+
{ value: 'editor', label: 'Editor' },
|
|
37
|
+
{ value: 'viewer', label: 'Viewer' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const statusOptions: SelectOption[] = [
|
|
41
|
+
{ value: 'active', label: 'Active' },
|
|
42
|
+
{ value: 'pending', label: 'Pending' },
|
|
43
|
+
{ value: 'banned', label: 'Banned' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// ✅ HELPER: Find the full object based on the string value
|
|
47
|
+
// This satisfies the TypeScript requirement: value={SelectOption}
|
|
48
|
+
const getOption = (options: SelectOption[], value: string | undefined) => {
|
|
49
|
+
return options.find((opt) => opt.value === value) || null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
53
|
+
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ✅ HELPER: Handle change for both Native Events and Custom Selects
|
|
57
|
+
const handleSelectChange = (name: keyof User, newValue: any) => {
|
|
58
|
+
// If the component returns a SelectOption object (Custom Select)
|
|
59
|
+
if (newValue && typeof newValue === 'object' && 'value' in newValue) {
|
|
60
|
+
setFormData({ ...formData, [name]: newValue.value });
|
|
61
|
+
}
|
|
62
|
+
// If the component returns a standard Event (Native Select)
|
|
63
|
+
else if (newValue?.target) {
|
|
64
|
+
setFormData({ ...formData, [name]: newValue.target.value });
|
|
65
|
+
}
|
|
66
|
+
// Fallback
|
|
67
|
+
else {
|
|
68
|
+
setFormData({ ...formData, [name]: newValue });
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
const idToSave = user?.id || '';
|
|
75
|
+
onSave(idToSave, formData);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Drawer
|
|
80
|
+
isOpen={isOpen}
|
|
81
|
+
onClose={onClose}
|
|
82
|
+
title={user ? 'Edit User' : 'Add New User'}
|
|
83
|
+
size="md"
|
|
84
|
+
>
|
|
85
|
+
<form onSubmit={handleSubmit} className="flex flex-col h-full">
|
|
86
|
+
<div className="flex-1 space-y-6 p-1">
|
|
87
|
+
<Input
|
|
88
|
+
label="Full Name"
|
|
89
|
+
name="name"
|
|
90
|
+
placeholder="Jane Doe"
|
|
91
|
+
value={formData.name || ''}
|
|
92
|
+
onChange={handleChange}
|
|
93
|
+
required
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<Input
|
|
97
|
+
label="Email Address"
|
|
98
|
+
name="email"
|
|
99
|
+
type="email"
|
|
100
|
+
placeholder="jane@example.com"
|
|
101
|
+
value={formData.email || ''}
|
|
102
|
+
onChange={handleChange}
|
|
103
|
+
required
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<Select
|
|
107
|
+
label="Role"
|
|
108
|
+
options={roleOptions}
|
|
109
|
+
// ✅ FIX: Pass the object, not the string
|
|
110
|
+
value={getOption(roleOptions, formData.role)}
|
|
111
|
+
// ✅ FIX: Handle the update safely
|
|
112
|
+
onChange={(val) => handleSelectChange('role', val)}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<Select
|
|
116
|
+
label="Status"
|
|
117
|
+
options={statusOptions}
|
|
118
|
+
// ✅ FIX: Pass the object, not the string
|
|
119
|
+
value={getOption(statusOptions, formData.status)}
|
|
120
|
+
// ✅ FIX: Handle the update safely
|
|
121
|
+
onChange={(val) => handleSelectChange('status', val)}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div className="flex justify-end gap-3 mt-8 pt-4 border-t border-border">
|
|
126
|
+
<Button variant="ghost" onClick={onClose} type="button">
|
|
127
|
+
Cancel
|
|
128
|
+
</Button>
|
|
129
|
+
<Button variant="primary" type="submit">
|
|
130
|
+
{user ? 'Save Changes' : 'Create User'}
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
</form>
|
|
134
|
+
</Drawer>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default UserDrawer;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
PageHeader,
|
|
4
|
+
DataTable,
|
|
5
|
+
Button,
|
|
6
|
+
useToast,
|
|
7
|
+
Badge,
|
|
8
|
+
Input,
|
|
9
|
+
type ColDef,
|
|
10
|
+
type ICellRendererParams,
|
|
11
|
+
} from '@ramme-io/ui';
|
|
12
|
+
|
|
13
|
+
// 1. REMOVE: userService and old User types
|
|
14
|
+
// import { userService } from '../api/user.service';
|
|
15
|
+
// import type { User } from '../api/user.types';
|
|
16
|
+
|
|
17
|
+
// 2. ADD: The Engine Hook & Shared Data
|
|
18
|
+
import { useCrudLocalStorage } from '../../../engine/runtime/useCrudLocalStorage';
|
|
19
|
+
import { SEED_USERS, type User } from '../../../data/mockData';
|
|
20
|
+
|
|
21
|
+
import UserDrawer from '../components/UserDrawer';
|
|
22
|
+
|
|
23
|
+
const UsersPage: React.FC = () => {
|
|
24
|
+
const { addToast } = useToast();
|
|
25
|
+
|
|
26
|
+
// 3. REPLACE: Manual state fetching with the Reactive Engine
|
|
27
|
+
// This automatically loads 'ramme_db_users' and keeps it in sync.
|
|
28
|
+
const {
|
|
29
|
+
data: users,
|
|
30
|
+
createItem,
|
|
31
|
+
updateItem,
|
|
32
|
+
deleteItem
|
|
33
|
+
} = useCrudLocalStorage<User>('ramme_db_users', SEED_USERS);
|
|
34
|
+
|
|
35
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
36
|
+
const [editingUser, setEditingUser] = useState<User | null>(null);
|
|
37
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
38
|
+
|
|
39
|
+
// (Removed refreshData & useEffect - the hook handles this automatically)
|
|
40
|
+
|
|
41
|
+
const handleOpenDrawer = (user: User | null = null) => {
|
|
42
|
+
setEditingUser(user);
|
|
43
|
+
setIsDrawerOpen(true);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleDelete = (user: User) => {
|
|
47
|
+
if (confirm(`Delete ${user.name}?`)) {
|
|
48
|
+
deleteItem(user.id);
|
|
49
|
+
addToast('User deleted', 'success');
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleSave = (_id: string, data: Partial<User>) => {
|
|
54
|
+
try {
|
|
55
|
+
if (editingUser) {
|
|
56
|
+
// Update existing user
|
|
57
|
+
updateItem({ ...editingUser, ...data } as User);
|
|
58
|
+
addToast('User updated', 'success');
|
|
59
|
+
} else {
|
|
60
|
+
// Create new user (Engine handles ID generation)
|
|
61
|
+
createItem({
|
|
62
|
+
...data,
|
|
63
|
+
role: data.role || 'viewer', // Ensure defaults
|
|
64
|
+
status: data.status || 'active',
|
|
65
|
+
joinedAt: new Date().toISOString()
|
|
66
|
+
} as User);
|
|
67
|
+
addToast('User created', 'success');
|
|
68
|
+
}
|
|
69
|
+
// Close drawer
|
|
70
|
+
setIsDrawerOpen(false);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
addToast('Error saving user', 'error');
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const columnDefs = useMemo<ColDef[]>(() => [
|
|
77
|
+
{ field: 'name', headerName: 'Name', flex: 1, filter: true },
|
|
78
|
+
{ field: 'email', headerName: 'Email', flex: 1, filter: true },
|
|
79
|
+
{
|
|
80
|
+
field: 'role',
|
|
81
|
+
headerName: 'Role',
|
|
82
|
+
width: 120,
|
|
83
|
+
cellRenderer: (params: any) => (
|
|
84
|
+
<Badge variant={params.value === 'admin' ? 'primary' : 'secondary'}>
|
|
85
|
+
{params.value}
|
|
86
|
+
</Badge>
|
|
87
|
+
)
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
field: 'status',
|
|
91
|
+
width: 120,
|
|
92
|
+
cellRenderer: (params: any) => (
|
|
93
|
+
<Badge variant={params.value === 'active' ? 'success' : 'danger'}>
|
|
94
|
+
{params.value}
|
|
95
|
+
</Badge>
|
|
96
|
+
)
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
headerName: 'Actions',
|
|
100
|
+
width: 120,
|
|
101
|
+
pinned: 'right',
|
|
102
|
+
cellRenderer: (params: ICellRendererParams) => (
|
|
103
|
+
<div className="flex gap-2">
|
|
104
|
+
<Button size="sm" variant="ghost" iconLeft="edit" onClick={() => handleOpenDrawer(params.data)} />
|
|
105
|
+
<Button size="sm" variant="ghost" className="text-red-500 hover:text-red-600" iconLeft="trash-2" onClick={() => handleDelete(params.data)} />
|
|
106
|
+
</div>
|
|
107
|
+
),
|
|
108
|
+
},
|
|
109
|
+
], [deleteItem]); // Added dependency
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="space-y-6 h-[calc(100vh-140px)] flex flex-col">
|
|
113
|
+
<PageHeader
|
|
114
|
+
title="User Management"
|
|
115
|
+
description="Manage system access and permissions."
|
|
116
|
+
actions={
|
|
117
|
+
<Button variant="primary" iconLeft="plus" onClick={() => handleOpenDrawer()}>
|
|
118
|
+
Add User
|
|
119
|
+
</Button>
|
|
120
|
+
}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<div className="w-full max-w-sm">
|
|
124
|
+
<Input
|
|
125
|
+
placeholder="Search users..."
|
|
126
|
+
value={searchTerm}
|
|
127
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<DataTable
|
|
132
|
+
rowData={users}
|
|
133
|
+
columnDefs={columnDefs}
|
|
134
|
+
height="100%"
|
|
135
|
+
quickFilterText={searchTerm}
|
|
136
|
+
pagination
|
|
137
|
+
paginationPageSize={10}
|
|
138
|
+
paginationPageSizeSelector={[10, 25, 50]}
|
|
139
|
+
/>
|
|
140
|
+
|
|
141
|
+
<UserDrawer
|
|
142
|
+
isOpen={isDrawerOpen}
|
|
143
|
+
onClose={() => setIsDrawerOpen(false)}
|
|
144
|
+
user={editingUser}
|
|
145
|
+
onSave={handleSave}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default UsersPage;
|
package/template/src/index.css
CHANGED
package/template/src/main.tsx
CHANGED
|
@@ -3,9 +3,9 @@ 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
|
// This import activates all AG Grid Enterprise features
|
|
@@ -1,127 +1,96 @@
|
|
|
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, // Type for safety
|
|
8
|
+
} from '@ramme-io/ui'; // ✅ Import only what exists in v1.2.0
|
|
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';
|
|
30
|
-
|
|
31
|
-
// Sidebar Content Component
|
|
32
|
-
const AppSidebarContent: React.FC = () => {
|
|
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>
|
|
48
|
-
|
|
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
|
-
};
|
|
14
|
+
import { AIChatWidget } from '../../components/AIChatWidget';
|
|
15
|
+
import { useWorkflowEngine } from '../../engine/runtime/useWorkflowEngine';
|
|
86
16
|
|
|
87
17
|
// Main Layout Component
|
|
88
18
|
const DashboardLayout: React.FC = () => {
|
|
89
|
-
|
|
19
|
+
const navigate = useNavigate();
|
|
20
|
+
const location = useLocation();
|
|
90
21
|
const [isChatOpen, setIsChatOpen] = useState(false);
|
|
22
|
+
|
|
91
23
|
useWorkflowEngine();
|
|
92
24
|
|
|
25
|
+
// 1. Transform Sitemap to Sidebar Items
|
|
26
|
+
// The new Sidebar expects a simple array of items.
|
|
27
|
+
const sidebarItems: SidebarItem[] = useMemo(() => {
|
|
28
|
+
return dashboardSitemap.map((route) => ({
|
|
29
|
+
id: route.id,
|
|
30
|
+
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.
|
|
35
|
+
}));
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
// 2. Determine Active Item based on URL
|
|
39
|
+
const activeItemId = useMemo(() => {
|
|
40
|
+
// Find the item whose href matches the start of the current path
|
|
41
|
+
const active = sidebarItems.find(item =>
|
|
42
|
+
item.href !== '/dashboard' && location.pathname.startsWith(item.href!)
|
|
43
|
+
);
|
|
44
|
+
// Default to dashboard (first item) if no specific match
|
|
45
|
+
return active?.id || sidebarItems[0]?.id;
|
|
46
|
+
}, [location.pathname, sidebarItems]);
|
|
47
|
+
|
|
93
48
|
return (
|
|
94
49
|
<SitemapProvider value={dashboardSitemap}>
|
|
95
50
|
<PageTitleUpdater />
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
51
|
+
|
|
52
|
+
{/* 3. New Layout Structure (No Provider needed) */}
|
|
53
|
+
<div className="flex h-screen bg-background text-foreground relative">
|
|
54
|
+
|
|
55
|
+
<Sidebar
|
|
56
|
+
className="relative border-border"
|
|
57
|
+
items={sidebarItems}
|
|
58
|
+
activeItemId={activeItemId}
|
|
59
|
+
onNavigate={(item) => {
|
|
60
|
+
if (item.href) navigate(item.href);
|
|
61
|
+
}}
|
|
62
|
+
user={{
|
|
63
|
+
name: "Demo User",
|
|
64
|
+
email: "user@example.com",
|
|
65
|
+
avatarUrl: "https://i.pravatar.cc/150?u=ramme"
|
|
66
|
+
}}
|
|
67
|
+
logo={
|
|
68
|
+
<div className="flex items-center gap-2 px-2 font-bold text-xl tracking-tight">
|
|
69
|
+
<span className="text-primary">Ramme</span>App
|
|
70
|
+
</div>
|
|
71
|
+
}
|
|
72
|
+
/>
|
|
107
73
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
74
|
+
<div className="flex flex-col flex-1 overflow-hidden">
|
|
75
|
+
<AppHeader />
|
|
76
|
+
<main className="flex-1 overflow-y-auto p-8 bg-muted/20">
|
|
77
|
+
<Outlet />
|
|
78
|
+
</main>
|
|
79
|
+
</div>
|
|
114
80
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
tooltipContent={isChatOpen ? "Close Assistant" : "Open Bodewell AI"}
|
|
120
|
-
/>
|
|
121
|
-
</div>
|
|
81
|
+
{/* --- AI COPILOT SECTION --- */}
|
|
82
|
+
{isChatOpen && (
|
|
83
|
+
<AIChatWidget onClose={() => setIsChatOpen(false)} />
|
|
84
|
+
)}
|
|
122
85
|
|
|
86
|
+
<div className="fixed bottom-6 right-6 z-50">
|
|
87
|
+
<ChatFAB
|
|
88
|
+
onClick={() => setIsChatOpen(!isChatOpen)}
|
|
89
|
+
tooltipContent={isChatOpen ? "Close Assistant" : "Open Bodewell AI"}
|
|
90
|
+
/>
|
|
123
91
|
</div>
|
|
124
|
-
|
|
92
|
+
|
|
93
|
+
</div>
|
|
125
94
|
</SitemapProvider>
|
|
126
95
|
);
|
|
127
96
|
};
|
|
@@ -1,25 +1,40 @@
|
|
|
1
|
-
import { type SitemapEntry } from '../../
|
|
1
|
+
import { type SitemapEntry } from '../../engine/types/sitemap-entry';
|
|
2
2
|
import { appManifest } from '../../config/app.manifest';
|
|
3
|
-
import Dashboard from '../../pages/Dashboard';
|
|
4
|
-
import AiChat from '../../pages/AiChat';
|
|
5
3
|
|
|
6
|
-
|
|
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';
|
|
7
8
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
// ✅ NEW: Import from the Feature Domain (Clean API)
|
|
10
|
+
import { UsersPage } from '../../features/users';
|
|
11
|
+
|
|
12
|
+
export const dashboardSitemap: SitemapEntry[] = [
|
|
13
|
+
// 1. The New Landing Page
|
|
14
|
+
{
|
|
15
|
+
id: 'welcome',
|
|
16
|
+
path: 'welcome',
|
|
17
|
+
title: 'Start Here',
|
|
18
|
+
icon: 'rocket',
|
|
19
|
+
component: Welcome,
|
|
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
|
+
},
|
|
29
|
+
// 3. ✅ The User Management Module
|
|
30
|
+
{
|
|
31
|
+
id: 'users',
|
|
32
|
+
path: 'users',
|
|
33
|
+
title: 'Users',
|
|
34
|
+
icon: 'users',
|
|
35
|
+
component: UsersPage,
|
|
36
|
+
}
|
|
37
|
+
];
|
|
23
38
|
|
|
24
39
|
// B. Dynamic Modules
|
|
25
40
|
if (appManifest.modules?.includes('ai-chat')) {
|