@ramme-io/create-app 1.1.9 → 1.2.0

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.
@@ -2,55 +2,55 @@ import type { AppSpecification } from '../types/schema';
2
2
 
3
3
  export const appManifest: AppSpecification = {
4
4
  meta: {
5
- name: "Acme Corp Admin",
6
- version: "2.0.0",
7
- description: "Executive overview of KPI metrics.",
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: 'corporate', // <--- Switches the visual theme automatically
12
- mockMode: false // <--- Critical: Tells the Adapter Factory to use REAL code (HTTP/MQTT)
11
+ theme: 'system',
12
+ mockMode: false,
13
+ brokerUrl: 'wss://test.mosquitto.org:8081',
13
14
  },
14
- domain: {
15
- signals: [
16
- {
17
- id: "mrr_stripe",
18
- label: "Monthly Revenue",
19
- kind: "metric",
20
- source: "http", // <--- This triggers your new generateHttpImplementation()
21
- endpoint: "/api_mock.json",
22
- unit: "USD",
23
- defaultValue: 0,
24
- refreshRate: 0
25
- },
26
- {
27
- id: "active_users",
28
- label: "Active Users",
29
- kind: "metric",
30
- source: "http",
31
- endpoint: "/api_mock.json",
32
- unit: "",
33
- defaultValue: 0,
34
- refreshRate: 0
35
- }
36
- ],
37
- entities: [
38
- {
39
- id: "widget_mrr",
40
- name: "Current MRR",
41
- type: "kpi",
42
- signals: ["mrr_stripe"],
43
- ui: { dashboardComponent: "StatCard", icon: "dollar-sign" },
44
- category: ''
45
- },
46
- {
47
- id: "widget_users",
48
- name: "Total Users",
49
- type: "kpi",
50
- signals: ["active_users"],
51
- ui: { dashboardComponent: "StatCard", icon: "users" },
52
- category: ''
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
- * The Registry maps the "string" name of a component (from the JSON manifest)
16
- * to the actual React component implementation.
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
- // --- Core Components ---
19
+ // IoT Primitives
20
20
  DeviceCard,
21
+
22
+ // Data Display
21
23
  StatCard,
22
24
  BarChart,
23
25
  LineChart,
24
26
  PieChart,
25
- DataTable,
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
- // Tables
35
- 'table': DataTable, // Fixes your specific error
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
- // 1. Try direct lookup
59
- let Component = COMPONENT_REGISTRY[name];
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
- // src/data/mockData.ts
2
-
3
- // --- 1. Your Existing Examples ---
4
- export const mockChartData = [
5
- { name: 'Jan', uv: 400, pv: 240 },
6
- { name: 'Feb', uv: 300, pv: 139 },
7
- { name: 'Mar', uv: 200, pv: 980 },
8
- { name: 'Apr', uv: 278, pv: 390 },
9
- { name: 'May', uv: 189, pv: 480 },
10
- { name: 'Jun', uv: 239, pv: 380 },
11
- { name: 'Jul', uv: 349, pv: 430 },
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 mockTableData = [
15
- { make: 'Tesla', model: 'Model Y', price: 64950, electric: true },
16
- { make: 'Ford', model: 'F-Series', price: 33850, electric: false },
17
- { make: 'Toyota', model: 'Corolla', price: 29600, electric: false },
18
- { make: 'Mercedes', model: 'EQS', price: 102310, electric: true },
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
- // 2. FIXED: Energy Data (Recharts Format)
23
- // Recharts expects an Array of Objects, NOT { labels, datasets }
24
- export const energyHistoryData = [
25
- { time: "12am", value: 12 },
26
- { time: "4am", value: 19 },
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
- // --- 3. The Lookup Registry ---
34
- // This allows the Manifest to refer to data by string ID.
35
- export const MOCK_DATA_REGISTRY: Record<string, any> = {
36
- 'energy_history': energyHistoryData,
37
- 'demo_chart': mockChartData,
38
- 'demo_cars': mockTableData
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
- export const getMockData = (id: string) => {
42
- return MOCK_DATA_REGISTRY[id] || null;
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
  };
@@ -1,61 +1,40 @@
1
- // ------------------------------------------------------------------
2
- // GENERATED ADAPTER CODE
3
- // Config: LIVE MODE (HTTP)
4
- // ------------------------------------------------------------------
5
- import { useEffect, useState } from 'react';
1
+ import { useSignal } from '../hooks/useSignal';
6
2
 
7
- // Signal IDs defined in your Acme Corp schema
8
- export type SignalId = 'mrr_stripe' | 'active_users';
9
-
10
- export function useGeneratedSignals() {
11
- // 1. Initialize state with default values (0)
12
- const [signals, setSignals] = useState<Record<SignalId, any>>({
13
- 'mrr_stripe': { value: 0, unit: 'USD', status: 'stale' },
14
- 'active_users': { value: 0, unit: '', status: 'stale' }
3
+ export const useGeneratedSignals = () => {
4
+
5
+ // 🟢 REAL: Connected to public MQTT test broker
6
+ const living_room_ac = useSignal('living_room_ac', {
7
+ initialValue: 72,
8
+ min: 60,
9
+ max: 90,
10
+ unit: '°F',
11
+ topic: 'ramme/test/temp' // <--- The magic link
15
12
  });
16
13
 
17
- // 2. HTTP Polling Implementation
18
- useEffect(() => {
19
- // Helper to extract nested data (e.g. "data.finance.mrr")
20
- const getNestedValue = (obj: any, path: string) => {
21
- return path.split('.').reduce((acc, part) => acc && acc[part], obj);
22
- };
14
+ // 🟠 MOCK: Simulation Mode
15
+ const living_room_hum = useSignal('living_room_hum', {
16
+ initialValue: 45,
17
+ min: 40,
18
+ max: 60,
19
+ unit: '%'
20
+ });
23
21
 
24
- const pollEndpoints = async () => {
25
- console.log('🔄 Polling /api_mock.json...');
26
-
27
- try {
28
- const response = await fetch('/api_mock.json');
29
- if (response.ok) {
30
- const json = await response.json();
31
-
32
- // Update Signals based on the paths defined in manifest
33
- setSignals(prev => ({
34
- ...prev,
35
- 'mrr_stripe': {
36
- value: getNestedValue(json, 'data.finance.mrr'),
37
- unit: 'USD',
38
- status: 'fresh'
39
- },
40
- 'active_users': {
41
- value: getNestedValue(json, 'data.users.total'),
42
- unit: '',
43
- status: 'fresh'
44
- }
45
- }));
46
- }
47
- } catch (err) {
48
- console.error("API Polling Error:", err);
49
- }
50
- };
22
+ const server_01 = useSignal('server_01', {
23
+ initialValue: 42,
24
+ min: 10,
25
+ max: 95,
26
+ unit: '%'
27
+ });
51
28
 
52
- // Initial Fetch
53
- pollEndpoints();
54
-
55
- // Poll every 5s
56
- const interval = setInterval(pollEndpoints, 5000);
57
- return () => clearInterval(interval);
58
- }, []);
29
+ const front_door_lock = useSignal('front_door_lock', {
30
+ initialValue: 'LOCKED',
31
+ unit: ''
32
+ });
59
33
 
60
- return signals;
61
- }
34
+ return {
35
+ living_room_ac,
36
+ living_room_hum,
37
+ server_01,
38
+ front_door_lock,
39
+ };
40
+ };
@@ -0,0 +1,84 @@
1
+ import { useMemo } from 'react';
2
+
3
+ // --- 1. Export the Missing Types (Fixes ts(2305)) ---
4
+
5
+ export type SortDirection = 'asc' | 'desc';
6
+
7
+ export interface SortOption {
8
+ field: string;
9
+ direction: SortDirection;
10
+ }
11
+
12
+ export interface FilterOption {
13
+ field: string;
14
+ operator: 'equals' | 'contains' | 'gt' | 'lt' | 'neq';
15
+ value: any;
16
+ }
17
+
18
+ export interface QueryOptions {
19
+ filters?: FilterOption[];
20
+ sort?: SortOption;
21
+ page?: number;
22
+ pageSize?: number;
23
+ }
24
+
25
+ export interface QueryResult<T> {
26
+ data: T[];
27
+ total: number;
28
+ pageCount: number;
29
+ }
30
+
31
+ // --- 2. Update the Hook Signature (Fixes ts(2345)) ---
32
+
33
+ export function useDataQuery<T>(
34
+ rawData: T[], // <--- Now accepts an Array, NOT a string ID
35
+ options: QueryOptions = {}
36
+ ): QueryResult<T> {
37
+ const { filters, sort, page = 1, pageSize = 10 } = options;
38
+
39
+ // A. Filtering Logic
40
+ const filteredData = useMemo(() => {
41
+ if (!filters || filters.length === 0) return rawData;
42
+
43
+ return rawData.filter((item: any) => {
44
+ return filters.every((filter) => {
45
+ const itemValue = item[filter.field];
46
+
47
+ switch (filter.operator) {
48
+ case 'equals': return itemValue == filter.value;
49
+ case 'neq': return itemValue != filter.value;
50
+ case 'contains':
51
+ return String(itemValue).toLowerCase().includes(String(filter.value).toLowerCase());
52
+ case 'gt': return itemValue > filter.value;
53
+ case 'lt': return itemValue < filter.value;
54
+ default: return true;
55
+ }
56
+ });
57
+ });
58
+ }, [rawData, filters]);
59
+
60
+ // B. Sorting Logic
61
+ const sortedData = useMemo(() => {
62
+ if (!sort) return filteredData;
63
+
64
+ return [...filteredData].sort((a: any, b: any) => {
65
+ const aValue = a[sort.field];
66
+ const bValue = b[sort.field];
67
+ if (aValue < bValue) return sort.direction === 'asc' ? -1 : 1;
68
+ if (aValue > bValue) return sort.direction === 'asc' ? 1 : -1;
69
+ return 0;
70
+ });
71
+ }, [filteredData, sort]);
72
+
73
+ // C. Pagination Logic
74
+ const paginatedResult = useMemo(() => {
75
+ const startIndex = (page - 1) * pageSize;
76
+ return sortedData.slice(startIndex, startIndex + pageSize);
77
+ }, [sortedData, page, pageSize]);
78
+
79
+ return {
80
+ data: paginatedResult,
81
+ total: filteredData.length,
82
+ pageCount: Math.ceil(filteredData.length / pageSize),
83
+ };
84
+ }