@ramme-io/create-app 1.0.5 → 1.1.2

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.
@@ -7,14 +7,23 @@ import {
7
7
  StatCard,
8
8
  DataTable,
9
9
  useDataFetch,
10
+ DeviceCard, // <-- NEW: Import DeviceCard
10
11
  type ColDef,
11
12
  type ValueFormatterParams,
12
13
  } from '@ramme-io/ui';
14
+ import { useSignal } from '../hooks/useSignal'; // <-- NEW: Import the Engine
13
15
  import { mockTableData, mockChartData } from '../data/mockData';
14
16
 
15
17
  const Dashboard: React.FC = () => {
18
+ // 1. Existing Logic (SaaS Data)
16
19
  const { data: fetchedTableData } = useDataFetch(null, mockTableData);
17
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: '%' });
25
+
26
+ // 3. Table Config
18
27
  const columnDefs: ColDef[] = [
19
28
  { field: 'make', headerName: 'Make', sortable: true, filter: true },
20
29
  { field: 'model', headerName: 'Model', sortable: true, filter: true },
@@ -33,43 +42,79 @@ const Dashboard: React.FC = () => {
33
42
  return (
34
43
  <div className="space-y-8">
35
44
  <PageHeader
36
- title="Dashboard Overview"
37
- description="A summary of key metrics and recent activity."
45
+ title="Command Center"
46
+ description="Real-time device monitoring and business analytics."
38
47
  />
39
48
 
40
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
41
- <StatCard
42
- title="Total Users"
43
- value="1,234"
44
- icon="users"
45
- changeText="+10% from last month"
46
- changeDirection="positive"
47
- />
48
- <StatCard
49
- title="Sales Today"
50
- value="$5,678"
51
- icon="dollar-sign"
52
- changeText="+5% from yesterday"
53
- changeDirection="positive"
54
- />
55
- <StatCard
56
- title="New Orders"
57
- value="89"
58
- icon="shopping-cart"
59
- changeText="-2 since last hour"
60
- changeDirection="negative"
61
- />
62
- <StatCard
63
- title="Active Projects"
64
- value="12"
65
- icon="briefcase"
66
- footerText="3 nearing completion"
67
- />
49
+ {/* --- SECTION 1: LIVE IOT STATUS (The New Stuff) --- */}
50
+ <div>
51
+ <h3 className="text-lg font-semibold mb-4 text-foreground">Live Device Status</h3>
52
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
53
+ <DeviceCard
54
+ title="Living Room AC"
55
+ description="Zone A • Floor 1"
56
+ icon="thermometer"
57
+ status="online"
58
+ value={`${temp.value}${temp.unit}`}
59
+ trend="Cooling to 70°"
60
+ />
61
+ <DeviceCard
62
+ title="Air Quality"
63
+ description="Sensor ID: #8842"
64
+ icon="droplets"
65
+ status="active"
66
+ value={`${hum.value}${hum.unit}`}
67
+ trend="Stable"
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>
79
+
80
+ {/* --- SECTION 2: BUSINESS METRICS (The Old Stuff) --- */}
81
+ <div>
82
+ <h3 className="text-lg font-semibold mb-4 text-foreground">Business Overview</h3>
83
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
84
+ <StatCard
85
+ title="Total Users"
86
+ value="1,234"
87
+ icon="users"
88
+ changeText="+10% from last month"
89
+ changeDirection="positive"
90
+ />
91
+ <StatCard
92
+ title="Sales Today"
93
+ value="$5,678"
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>
68
112
  </div>
69
113
 
114
+ {/* --- SECTION 3: ANALYTICS CHARTS --- */}
70
115
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
71
116
  <Card className="p-6">
72
- <h3 className="text-lg font-semibold mb-4 text-text-foreground">Monthly Revenue</h3>
117
+ <h3 className="text-lg font-semibold mb-4 text-foreground">Monthly Revenue</h3>
73
118
  <div className="h-[350px] w-full">
74
119
  <BarChart
75
120
  data={mockChartData}
@@ -79,7 +124,7 @@ const Dashboard: React.FC = () => {
79
124
  </div>
80
125
  </Card>
81
126
  <Card className="p-6">
82
- <h3 className="text-lg font-semibold mb-4 text-text-foreground">User Growth</h3>
127
+ <h3 className="text-lg font-semibold mb-4 text-foreground">User Growth</h3>
83
128
  <div className="h-[350px] w-full">
84
129
  <LineChart
85
130
  data={mockChartData}
@@ -90,8 +135,9 @@ const Dashboard: React.FC = () => {
90
135
  </Card>
91
136
  </div>
92
137
 
138
+ {/* --- SECTION 4: DATA TABLE --- */}
93
139
  <Card className="p-6">
94
- <h3 className="text-2xl font-semibold mb-4 text-text">Recent Vehicle Data</h3>
140
+ <h3 className="text-2xl font-semibold mb-4 text-foreground">Recent Vehicle Data</h3>
95
141
  {fetchedTableData && (
96
142
  <DataTable
97
143
  rowData={fetchedTableData}
@@ -1,5 +1,4 @@
1
- // ramme-app-starter/template/src/templates/dashboard/DashboardLayout.tsx
2
- import React from 'react';
1
+ import React, { useState } from 'react'; // <-- Added useState
3
2
  import { Outlet, NavLink } from 'react-router-dom';
4
3
  import {
5
4
  Sidebar,
@@ -12,11 +11,13 @@ import {
12
11
  useSidebar,
13
12
  Button,
14
13
  Icon,
14
+ ChatFAB, // <-- NEW: Import FAB
15
15
  } from '@ramme-io/ui';
16
16
  import { dashboardSitemap } from './dashboard.sitemap';
17
17
  import { SitemapProvider } from '../../contexts/SitemapContext';
18
18
  import PageTitleUpdater from '../../components/PageTitleUpdater';
19
19
  import AppHeader from '../../components/AppHeader';
20
+ import { AIChatWidget } from '../../components/AIChatWidget'; // <-- NEW: Import Widget
20
21
 
21
22
  // NavLink wrapper - Correct
22
23
  const SidebarNavLink = React.forwardRef<HTMLAnchorElement, any>(
@@ -51,8 +52,6 @@ const AppSidebarContent: React.FC = () => {
51
52
  <SidebarMenuItem
52
53
  as={SidebarNavLink}
53
54
  href={item.path ? `/dashboard/${item.path}` : '/dashboard'}
54
- // --- THIS IS THE FIX ---
55
- // Force exact match for all parent links
56
55
  end
57
56
  icon={item.icon ? <Icon name={item.icon} /> : undefined}
58
57
  tooltip={item.title}
@@ -66,12 +65,10 @@ const AppSidebarContent: React.FC = () => {
66
65
  key={child.id}
67
66
  as={SidebarNavLink}
68
67
  href={`/dashboard/${item.path}/${child.path}`}
69
- // --- THIS IS THE FIX ---
70
- // 'end' was already here, which is correct
71
68
  end
72
69
  icon={child.icon ? <Icon name={child.icon} /> : undefined}
73
70
  tooltip={child.title}
74
- className="pl-10" // Indent child items
71
+ className="pl-10"
75
72
  >
76
73
  {child.title}
77
74
  </SidebarMenuItem>
@@ -88,11 +85,14 @@ const AppSidebarContent: React.FC = () => {
88
85
 
89
86
  // Main Layout Component
90
87
  const DashboardLayout: React.FC = () => {
88
+ // 1. STATE: Track if the chat window is open
89
+ const [isChatOpen, setIsChatOpen] = useState(false);
90
+
91
91
  return (
92
92
  <SitemapProvider value={dashboardSitemap}>
93
93
  <PageTitleUpdater />
94
94
  <SidebarProvider>
95
- <div className="flex h-screen bg-background text-foreground">
95
+ <div className="flex h-screen bg-background text-foreground relative">
96
96
  <Sidebar>
97
97
  <AppSidebarContent />
98
98
  </Sidebar>
@@ -102,6 +102,22 @@ const DashboardLayout: React.FC = () => {
102
102
  <Outlet />
103
103
  </main>
104
104
  </div>
105
+
106
+ {/* --- AI COPILOT SECTION --- */}
107
+
108
+ {/* 2. The Widget: Only renders when open */}
109
+ {isChatOpen && (
110
+ <AIChatWidget onClose={() => setIsChatOpen(false)} />
111
+ )}
112
+
113
+ {/* 3. The Button: Fixed to bottom-right */}
114
+ <div className="fixed bottom-6 right-6 z-50">
115
+ <ChatFAB
116
+ onClick={() => setIsChatOpen(!isChatOpen)}
117
+ tooltipContent={isChatOpen ? "Close Assistant" : "Open Bodewell AI"}
118
+ />
119
+ </div>
120
+
105
121
  </div>
106
122
  </SidebarProvider>
107
123
  </SitemapProvider>
@@ -29,7 +29,7 @@ import {
29
29
  import { settingsSitemap } from './settings.sitemap';
30
30
  import rammeLogo from '../../assets/orange.png';
31
31
  import { useAuth } from '../../contexts/AuthContext';
32
- import { appManifest } from '../../config/app.manifest';
32
+ import { appManifest } from '../../config/navigation';
33
33
  import type { ManifestLink } from '../../core/manifest-types';
34
34
  import { SitemapProvider } from '../../contexts/SitemapContext';
35
35
  import PageTitleUpdater from '../../components/PageTitleUpdater';
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod';
2
+
3
+ // ------------------------------------------------------------------
4
+ // 1. Signal Schema (The "Nerve")
5
+ // Defines a single data point (e.g., "Living Room Temp", "Light Switch")
6
+ // ------------------------------------------------------------------
7
+ export const SignalSchema = z.object({
8
+ id: z.string().min(1, "Signal ID is required"),
9
+ label: z.string(),
10
+ description: z.string().optional(),
11
+
12
+ // Classification
13
+ kind: z.enum(['sensor', 'actuator', 'setpoint', 'metric', 'status']),
14
+
15
+ // Data Source Configuration (CRITICAL for the Adapter Factory)
16
+ source: z.enum(['mock', 'mqtt', 'http', 'derived', 'local']),
17
+
18
+ // Source-Specific Config
19
+ topic: z.string().optional(), // For MQTT (e.g., "home/living/temp")
20
+ endpoint: z.string().optional(), // For HTTP (e.g., "/api/v1/weather")
21
+ refreshRate: z.number().default(1000),
22
+
23
+ // Value Configuration
24
+ defaultValue: z.any().optional(),
25
+ unit: z.string().optional(),
26
+
27
+ // Validation
28
+ min: z.number().optional(),
29
+ max: z.number().optional(),
30
+ });
31
+
32
+ export type SignalDefinition = z.infer<typeof SignalSchema>;
33
+
34
+
35
+ // ------------------------------------------------------------------
36
+ // 2. Entity Schema (The "Atom")
37
+ // Defines a grouping of signals into a logical object (e.g., "Smart Thermostat")
38
+ // ------------------------------------------------------------------
39
+ export const EntitySchema = z.object({
40
+ id: z.string(),
41
+ name: z.string(),
42
+ description: z.string().optional(),
43
+
44
+ // Taxonomy
45
+ type: z.string(), // e.g., 'device', 'room', 'service'
46
+ category: z.enum(['hardware', 'software', 'logical']).default('logical'),
47
+
48
+ // The Signals that belong to this entity (References Signal IDs)
49
+ signals: z.array(z.string()),
50
+
51
+ // UI Hints (How should this entity look?)
52
+ ui: z.object({
53
+ icon: z.string().optional(), // IconName
54
+ color: z.string().optional(),
55
+ dashboardComponent: z.string().optional(), // e.g., 'DeviceCard'
56
+ }).optional(),
57
+ });
58
+
59
+ export type EntityDefinition = z.infer<typeof EntitySchema>;
60
+
61
+
62
+ // ------------------------------------------------------------------
63
+ // 3. App Specification (The "Master Plan")
64
+ // This is the JSON structure the App Builder will generate.
65
+ // ------------------------------------------------------------------
66
+ export const AppSpecificationSchema = z.object({
67
+ meta: z.object({
68
+ name: z.string(),
69
+ version: z.string(),
70
+ description: z.string().optional(),
71
+ author: z.string().optional(),
72
+ createdAt: z.string().optional(),
73
+ }),
74
+
75
+ // The Data Layer
76
+ domain: z.object({
77
+ signals: z.array(SignalSchema),
78
+ entities: z.array(EntitySchema),
79
+ }),
80
+
81
+ // Global Config
82
+ config: z.object({
83
+ theme: z.enum(['light', 'dark', 'system']).default('system'),
84
+ mockMode: z.boolean().default(true),
85
+ }),
86
+ });
87
+
88
+ export type AppSpecification = z.infer<typeof AppSpecificationSchema>;
@@ -0,0 +1,22 @@
1
+ // src/types/signal.ts
2
+ /**
3
+ * @file signal.d.ts
4
+ * @description Defines the universal contract for "Live Data" in the Ramme ecosystem.
5
+ *
6
+ * A "Signal" is a single data point that changes over time.
7
+ * unlike a "Record" (which is static database row), a Signal is ephemeral.
8
+ */
9
+
10
+ export type SignalStatus = 'fresh' | 'stale' | 'disconnected' | 'error';
11
+
12
+ export interface Signal<T = any> {
13
+ id: string; // The unique ID (e.g., "temp_01")
14
+ value: T; // The actual data (e.g., 24.5)
15
+ unit?: string; // Optional unit (e.g., "°C")
16
+ timestamp: number; // Unix timestamp of last update
17
+ status: SignalStatus;
18
+ meta?: Record<string, any>; // Extra metadata (e.g., limits)
19
+ }
20
+
21
+ // A simple map for looking up signals by ID
22
+ export type SignalMap = Record<string, Signal>;