@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
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Icon } from '@ramme-io/ui';
|
|
3
|
-
|
|
4
|
-
interface GhostOverlayProps {
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
componentId: string;
|
|
7
|
-
componentType: string;
|
|
8
|
-
signalId?: string;
|
|
9
|
-
isActive?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const GhostOverlay: React.FC<GhostOverlayProps> = ({
|
|
13
|
-
children,
|
|
14
|
-
componentId,
|
|
15
|
-
componentType,
|
|
16
|
-
signalId,
|
|
17
|
-
isActive = false,
|
|
18
|
-
}) => {
|
|
19
|
-
|
|
20
|
-
if (!isActive) {
|
|
21
|
-
return <>{children}</>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const handleClick = (e: React.MouseEvent) => {
|
|
25
|
-
// 🛑 Stop the click from triggering app logic (like navigation or toggles)
|
|
26
|
-
e.preventDefault();
|
|
27
|
-
e.stopPropagation();
|
|
28
|
-
|
|
29
|
-
// 🚀 THE GHOST BRIDGE: Signal the Parent (The Builder)
|
|
30
|
-
window.parent.postMessage({
|
|
31
|
-
type: 'RAMME_SELECT_BLOCK',
|
|
32
|
-
payload: { blockId: componentId }
|
|
33
|
-
}, '*');
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div
|
|
38
|
-
className="relative group cursor-pointer"
|
|
39
|
-
onClick={handleClick} // ✅ Add Click Handler
|
|
40
|
-
>
|
|
41
|
-
{/* The "Ghost" Border */}
|
|
42
|
-
<div className="absolute inset-0 z-50 border-2 border-dashed border-accent/50 rounded-lg bg-accent/5 group-hover:bg-accent/10 transition-colors pointer-events-none" />
|
|
43
|
-
|
|
44
|
-
{/* The "Info Tag" */}
|
|
45
|
-
<div className="absolute top-0 left-0 z-50 p-2 transform -translate-y-1/2 translate-x-2 pointer-events-none">
|
|
46
|
-
<div className="flex items-center gap-2 bg-accent text-accent-foreground text-xs font-mono py-1 px-2 rounded shadow-sm">
|
|
47
|
-
<Icon name="box" size={12} />
|
|
48
|
-
<span className="font-bold">{componentType}</span>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
{/* The "Signal Wire" */}
|
|
53
|
-
{signalId && (
|
|
54
|
-
<div className="absolute bottom-0 right-0 z-50 p-2 transform translate-y-1/2 -translate-x-2 pointer-events-none">
|
|
55
|
-
<div className="flex items-center gap-1.5 bg-blue-600 text-white text-xs font-mono py-1 px-2 rounded shadow-sm">
|
|
56
|
-
<Icon name="activity" size={12} />
|
|
57
|
-
<span>{signalId}</span>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
)}
|
|
61
|
-
|
|
62
|
-
{/* Render the actual component underneath */}
|
|
63
|
-
<div className="opacity-50 grayscale transition-all duration-200 group-hover:opacity-75 group-hover:grayscale-0 pointer-events-none">
|
|
64
|
-
{children}
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file dashboard.layout.ts
|
|
3
|
-
* Defines the schema and data for the dynamic dashboard.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// 1. Define the Shape of our "Brain"
|
|
7
|
-
export interface DashboardItem {
|
|
8
|
-
id: string;
|
|
9
|
-
component: string;
|
|
10
|
-
props: Record<string, any>;
|
|
11
|
-
signalId?: string; // <-- Mark as Optional (?)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface DashboardSection {
|
|
15
|
-
id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
type: string;
|
|
18
|
-
columns: number;
|
|
19
|
-
items: DashboardItem[]; // <-- Enforce the type here
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// 2. The Data (Typed)
|
|
23
|
-
export const dashboardLayout: DashboardSection[] = [
|
|
24
|
-
{
|
|
25
|
-
id: "section_iot",
|
|
26
|
-
title: "Live Device Status",
|
|
27
|
-
type: "grid",
|
|
28
|
-
columns: 3,
|
|
29
|
-
items: [
|
|
30
|
-
{
|
|
31
|
-
id: "dev_1",
|
|
32
|
-
component: "DeviceCard",
|
|
33
|
-
props: {
|
|
34
|
-
title: "Living Room AC",
|
|
35
|
-
description: "Zone A • Floor 1",
|
|
36
|
-
icon: "thermometer",
|
|
37
|
-
status: "online",
|
|
38
|
-
trend: "Cooling to 70°"
|
|
39
|
-
},
|
|
40
|
-
signalId: "living_room_ac"
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "dev_2",
|
|
44
|
-
component: "DeviceCard",
|
|
45
|
-
props: {
|
|
46
|
-
title: "Air Quality",
|
|
47
|
-
description: "Sensor ID: #8842",
|
|
48
|
-
icon: "droplets",
|
|
49
|
-
status: "active",
|
|
50
|
-
trend: "Stable"
|
|
51
|
-
},
|
|
52
|
-
signalId: "living_room_hum"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: "dev_3",
|
|
56
|
-
component: "DeviceCard",
|
|
57
|
-
props: {
|
|
58
|
-
title: "Main Server",
|
|
59
|
-
description: "192.168.1.42",
|
|
60
|
-
icon: "server",
|
|
61
|
-
status: "online",
|
|
62
|
-
trend: "CPU Load"
|
|
63
|
-
},
|
|
64
|
-
signalId: "server_01"
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: "dev_4",
|
|
68
|
-
component: "DeviceCard",
|
|
69
|
-
props: {
|
|
70
|
-
title: "Front Door",
|
|
71
|
-
description: "Entryway • Camera 01",
|
|
72
|
-
icon: "lock", // This maps to the Lucide icon 'lock'
|
|
73
|
-
status: "offline", // Default state before data loads
|
|
74
|
-
trend: "Locked"
|
|
75
|
-
},
|
|
76
|
-
signalId: "front_door_lock" // <--- We will wire this next
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: "section_metrics",
|
|
82
|
-
title: "Business Overview",
|
|
83
|
-
type: "grid",
|
|
84
|
-
columns: 4,
|
|
85
|
-
items: [
|
|
86
|
-
{
|
|
87
|
-
id: "stat_1",
|
|
88
|
-
component: "StatCard",
|
|
89
|
-
props: {
|
|
90
|
-
title: "Total Users",
|
|
91
|
-
value: "1,234",
|
|
92
|
-
icon: "users",
|
|
93
|
-
changeText: "+10% from last month",
|
|
94
|
-
changeDirection: "positive"
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
id: "stat_2",
|
|
99
|
-
component: "StatCard",
|
|
100
|
-
props: {
|
|
101
|
-
title: "Sales Today",
|
|
102
|
-
value: "$5,678",
|
|
103
|
-
icon: "dollar-sign",
|
|
104
|
-
changeText: "+5% from yesterday",
|
|
105
|
-
changeDirection: "positive"
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
]
|
|
109
|
-
}
|
|
110
|
-
];
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
|
2
|
-
import { mockUsers, type User } from '../data/mockUsers';
|
|
3
|
-
|
|
4
|
-
interface AuthContextType {
|
|
5
|
-
user: User | null;
|
|
6
|
-
login: (username: string, password?: string) => Promise<User | null>;
|
|
7
|
-
logout: () => void;
|
|
8
|
-
isLoading: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
12
|
-
|
|
13
|
-
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
14
|
-
const [user, setUser] = useState<User | null>(null);
|
|
15
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
// Check for a logged-in user in localStorage on initial load
|
|
19
|
-
const storedUser = localStorage.getItem('authUser');
|
|
20
|
-
if (storedUser) {
|
|
21
|
-
setUser(JSON.parse(storedUser));
|
|
22
|
-
}
|
|
23
|
-
setIsLoading(false);
|
|
24
|
-
}, []);
|
|
25
|
-
|
|
26
|
-
const login = async (username: string, password?: string): Promise<User | null> => {
|
|
27
|
-
// Simulate API call
|
|
28
|
-
return new Promise((resolve) => {
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
const foundUser = mockUsers.find(
|
|
31
|
-
u => u.username === username && u.password === password
|
|
32
|
-
);
|
|
33
|
-
if (foundUser) {
|
|
34
|
-
const userToStore = { ...foundUser };
|
|
35
|
-
delete userToStore.password; // Don't store password
|
|
36
|
-
localStorage.setItem('authUser', JSON.stringify(userToStore));
|
|
37
|
-
setUser(userToStore);
|
|
38
|
-
resolve(userToStore);
|
|
39
|
-
} else {
|
|
40
|
-
resolve(null);
|
|
41
|
-
}
|
|
42
|
-
}, 500);
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const logout = () => {
|
|
47
|
-
localStorage.removeItem('authUser');
|
|
48
|
-
setUser(null);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<AuthContext.Provider value={{ user, login, logout, isLoading }}>
|
|
53
|
-
{children}
|
|
54
|
-
</AuthContext.Provider>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const useAuth = () => {
|
|
59
|
-
const context = useContext(AuthContext);
|
|
60
|
-
if (context === undefined) {
|
|
61
|
-
throw new Error('useAuth must be used within an AuthProvider');
|
|
62
|
-
}
|
|
63
|
-
return context;
|
|
64
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
DeviceCard,
|
|
4
|
-
StatCard,
|
|
5
|
-
BarChart,
|
|
6
|
-
LineChart,
|
|
7
|
-
PieChart,
|
|
8
|
-
DataTable,
|
|
9
|
-
Card,
|
|
10
|
-
Alert,
|
|
11
|
-
EmptyState,
|
|
12
|
-
ToggleSwitch
|
|
13
|
-
} from '@ramme-io/ui';
|
|
14
|
-
|
|
15
|
-
// ✅ IMPORT YOUR CUSTOM COMPONENT
|
|
16
|
-
import { SmartTable } from '../blocks/SmartTable';
|
|
17
|
-
|
|
18
|
-
export const COMPONENT_REGISTRY: Record<string, React.FC<any>> = {
|
|
19
|
-
// IoT Primitives
|
|
20
|
-
DeviceCard,
|
|
21
|
-
|
|
22
|
-
// Data Display
|
|
23
|
-
StatCard,
|
|
24
|
-
BarChart,
|
|
25
|
-
LineChart,
|
|
26
|
-
PieChart,
|
|
27
|
-
|
|
28
|
-
// Tables
|
|
29
|
-
DataTable, // The raw grid
|
|
30
|
-
|
|
31
|
-
// ✅ FIX: Map "SmartTable" to the actual SmartTable component
|
|
32
|
-
// (Previously it was aliased to DataTable, which broke the UI)
|
|
33
|
-
SmartTable: SmartTable,
|
|
34
|
-
|
|
35
|
-
// Layout & Feedback
|
|
36
|
-
Card,
|
|
37
|
-
Alert,
|
|
38
|
-
EmptyState,
|
|
39
|
-
|
|
40
|
-
// Forms/Controls
|
|
41
|
-
ToggleSwitch
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const getComponent = (name: string) => {
|
|
45
|
-
const Component = COMPONENT_REGISTRY[name];
|
|
46
|
-
|
|
47
|
-
if (!Component) {
|
|
48
|
-
console.warn(`[Registry] Unknown component type: "${name}"`);
|
|
49
|
-
return () => (
|
|
50
|
-
<Alert variant="danger" title="Unknown Component">
|
|
51
|
-
The system tried to render <code>{name}</code> but it was not found in the registry.
|
|
52
|
-
</Alert>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
return Component;
|
|
56
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// ✅ Match the export from your new mockData.ts
|
|
2
|
-
import { DATA_REGISTRY } from '../data/mockData';
|
|
3
|
-
|
|
4
|
-
const DB_PREFIX = 'ramme_db_';
|
|
5
|
-
|
|
6
|
-
export const initializeDataLake = () => {
|
|
7
|
-
if (typeof window === 'undefined') return;
|
|
8
|
-
|
|
9
|
-
console.groupCollapsed('🌊 [Data Lake] Initialization');
|
|
10
|
-
|
|
11
|
-
Object.entries(DATA_REGISTRY).forEach(([key, seedData]) => {
|
|
12
|
-
const storageKey = `${DB_PREFIX}${key}`;
|
|
13
|
-
const existing = localStorage.getItem(storageKey);
|
|
14
|
-
|
|
15
|
-
if (!existing) {
|
|
16
|
-
console.log(`✨ Seeding collection: ${key} (${seedData.length} records)`);
|
|
17
|
-
localStorage.setItem(storageKey, JSON.stringify(seedData));
|
|
18
|
-
} else {
|
|
19
|
-
console.log(`✅ Collection exists: ${key}`);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
console.groupEnd();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Utility to clear the lake (useful for a "Reset Data" button)
|
|
28
|
-
*/
|
|
29
|
-
export const resetDataLake = () => {
|
|
30
|
-
// ✅ FIX: Use DATA_REGISTRY (the new name)
|
|
31
|
-
Object.keys(DATA_REGISTRY).forEach((key) => {
|
|
32
|
-
localStorage.removeItem(`${DB_PREFIX}${key}`);
|
|
33
|
-
});
|
|
34
|
-
window.location.reload();
|
|
35
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface User {
|
|
2
|
-
id: number;
|
|
3
|
-
name: string;
|
|
4
|
-
email: string;
|
|
5
|
-
username: string; // <-- Add this
|
|
6
|
-
password?: string; // <-- Add this (optional for security)
|
|
7
|
-
role: 'Admin' | 'Editor' | 'Viewer';
|
|
8
|
-
status: 'Active' | 'Pending' | 'Banned';
|
|
9
|
-
createdAt: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// NOTE: In a real app, passwords would be hashed. This is for simulation only.
|
|
13
|
-
export const mockUsers: User[] = [
|
|
14
|
-
{ id: 1, name: 'Jane Cooper', email: 'jane.cooper@example.com', username: 'jane', password: 'password', role: 'Admin', status: 'Active', createdAt: '2023-01-15T10:00:00Z' },
|
|
15
|
-
{ id: 2, name: 'Cody Fisher', email: 'cody.fisher@example.com', username: 'cody', password: 'password', role: 'Editor', status: 'Active', createdAt: '2023-02-20T11:30:00Z' },
|
|
16
|
-
{ id: 3, name: 'Esther Howard', email: 'esther.howard@example.com', username: 'esther', password: 'password', role: 'Viewer', status: 'Pending', createdAt: '2023-03-05T09:15:00Z' },
|
|
17
|
-
// ... other users
|
|
18
|
-
];
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { useMqtt } from '../contexts/MqttContext';
|
|
3
|
-
import type { Signal } from '../types/signal';
|
|
4
|
-
|
|
5
|
-
interface SignalConfig<T> {
|
|
6
|
-
initialValue?: T;
|
|
7
|
-
min?: number;
|
|
8
|
-
max?: number;
|
|
9
|
-
interval?: number; // Mock mode only
|
|
10
|
-
unit?: string;
|
|
11
|
-
topic?: string; // Real mode only
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useSignal<T = any>(signalId: string, config: SignalConfig<T> = {}): Signal<T> {
|
|
15
|
-
const {
|
|
16
|
-
initialValue,
|
|
17
|
-
min = -Infinity,
|
|
18
|
-
max = Infinity,
|
|
19
|
-
interval = 2000,
|
|
20
|
-
unit,
|
|
21
|
-
topic
|
|
22
|
-
} = config;
|
|
23
|
-
|
|
24
|
-
const { subscribe, unsubscribe, lastMessage, isConnected } = useMqtt();
|
|
25
|
-
|
|
26
|
-
const [signal, setSignal] = useState<Signal<T>>({
|
|
27
|
-
id: signalId,
|
|
28
|
-
value: initialValue as T,
|
|
29
|
-
unit: unit,
|
|
30
|
-
timestamp: Date.now(),
|
|
31
|
-
status: 'fresh',
|
|
32
|
-
max: max
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// --- REAL MODE: MQTT ---
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (!topic || !isConnected) return;
|
|
38
|
-
subscribe(topic);
|
|
39
|
-
return () => unsubscribe(topic);
|
|
40
|
-
}, [topic, isConnected, subscribe, unsubscribe]);
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (!topic || !lastMessage[topic]) return;
|
|
44
|
-
|
|
45
|
-
const rawValue = lastMessage[topic];
|
|
46
|
-
let parsedValue: any = rawValue;
|
|
47
|
-
|
|
48
|
-
// Auto-parse numbers and booleans
|
|
49
|
-
if (!isNaN(Number(rawValue))) parsedValue = Number(rawValue);
|
|
50
|
-
else if (rawValue.toLowerCase() === 'true' || rawValue === 'on') parsedValue = true;
|
|
51
|
-
else if (rawValue.toLowerCase() === 'false' || rawValue === 'off') parsedValue = false;
|
|
52
|
-
|
|
53
|
-
setSignal(prev => ({
|
|
54
|
-
...prev,
|
|
55
|
-
value: parsedValue,
|
|
56
|
-
timestamp: Date.now(),
|
|
57
|
-
status: 'fresh'
|
|
58
|
-
}));
|
|
59
|
-
}, [lastMessage, topic]);
|
|
60
|
-
|
|
61
|
-
// --- MOCK MODE: SIMULATION ---
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (topic) return; // Disable mock if topic exists
|
|
64
|
-
|
|
65
|
-
const timer = setInterval(() => {
|
|
66
|
-
setSignal(prev => {
|
|
67
|
-
let newValue: any = prev.value;
|
|
68
|
-
if (typeof prev.value === 'number') {
|
|
69
|
-
const variance = (Math.random() - 0.5) * 2;
|
|
70
|
-
let nextNum = prev.value + variance;
|
|
71
|
-
if (min !== undefined) nextNum = Math.max(min, nextNum);
|
|
72
|
-
if (max !== undefined) nextNum = Math.min(max, nextNum);
|
|
73
|
-
newValue = Number(nextNum.toFixed(1));
|
|
74
|
-
}
|
|
75
|
-
return { ...prev, value: newValue, timestamp: Date.now(), status: 'fresh' };
|
|
76
|
-
});
|
|
77
|
-
}, interval);
|
|
78
|
-
|
|
79
|
-
return () => clearInterval(timer);
|
|
80
|
-
}, [topic, min, max, interval]);
|
|
81
|
-
|
|
82
|
-
return signal;
|
|
83
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { useToast } from '@ramme-io/ui';
|
|
3
|
-
// @ts-ignore - These generated hooks exist in the build
|
|
4
|
-
import { useGeneratedSignals, useSimulation } from '../generated/hooks';
|
|
5
|
-
import { appManifest } from '../config/app.manifest';
|
|
6
|
-
// ✅ Import types to fix implicit 'any' errors
|
|
7
|
-
import type { ActionDefinition, WorkflowDefinition } from '../types/schema';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* THE RUNTIME LOGIC ENGINE
|
|
11
|
-
* This hook breathes life into the application.
|
|
12
|
-
* It watches for signal changes and executes the workflows defined in the manifest.
|
|
13
|
-
*/
|
|
14
|
-
export const useWorkflowEngine = () => {
|
|
15
|
-
const signals = useGeneratedSignals();
|
|
16
|
-
const { addToast } = useToast();
|
|
17
|
-
|
|
18
|
-
// 1. Activate the Data Simulation (Randomizer)
|
|
19
|
-
useSimulation();
|
|
20
|
-
|
|
21
|
-
// 2. Define the Action Executor
|
|
22
|
-
// ✅ Explicitly typed 'action'
|
|
23
|
-
const executeAction = async (action: ActionDefinition, context: any) => {
|
|
24
|
-
console.log(`[Engine] Executing: ${action.type}`, action);
|
|
25
|
-
|
|
26
|
-
switch (action.type) {
|
|
27
|
-
case 'send_notification':
|
|
28
|
-
addToast(action.config.message || 'Notification Sent', 'info');
|
|
29
|
-
break;
|
|
30
|
-
|
|
31
|
-
case 'update_resource':
|
|
32
|
-
// In a real app, this would call the API
|
|
33
|
-
addToast(`Updating Resource: ${JSON.stringify(action.config)}`, 'success');
|
|
34
|
-
break;
|
|
35
|
-
|
|
36
|
-
case 'agent_task':
|
|
37
|
-
// Simulate AI Agent processing
|
|
38
|
-
console.log(`[AI Agent] Thinking about: "${action.config.prompt}"...`);
|
|
39
|
-
// ✅ FIX: 'loading' is not a valid ToastType. Using 'info' instead.
|
|
40
|
-
addToast('AI Agent Analyzing...', 'info');
|
|
41
|
-
|
|
42
|
-
setTimeout(() => {
|
|
43
|
-
// Mock response based on prompt context
|
|
44
|
-
const response = action.config.prompt?.includes('health')
|
|
45
|
-
? "System Operating Normally."
|
|
46
|
-
: "Optimization Recommended.";
|
|
47
|
-
|
|
48
|
-
addToast(`🤖 Agent: "${response}"`, 'success', 5000);
|
|
49
|
-
}, 1500);
|
|
50
|
-
break;
|
|
51
|
-
|
|
52
|
-
case 'navigate':
|
|
53
|
-
window.location.href = action.config.path;
|
|
54
|
-
break;
|
|
55
|
-
|
|
56
|
-
default:
|
|
57
|
-
console.warn('Unknown action type:', action.type);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// 3. Watch Signals & Trigger Workflows
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (!appManifest.domain?.workflows) return;
|
|
64
|
-
|
|
65
|
-
// ✅ Explicitly typed 'flow'
|
|
66
|
-
appManifest.domain.workflows.forEach((flow: WorkflowDefinition) => {
|
|
67
|
-
if (!flow.active) return;
|
|
68
|
-
|
|
69
|
-
// Check Trigger: Signal Change
|
|
70
|
-
if (flow.trigger.type === 'signal_change') {
|
|
71
|
-
const signalId = flow.trigger.config.signalId;
|
|
72
|
-
const condition = flow.trigger.config.condition; // e.g., "> 80"
|
|
73
|
-
|
|
74
|
-
// @ts-ignore
|
|
75
|
-
const signal = signals[signalId];
|
|
76
|
-
|
|
77
|
-
if (signal) {
|
|
78
|
-
const val = typeof signal === 'object' ? signal.value : signal;
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
// Check if condition is met (e.g. "50 > 80")
|
|
82
|
-
const isMet = checkCondition(val, condition);
|
|
83
|
-
|
|
84
|
-
if (isMet) {
|
|
85
|
-
// Throttling logic would go here to prevent spam
|
|
86
|
-
console.log(`[Engine] Trigger Fired: ${flow.name}`);
|
|
87
|
-
flow.actions.forEach(action => executeAction(action, { signal: val }));
|
|
88
|
-
}
|
|
89
|
-
} catch (e) {
|
|
90
|
-
// Ignore parse errors
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}, [signals, addToast]);
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
// Exposed for manual triggering (e.g. Buttons)
|
|
99
|
-
triggerWorkflow: (workflowId: string) => {
|
|
100
|
-
const flow = appManifest.domain?.workflows?.find(w => w.id === workflowId);
|
|
101
|
-
if (flow) {
|
|
102
|
-
flow.actions.forEach(action => executeAction(action, { manual: true }));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// --- HELPER: Safe Condition Checker ---
|
|
109
|
-
const checkCondition = (value: number, condition: string): boolean => {
|
|
110
|
-
if (!condition) return false;
|
|
111
|
-
const parts = condition.trim().split(' ');
|
|
112
|
-
const operator = parts[0];
|
|
113
|
-
const target = parseFloat(parts[1]);
|
|
114
|
-
|
|
115
|
-
switch (operator) {
|
|
116
|
-
case '>': return value > target;
|
|
117
|
-
case '<': return value < target;
|
|
118
|
-
case '>=': return value >= target;
|
|
119
|
-
case '<=': return value <= target;
|
|
120
|
-
case '==': return value === target;
|
|
121
|
-
default: return false;
|
|
122
|
-
}
|
|
123
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Outlet, useLocation } from 'react-router-dom';
|
|
3
|
-
import { PageWithSideNav } from '../components/PageWithSideNav';
|
|
4
|
-
import { useSitemap } from '../contexts/SitemapContext'; // Import the custom hook
|
|
5
|
-
|
|
6
|
-
const DataLayout: React.FC = () => {
|
|
7
|
-
const location = useLocation();
|
|
8
|
-
const sitemap = useSitemap(); // Consume the sitemap from context
|
|
9
|
-
const isDocsTemplate = location.pathname.startsWith('/docs');
|
|
10
|
-
|
|
11
|
-
if (isDocsTemplate) {
|
|
12
|
-
const dataSitemapSection = sitemap.find(item => item.id === 'data');
|
|
13
|
-
|
|
14
|
-
const navItems = (dataSitemapSection?.children || []).map(child => ({
|
|
15
|
-
label: child.title,
|
|
16
|
-
href: child.path,
|
|
17
|
-
icon: child.icon,
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<PageWithSideNav
|
|
22
|
-
sideNavHeader={
|
|
23
|
-
<h2 className="text-lg font-semibold tracking-tight mb-2">
|
|
24
|
-
{dataSitemapSection?.title || 'Data'}
|
|
25
|
-
</h2>
|
|
26
|
-
}
|
|
27
|
-
navItems={navItems}
|
|
28
|
-
>
|
|
29
|
-
<Outlet />
|
|
30
|
-
</PageWithSideNav>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return <Outlet />;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export default DataLayout;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Outlet } from 'react-router-dom';
|
|
3
|
-
import { PageWithSideNav } from '../components/PageWithSideNav';
|
|
4
|
-
import type { NavItem } from '../components/LocalSideNav';
|
|
5
|
-
|
|
6
|
-
interface SideNavLayoutProps {
|
|
7
|
-
navItems: NavItem[];
|
|
8
|
-
sideNavHeader?: React.ReactNode;
|
|
9
|
-
contentWidth?: 'fixed' | 'full';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const SideNavLayout: React.FC<SideNavLayoutProps> = ({
|
|
13
|
-
navItems,
|
|
14
|
-
sideNavHeader,
|
|
15
|
-
contentWidth = 'fixed',
|
|
16
|
-
}) => {
|
|
17
|
-
return (
|
|
18
|
-
<PageWithSideNav
|
|
19
|
-
navItems={navItems}
|
|
20
|
-
sideNavHeader={sideNavHeader}
|
|
21
|
-
contentWidth={contentWidth}
|
|
22
|
-
>
|
|
23
|
-
<Outlet />
|
|
24
|
-
</PageWithSideNav>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export default SideNavLayout;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useLocation } from 'react-router-dom';
|
|
3
|
-
import { PageHeader, Alert } from '@ramme-io/ui';
|
|
4
|
-
import { appManifest } from '../config/app.manifest';
|
|
5
|
-
import { DynamicBlock } from '../components/DynamicBlock';
|
|
6
|
-
|
|
7
|
-
const Dashboard: React.FC = () => {
|
|
8
|
-
const location = useLocation();
|
|
9
|
-
|
|
10
|
-
// 1. Determine current slug from URL
|
|
11
|
-
const pathParts = location.pathname.split('/').filter(Boolean);
|
|
12
|
-
// If path is root or /dashboard, slug is 'dashboard', else take the last part
|
|
13
|
-
const currentSlug = pathParts.length > 1 ? pathParts[pathParts.length - 1] : 'dashboard';
|
|
14
|
-
|
|
15
|
-
// 2. Find the matching Page Definition
|
|
16
|
-
const pageDef = appManifest.pages?.find(p => p.slug === currentSlug)
|
|
17
|
-
|| appManifest.pages?.[0];
|
|
18
|
-
|
|
19
|
-
if (!pageDef) {
|
|
20
|
-
return (
|
|
21
|
-
<div className="p-8">
|
|
22
|
-
<Alert variant="warning" title="Page Not Found">
|
|
23
|
-
Could not find a definition for slug: <code>{currentSlug}</code>
|
|
24
|
-
</Alert>
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div className="space-y-8 relative">
|
|
31
|
-
<PageHeader
|
|
32
|
-
title={pageDef.title}
|
|
33
|
-
description={pageDef.description}
|
|
34
|
-
/>
|
|
35
|
-
|
|
36
|
-
{pageDef.sections.map((section) => (
|
|
37
|
-
<div key={section.id} className="relative">
|
|
38
|
-
{section.title && (
|
|
39
|
-
<h3 className="text-lg font-semibold mb-4 text-foreground">
|
|
40
|
-
{section.title}
|
|
41
|
-
</h3>
|
|
42
|
-
)}
|
|
43
|
-
|
|
44
|
-
<div
|
|
45
|
-
className="grid gap-6"
|
|
46
|
-
style={{
|
|
47
|
-
gridTemplateColumns: `repeat(${section.layout?.columns || 3}, minmax(300px, 1fr))`
|
|
48
|
-
}}
|
|
49
|
-
>
|
|
50
|
-
{section.blocks.map((block) => (
|
|
51
|
-
<DynamicBlock key={block.id} block={block} />
|
|
52
|
-
))}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
))}
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export default Dashboard;
|