@ramme-io/create-app 1.1.3 → 1.1.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 +1 -1
- package/template/pkg.json +1 -0
- package/template/src/config/dashboard.layout.ts +98 -0
- package/template/src/core/component-registry.tsx +50 -0
- package/template/src/generated/hooks.ts +47 -0
- package/template/src/pages/Dashboard.tsx +48 -134
- package/template/src/types/signal.ts +1 -0
- package/template/package-lock.json +0 -78
package/package.json
CHANGED
package/template/pkg.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "section_metrics",
|
|
70
|
+
title: "Business Overview",
|
|
71
|
+
type: "grid",
|
|
72
|
+
columns: 4,
|
|
73
|
+
items: [
|
|
74
|
+
{
|
|
75
|
+
id: "stat_1",
|
|
76
|
+
component: "StatCard",
|
|
77
|
+
props: {
|
|
78
|
+
title: "Total Users",
|
|
79
|
+
value: "1,234",
|
|
80
|
+
icon: "users",
|
|
81
|
+
changeText: "+10% from last month",
|
|
82
|
+
changeDirection: "positive"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "stat_2",
|
|
87
|
+
component: "StatCard",
|
|
88
|
+
props: {
|
|
89
|
+
title: "Sales Today",
|
|
90
|
+
value: "$5,678",
|
|
91
|
+
icon: "dollar-sign",
|
|
92
|
+
changeText: "+5% from yesterday",
|
|
93
|
+
changeDirection: "positive"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
];
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
} from '@ramme-io/ui';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The Registry maps the "string" name of a component (from the JSON manifest)
|
|
16
|
+
* to the actual React component implementation.
|
|
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
|
+
DataTable,
|
|
28
|
+
|
|
29
|
+
// Layout & Feedback
|
|
30
|
+
Card,
|
|
31
|
+
Alert,
|
|
32
|
+
EmptyState,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Helper to safely resolve a component.
|
|
37
|
+
* Returns a fallback if the component name is unknown.
|
|
38
|
+
*/
|
|
39
|
+
export const getComponent = (name: string) => {
|
|
40
|
+
const Component = COMPONENT_REGISTRY[name];
|
|
41
|
+
if (!Component) {
|
|
42
|
+
console.warn(`[Registry] Unknown component type: "${name}"`);
|
|
43
|
+
return () => (
|
|
44
|
+
<Alert variant="warning" title="Unknown Component">
|
|
45
|
+
The system tried to render <code>{name}</code> but it was not found in the registry.
|
|
46
|
+
</Alert>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return Component;
|
|
50
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file hooks.ts
|
|
3
|
+
* @generated by @ramme-io/builder
|
|
4
|
+
* @description
|
|
5
|
+
* This file is AUTO-GENERATED. Do not edit it directly.
|
|
6
|
+
* It defines the "State Machine" for the application, initializing
|
|
7
|
+
* all necessary signals based on the project manifest.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useSignal } from '../hooks/useSignal';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initializes all global signals defined in the project.
|
|
14
|
+
* This hook is called once by the Dashboard (or App root).
|
|
15
|
+
*/
|
|
16
|
+
export const useGeneratedSignals = () => {
|
|
17
|
+
// 1. Living Room AC (Thermostat)
|
|
18
|
+
const living_room_ac = useSignal('living_room_ac', {
|
|
19
|
+
initialValue: 72,
|
|
20
|
+
min: 68,
|
|
21
|
+
max: 76,
|
|
22
|
+
unit: '°F'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 2. Air Quality Sensor
|
|
26
|
+
const living_room_hum = useSignal('living_room_hum', {
|
|
27
|
+
initialValue: 45,
|
|
28
|
+
min: 40,
|
|
29
|
+
max: 60,
|
|
30
|
+
unit: '%'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 3. Main Server CPU Load
|
|
34
|
+
const server_01 = useSignal('server_01', {
|
|
35
|
+
initialValue: 42,
|
|
36
|
+
min: 10,
|
|
37
|
+
max: 95,
|
|
38
|
+
unit: '%'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Return a dictionary for easy O(1) lookup by signalId
|
|
42
|
+
return {
|
|
43
|
+
living_room_ac,
|
|
44
|
+
living_room_hum,
|
|
45
|
+
server_01,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -1,43 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
BarChart,
|
|
4
|
-
LineChart,
|
|
5
|
-
Card,
|
|
6
|
-
PageHeader,
|
|
7
|
-
StatCard,
|
|
8
|
-
DataTable,
|
|
9
|
-
useDataFetch,
|
|
10
|
-
DeviceCard, // <-- NEW: Import DeviceCard
|
|
11
|
-
type ColDef,
|
|
12
|
-
type ValueFormatterParams,
|
|
13
|
-
} from '@ramme-io/ui';
|
|
14
|
-
import { useSignal } from '../hooks/useSignal'; // <-- NEW: Import the Engine
|
|
15
|
-
import { mockTableData, mockChartData } from '../data/mockData';
|
|
2
|
+
import { PageHeader } from '@ramme-io/ui';
|
|
16
3
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// 2. New Logic (IoT Engine)
|
|
22
|
-
const temp = useSignal('living_room_ac', { initialValue: 72, min: 68, max: 76, unit: '°F' });
|
|
23
|
-
const hum = useSignal('living_room_hum', { initialValue: 45, min: 40, max: 60, unit: '%' });
|
|
24
|
-
const cpu = useSignal('server_01', { initialValue: 42, min: 10, max: 95, unit: '%' });
|
|
4
|
+
// --- IMPORTS ---
|
|
5
|
+
import { getComponent } from '../core/component-registry';
|
|
6
|
+
import { dashboardLayout } from '../config/dashboard.layout';
|
|
7
|
+
import { useGeneratedSignals } from '../generated/hooks'; // <-- The New Engine
|
|
25
8
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
field: 'price',
|
|
32
|
-
headerName: 'Price',
|
|
33
|
-
valueFormatter: (p: ValueFormatterParams) => '$' + p.value.toLocaleString(),
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
field: 'electric',
|
|
37
|
-
headerName: 'Electric',
|
|
38
|
-
cellRenderer: (params: any) => (params.value ? '⚡' : '⛽'),
|
|
39
|
-
},
|
|
40
|
-
];
|
|
9
|
+
const Dashboard: React.FC = () => {
|
|
10
|
+
// 1. Initialize the "Brain"
|
|
11
|
+
// This single line replaces all the hardcoded hooks.
|
|
12
|
+
const signals = useGeneratedSignals();
|
|
41
13
|
|
|
42
14
|
return (
|
|
43
15
|
<div className="space-y-8">
|
|
@@ -46,106 +18,48 @@ const Dashboard: React.FC = () => {
|
|
|
46
18
|
description="Real-time device monitoring and business analytics."
|
|
47
19
|
/>
|
|
48
20
|
|
|
49
|
-
{/* ---
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/>
|
|
69
|
-
<DeviceCard
|
|
70
|
-
title="Main Server"
|
|
71
|
-
description="192.168.1.42"
|
|
72
|
-
icon="server"
|
|
73
|
-
status={cpu.value > 90 ? 'error' : 'online'}
|
|
74
|
-
value={`${cpu.value}${cpu.unit}`}
|
|
75
|
-
trend="CPU Load"
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
21
|
+
{/* --- DYNAMIC RUNTIME ENGINE --- */}
|
|
22
|
+
{dashboardLayout.map((section) => (
|
|
23
|
+
<div key={section.id}>
|
|
24
|
+
<h3 className="text-lg font-semibold mb-4 text-foreground">
|
|
25
|
+
{section.title}
|
|
26
|
+
</h3>
|
|
27
|
+
|
|
28
|
+
<div
|
|
29
|
+
className="grid gap-6"
|
|
30
|
+
// Use the 'columns' prop from the manifest to control density
|
|
31
|
+
style={{
|
|
32
|
+
gridTemplateColumns: `repeat(${section.columns || 3}, minmax(300px, 1fr))`
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{section.items.map((item) => {
|
|
36
|
+
const Component = getComponent(item.component);
|
|
37
|
+
|
|
38
|
+
// Prepare Props
|
|
39
|
+
let dynamicProps = { ...item.props };
|
|
79
40
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
icon="dollar-sign"
|
|
95
|
-
changeText="+5% from yesterday"
|
|
96
|
-
changeDirection="positive"
|
|
97
|
-
/>
|
|
98
|
-
<StatCard
|
|
99
|
-
title="New Orders"
|
|
100
|
-
value="89"
|
|
101
|
-
icon="shopping-cart"
|
|
102
|
-
changeText="-2 since last hour"
|
|
103
|
-
changeDirection="negative"
|
|
104
|
-
/>
|
|
105
|
-
<StatCard
|
|
106
|
-
title="Active Projects"
|
|
107
|
-
value="12"
|
|
108
|
-
icon="briefcase"
|
|
109
|
-
footerText="3 nearing completion"
|
|
110
|
-
/>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
41
|
+
// Inject Signal Data
|
|
42
|
+
if (item.signalId && signals[item.signalId as keyof typeof signals]) {
|
|
43
|
+
const sig = signals[item.signalId as keyof typeof signals];
|
|
44
|
+
|
|
45
|
+
// Standard Value Injection
|
|
46
|
+
dynamicProps.value = `${sig.value}${sig.unit}`;
|
|
47
|
+
|
|
48
|
+
// Auto-Status Logic (The "Smart" Layer)
|
|
49
|
+
// If a signal exceeds its defined max, auto-flag it as error
|
|
50
|
+
if (sig.value > (sig.max || 100)) {
|
|
51
|
+
dynamicProps.status = 'error';
|
|
52
|
+
dynamicProps.trend = 'CRITICAL';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
113
55
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<div className="h-[350px] w-full">
|
|
119
|
-
<BarChart
|
|
120
|
-
data={mockChartData}
|
|
121
|
-
dataKeyX="name"
|
|
122
|
-
barKeys={['pv', 'uv']}
|
|
123
|
-
/>
|
|
124
|
-
</div>
|
|
125
|
-
</Card>
|
|
126
|
-
<Card className="p-6">
|
|
127
|
-
<h3 className="text-lg font-semibold mb-4 text-foreground">User Growth</h3>
|
|
128
|
-
<div className="h-[350px] w-full">
|
|
129
|
-
<LineChart
|
|
130
|
-
data={mockChartData}
|
|
131
|
-
dataKeyX="name"
|
|
132
|
-
lineKeys={['uv', 'pv']}
|
|
133
|
-
/>
|
|
56
|
+
return (
|
|
57
|
+
<Component key={item.id} {...dynamicProps} />
|
|
58
|
+
);
|
|
59
|
+
})}
|
|
134
60
|
</div>
|
|
135
|
-
</
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{/* --- SECTION 4: DATA TABLE --- */}
|
|
139
|
-
<Card className="p-6">
|
|
140
|
-
<h3 className="text-2xl font-semibold mb-4 text-foreground">Recent Vehicle Data</h3>
|
|
141
|
-
{fetchedTableData && (
|
|
142
|
-
<DataTable
|
|
143
|
-
rowData={fetchedTableData}
|
|
144
|
-
columnDefs={columnDefs}
|
|
145
|
-
height="400px"
|
|
146
|
-
/>
|
|
147
|
-
)}
|
|
148
|
-
</Card>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
149
63
|
</div>
|
|
150
64
|
);
|
|
151
65
|
};
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
export type SignalStatus = 'fresh' | 'stale' | 'disconnected' | 'error';
|
|
11
11
|
|
|
12
12
|
export interface Signal<T = any> {
|
|
13
|
+
max: number;
|
|
13
14
|
id: string; // The unique ID (e.g., "temp_01")
|
|
14
15
|
value: T; // The actual data (e.g., 24.5)
|
|
15
16
|
unit?: string; // Optional unit (e.g., "°C")
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "template",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"lockfileVersion": 3,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"packages": {
|
|
7
|
-
"": {
|
|
8
|
-
"dependencies": {
|
|
9
|
-
"zod": "^4.1.13"
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"../../ramme-ui": {
|
|
13
|
-
"name": "@ramme-io/ui",
|
|
14
|
-
"version": "1.1.0",
|
|
15
|
-
"extraneous": true,
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@radix-ui/react-slot": "^1.2.3",
|
|
18
|
-
"@tippyjs/react": "^4.2.6",
|
|
19
|
-
"ag-grid-community": "^31.3.1",
|
|
20
|
-
"ag-grid-enterprise": "^31.3.1",
|
|
21
|
-
"ag-grid-react": "^31.3.1",
|
|
22
|
-
"clsx": "^2.1.1",
|
|
23
|
-
"date-fns": "^3.6.0",
|
|
24
|
-
"lucide-react": "^0.537.0",
|
|
25
|
-
"react-datepicker": "^7.3.0",
|
|
26
|
-
"react-select": "^5.8.0",
|
|
27
|
-
"react-syntax-highlighter": "^15.6.6",
|
|
28
|
-
"recharts": "^2.12.7",
|
|
29
|
-
"tailwind-merge": "^3.3.1",
|
|
30
|
-
"tippy.js": "^6.3.7",
|
|
31
|
-
"zod": "^4.1.13"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@storybook/addon-essentials": "8.2.2",
|
|
35
|
-
"@storybook/addon-interactions": "8.2.2",
|
|
36
|
-
"@storybook/addon-links": "8.2.2",
|
|
37
|
-
"@storybook/addon-themes": "8.2.2",
|
|
38
|
-
"@storybook/blocks": "8.2.2",
|
|
39
|
-
"@storybook/react": "8.2.2",
|
|
40
|
-
"@storybook/react-vite": "8.2.2",
|
|
41
|
-
"@storybook/testing-library": "0.2.2",
|
|
42
|
-
"@types/doctrine": "^0.0.9",
|
|
43
|
-
"@types/node": "^24.2.1",
|
|
44
|
-
"@types/react": "^18.3.23",
|
|
45
|
-
"@types/react-dom": "^18.3.7",
|
|
46
|
-
"@types/react-syntax-highlighter": "^15.5.13",
|
|
47
|
-
"@vitejs/plugin-react": "^4.3.1",
|
|
48
|
-
"autoprefixer": "^10.4.19",
|
|
49
|
-
"class-variance-authority": "^0.7.1",
|
|
50
|
-
"doctrine": "^3.0.0",
|
|
51
|
-
"postcss": "^8.4.38",
|
|
52
|
-
"react": "^18.2.0",
|
|
53
|
-
"react-dom": "^18.2.0",
|
|
54
|
-
"sass": "^1.77.8",
|
|
55
|
-
"storybook": "8.2.2",
|
|
56
|
-
"tailwindcss": "^3.4.4",
|
|
57
|
-
"typescript": "^5.9.2",
|
|
58
|
-
"vite": "^5.3.1",
|
|
59
|
-
"vite-plugin-dts": "^3.9.1"
|
|
60
|
-
},
|
|
61
|
-
"peerDependencies": {
|
|
62
|
-
"react": ">=18.2.0",
|
|
63
|
-
"react-dom": ">=18.2.0",
|
|
64
|
-
"react-router-dom": "^6.25.1",
|
|
65
|
-
"tailwindcss": ">=3.0.0"
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"node_modules/zod": {
|
|
69
|
-
"version": "4.1.13",
|
|
70
|
-
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
|
|
71
|
-
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
|
72
|
-
"license": "MIT",
|
|
73
|
-
"funding": {
|
|
74
|
-
"url": "https://github.com/sponsors/colinhacks"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|