@ramme-io/create-app 1.1.9 → 1.2.1
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 +4 -3
- package/template/pkg.json +16 -13
- package/template/src/App.tsx +17 -11
- package/template/src/blocks/SmartTable.tsx +224 -0
- package/template/src/components/AutoForm.tsx +128 -0
- package/template/src/components/DynamicBlock.tsx +37 -31
- package/template/src/components/dev/GhostOverlay.tsx +26 -59
- package/template/src/config/app.manifest.ts +48 -48
- package/template/src/core/component-registry.tsx +21 -41
- package/template/src/core/data-seeder.ts +35 -0
- package/template/src/data/mockData.ts +163 -34
- package/template/src/generated/hooks.ts +64 -58
- package/template/src/hooks/useDataQuery.ts +84 -0
- package/template/src/hooks/useSignal.ts +43 -33
- package/template/src/hooks/useWorkflowEngine.ts +123 -0
- package/template/src/pages/Dashboard.tsx +43 -90
- package/template/src/pages/DynamicPage.tsx +54 -22
- package/template/src/pages/Welcome.tsx +162 -0
- package/template/src/templates/dashboard/DashboardLayout.tsx +2 -0
- package/template/src/templates/dashboard/dashboard.sitemap.ts +33 -72
- package/template/src/types/schema.ts +117 -30
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { PageHeader, Alert } from '@ramme-io/ui';
|
|
2
|
+
import { PageHeader, Alert, Button, Icon } from '@ramme-io/ui';
|
|
3
3
|
import { appManifest } from '../config/app.manifest';
|
|
4
|
-
import { getComponent } from '../core/component-registry';
|
|
5
4
|
// Reuse the GhostOverlay for structure visualization
|
|
6
5
|
import { GhostOverlay } from '../components/dev/GhostOverlay';
|
|
7
|
-
// Import the
|
|
6
|
+
// Import the Worker Bee
|
|
8
7
|
import { DynamicBlock } from '../components/DynamicBlock';
|
|
8
|
+
// Import the Hook to control the overlay
|
|
9
|
+
import { useDevTools } from '../hooks/useDevTools';
|
|
9
10
|
|
|
10
11
|
interface DynamicPageProps {
|
|
11
12
|
pageId: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
|
|
15
|
-
// 1.
|
|
16
|
-
const
|
|
15
|
+
export const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
|
|
16
|
+
// 1. Initialize DevTools
|
|
17
|
+
const { isGhostMode, toggleGhostMode } = useDevTools();
|
|
18
|
+
|
|
19
|
+
// 2. Look up the page definition
|
|
20
|
+
const pageConfig = (appManifest as any).pages?.find((p: any) => p.id === pageId);
|
|
17
21
|
|
|
18
22
|
if (!pageConfig) {
|
|
19
23
|
return (
|
|
20
24
|
<div className="p-8">
|
|
21
|
-
<Alert variant="danger" title="
|
|
25
|
+
<Alert variant="danger" title="Page Not Found">
|
|
22
26
|
The manifest does not contain a page with ID: <code>{pageId}</code>.
|
|
23
27
|
</Alert>
|
|
24
28
|
</div>
|
|
@@ -26,33 +30,61 @@ const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
return (
|
|
29
|
-
<div className="space-y-8 fade-in">
|
|
33
|
+
<div className="space-y-8 fade-in p-6">
|
|
30
34
|
<PageHeader
|
|
31
35
|
title={pageConfig.title}
|
|
32
|
-
description={pageConfig.description}
|
|
36
|
+
description={pageConfig.description}
|
|
37
|
+
actions={
|
|
38
|
+
<Button
|
|
39
|
+
variant={isGhostMode ? 'accent' : 'outline'}
|
|
40
|
+
size="sm"
|
|
41
|
+
onClick={toggleGhostMode}
|
|
42
|
+
title="Toggle Ghost Mode (Ctrl+Shift+G)"
|
|
43
|
+
>
|
|
44
|
+
<Icon name={isGhostMode ? 'eye' : 'eye-off'} className="mr-2" />
|
|
45
|
+
{isGhostMode ? 'Ghost Mode: ON' : 'Dev Tools'}
|
|
46
|
+
</Button>
|
|
47
|
+
}
|
|
33
48
|
/>
|
|
34
49
|
|
|
35
|
-
{/* Render Sections */}
|
|
36
|
-
{pageConfig.sections.map((section:
|
|
50
|
+
{/* 4. Render Sections */}
|
|
51
|
+
{pageConfig.sections.map((section: any) => (
|
|
37
52
|
<div key={section.id} className="space-y-4">
|
|
38
53
|
{section.title && <h3 className="text-xl font-semibold">{section.title}</h3>}
|
|
39
54
|
|
|
40
55
|
<div
|
|
41
56
|
className="grid gap-6"
|
|
42
57
|
style={{
|
|
43
|
-
|
|
58
|
+
// ⚡️ IMPROVEMENT: Default to 1 column (Full Width) instead of 3.
|
|
59
|
+
// This fixes the "narrow table" issue immediately.
|
|
60
|
+
gridTemplateColumns: `repeat(${section.layout?.columns || 1}, minmax(0, 1fr))`
|
|
44
61
|
}}
|
|
45
62
|
>
|
|
46
|
-
{section.blocks.map(block =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
{section.blocks.map((block: any) => {
|
|
64
|
+
// ⚡️ IMPROVEMENT: Calculate Spanning
|
|
65
|
+
// Allows a block to stretch across multiple columns if needed.
|
|
66
|
+
const colSpan = block.layout?.colSpan || 1;
|
|
67
|
+
const rowSpan = block.layout?.rowSpan || 1;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
key={block.id}
|
|
72
|
+
style={{
|
|
73
|
+
gridColumn: `span ${colSpan}`,
|
|
74
|
+
gridRow: `span ${rowSpan}`
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<GhostOverlay
|
|
78
|
+
isActive={isGhostMode}
|
|
79
|
+
componentId={block.id}
|
|
80
|
+
componentType={block.type}
|
|
81
|
+
signalId={block.props.signalId}
|
|
82
|
+
>
|
|
83
|
+
<DynamicBlock block={block} />
|
|
84
|
+
</GhostOverlay>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
56
88
|
</div>
|
|
57
89
|
</div>
|
|
58
90
|
))}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
PageHeader,
|
|
5
|
+
Card,
|
|
6
|
+
Button,
|
|
7
|
+
Icon,
|
|
8
|
+
Badge,
|
|
9
|
+
SectionHeader
|
|
10
|
+
} from '@ramme-io/ui';
|
|
11
|
+
|
|
12
|
+
const Welcome: React.FC = () => {
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
|
17
|
+
|
|
18
|
+
{/* HERO */}
|
|
19
|
+
<div className="relative overflow-hidden rounded-xl bg-gradient-to-r from-primary to-violet-600 text-primary-foreground p-8 md:p-12">
|
|
20
|
+
<div className="relative z-10 max-w-2xl">
|
|
21
|
+
<Badge variant="secondary" className="mb-4 bg-white/20 text-white border-none backdrop-blur-sm">
|
|
22
|
+
v1.2.0 Starter Kit
|
|
23
|
+
</Badge>
|
|
24
|
+
<h1 className="text-4xl md:text-5xl font-extrabold mb-4 tracking-tight">
|
|
25
|
+
Your Ramme App is Ready.
|
|
26
|
+
</h1>
|
|
27
|
+
<p className="text-lg md:text-xl text-primary-foreground/90 mb-8 leading-relaxed">
|
|
28
|
+
You have successfully scaffolded a production-ready prototype environment.
|
|
29
|
+
This kit comes pre-wired with Authentication, Mock Data, and the A.D.A.P.T. architecture.
|
|
30
|
+
</p>
|
|
31
|
+
<div className="flex flex-wrap gap-4">
|
|
32
|
+
<Button
|
|
33
|
+
size="lg"
|
|
34
|
+
variant="secondary"
|
|
35
|
+
className="font-semibold"
|
|
36
|
+
iconLeft="layout-dashboard"
|
|
37
|
+
onClick={() => navigate('/dashboard/app')}
|
|
38
|
+
>
|
|
39
|
+
Open Live Dashboard
|
|
40
|
+
</Button>
|
|
41
|
+
<Button
|
|
42
|
+
size="lg"
|
|
43
|
+
variant="outline"
|
|
44
|
+
className="bg-transparent border-white/30 text-white hover:bg-white/10 hover:text-white"
|
|
45
|
+
iconLeft="book-open"
|
|
46
|
+
onClick={() => navigate('/docs')}
|
|
47
|
+
>
|
|
48
|
+
Read Documentation
|
|
49
|
+
</Button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Decorative Background Icon */}
|
|
54
|
+
<div className="absolute -right-10 -bottom-10 opacity-10 rotate-12">
|
|
55
|
+
<Icon name="box" size={300} />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
60
|
+
|
|
61
|
+
{/* SECTION 1: ARCHITECTURE */}
|
|
62
|
+
<div className="space-y-4">
|
|
63
|
+
<SectionHeader title="Project Architecture" />
|
|
64
|
+
<div className="grid gap-4">
|
|
65
|
+
<Card className="p-4 flex gap-4 hover:border-primary/50 transition-colors cursor-default">
|
|
66
|
+
<div className="p-3 rounded-lg bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 h-fit">
|
|
67
|
+
<Icon name="database" size={24} />
|
|
68
|
+
</div>
|
|
69
|
+
<div>
|
|
70
|
+
<h3 className="font-semibold text-foreground">Data Lake</h3>
|
|
71
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
72
|
+
Mock data is seeded into <code>localStorage</code> on boot.
|
|
73
|
+
Edit <code>src/data/mockData.ts</code> to change the schema.
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</Card>
|
|
77
|
+
|
|
78
|
+
<Card className="p-4 flex gap-4 hover:border-primary/50 transition-colors cursor-default">
|
|
79
|
+
<div className="p-3 rounded-lg bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400 h-fit">
|
|
80
|
+
<Icon name="git-branch" size={24} />
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<h3 className="font-semibold text-foreground">Logic Engine</h3>
|
|
84
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
85
|
+
Workflows and signals are processed in real-time by
|
|
86
|
+
<code>useWorkflowEngine.ts</code>. Supports MQTT and simulated sensors.
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
</Card>
|
|
90
|
+
|
|
91
|
+
<Card className="p-4 flex gap-4 hover:border-primary/50 transition-colors cursor-default">
|
|
92
|
+
<div className="p-3 rounded-lg bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400 h-fit">
|
|
93
|
+
<Icon name="layout" size={24} />
|
|
94
|
+
</div>
|
|
95
|
+
<div>
|
|
96
|
+
<h3 className="font-semibold text-foreground">Dynamic Routing</h3>
|
|
97
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
98
|
+
Pages are generated from <code>app.manifest.ts</code>.
|
|
99
|
+
Visual blocks connect automatically to data sources.
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
</Card>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* SECTION 2: RESOURCES */}
|
|
107
|
+
<div className="space-y-4">
|
|
108
|
+
<SectionHeader title="Developer Resources" />
|
|
109
|
+
|
|
110
|
+
<Card className="p-6 space-y-6">
|
|
111
|
+
<div>
|
|
112
|
+
<h4 className="font-semibold flex items-center gap-2 mb-2">
|
|
113
|
+
<Icon name="palette" size={16} className="text-muted-foreground" />
|
|
114
|
+
Component Library
|
|
115
|
+
</h4>
|
|
116
|
+
<p className="text-sm text-muted-foreground mb-3">
|
|
117
|
+
Browse the full suite of accessible UI components available in this project.
|
|
118
|
+
</p>
|
|
119
|
+
<Button variant="outline" size="sm" onClick={() => navigate('/styleguide')}>
|
|
120
|
+
View Style Guide
|
|
121
|
+
</Button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className="border-t border-border pt-6">
|
|
125
|
+
<h4 className="font-semibold flex items-center gap-2 mb-2">
|
|
126
|
+
<Icon name="settings" size={16} className="text-muted-foreground" />
|
|
127
|
+
Configuration
|
|
128
|
+
</h4>
|
|
129
|
+
<p className="text-sm text-muted-foreground mb-3">
|
|
130
|
+
Manage global settings, user profile templates, and billing layouts.
|
|
131
|
+
</p>
|
|
132
|
+
<Button variant="outline" size="sm" onClick={() => navigate('/settings')}>
|
|
133
|
+
Open Settings
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div className="border-t border-border pt-6">
|
|
138
|
+
<h4 className="font-semibold flex items-center gap-2 mb-2">
|
|
139
|
+
<Icon name="github" size={16} className="text-muted-foreground" />
|
|
140
|
+
Community
|
|
141
|
+
</h4>
|
|
142
|
+
<p className="text-sm text-muted-foreground mb-3">
|
|
143
|
+
Need help? Check the docs or open an issue on GitHub.
|
|
144
|
+
</p>
|
|
145
|
+
<a
|
|
146
|
+
href="https://github.com/ramme-io/create-app"
|
|
147
|
+
target="_blank"
|
|
148
|
+
rel="noreferrer"
|
|
149
|
+
className="inline-flex"
|
|
150
|
+
>
|
|
151
|
+
<Button variant="ghost" size="sm">GitHub Repo →</Button>
|
|
152
|
+
</a>
|
|
153
|
+
</div>
|
|
154
|
+
</Card>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export default Welcome;
|
|
@@ -18,6 +18,7 @@ import { SitemapProvider } from '../../contexts/SitemapContext';
|
|
|
18
18
|
import PageTitleUpdater from '../../components/PageTitleUpdater';
|
|
19
19
|
import AppHeader from '../../components/AppHeader';
|
|
20
20
|
import { AIChatWidget } from '../../components/AIChatWidget'; // <-- NEW: Import Widget
|
|
21
|
+
import { useWorkflowEngine } from '../../hooks/useWorkflowEngine';
|
|
21
22
|
|
|
22
23
|
// NavLink wrapper - Correct
|
|
23
24
|
const SidebarNavLink = React.forwardRef<HTMLAnchorElement, any>(
|
|
@@ -87,6 +88,7 @@ const AppSidebarContent: React.FC = () => {
|
|
|
87
88
|
const DashboardLayout: React.FC = () => {
|
|
88
89
|
// 1. STATE: Track if the chat window is open
|
|
89
90
|
const [isChatOpen, setIsChatOpen] = useState(false);
|
|
91
|
+
useWorkflowEngine();
|
|
90
92
|
|
|
91
93
|
return (
|
|
92
94
|
<SitemapProvider value={dashboardSitemap}>
|
|
@@ -1,83 +1,44 @@
|
|
|
1
1
|
import { type SitemapEntry } from '../../core/sitemap-entry';
|
|
2
|
-
|
|
3
|
-
// Component Imports
|
|
2
|
+
import { appManifest } from '../../config/app.manifest';
|
|
4
3
|
import Dashboard from '../../pages/Dashboard';
|
|
5
4
|
import AiChat from '../../pages/AiChat';
|
|
6
|
-
import
|
|
7
|
-
import Styleguide from '../../pages/styleguide/Styleguide';
|
|
8
|
-
import DataLayout from '../../layouts/DataLayout';
|
|
9
|
-
|
|
10
|
-
// Style Guide Section Imports
|
|
11
|
-
import TemplatesSection from '../../pages/styleguide/sections/templates/TemplatesSection';
|
|
12
|
-
import LayoutSection from '../../pages/styleguide/sections/layout/LayoutSection';
|
|
13
|
-
import ThemingSection from '../../pages/styleguide/sections/theming/ThemingSection';
|
|
14
|
-
import NavigationSection from '../../pages/styleguide/sections/navigation/NavigationSection';
|
|
15
|
-
import TablesSection from '../../pages/styleguide/sections/tables/TablesSection';
|
|
16
|
-
import ChartsSection from '../../pages/styleguide/sections/charts/ChartsSection';
|
|
17
|
-
import ElementsSection from '../../pages/styleguide/sections/elements/ElementsSection';
|
|
18
|
-
import FormsSection from '../../pages/styleguide/sections/forms/FormsSection';
|
|
19
|
-
import FeedbackSection from '../../pages/styleguide/sections/feedback/FeedbackSection';
|
|
20
|
-
import UtilitiesSection from '../../pages/styleguide/sections/utilities/UtilitiesSection';
|
|
21
|
-
import ColorsSection from '../../pages/styleguide/sections/colors/ColorsSection';
|
|
22
|
-
import IconsSection from '../../pages/styleguide/sections/icons/IconsSection';
|
|
23
|
-
|
|
24
|
-
// --- ADD THIS IMPORT ---
|
|
25
|
-
import ItemSelectorPage from '../../pages/prototypes/ItemSelectorPage';
|
|
26
|
-
|
|
5
|
+
import Welcome from '../../pages/Welcome'; // ✅ Import the new page
|
|
27
6
|
|
|
28
7
|
export const dashboardSitemap: SitemapEntry[] = [
|
|
8
|
+
// ✅ 1. The New Landing Page
|
|
29
9
|
{
|
|
30
|
-
id: '
|
|
31
|
-
path: '',
|
|
32
|
-
title: '
|
|
33
|
-
icon: '
|
|
34
|
-
component:
|
|
10
|
+
id: 'welcome',
|
|
11
|
+
path: 'welcome',
|
|
12
|
+
title: 'Start Here',
|
|
13
|
+
icon: 'rocket',
|
|
14
|
+
component: Welcome,
|
|
35
15
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// A. Dynamic Pages from Manifest
|
|
19
|
+
if (appManifest.pages) {
|
|
20
|
+
appManifest.pages.forEach(page => {
|
|
21
|
+
const isDashboard = page.slug === 'dashboard';
|
|
22
|
+
|
|
23
|
+
dashboardSitemap.push({
|
|
24
|
+
id: page.id,
|
|
25
|
+
title: page.title,
|
|
26
|
+
// ✅ FIX: Map the main dashboard to 'app' instead of root ''
|
|
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
|
+
}
|
|
34
|
+
|
|
35
|
+
// B. Dynamic Modules
|
|
36
|
+
if (appManifest.modules?.includes('ai-chat')) {
|
|
37
|
+
dashboardSitemap.push({
|
|
46
38
|
id: 'ai-chat',
|
|
47
39
|
path: 'ai-chat',
|
|
48
|
-
title: 'AI
|
|
40
|
+
title: 'AI Assistant',
|
|
49
41
|
icon: 'bot',
|
|
50
42
|
component: AiChat,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
id: 'data',
|
|
54
|
-
path: 'data',
|
|
55
|
-
title: 'Data',
|
|
56
|
-
icon: 'database',
|
|
57
|
-
component: DataLayout,
|
|
58
|
-
children: [
|
|
59
|
-
{ id: 'users', path: 'users', title: 'Users', component: DataGridPage, icon: 'table' }
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: 'styleguide',
|
|
64
|
-
path: 'styleguide',
|
|
65
|
-
title: 'Style Guide',
|
|
66
|
-
icon: 'palette',
|
|
67
|
-
component: Styleguide,
|
|
68
|
-
children: [
|
|
69
|
-
{ id: 'templates', path: 'templates', title: 'Templates', component: TemplatesSection },
|
|
70
|
-
{ id: 'layout', path: 'layout', title: 'Layout', component: LayoutSection },
|
|
71
|
-
{ id: 'theming', path: 'theming', title: 'Theming', component: ThemingSection },
|
|
72
|
-
{ id: 'navigation', path: 'navigation', title: 'Navigation', component: NavigationSection },
|
|
73
|
-
{ id: 'tables', path: 'tables', title: 'Tables', component: TablesSection },
|
|
74
|
-
{ id: 'charts', path: 'charts', title: 'Charts', component: ChartsSection },
|
|
75
|
-
{ id: 'elements', path: 'elements', title: 'Elements', component: ElementsSection },
|
|
76
|
-
{ id: 'forms', path: 'forms', title: 'Forms', component: FormsSection },
|
|
77
|
-
{ id: 'feedback', path: 'feedback', title: 'Feedback', component: FeedbackSection },
|
|
78
|
-
{ id: 'utilities', path: 'utilities', title: 'Utilities', component: UtilitiesSection },
|
|
79
|
-
{ id: 'colors', path: 'colors', title: 'Colors', component: ColorsSection },
|
|
80
|
-
{ id: 'icons', path: 'icons', title: 'Icons', component: IconsSection },
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -1,67 +1,146 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
// ------------------------------------------------------------------
|
|
4
|
-
// 1.
|
|
4
|
+
// 1. DATA RESOURCE DEFINITIONS (SaaS Layer)
|
|
5
5
|
// ------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
export const FieldSchema = z.object({
|
|
8
|
+
key: z.string(),
|
|
9
|
+
label: z.string(),
|
|
10
|
+
type: z.enum(['text', 'number', 'currency', 'date', 'boolean', 'status', 'email', 'image', 'textarea']),
|
|
11
|
+
required: z.boolean().optional(),
|
|
12
|
+
description: z.string().optional(),
|
|
13
|
+
defaultValue: z.any().optional(),
|
|
14
|
+
});
|
|
15
|
+
export type FieldDefinition = z.infer<typeof FieldSchema>;
|
|
16
|
+
|
|
17
|
+
export const ResourceSchema = z.object({
|
|
18
|
+
id: z.string(),
|
|
19
|
+
name: z.string(),
|
|
20
|
+
fields: z.array(FieldSchema),
|
|
21
|
+
defaultView: z.enum(['table', 'grid', 'list']).optional(),
|
|
22
|
+
features: z.object({
|
|
23
|
+
searchable: z.boolean().optional(),
|
|
24
|
+
creatable: z.boolean().optional(),
|
|
25
|
+
editable: z.boolean().optional(),
|
|
26
|
+
deletable: z.boolean().optional(),
|
|
27
|
+
exportable: z.boolean().optional(),
|
|
28
|
+
}).optional(),
|
|
29
|
+
});
|
|
30
|
+
export type ResourceDefinition = z.infer<typeof ResourceSchema>;
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
// ------------------------------------------------------------------
|
|
34
|
+
// 2. SIGNAL & IOT DEFINITIONS (Physical Layer)
|
|
35
|
+
// ------------------------------------------------------------------
|
|
36
|
+
|
|
6
37
|
export const SignalSchema = z.object({
|
|
7
38
|
id: z.string().min(1, "Signal ID is required"),
|
|
8
39
|
label: z.string(),
|
|
9
40
|
description: z.string().optional(),
|
|
10
|
-
|
|
11
|
-
// Classification
|
|
12
41
|
kind: z.enum(['sensor', 'actuator', 'setpoint', 'metric', 'status', 'kpi']),
|
|
13
|
-
|
|
14
|
-
// Data Source Configuration
|
|
15
42
|
source: z.enum(['mock', 'mqtt', 'http', 'derived', 'local']),
|
|
16
43
|
|
|
17
|
-
//
|
|
18
|
-
topic: z.string().optional(),
|
|
19
|
-
endpoint: z.string().optional(),
|
|
20
|
-
jsonPath: z.string().optional(),
|
|
44
|
+
// Connectivity
|
|
45
|
+
topic: z.string().optional(),
|
|
46
|
+
endpoint: z.string().optional(),
|
|
47
|
+
jsonPath: z.string().optional(),
|
|
21
48
|
refreshRate: z.number().optional().default(1000),
|
|
22
49
|
|
|
23
|
-
//
|
|
50
|
+
// Values
|
|
24
51
|
defaultValue: z.any().optional(),
|
|
25
52
|
unit: z.string().optional(),
|
|
26
|
-
|
|
27
|
-
// Validation
|
|
28
53
|
min: z.number().optional(),
|
|
29
54
|
max: z.number().optional(),
|
|
30
55
|
});
|
|
31
|
-
|
|
32
56
|
export type SignalDefinition = z.infer<typeof SignalSchema>;
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
// ------------------------------------------------------------------
|
|
36
|
-
// 2. Entity Schema
|
|
37
|
-
// ------------------------------------------------------------------
|
|
38
58
|
export const EntitySchema = z.object({
|
|
39
59
|
id: z.string(),
|
|
40
60
|
name: z.string(),
|
|
41
61
|
description: z.string().optional(),
|
|
42
|
-
|
|
43
|
-
// Taxonomy
|
|
44
62
|
type: z.string(),
|
|
45
63
|
category: z.string().default('logical'),
|
|
46
|
-
|
|
47
|
-
// Linkage
|
|
48
64
|
signals: z.array(z.string()),
|
|
49
|
-
|
|
50
|
-
// UI Hints
|
|
51
65
|
ui: z.object({
|
|
52
66
|
icon: z.string().optional(),
|
|
53
67
|
color: z.string().optional(),
|
|
54
68
|
dashboardComponent: z.string().optional(),
|
|
55
69
|
}).optional(),
|
|
56
70
|
});
|
|
57
|
-
|
|
58
|
-
// <--- THIS WAS MISSING
|
|
59
71
|
export type EntityDefinition = z.infer<typeof EntitySchema>;
|
|
60
72
|
|
|
73
|
+
// ------------------------------------------------------------------
|
|
74
|
+
// ✅ NEW: LOGIC & WORKFLOW DEFINITIONS
|
|
75
|
+
// ------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export const TriggerSchema = z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
type: z.enum(['signal_change', 'manual_action', 'schedule', 'webhook']),
|
|
80
|
+
config: z.record(z.string(), z.any()),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const ActionSchema = z.object({
|
|
84
|
+
id: z.string(),
|
|
85
|
+
type: z.enum([
|
|
86
|
+
'update_resource',
|
|
87
|
+
'send_notification',
|
|
88
|
+
'mqtt_publish',
|
|
89
|
+
'api_call',
|
|
90
|
+
'navigate',
|
|
91
|
+
'agent_task'
|
|
92
|
+
]),
|
|
93
|
+
config: z.record(z.string(), z.any()),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const WorkflowSchema = z.object({
|
|
97
|
+
id: z.string(),
|
|
98
|
+
name: z.string(),
|
|
99
|
+
active: z.boolean().default(true),
|
|
100
|
+
trigger: TriggerSchema,
|
|
101
|
+
actions: z.array(ActionSchema),
|
|
102
|
+
});
|
|
103
|
+
export type WorkflowDefinition = z.infer<typeof WorkflowSchema>;
|
|
104
|
+
export type ActionDefinition = z.infer<typeof ActionSchema>;
|
|
105
|
+
|
|
61
106
|
|
|
62
107
|
// ------------------------------------------------------------------
|
|
63
|
-
// 3.
|
|
108
|
+
// 3. UI LAYOUT DEFINITIONS (Presentation Layer)
|
|
64
109
|
// ------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
export const BlockSchema = z.object({
|
|
112
|
+
id: z.string(),
|
|
113
|
+
type: z.string(),
|
|
114
|
+
props: z.record(z.string(), z.any()),
|
|
115
|
+
layout: z.object({
|
|
116
|
+
colSpan: z.number().optional(),
|
|
117
|
+
rowSpan: z.number().optional(),
|
|
118
|
+
}).optional(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export const PageSectionSchema = z.object({
|
|
122
|
+
id: z.string(),
|
|
123
|
+
title: z.string().optional(),
|
|
124
|
+
description: z.string().optional(),
|
|
125
|
+
layout: z.object({
|
|
126
|
+
columns: z.number().optional(),
|
|
127
|
+
variant: z.enum(['grid', 'stack', 'split']).optional()
|
|
128
|
+
}).optional(),
|
|
129
|
+
blocks: z.array(BlockSchema),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const PageSchema = z.object({
|
|
133
|
+
id: z.string(),
|
|
134
|
+
slug: z.string(),
|
|
135
|
+
title: z.string(),
|
|
136
|
+
description: z.string().optional(),
|
|
137
|
+
icon: z.string().optional(),
|
|
138
|
+
sections: z.array(PageSectionSchema),
|
|
139
|
+
});
|
|
140
|
+
export type PageDefinition = z.infer<typeof PageSchema>;
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
// MASTER APP SPECIFICATION
|
|
65
144
|
export const AppSpecificationSchema = z.object({
|
|
66
145
|
meta: z.object({
|
|
67
146
|
name: z.string(),
|
|
@@ -71,15 +150,23 @@ export const AppSpecificationSchema = z.object({
|
|
|
71
150
|
createdAt: z.string().optional(),
|
|
72
151
|
}),
|
|
73
152
|
|
|
153
|
+
config: z.object({
|
|
154
|
+
theme: z.enum(['light', 'dark', 'system', 'corporate', 'midnight', 'blueprint']).default('system'),
|
|
155
|
+
mockMode: z.boolean().default(true),
|
|
156
|
+
brokerUrl: z.string().optional(),
|
|
157
|
+
}),
|
|
158
|
+
|
|
159
|
+
modules: z.array(z.string()).optional(),
|
|
160
|
+
resources: z.array(ResourceSchema).optional(),
|
|
161
|
+
|
|
74
162
|
domain: z.object({
|
|
75
163
|
signals: z.array(SignalSchema),
|
|
76
164
|
entities: z.array(EntitySchema),
|
|
165
|
+
// ✅ ADDED WORKFLOWS HERE
|
|
166
|
+
workflows: z.array(WorkflowSchema).optional(),
|
|
77
167
|
}),
|
|
78
168
|
|
|
79
|
-
|
|
80
|
-
theme: z.enum(['light', 'dark', 'system', 'corporate', 'midnight', 'blueprint']).default('system'),
|
|
81
|
-
mockMode: z.boolean().default(true),
|
|
82
|
-
}),
|
|
169
|
+
pages: z.array(PageSchema).optional(),
|
|
83
170
|
});
|
|
84
171
|
|
|
85
172
|
export type AppSpecification = z.infer<typeof AppSpecificationSchema>;
|