@ramme-io/create-app 1.2.1 → 1.2.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 +9 -15
- package/template/package.json +41 -0
- package/template/pkg.json +9 -7
- package/template/src/App.tsx +72 -31
- package/template/src/components/AIChatWidget.tsx +2 -2
- package/template/src/components/AppHeader.tsx +12 -12
- package/template/src/components/AutoForm.tsx +13 -0
- package/template/src/{pages/styleguide → components}/NotFound.tsx +1 -1
- package/template/src/components/PageTitleUpdater.tsx +2 -2
- package/template/src/components/ProtectedRoute.tsx +18 -1
- package/template/src/components/ScrollToTop.tsx +19 -0
- package/template/src/components/dashboard/ChartLine.tsx +28 -0
- package/template/src/components/dashboard/StatCard.tsx +61 -0
- package/template/src/config/app.manifest.ts +3 -1
- package/template/src/config/component-registry.tsx +69 -0
- package/template/src/config/navigation.ts +1 -1
- package/template/src/data/mock-charts.ts +32 -28
- package/template/src/{components → engine/renderers}/DynamicBlock.tsx +28 -8
- package/template/src/engine/renderers/DynamicPage.tsx +150 -0
- package/template/src/engine/runtime/ManifestContext.tsx +79 -0
- package/template/src/{contexts → engine/runtime}/MqttContext.tsx +23 -14
- package/template/src/{contexts → engine/runtime}/SitemapContext.tsx +1 -1
- package/template/src/engine/runtime/data-seeder.ts +47 -0
- package/template/src/{hooks → engine/runtime}/useAction.ts +11 -14
- package/template/src/{hooks → engine/runtime}/useCrudLocalStorage.ts +27 -8
- package/template/src/{hooks → engine/runtime}/useDataQuery.ts +15 -1
- package/template/src/engine/runtime/useDynamicSitemap.tsx +43 -0
- package/template/src/engine/runtime/useJustInTimeSeeder.ts +76 -0
- package/template/src/engine/runtime/useLiveBridge.ts +44 -0
- package/template/src/engine/runtime/useSignal.ts +40 -0
- package/template/src/{generated/hooks.ts → engine/runtime/useSignalStore.ts} +35 -8
- package/template/src/engine/runtime/useWorkflowEngine.ts +89 -0
- package/template/src/{core → engine/types}/manifest-types.ts +35 -3
- package/template/src/{types → engine/validation}/schema.ts +17 -0
- package/template/src/{pages → features/ai/pages}/AiChat.tsx +1 -1
- package/template/src/features/auth/AuthContext.tsx +118 -0
- package/template/src/features/auth/pages/AuthLayout.tsx +55 -0
- package/template/src/features/auth/pages/LoginPage.tsx +106 -0
- package/template/src/features/auth/pages/SignupPage.tsx +96 -0
- package/template/src/{blocks → features/datagrid}/SmartTable.tsx +41 -25
- package/template/src/features/developer/GhostOverlay.tsx +114 -0
- package/template/src/{pages → features/onboarding/pages}/Welcome.tsx +0 -1
- package/template/src/features/overview/index.ts +1 -0
- package/template/src/features/overview/pages/OverviewPage.tsx +127 -0
- package/template/src/{pages → features/playground/pages}/AccountingLedgerPage.tsx +1 -1
- package/template/src/{pages/prototypes → features/playground/pages}/ItemSelectorPage.tsx +1 -1
- package/template/src/{pages/settings → features/settings/pages}/BillingPage.tsx +1 -1
- package/template/src/features/settings/pages/ProfilePage.tsx +153 -0
- package/template/src/{pages/settings → features/settings/pages}/TeamPage.tsx +1 -1
- package/template/src/features/styleguide/Styleguide.tsx +75 -0
- package/template/src/features/users/components/UserDrawer.tsx +138 -0
- package/template/src/features/users/index.ts +2 -0
- package/template/src/features/users/pages/UsersPage.tsx +151 -0
- package/template/src/features/visualizations/SmartChart.tsx +178 -0
- package/template/src/index.css +1 -1
- package/template/src/main.tsx +27 -15
- package/template/src/templates/dashboard/DashboardLayout.tsx +77 -107
- package/template/src/templates/dashboard/dashboard.sitemap.ts +19 -22
- package/template/src/templates/docs/DocsLayout.tsx +49 -38
- package/template/src/templates/docs/docs.sitemap.ts +22 -34
- package/template/src/templates/settings/SettingsLayout.tsx +83 -143
- package/template/src/templates/settings/settings.sitemap.ts +6 -6
- package/template/tailwind.config.cjs +10 -9
- package/template/vite.config.ts +0 -11
- package/template/src/adaptors/.gitkeep +0 -0
- package/template/src/components/LocalSideNav.tsx +0 -120
- package/template/src/components/PageWithSideNav.tsx +0 -69
- package/template/src/components/dev/GhostOverlay.tsx +0 -68
- package/template/src/config/dashboard.layout.ts +0 -110
- package/template/src/contexts/AuthContext.tsx +0 -64
- package/template/src/core/component-registry.tsx +0 -56
- package/template/src/core/data-seeder.ts +0 -35
- package/template/src/data/mockUsers.ts +0 -18
- package/template/src/hooks/useSignal.ts +0 -83
- package/template/src/hooks/useWorkflowEngine.ts +0 -123
- package/template/src/layouts/DataLayout.tsx +0 -37
- package/template/src/layouts/SideNavLayout.tsx +0 -28
- package/template/src/pages/Dashboard.tsx +0 -60
- package/template/src/pages/DataGridPage.tsx +0 -184
- package/template/src/pages/DynamicPage.tsx +0 -95
- package/template/src/pages/LoginPage.tsx +0 -58
- package/template/src/pages/settings/ProfilePage.tsx +0 -10
- package/template/src/pages/styleguide/Styleguide.tsx +0 -40
- package/template/src/templates/docs/pages/Introduction.tsx +0 -13
- package/template/src/types/signal.ts +0 -23
- /package/template/src/{core → engine/renderers}/route-generator.tsx +0 -0
- /package/template/src/{core → engine/types}/sitemap-entry.ts +0 -0
- /package/template/src/{pages → features}/GenericContentPage.tsx +0 -0
- /package/template/src/{hooks → features/assistant}/useMockChat.ts +0 -0
- /package/template/src/{hooks → features/developer}/useDevTools.ts +0 -0
- /package/template/src/{pages → features}/styleguide/sections/charts/ChartsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/colors/ColorsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/elements/ElementsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/feedback/FeedbackSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/forms/FormsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/icons/IconsSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/layout/LayoutSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/navigation/NavigationSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/tables/TablesSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/templates/TemplatesSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/theming/ThemingSection.tsx +0 -0
- /package/template/src/{pages → features}/styleguide/sections/utilities/UtilitiesSection.tsx +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useNavigate, Link } from 'react-router-dom';
|
|
3
|
+
import { useAuth } from '../AuthContext';
|
|
4
|
+
// 1. Remove 'Card' from imports
|
|
5
|
+
import { Button, Alert, Icon, Input } from '@ramme-io/ui';
|
|
6
|
+
|
|
7
|
+
const LoginPage: React.FC = () => {
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
const { login } = useAuth();
|
|
10
|
+
|
|
11
|
+
const [formData, setFormData] = useState({ email: '', password: '' });
|
|
12
|
+
const [error, setError] = useState<string | null>(null);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
16
|
+
setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }));
|
|
17
|
+
if (error) setError(null);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
setIsLoading(true);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
await login(formData.email, formData.password);
|
|
26
|
+
navigate('/dashboard');
|
|
27
|
+
} catch (err: any) {
|
|
28
|
+
setError(err.message || 'Invalid email or password.');
|
|
29
|
+
} finally {
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-4 transition-colors duration-300">
|
|
36
|
+
|
|
37
|
+
{/* 2. REPLACED <Card> with a native <div>
|
|
38
|
+
- Removes conflicting library styles (border-border vs border-t-primary).
|
|
39
|
+
- Adds 'sm:w-[400px]' for better mobile responsiveness.
|
|
40
|
+
- Adds 'bg-card' explicitly for theme support.
|
|
41
|
+
*/}
|
|
42
|
+
<div className="w-full sm:w-[400px] p-8 rounded-xl shadow-2xl border-t-4 border-t-primary bg-card ring-1 ring-black/5 dark:ring-white/10">
|
|
43
|
+
|
|
44
|
+
{/* Header */}
|
|
45
|
+
<div className="text-center mb-8">
|
|
46
|
+
<div className="mx-auto w-12 h-12 bg-primary/10 rounded-xl flex items-center justify-center mb-4 text-primary transition-transform hover:scale-110">
|
|
47
|
+
<Icon name="layout-template" size={24} />
|
|
48
|
+
</div>
|
|
49
|
+
<h1 className="text-2xl font-bold text-foreground tracking-tight">Welcome Back</h1>
|
|
50
|
+
<p className="text-sm text-muted-foreground mt-2">Enter your credentials to access your account</p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Error State */}
|
|
54
|
+
{error && (
|
|
55
|
+
<Alert variant="danger" title="Access Denied" className="mb-6 animate-in fade-in slide-in-from-top-2">
|
|
56
|
+
{error}
|
|
57
|
+
</Alert>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
{/* Form */}
|
|
61
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
62
|
+
<Input
|
|
63
|
+
name="email"
|
|
64
|
+
label="Email Address"
|
|
65
|
+
type="email"
|
|
66
|
+
placeholder="alex@example.com"
|
|
67
|
+
required
|
|
68
|
+
autoComplete="email"
|
|
69
|
+
value={formData.email}
|
|
70
|
+
onChange={handleChange}
|
|
71
|
+
// Explicit background to prevent transparency issues
|
|
72
|
+
className="bg-background"
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
<div className="space-y-1">
|
|
76
|
+
<Input
|
|
77
|
+
name="password"
|
|
78
|
+
label="Password"
|
|
79
|
+
type="password"
|
|
80
|
+
placeholder="••••••••"
|
|
81
|
+
required
|
|
82
|
+
autoComplete="current-password"
|
|
83
|
+
value={formData.password}
|
|
84
|
+
onChange={handleChange}
|
|
85
|
+
className="bg-background"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<Button type="submit" loading={isLoading} className="w-full mt-2 font-medium" size="lg">
|
|
90
|
+
Sign In
|
|
91
|
+
</Button>
|
|
92
|
+
</form>
|
|
93
|
+
|
|
94
|
+
{/* Footer */}
|
|
95
|
+
<div className="mt-8 pt-6 border-t border-border text-center text-sm">
|
|
96
|
+
<span className="text-muted-foreground">Don't have an account? </span>
|
|
97
|
+
<Link to="/signup" className="font-semibold text-primary hover:text-primary/80 transition-colors">
|
|
98
|
+
Create one
|
|
99
|
+
</Link>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default LoginPage;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useNavigate, Link } from 'react-router-dom';
|
|
3
|
+
import { useAuth } from '../AuthContext';
|
|
4
|
+
import { useCrudLocalStorage } from '../../../engine/runtime/useCrudLocalStorage';
|
|
5
|
+
import { Button, Card, FormTemplate, Alert, Icon, type FormField } from '@ramme-io/ui';
|
|
6
|
+
// Import SEED_USERS to safe-guard initialization if the DB is empty
|
|
7
|
+
import { SEED_USERS, type User } from '../../../data/mockData';
|
|
8
|
+
|
|
9
|
+
const SignupPage: React.FC = () => {
|
|
10
|
+
const navigate = useNavigate();
|
|
11
|
+
const { login } = useAuth();
|
|
12
|
+
|
|
13
|
+
// ✅ DATA HOOK: Direct access to the User Data Lake
|
|
14
|
+
// This ensures new users are saved to 'ramme_db_users' alongside seed data
|
|
15
|
+
const { data: users, createItem } = useCrudLocalStorage<User>('ramme_db_users', SEED_USERS);
|
|
16
|
+
|
|
17
|
+
const [error, setError] = useState<string | null>(null);
|
|
18
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
19
|
+
|
|
20
|
+
const handleSignup = async (formData: Record<string, any>) => {
|
|
21
|
+
setIsLoading(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
|
|
24
|
+
// 1. Validation: Check for duplicates
|
|
25
|
+
if (users.some(u => u.email.toLowerCase() === formData.email.toLowerCase())) {
|
|
26
|
+
setError('An account with this email already exists.');
|
|
27
|
+
setIsLoading(false);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// 2. Create the User Record in the Data Lake
|
|
33
|
+
// We cast to 'User' because we handle the 'id' generation in createItem
|
|
34
|
+
const newUser = {
|
|
35
|
+
name: formData.name,
|
|
36
|
+
email: formData.email,
|
|
37
|
+
role: 'viewer', // Default role for new signups
|
|
38
|
+
status: 'active',
|
|
39
|
+
joinedAt: new Date().toISOString().split('T')[0],
|
|
40
|
+
// In a real app, you'd hash the password here.
|
|
41
|
+
// For this prototype, the presence of the record implies access.
|
|
42
|
+
} as User;
|
|
43
|
+
|
|
44
|
+
createItem(newUser);
|
|
45
|
+
|
|
46
|
+
// 3. Auto-Login immediately after creation
|
|
47
|
+
await login(formData.email, formData.password);
|
|
48
|
+
navigate('/dashboard');
|
|
49
|
+
|
|
50
|
+
} catch (err: any) {
|
|
51
|
+
setError(err.message || 'Failed to create account.');
|
|
52
|
+
} finally {
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const formFields: FormField[] = [
|
|
58
|
+
{ name: 'name', label: 'Full Name', type: 'text', placeholder: 'Jane Doe', required: true },
|
|
59
|
+
{ name: 'email', label: 'Email Address', type: 'email', placeholder: 'jane@example.com', required: true },
|
|
60
|
+
{ name: 'password', label: 'Password', type: 'password', placeholder: 'Min. 3 characters', required: true },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
65
|
+
<Card className="w-full max-w-md p-8 shadow-xl border-t-4 border-t-secondary">
|
|
66
|
+
<div className="text-center mb-8">
|
|
67
|
+
<div className="mx-auto w-12 h-12 bg-secondary/10 rounded-full flex items-center justify-center mb-4 text-secondary">
|
|
68
|
+
<Icon name="user-plus" size={24} />
|
|
69
|
+
</div>
|
|
70
|
+
<h1 className="text-3xl font-bold text-foreground">Create Account</h1>
|
|
71
|
+
<p className="text-muted-foreground mt-2">Join the platform to get started</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{error && <Alert variant="danger" title="Registration Failed" className="mb-4">{error}</Alert>}
|
|
75
|
+
|
|
76
|
+
<FormTemplate
|
|
77
|
+
fields={formFields}
|
|
78
|
+
onSubmit={handleSignup}
|
|
79
|
+
>
|
|
80
|
+
<Button type="submit" loading={isLoading} className="w-full mt-4" size="lg" variant="secondary">
|
|
81
|
+
Create Account
|
|
82
|
+
</Button>
|
|
83
|
+
</FormTemplate>
|
|
84
|
+
|
|
85
|
+
<div className="mt-6 text-center text-sm">
|
|
86
|
+
<span className="text-muted-foreground">Already have an account? </span>
|
|
87
|
+
<Link to="/login" className="font-semibold text-primary hover:underline">
|
|
88
|
+
Sign In
|
|
89
|
+
</Link>
|
|
90
|
+
</div>
|
|
91
|
+
</Card>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default SignupPage;
|
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
type ColDef,
|
|
10
10
|
type GridApi
|
|
11
11
|
} from '@ramme-io/ui';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { useJustInTimeSeeder } from '../../engine/runtime/useJustInTimeSeeder';
|
|
13
|
+
import { getResourceMeta } from '../../data/mockData';
|
|
14
|
+
import { AutoForm } from '../../components/AutoForm';
|
|
15
|
+
import { useCrudLocalStorage } from '../../engine/runtime/useCrudLocalStorage';
|
|
16
|
+
import { useManifest } from '../../engine/runtime/ManifestContext';
|
|
17
|
+
import type { ResourceDefinition } from '../../engine/validation/schema';
|
|
15
18
|
|
|
16
19
|
interface SmartTableProps {
|
|
17
20
|
dataId: string;
|
|
@@ -21,16 +24,40 @@ interface SmartTableProps {
|
|
|
21
24
|
|
|
22
25
|
export const SmartTable: React.FC<SmartTableProps> = ({
|
|
23
26
|
dataId,
|
|
24
|
-
title
|
|
25
|
-
initialFilter
|
|
26
|
-
}) => {
|
|
27
|
+
title}) => {
|
|
27
28
|
const { addToast } = useToast();
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const manifest = useManifest();
|
|
31
|
+
|
|
32
|
+
// ✅ FIX: Normalize 'meta' to always match 'ResourceDefinition'
|
|
33
|
+
const meta = useMemo<ResourceDefinition | null>(() => {
|
|
34
|
+
// 1. Try Dynamic Manifest (Preview Mode)
|
|
35
|
+
const dynamicResource = manifest.resources?.find((r: ResourceDefinition) => r.id === dataId);
|
|
36
|
+
|
|
37
|
+
if (dynamicResource) {
|
|
38
|
+
return dynamicResource;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Try Static Data (Deployed Mode)
|
|
42
|
+
const staticMeta = getResourceMeta(dataId);
|
|
43
|
+
|
|
44
|
+
if (staticMeta) {
|
|
45
|
+
// ⚠️ Type Patch: Inject 'id' so it satisfies ResourceDefinition
|
|
46
|
+
// The static file uses the object key as the ID, but the type expects it inline.
|
|
47
|
+
return {
|
|
48
|
+
...staticMeta,
|
|
49
|
+
id: dataId,
|
|
50
|
+
// Ensure 'type' strings from mockData match the Zod enum if needed,
|
|
51
|
+
// but typically 'text' | 'number' overlaps fine.
|
|
52
|
+
} as unknown as ResourceDefinition;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}, [manifest, dataId]);
|
|
57
|
+
|
|
58
|
+
// ✅ JIT Seeder now receives a valid ResourceDefinition
|
|
59
|
+
const seedData = useJustInTimeSeeder(dataId, meta);
|
|
32
60
|
|
|
33
|
-
// We use the CRUD hook to persist changes to localStorage
|
|
34
61
|
const {
|
|
35
62
|
data: rowData,
|
|
36
63
|
createItem,
|
|
@@ -38,14 +65,14 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
38
65
|
deleteItem
|
|
39
66
|
} = useCrudLocalStorage<any>(`ramme_db_${dataId}`, seedData);
|
|
40
67
|
|
|
41
|
-
//
|
|
68
|
+
// --- UI STATE ---
|
|
42
69
|
const [isEditOpen, setIsEditOpen] = useState(false);
|
|
43
70
|
const [currentRecord, setCurrentRecord] = useState<any>(null);
|
|
44
71
|
const [gridApi, setGridApi] = useState<GridApi | null>(null);
|
|
45
72
|
const [selectedRows, setSelectedRows] = useState<any[]>([]);
|
|
46
73
|
const [quickFilterText, setQuickFilterText] = useState('');
|
|
47
74
|
|
|
48
|
-
//
|
|
75
|
+
// --- COLUMN DEFINITIONS ---
|
|
49
76
|
const columns = useMemo<ColDef[]>(() => {
|
|
50
77
|
if (!meta?.fields) return [];
|
|
51
78
|
|
|
@@ -59,7 +86,6 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
59
86
|
flex: 1,
|
|
60
87
|
};
|
|
61
88
|
|
|
62
|
-
// Smart Formatting based on type
|
|
63
89
|
if (f.type === 'currency') {
|
|
64
90
|
col.valueFormatter = (p: any) => p.value ? `$${Number(p.value).toLocaleString()}` : '';
|
|
65
91
|
}
|
|
@@ -86,14 +112,12 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
86
112
|
return col;
|
|
87
113
|
});
|
|
88
114
|
|
|
89
|
-
// Add Checkbox Selection to the first column
|
|
90
115
|
if (generatedCols.length > 0) {
|
|
91
116
|
generatedCols[0].headerCheckboxSelection = true;
|
|
92
117
|
generatedCols[0].checkboxSelection = true;
|
|
93
118
|
generatedCols[0].minWidth = 180;
|
|
94
119
|
}
|
|
95
120
|
|
|
96
|
-
// Add Actions Column
|
|
97
121
|
generatedCols.push({
|
|
98
122
|
headerName: "Actions",
|
|
99
123
|
field: "id",
|
|
@@ -111,7 +135,7 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
111
135
|
return generatedCols;
|
|
112
136
|
}, [meta]);
|
|
113
137
|
|
|
114
|
-
//
|
|
138
|
+
// --- HANDLERS ---
|
|
115
139
|
const onGridReady = useCallback((params: any) => {
|
|
116
140
|
setGridApi(params.api);
|
|
117
141
|
}, []);
|
|
@@ -142,14 +166,10 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
142
166
|
setIsEditOpen(false);
|
|
143
167
|
};
|
|
144
168
|
|
|
145
|
-
// --- RENDER ---
|
|
146
169
|
return (
|
|
147
170
|
<Card className="flex flex-col h-[600px] border border-border shadow-sm overflow-hidden bg-card">
|
|
148
171
|
|
|
149
|
-
{/* HEADER TOOLBAR */}
|
|
150
172
|
<div className="p-4 border-b border-border flex justify-between items-center gap-4 bg-muted/5">
|
|
151
|
-
|
|
152
|
-
{/* Left: Title or Bulk Actions */}
|
|
153
173
|
{selectedRows.length > 0 ? (
|
|
154
174
|
<div className="flex items-center gap-3 animate-in fade-in slide-in-from-left-2 duration-200">
|
|
155
175
|
<span className="bg-primary text-primary-foreground text-xs font-bold px-2 py-1 rounded-md">
|
|
@@ -175,7 +195,6 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
175
195
|
</div>
|
|
176
196
|
)}
|
|
177
197
|
|
|
178
|
-
{/* Right: Actions & Filter */}
|
|
179
198
|
<div className="flex items-center gap-2">
|
|
180
199
|
<div className="w-64">
|
|
181
200
|
<SearchInput
|
|
@@ -183,8 +202,7 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
183
202
|
value={quickFilterText}
|
|
184
203
|
onChange={(e) => {
|
|
185
204
|
setQuickFilterText(e.target.value);
|
|
186
|
-
gridApi?.
|
|
187
|
-
}}
|
|
205
|
+
gridApi?.updateGridOptions({ quickFilterText: e.target.value }); }}
|
|
188
206
|
/>
|
|
189
207
|
</div>
|
|
190
208
|
<div className="h-6 w-px bg-border mx-1" />
|
|
@@ -194,7 +212,6 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
194
212
|
</div>
|
|
195
213
|
</div>
|
|
196
214
|
|
|
197
|
-
{/* AG GRID */}
|
|
198
215
|
<div className="flex-1 w-full bg-card relative">
|
|
199
216
|
<DataTable
|
|
200
217
|
rowData={rowData}
|
|
@@ -210,7 +227,6 @@ export const SmartTable: React.FC<SmartTableProps> = ({
|
|
|
210
227
|
/>
|
|
211
228
|
</div>
|
|
212
229
|
|
|
213
|
-
{/* EDIT DRAWER */}
|
|
214
230
|
<AutoForm
|
|
215
231
|
isOpen={isEditOpen}
|
|
216
232
|
onClose={() => setIsEditOpen(false)}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Icon, cn } from '@ramme-io/ui';
|
|
3
|
+
|
|
4
|
+
interface GhostOverlayProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
componentId: string;
|
|
7
|
+
componentType: string;
|
|
8
|
+
signalId?: string;
|
|
9
|
+
isActive?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const GhostOverlay: React.FC<GhostOverlayProps> = ({
|
|
13
|
+
children,
|
|
14
|
+
componentId,
|
|
15
|
+
componentType,
|
|
16
|
+
signalId,
|
|
17
|
+
isActive = false,
|
|
18
|
+
}) => {
|
|
19
|
+
const [isRemoteSelected, setIsRemoteSelected] = useState(false);
|
|
20
|
+
|
|
21
|
+
// --- 1. INBOUND BRIDGE: Listen for Builder Selections ---
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!isActive) return;
|
|
24
|
+
|
|
25
|
+
const handleMessage = (event: MessageEvent) => {
|
|
26
|
+
// Security Check: In prod, you'd check event.origin
|
|
27
|
+
const { type, payload } = event.data || {};
|
|
28
|
+
|
|
29
|
+
if (type === 'RAMME_HIGHLIGHT_BLOCK') {
|
|
30
|
+
// If the Builder says "Highlight Block X", checking if it's us
|
|
31
|
+
setIsRemoteSelected(payload?.blockId === componentId);
|
|
32
|
+
|
|
33
|
+
// Optional: Scroll into view if selected remotely
|
|
34
|
+
if (payload?.blockId === componentId) {
|
|
35
|
+
document.getElementById(`ghost-${componentId}`)?.scrollIntoView({
|
|
36
|
+
behavior: 'smooth',
|
|
37
|
+
block: 'center'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
window.addEventListener('message', handleMessage);
|
|
44
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
45
|
+
}, [isActive, componentId]);
|
|
46
|
+
|
|
47
|
+
if (!isActive) {
|
|
48
|
+
return <>{children}</>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- 2. OUTBOUND BRIDGE: Signal the Builder ---
|
|
52
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
53
|
+
// 🛑 Zero Jank: Stop the click from triggering links/buttons
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
|
|
57
|
+
// 🚀 Signal the Parent
|
|
58
|
+
window.parent.postMessage({
|
|
59
|
+
type: 'RAMME_SELECT_BLOCK',
|
|
60
|
+
payload: { blockId: componentId, type: componentType }
|
|
61
|
+
}, '*');
|
|
62
|
+
|
|
63
|
+
// Optimistic UI update (optional, usually we wait for parent to confirm)
|
|
64
|
+
setIsRemoteSelected(true);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
id={`ghost-${componentId}`}
|
|
70
|
+
className="relative group isolate" // isolate creates new stacking context
|
|
71
|
+
onClick={handleClick}
|
|
72
|
+
>
|
|
73
|
+
{/* --- THE GHOST LAYER --- */}
|
|
74
|
+
<div className={cn(
|
|
75
|
+
"absolute inset-0 z-50 rounded-lg transition-all duration-200 pointer-events-auto cursor-pointer",
|
|
76
|
+
// Default State (Hover)
|
|
77
|
+
"hover:ring-2 hover:ring-primary/50 hover:bg-primary/5",
|
|
78
|
+
// Selected State (Remote or Local)
|
|
79
|
+
isRemoteSelected ? "ring-2 ring-primary bg-primary/10 shadow-[0_0_0_4px_rgba(var(--app-primary-color),0.1)]" : "border-2 border-dashed border-transparent"
|
|
80
|
+
)} />
|
|
81
|
+
|
|
82
|
+
{/* --- INFO BADGE (Top Left) --- */}
|
|
83
|
+
<div className={cn(
|
|
84
|
+
"absolute top-0 left-0 z-50 p-2 transform -translate-y-1/2 transition-opacity duration-200",
|
|
85
|
+
isRemoteSelected || "group-hover:opacity-100 opacity-0"
|
|
86
|
+
)}>
|
|
87
|
+
<div className="flex items-center gap-2 bg-foreground text-background text-[10px] font-mono py-1 px-2 rounded shadow-sm border border-border">
|
|
88
|
+
<Icon name="box" size={10} />
|
|
89
|
+
<span className="font-bold">{componentType}</span>
|
|
90
|
+
<span className="opacity-50">#{componentId.slice(0, 4)}</span>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* --- DATA WIRE (Bottom Right) --- */}
|
|
95
|
+
{signalId && (
|
|
96
|
+
<div className={cn(
|
|
97
|
+
"absolute bottom-0 right-0 z-50 p-2 transform translate-y-1/2 transition-opacity duration-200",
|
|
98
|
+
isRemoteSelected || "group-hover:opacity-100 opacity-0"
|
|
99
|
+
)}>
|
|
100
|
+
<div className="flex items-center gap-1.5 bg-accent text-accent-foreground text-[10px] font-mono py-1 px-2 rounded shadow-sm border border-accent/20">
|
|
101
|
+
<Icon name="activity" size={10} />
|
|
102
|
+
<span>{signalId}</span>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{/* --- CONTENT (Frozen) --- */}
|
|
108
|
+
{/* We disable pointer events on children so buttons don't fire while Ghost is active */}
|
|
109
|
+
<div className="pointer-events-none select-none">
|
|
110
|
+
{children}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OverviewPage } from './pages/OverviewPage';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
PageHeader,
|
|
5
|
+
Card,
|
|
6
|
+
BarChart,
|
|
7
|
+
Button,
|
|
8
|
+
Icon,
|
|
9
|
+
Badge
|
|
10
|
+
} from '@ramme-io/ui';
|
|
11
|
+
|
|
12
|
+
// 1. REMOVE: The Zombie Service Import
|
|
13
|
+
// import { userService } from '../../users';
|
|
14
|
+
|
|
15
|
+
// 2. ADD: The Engine Hook & Shared Data
|
|
16
|
+
// (Adjust path '../../engine/...' if needed based on your folder structure)
|
|
17
|
+
import { useCrudLocalStorage } from '../../../engine/runtime/useCrudLocalStorage';
|
|
18
|
+
import { SEED_USERS, type User } from '../../../data/mockData';
|
|
19
|
+
|
|
20
|
+
// Mock Data for Chart
|
|
21
|
+
const revenueData = [
|
|
22
|
+
{ name: 'Mon', revenue: 4000, cost: 2400 },
|
|
23
|
+
{ name: 'Tue', revenue: 3000, cost: 1398 },
|
|
24
|
+
{ name: 'Wed', revenue: 2000, cost: 9800 },
|
|
25
|
+
{ name: 'Thu', revenue: 2780, cost: 3908 },
|
|
26
|
+
{ name: 'Fri', revenue: 1890, cost: 4800 },
|
|
27
|
+
{ name: 'Sat', revenue: 2390, cost: 3800 },
|
|
28
|
+
{ name: 'Sun', revenue: 3490, cost: 4300 },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// Recreating the StatCard locally (since the old one was custom)
|
|
32
|
+
const StatCard = ({ title, value, trend, icon, onClick, className }: any) => (
|
|
33
|
+
<Card
|
|
34
|
+
className={`p-6 flex items-center justify-between space-x-4 transition-all hover:border-primary/50 ${className || ''}`}
|
|
35
|
+
onClick={onClick}
|
|
36
|
+
>
|
|
37
|
+
<div>
|
|
38
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
39
|
+
<h3 className="text-2xl font-bold mt-1">{value}</h3>
|
|
40
|
+
<div className={`flex items-center text-xs mt-1 ${trend > 0 ? 'text-green-500' : 'text-red-500'}`}>
|
|
41
|
+
<Icon name={trend > 0 ? 'trending-up' : 'trending-down'} className="h-3 w-3 mr-1" />
|
|
42
|
+
{Math.abs(trend)}% from last month
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="p-3 bg-muted/50 rounded-full">
|
|
46
|
+
<Icon name={icon} className="h-5 w-5 text-primary" />
|
|
47
|
+
</div>
|
|
48
|
+
</Card>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const OverviewPage: React.FC = () => {
|
|
52
|
+
const navigate = useNavigate();
|
|
53
|
+
|
|
54
|
+
// 3. REPLACE: Manual fetching with the Reactive Engine
|
|
55
|
+
// This automatically connects to 'ramme_db_users' and keeps the count live
|
|
56
|
+
const { data: users } = useCrudLocalStorage<User>('ramme_db_users', SEED_USERS);
|
|
57
|
+
|
|
58
|
+
// Calculate count directly from the hook data
|
|
59
|
+
const userCount = users.length;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-8 pb-10">
|
|
63
|
+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
64
|
+
<div>
|
|
65
|
+
<PageHeader
|
|
66
|
+
title="Dashboard"
|
|
67
|
+
description="Overview of your application performance."
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="flex gap-2">
|
|
71
|
+
<Button variant="outline" iconLeft="download">Export</Button>
|
|
72
|
+
<Button iconLeft="plus">New Report</Button>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
77
|
+
<StatCard
|
|
78
|
+
title="Total Users"
|
|
79
|
+
value={userCount} // ✅ Uses live data from Data Lake
|
|
80
|
+
trend={+12.5}
|
|
81
|
+
icon="users"
|
|
82
|
+
className="cursor-pointer bg-primary/5 border-primary/20"
|
|
83
|
+
onClick={() => navigate('/dashboard/users')}
|
|
84
|
+
/>
|
|
85
|
+
<StatCard title="Total Revenue" value="$45,231" trend={+20.1} icon="dollar-sign" />
|
|
86
|
+
<StatCard title="Sales" value="+12,234" trend={+19} icon="credit-card" />
|
|
87
|
+
<StatCard title="Active Now" value="+573" trend={-4} icon="activity" />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Main Content Grid */}
|
|
91
|
+
<div className="grid grid-cols-1 lg:grid-cols-7 gap-8">
|
|
92
|
+
<Card className="lg:col-span-4 p-6">
|
|
93
|
+
<div className="flex items-center justify-between mb-6">
|
|
94
|
+
<h3 className="font-semibold text-lg">Revenue Overview</h3>
|
|
95
|
+
<Badge variant="outline">Weekly</Badge>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="h-[350px] w-full">
|
|
98
|
+
<BarChart
|
|
99
|
+
data={revenueData}
|
|
100
|
+
dataKeyX="name"
|
|
101
|
+
barKeys={['revenue', 'cost']}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
</Card>
|
|
105
|
+
|
|
106
|
+
<Card className="lg:col-span-3 p-6 flex flex-col">
|
|
107
|
+
<h3 className="font-semibold text-lg mb-4">Recent Activity</h3>
|
|
108
|
+
<div className="space-y-6 overflow-y-auto pr-2">
|
|
109
|
+
{[1, 2, 3].map((i) => (
|
|
110
|
+
<div key={i} className="flex items-start gap-4">
|
|
111
|
+
<span className="relative flex h-2 w-2 mt-2">
|
|
112
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
|
113
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
|
114
|
+
</span>
|
|
115
|
+
<div className="space-y-1">
|
|
116
|
+
<p className="text-sm font-medium leading-none">System Alert</p>
|
|
117
|
+
<p className="text-xs text-muted-foreground">Database backup completed.</p>
|
|
118
|
+
<p className="text-xs text-muted-foreground pt-1">Just now</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
))}
|
|
122
|
+
</div>
|
|
123
|
+
</Card>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
// 1. Import the specific event type for cell value changes
|
|
9
9
|
type CellValueChangedEvent,
|
|
10
10
|
} from '@ramme-io/ui';
|
|
11
|
-
import { mockLedgerData, type LedgerEntry } from '
|
|
11
|
+
import { mockLedgerData, type LedgerEntry } from '../../../data/mockLedger';
|
|
12
12
|
|
|
13
13
|
// --- Custom Renderer for Variance ---
|
|
14
14
|
// 2. Correctly type the component and ensure it returns a ReactNode (JSX or null)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
// --- 1. Import the new generic page ---
|
|
3
|
-
import GenericContentPage from '
|
|
3
|
+
import GenericContentPage from '../../GenericContentPage';
|
|
4
4
|
|
|
5
5
|
// A simple page component for settings
|
|
6
6
|
const BillingPage: React.FC = () => {
|