@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,49 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file GhostOverlay.tsx
|
|
3
|
-
* @repository ramme-app-starter
|
|
4
|
-
* @description
|
|
5
|
-
* A development-mode wrapper that provides "X-Ray vision" into the
|
|
6
|
-
* application's structure.
|
|
7
|
-
*
|
|
8
|
-
* INTENT & PHILOSOPHY:
|
|
9
|
-
* This component embodies the "Glass Box" doctrine of the Ramme Framework.
|
|
10
|
-
* It allows creators to peer behind the "Visual Fidelity" of the prototype
|
|
11
|
-
* and inspect the "Functional Fidelity" underneath.
|
|
12
|
-
*
|
|
13
|
-
* STRATEGIC VALUE:
|
|
14
|
-
* 1. Traceability: It visually links a UI element (e.g., a Gauge) back to
|
|
15
|
-
* its source definition in the manifest (e.g., Signal ID: 'temp_01').
|
|
16
|
-
* 2. Debugging: It helps creators understand layout boundaries (Grid Cells)
|
|
17
|
-
* and data flow without needing browser DevTools.
|
|
18
|
-
* 3. Education: It reinforces the "Architect" mindset by exposing the
|
|
19
|
-
* component hierarchy.
|
|
20
|
-
*
|
|
21
|
-
* USAGE:
|
|
22
|
-
* Wrap any dynamic component in the `Dashboard.tsx` loop with this overlay.
|
|
23
|
-
* It listens to a global `debugMode` state (likely from a store or context)
|
|
24
|
-
* to toggle its visibility.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
1
|
import React from 'react';
|
|
28
|
-
import {
|
|
29
|
-
// We'll need a way to check if we are in "Debug/Ghost" mode.
|
|
30
|
-
// For now, we can pass it as a prop or assume a context hook exists.
|
|
31
|
-
// import { useDevTools } from '../../contexts/DevToolsContext';
|
|
2
|
+
import { Icon } from '@ramme-io/ui';
|
|
32
3
|
|
|
33
4
|
interface GhostOverlayProps {
|
|
34
|
-
/** The actual UI component being wrapped (e.g., DeviceCard) */
|
|
35
5
|
children: React.ReactNode;
|
|
36
|
-
|
|
37
|
-
/** The unique ID of the component from the manifest */
|
|
38
6
|
componentId: string;
|
|
39
|
-
|
|
40
|
-
/** The name of the React component being rendered (e.g., "StatCard") */
|
|
41
7
|
componentType: string;
|
|
42
|
-
|
|
43
|
-
/** (Optional) The ID of the data signal driving this component */
|
|
44
8
|
signalId?: string;
|
|
45
|
-
|
|
46
|
-
/** Whether the overlay is currently active (X-Ray Mode on) */
|
|
47
9
|
isActive?: boolean;
|
|
48
10
|
}
|
|
49
11
|
|
|
@@ -52,48 +14,53 @@ export const GhostOverlay: React.FC<GhostOverlayProps> = ({
|
|
|
52
14
|
componentId,
|
|
53
15
|
componentType,
|
|
54
16
|
signalId,
|
|
55
|
-
isActive = false,
|
|
17
|
+
isActive = false,
|
|
56
18
|
}) => {
|
|
57
19
|
|
|
58
20
|
if (!isActive) {
|
|
59
21
|
return <>{children}</>;
|
|
60
22
|
}
|
|
61
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
|
+
|
|
62
36
|
return (
|
|
63
|
-
<div
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*/}
|
|
68
|
-
<div className="absolute inset-0 z-50
|
|
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" />
|
|
69
43
|
|
|
70
|
-
{/* The "Info Tag"
|
|
71
|
-
|
|
72
|
-
- Displays the technical 'bones' of the component
|
|
73
|
-
*/}
|
|
74
|
-
<div className="absolute top-0 left-0 z-50 p-2 transform -translate-y-1/2 translate-x-2">
|
|
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">
|
|
75
46
|
<div className="flex items-center gap-2 bg-accent text-accent-foreground text-xs font-mono py-1 px-2 rounded shadow-sm">
|
|
76
47
|
<Icon name="box" size={12} />
|
|
77
48
|
<span className="font-bold">{componentType}</span>
|
|
78
|
-
<span className="opacity-75">#{componentId}</span>
|
|
79
49
|
</div>
|
|
80
50
|
</div>
|
|
81
51
|
|
|
82
|
-
{/* The "Signal Wire"
|
|
83
|
-
- Floats bottom-right if a signal is connected
|
|
84
|
-
- Shows the data source wiring
|
|
85
|
-
*/}
|
|
52
|
+
{/* The "Signal Wire" */}
|
|
86
53
|
{signalId && (
|
|
87
|
-
<div className="absolute bottom-0 right-0 z-50 p-2 transform translate-y-1/2 -translate-x-2">
|
|
54
|
+
<div className="absolute bottom-0 right-0 z-50 p-2 transform translate-y-1/2 -translate-x-2 pointer-events-none">
|
|
88
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">
|
|
89
56
|
<Icon name="activity" size={12} />
|
|
90
|
-
<span>
|
|
57
|
+
<span>{signalId}</span>
|
|
91
58
|
</div>
|
|
92
59
|
</div>
|
|
93
60
|
)}
|
|
94
61
|
|
|
95
62
|
{/* Render the actual component underneath */}
|
|
96
|
-
<div className="opacity-50 grayscale transition-all duration-200 group-hover:opacity-75 group-hover:grayscale-0">
|
|
63
|
+
<div className="opacity-50 grayscale transition-all duration-200 group-hover:opacity-75 group-hover:grayscale-0 pointer-events-none">
|
|
97
64
|
{children}
|
|
98
65
|
</div>
|
|
99
66
|
</div>
|
|
@@ -2,55 +2,55 @@ import type { AppSpecification } from '../types/schema';
|
|
|
2
2
|
|
|
3
3
|
export const appManifest: AppSpecification = {
|
|
4
4
|
meta: {
|
|
5
|
-
name: "
|
|
6
|
-
version: "
|
|
7
|
-
description: "
|
|
8
|
-
author: "Ramme Builder"
|
|
5
|
+
name: "Ramme System Check",
|
|
6
|
+
version: "1.0.0",
|
|
7
|
+
description: "Verifying the Smart Runtime.",
|
|
8
|
+
author: "Ramme Builder",
|
|
9
9
|
},
|
|
10
10
|
config: {
|
|
11
|
-
theme: '
|
|
12
|
-
mockMode: false
|
|
11
|
+
theme: 'system',
|
|
12
|
+
mockMode: false,
|
|
13
|
+
brokerUrl: 'wss://test.mosquitto.org:8081',
|
|
13
14
|
},
|
|
14
|
-
domain: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
15
|
+
domain: { signals: [], entities: [] },
|
|
16
|
+
pages: [
|
|
17
|
+
{
|
|
18
|
+
id: "dashboard",
|
|
19
|
+
slug: "dashboard",
|
|
20
|
+
title: "System Status",
|
|
21
|
+
description: "Verifying Week 1 Logic Engine",
|
|
22
|
+
sections: [
|
|
23
|
+
{
|
|
24
|
+
id: "sect-users",
|
|
25
|
+
title: "User Management (CRUD Test)",
|
|
26
|
+
layout: { columns: 1 },
|
|
27
|
+
blocks: [
|
|
28
|
+
{
|
|
29
|
+
id: "table_users",
|
|
30
|
+
type: "SmartTable",
|
|
31
|
+
props: {
|
|
32
|
+
title: "Active Users",
|
|
33
|
+
dataId: "users" // ✅ Points to SEED_USERS
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "sect-invoices",
|
|
40
|
+
title: "Invoices (Relational Test)",
|
|
41
|
+
layout: { columns: 1 },
|
|
42
|
+
blocks: [
|
|
43
|
+
{
|
|
44
|
+
id: "table_invoices",
|
|
45
|
+
type: "SmartTable",
|
|
46
|
+
props: {
|
|
47
|
+
title: "Recent Invoices",
|
|
48
|
+
dataId: "invoices" // ✅ Points to SEED_INVOICES
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
56
|
};
|
|
@@ -5,65 +5,45 @@ import {
|
|
|
5
5
|
BarChart,
|
|
6
6
|
LineChart,
|
|
7
7
|
PieChart,
|
|
8
|
-
DataTable,
|
|
8
|
+
DataTable,
|
|
9
9
|
Card,
|
|
10
10
|
Alert,
|
|
11
11
|
EmptyState,
|
|
12
|
+
ToggleSwitch
|
|
12
13
|
} from '@ramme-io/ui';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
15
|
+
// ✅ IMPORT YOUR CUSTOM COMPONENT
|
|
16
|
+
import { SmartTable } from '../blocks/SmartTable';
|
|
17
|
+
|
|
18
18
|
export const COMPONENT_REGISTRY: Record<string, React.FC<any>> = {
|
|
19
|
-
//
|
|
19
|
+
// IoT Primitives
|
|
20
20
|
DeviceCard,
|
|
21
|
+
|
|
22
|
+
// Data Display
|
|
21
23
|
StatCard,
|
|
22
24
|
BarChart,
|
|
23
25
|
LineChart,
|
|
24
26
|
PieChart,
|
|
25
|
-
|
|
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
|
|
26
36
|
Card,
|
|
27
37
|
Alert,
|
|
28
38
|
EmptyState,
|
|
29
|
-
|
|
30
|
-
// --- 🛡️ ROBUSTNESS ALIASES ---
|
|
31
|
-
// These mappings allow the manifest to use lowercase or alternate names
|
|
32
|
-
// without crashing the application.
|
|
33
39
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
'Table': DataTable,
|
|
37
|
-
'grid': DataTable,
|
|
38
|
-
|
|
39
|
-
// Charts
|
|
40
|
-
'chart': BarChart, // Default generic chart to BarChart
|
|
41
|
-
'line': LineChart,
|
|
42
|
-
'bar': BarChart,
|
|
43
|
-
'pie': PieChart,
|
|
44
|
-
|
|
45
|
-
// IoT Fallbacks
|
|
46
|
-
'DataCard': DeviceCard,
|
|
47
|
-
'GaugeCard': DeviceCard,
|
|
48
|
-
'ToggleCard': DeviceCard,
|
|
49
|
-
'SliderCard': DeviceCard,
|
|
50
|
-
'SparklineCard': DeviceCard,
|
|
40
|
+
// Forms/Controls
|
|
41
|
+
ToggleSwitch
|
|
51
42
|
};
|
|
52
43
|
|
|
53
|
-
/**
|
|
54
|
-
* Helper to safely resolve a component.
|
|
55
|
-
* Returns a fallback if the component name is unknown.
|
|
56
|
-
*/
|
|
57
44
|
export const getComponent = (name: string) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// 2. If not found, try PascalCase (e.g. "deviceCard" -> "DeviceCard")
|
|
62
|
-
if (!Component) {
|
|
63
|
-
const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
64
|
-
Component = COMPONENT_REGISTRY[pascalName];
|
|
65
|
-
}
|
|
66
|
-
|
|
45
|
+
const Component = COMPONENT_REGISTRY[name];
|
|
46
|
+
|
|
67
47
|
if (!Component) {
|
|
68
48
|
console.warn(`[Registry] Unknown component type: "${name}"`);
|
|
69
49
|
return () => (
|
|
@@ -0,0 +1,35 @@
|
|
|
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,43 +1,172 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file src/data/mockData.ts
|
|
3
|
+
* @description The "Golden Copy" of seed data.
|
|
4
|
+
* These records are injected into localStorage when the app first launches.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ✅ 1. Interface for the Metadata (Schema)
|
|
8
|
+
export interface ResourceMeta {
|
|
9
|
+
name: string;
|
|
10
|
+
fields: {
|
|
11
|
+
key: string;
|
|
12
|
+
label: string;
|
|
13
|
+
type: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
defaultValue?: any;
|
|
16
|
+
description?: string;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// --- DATA INTERFACES ---
|
|
21
|
+
|
|
22
|
+
export interface User {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
role: 'admin' | 'editor' | 'viewer';
|
|
27
|
+
status: 'active' | 'pending' | 'banned';
|
|
28
|
+
avatar?: string;
|
|
29
|
+
joinedAt: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Product {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
category: string;
|
|
36
|
+
price: number;
|
|
37
|
+
stock: number;
|
|
38
|
+
status: 'in_stock' | 'low_stock' | 'out_of_stock';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Invoice {
|
|
42
|
+
id: string;
|
|
43
|
+
userId: string; // Foreign Key -> User
|
|
44
|
+
amount: number;
|
|
45
|
+
status: 'paid' | 'pending' | 'overdue';
|
|
46
|
+
date: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Review {
|
|
50
|
+
id: string;
|
|
51
|
+
productId: string; // Foreign Key -> Product
|
|
52
|
+
rating: number;
|
|
53
|
+
comment: string;
|
|
54
|
+
author: string;
|
|
55
|
+
date: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ActivityLog {
|
|
59
|
+
id: string;
|
|
60
|
+
action: string;
|
|
61
|
+
entity: string;
|
|
62
|
+
timestamp: string;
|
|
63
|
+
user: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- SEED DATA ---
|
|
67
|
+
|
|
68
|
+
export const SEED_USERS: User[] = [
|
|
69
|
+
{ id: 'usr_1', name: 'Alex Carter', email: 'alex@example.com', role: 'admin', status: 'active', joinedAt: '2023-01-15' },
|
|
70
|
+
{ id: 'usr_2', name: 'Sarah Jenkins', email: 'sarah@example.com', role: 'editor', status: 'active', joinedAt: '2023-03-22' },
|
|
71
|
+
{ id: 'usr_3', name: 'Mike Ross', email: 'mike@example.com', role: 'viewer', status: 'pending', joinedAt: '2023-05-10' },
|
|
72
|
+
{ id: 'usr_4', name: 'Emily Blunt', email: 'emily@example.com', role: 'editor', status: 'banned', joinedAt: '2023-06-01' },
|
|
12
73
|
];
|
|
13
74
|
|
|
14
|
-
export const
|
|
15
|
-
{
|
|
16
|
-
{
|
|
17
|
-
{
|
|
18
|
-
{
|
|
19
|
-
{ make: 'BMW', model: 'i4', price: 51400, electric: true },
|
|
75
|
+
export const SEED_PRODUCTS: Product[] = [
|
|
76
|
+
{ id: 'prod_1', name: 'ErgoChair Pro', category: 'Furniture', price: 450, stock: 12, status: 'in_stock' },
|
|
77
|
+
{ id: 'prod_2', name: 'Standing Desk', category: 'Furniture', price: 600, stock: 3, status: 'low_stock' },
|
|
78
|
+
{ id: 'prod_3', name: 'Monitor Arm', category: 'Accessories', price: 120, stock: 0, status: 'out_of_stock' },
|
|
79
|
+
{ id: 'prod_4', name: 'Mechanical Keyboard', category: 'Electronics', price: 180, stock: 25, status: 'in_stock' },
|
|
20
80
|
];
|
|
21
81
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
{
|
|
27
|
-
{ time: "8am", value: 3 },
|
|
28
|
-
{ time: "12pm", value: 5 },
|
|
29
|
-
{ time: "4pm", value: 2 },
|
|
30
|
-
{ time: "8pm", value: 3 }
|
|
82
|
+
export const SEED_INVOICES: Invoice[] = [
|
|
83
|
+
{ id: 'inv_001', userId: 'usr_2', amount: 450.00, status: 'paid', date: '2023-10-01' },
|
|
84
|
+
{ id: 'inv_002', userId: 'usr_1', amount: 1200.50, status: 'pending', date: '2023-10-05' },
|
|
85
|
+
{ id: 'inv_003', userId: 'usr_3', amount: 180.00, status: 'overdue', date: '2023-09-15' },
|
|
86
|
+
{ id: 'inv_004', userId: 'usr_2', amount: 600.00, status: 'paid', date: '2023-10-10' },
|
|
31
87
|
];
|
|
32
88
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
|
|
89
|
+
export const SEED_REVIEWS: Review[] = [
|
|
90
|
+
{ id: 'rev_1', productId: 'prod_1', rating: 5, comment: 'Life changing comfort!', author: 'Alex Carter', date: '2023-09-01' },
|
|
91
|
+
{ id: 'rev_2', productId: 'prod_4', rating: 4, comment: 'Clicky but loud.', author: 'Mike Ross', date: '2023-09-10' },
|
|
92
|
+
{ id: 'rev_3', productId: 'prod_2', rating: 2, comment: 'Wobbles at max height.', author: 'Sarah Jenkins', date: '2023-09-12' },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
export const SEED_LOGS: ActivityLog[] = [
|
|
96
|
+
{ id: 'log_1', action: 'User Created', entity: 'User', user: 'System', timestamp: '2023-10-01T10:00:00Z' },
|
|
97
|
+
{ id: 'log_2', action: 'Invoice Paid', entity: 'Invoice', user: 'Alex Carter', timestamp: '2023-10-02T14:30:00Z' },
|
|
98
|
+
{ id: 'log_3', action: 'Stock Updated', entity: 'Product', user: 'Sarah Jenkins', timestamp: '2023-10-03T09:15:00Z' },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
// --- REGISTRIES ---
|
|
102
|
+
|
|
103
|
+
// 1. Data Registry (The Records)
|
|
104
|
+
export const DATA_REGISTRY: Record<string, any[]> = {
|
|
105
|
+
users: SEED_USERS,
|
|
106
|
+
products: SEED_PRODUCTS,
|
|
107
|
+
invoices: SEED_INVOICES,
|
|
108
|
+
reviews: SEED_REVIEWS,
|
|
109
|
+
logs: SEED_LOGS,
|
|
39
110
|
};
|
|
40
111
|
|
|
41
|
-
|
|
42
|
-
|
|
112
|
+
// 2. Metadata Registry (The Schema/Definitions)
|
|
113
|
+
export const RESOURCE_METADATA: Record<string, ResourceMeta> = {
|
|
114
|
+
users: {
|
|
115
|
+
name: 'Users',
|
|
116
|
+
fields: [
|
|
117
|
+
{ key: 'name', label: 'Name', type: 'text', required: true },
|
|
118
|
+
{ key: 'email', label: 'Email', type: 'email', required: true },
|
|
119
|
+
{ key: 'role', label: 'Role', type: 'status' },
|
|
120
|
+
{ key: 'status', label: 'Status', type: 'status' },
|
|
121
|
+
{ key: 'joinedAt', label: 'Joined', type: 'date' },
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
products: {
|
|
125
|
+
name: 'Products',
|
|
126
|
+
fields: [
|
|
127
|
+
{ key: 'name', label: 'Product Name', type: 'text', required: true },
|
|
128
|
+
{ key: 'category', label: 'Category', type: 'text' },
|
|
129
|
+
{ key: 'price', label: 'Price', type: 'currency' },
|
|
130
|
+
{ key: 'stock', label: 'Stock', type: 'number' },
|
|
131
|
+
{ key: 'status', label: 'Availability', type: 'status' },
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
invoices: {
|
|
135
|
+
name: 'Invoices',
|
|
136
|
+
fields: [
|
|
137
|
+
// ✅ FIX: Changed label from 'User ID' to 'User'
|
|
138
|
+
{ key: 'userId', label: 'User', type: 'text', required: true },
|
|
139
|
+
{ key: 'amount', label: 'Amount', type: 'currency' },
|
|
140
|
+
{ key: 'status', label: 'Status', type: 'status' },
|
|
141
|
+
{ key: 'date', label: 'Date', type: 'date' },
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
reviews: {
|
|
145
|
+
name: 'Reviews',
|
|
146
|
+
fields: [
|
|
147
|
+
// ✅ FIX: Changed label from 'Product ID' to 'Product'
|
|
148
|
+
{ key: 'productId', label: 'Product', type: 'text' },
|
|
149
|
+
{ key: 'rating', label: 'Rating', type: 'number' },
|
|
150
|
+
{ key: 'comment', label: 'Comment', type: 'text' },
|
|
151
|
+
{ key: 'author', label: 'Author', type: 'text' },
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
logs: {
|
|
155
|
+
name: 'Activity Logs',
|
|
156
|
+
fields: [
|
|
157
|
+
{ key: 'action', label: 'Action', type: 'text' },
|
|
158
|
+
{ key: 'entity', label: 'Entity', type: 'text' },
|
|
159
|
+
{ key: 'user', label: 'User', type: 'text' },
|
|
160
|
+
{ key: 'timestamp', label: 'Time', type: 'date' },
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// --- HELPERS ---
|
|
166
|
+
|
|
167
|
+
export const getMockData = (id: string) => DATA_REGISTRY[id] || [];
|
|
168
|
+
|
|
169
|
+
// ✅ Updated to return ResourceMeta or null (instead of never/null)
|
|
170
|
+
export const getResourceMeta = (id: string): ResourceMeta | null => {
|
|
171
|
+
return RESOURCE_METADATA[id] || null;
|
|
43
172
|
};
|