@llmnative/react 1.19.0 → 1.19.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.
- package/README.md +4 -2
- package/dist/index.css +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -139
- package/dist/index.mjs.map +1 -1
- package/dist/logHeader-Busbc8OO.mjs.map +1 -1
- package/dist/logHeader-CUsdmGAo.js +2 -0
- package/dist/{logHeader-CbzOhx-u.js.map → logHeader-CUsdmGAo.js.map} +1 -1
- package/dist/vite.js +2 -2
- package/dist/vite.js.map +1 -1
- package/dist/vite.mjs +1 -1
- package/dist/vite.mjs.map +1 -1
- package/llms-full.txt +1140 -0
- package/llms.txt +82 -0
- package/package.json +48 -43
- package/scripts/cli/setup-project.js +60 -36
- package/templates/_shared/layouts/Default.tsx +23 -0
- package/templates/_shared/sections/Footer.tsx +9 -0
- package/templates/_shared/sections/Header.tsx +24 -0
- package/templates/_shared/sections/PageHeader.tsx +10 -0
- package/templates/_shared/sections/Sidebar.tsx +22 -0
- package/templates/admin/conf/menu.ts +14 -0
- package/templates/admin/data/mockData.ts +21 -0
- package/templates/admin/pages/home/HomePage.tsx +32 -0
- package/templates/admin/pages/roles/RolesPage.tsx +25 -0
- package/templates/admin/pages/settings/SettingsPage.tsx +24 -0
- package/templates/admin/pages/users/UsersPage.tsx +54 -0
- package/templates/blank/conf/menu.ts +8 -0
- package/templates/blank/data/mockData.ts +6 -0
- package/templates/blank/pages/home/HomePage.tsx +25 -0
- package/templates/crm/conf/menu.ts +14 -0
- package/templates/crm/data/mockData.ts +24 -0
- package/templates/crm/pages/companies/CompaniesPage.tsx +42 -0
- package/templates/crm/pages/contacts/ContactsPage.tsx +48 -0
- package/templates/crm/pages/deals/DealsPage.tsx +61 -0
- package/templates/crm/pages/home/HomePage.tsx +35 -0
- package/templates/inventory/conf/menu.ts +12 -0
- package/templates/inventory/data/mockData.ts +16 -0
- package/templates/inventory/pages/categories/CategoriesPage.tsx +23 -0
- package/templates/inventory/pages/home/HomePage.tsx +35 -0
- package/templates/inventory/pages/products/ProductsPage.tsx +56 -0
- package/templates/project/conf/menu.ts +14 -0
- package/templates/project/data/mockData.ts +21 -0
- package/templates/project/pages/home/HomePage.tsx +33 -0
- package/templates/project/pages/projects/ProjectsPage.tsx +50 -0
- package/templates/project/pages/tasks/TasksPage.tsx +67 -0
- package/templates/project/pages/team/TeamPage.tsx +25 -0
- package/dist/logHeader-CbzOhx-u.js +0 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Badge, Input, Select, type BadgeType } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const roleOptions = [
|
|
5
|
+
{ label: 'Administrator', value: 'admin' },
|
|
6
|
+
{ label: 'Editor', value: 'editor' },
|
|
7
|
+
{ label: 'Viewer', value: 'viewer' },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const statusOptions = [
|
|
11
|
+
{ label: 'Active', value: 'active' },
|
|
12
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const roleVariant: Record<string, BadgeType> = {
|
|
16
|
+
admin: 'danger',
|
|
17
|
+
editor: 'primary',
|
|
18
|
+
viewer: 'secondary',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function UsersPage() {
|
|
22
|
+
return (
|
|
23
|
+
<Grid
|
|
24
|
+
path="/users"
|
|
25
|
+
columns={[
|
|
26
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
27
|
+
{ key: 'email', label: 'Email' },
|
|
28
|
+
{ key: 'role', label: 'Role',
|
|
29
|
+
render: ({ value }) => (
|
|
30
|
+
<Badge variant={roleVariant[String(value)] ?? 'secondary'}>{String(value)}</Badge>
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
{ key: 'status', label: 'Status',
|
|
34
|
+
render: ({ value }) => (
|
|
35
|
+
<Badge variant={value === 'active' ? 'success' : 'secondary'}>{String(value)}</Badge>
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
{ key: 'createdAt', label: 'Created', render: 'date' },
|
|
39
|
+
]}
|
|
40
|
+
actions={['add', 'edit', 'delete']}
|
|
41
|
+
view="table"
|
|
42
|
+
sortable
|
|
43
|
+
pagination={{ limit: 25 }}
|
|
44
|
+
form={
|
|
45
|
+
<>
|
|
46
|
+
<Input name="name" label="Full name" required />
|
|
47
|
+
<Input name="email" label="Email" type="email" required />
|
|
48
|
+
<Select name="role" label="Role" options={roleOptions} />
|
|
49
|
+
<Select name="status" label="Status" options={statusOptions} />
|
|
50
|
+
</>
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
export default function HomePage() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="mx-auto max-w-5xl space-y-8 px-2 py-4">
|
|
7
|
+
<div className="space-y-1">
|
|
8
|
+
<h1 className="text-2xl font-semibold">Welcome to [projectname]</h1>
|
|
9
|
+
<p className="text-sm text-muted-foreground">
|
|
10
|
+
Scaffolded with LLM Native. Edit this page at <code>src/pages/home/HomePage.tsx</code>.
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<Grid
|
|
15
|
+
path="/tasks"
|
|
16
|
+
columns={[
|
|
17
|
+
{ key: 'title', label: 'Task' },
|
|
18
|
+
{ key: 'status', label: 'Status' },
|
|
19
|
+
]}
|
|
20
|
+
actions={['add', 'edit', 'delete']}
|
|
21
|
+
view="table"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MenuConfig } from '@llmnative/react';
|
|
2
|
+
import HomePage from '../pages/home/HomePage';
|
|
3
|
+
import ContactsPage from '../pages/contacts/ContactsPage';
|
|
4
|
+
import CompaniesPage from '../pages/companies/CompaniesPage';
|
|
5
|
+
import DealsPage from '../pages/deals/DealsPage';
|
|
6
|
+
|
|
7
|
+
export const menu: MenuConfig = {
|
|
8
|
+
main: [
|
|
9
|
+
{ path: '/', title: 'Dashboard', icon: 'layout-dashboard', page: HomePage, end: true },
|
|
10
|
+
{ path: '/contacts', title: 'Contacts', icon: 'users', page: ContactsPage, group: 'Sales' },
|
|
11
|
+
{ path: '/companies', title: 'Companies', icon: 'building-2', page: CompaniesPage, group: 'Sales' },
|
|
12
|
+
{ path: '/deals', title: 'Deals', icon: 'handshake', page: DealsPage, group: 'Sales' },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const mockData = {
|
|
2
|
+
'/contacts': {
|
|
3
|
+
'c-1': { name: 'Alice Martin', email: 'alice@acme.com', company: 'Acme Corp', status: 'active', phone: '+1 555 0101' },
|
|
4
|
+
'c-2': { name: 'Bob Chen', email: 'bob@globex.com', company: 'Globex', status: 'active', phone: '+1 555 0102' },
|
|
5
|
+
'c-3': { name: 'Clara Rossi', email: 'clara@initech.it', company: 'Initech', status: 'inactive', phone: '+39 02 0103' },
|
|
6
|
+
'c-4': { name: 'David Park', email: 'd.park@umbrella.co', company: 'Umbrella', status: 'active', phone: '+1 555 0104' },
|
|
7
|
+
'c-5': { name: 'Eva Müller', email: 'eva@hooli.de', company: 'Hooli', status: 'lead', phone: '+49 89 0105' },
|
|
8
|
+
},
|
|
9
|
+
'/companies': {
|
|
10
|
+
'co-1': { name: 'Acme Corp', industry: 'Manufacturing', website: 'acme.com', employees: 320 },
|
|
11
|
+
'co-2': { name: 'Globex', industry: 'Technology', website: 'globex.com', employees: 85 },
|
|
12
|
+
'co-3': { name: 'Initech', industry: 'IT Services', website: 'initech.it', employees: 50 },
|
|
13
|
+
'co-4': { name: 'Umbrella', industry: 'Pharma', website: 'umbrella.co', employees: 1200 },
|
|
14
|
+
'co-5': { name: 'Hooli', industry: 'Technology', website: 'hooli.de', employees: 600 },
|
|
15
|
+
},
|
|
16
|
+
'/deals': {
|
|
17
|
+
'd-1': { title: 'Enterprise license', contact: 'Alice Martin', value: 12000, stage: 'qualified', closedAt: '' },
|
|
18
|
+
'd-2': { title: 'SaaS subscription', contact: 'Bob Chen', value: 4800, stage: 'prospect', closedAt: '' },
|
|
19
|
+
'd-3': { title: 'Consulting package', contact: 'Clara Rossi', value: 7500, stage: 'proposal', closedAt: '' },
|
|
20
|
+
'd-4': { title: 'Support renewal', contact: 'David Park', value: 2400, stage: 'won', closedAt: '2025-03-15' },
|
|
21
|
+
'd-5': { title: 'Migration project', contact: 'Eva Müller', value: 18000, stage: 'prospect', closedAt: '' },
|
|
22
|
+
'd-6': { title: 'Hardware bundle', contact: 'Alice Martin', value: 5600, stage: 'lost', closedAt: '2025-02-28' },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Input, Select } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const industryOptions = [
|
|
5
|
+
{ label: 'Technology', value: 'Technology' },
|
|
6
|
+
{ label: 'Manufacturing', value: 'Manufacturing' },
|
|
7
|
+
{ label: 'IT Services', value: 'IT Services' },
|
|
8
|
+
{ label: 'Pharma', value: 'Pharma' },
|
|
9
|
+
{ label: 'Finance', value: 'Finance' },
|
|
10
|
+
{ label: 'Retail', value: 'Retail' },
|
|
11
|
+
{ label: 'Other', value: 'Other' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export default function CompaniesPage() {
|
|
15
|
+
return (
|
|
16
|
+
<Grid
|
|
17
|
+
path="/companies"
|
|
18
|
+
columns={[
|
|
19
|
+
{ key: 'name', label: 'Company', sortable: true },
|
|
20
|
+
{ key: 'industry', label: 'Industry', sortable: true },
|
|
21
|
+
{ key: 'website', label: 'Website' },
|
|
22
|
+
{
|
|
23
|
+
key: 'employees',
|
|
24
|
+
label: 'Employees',
|
|
25
|
+
render: ({ value }) => value ? Number(value).toLocaleString() : '-',
|
|
26
|
+
},
|
|
27
|
+
]}
|
|
28
|
+
actions={['add', 'edit', 'delete']}
|
|
29
|
+
view="table"
|
|
30
|
+
sortable
|
|
31
|
+
pagination={{ limit: 20 }}
|
|
32
|
+
form={
|
|
33
|
+
<>
|
|
34
|
+
<Input name="name" label="Company name" required />
|
|
35
|
+
<Select name="industry" label="Industry" options={industryOptions} />
|
|
36
|
+
<Input name="website" label="Website" />
|
|
37
|
+
<Input name="employees" label="Employees" type="number" />
|
|
38
|
+
</>
|
|
39
|
+
}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Badge, Input, Select, type BadgeType } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const statusOptions = [
|
|
5
|
+
{ label: 'Active', value: 'active' },
|
|
6
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
7
|
+
{ label: 'Lead', value: 'lead' },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const statusVariant: Record<string, BadgeType> = {
|
|
11
|
+
active: 'success',
|
|
12
|
+
inactive: 'secondary',
|
|
13
|
+
lead: 'warning',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default function ContactsPage() {
|
|
17
|
+
return (
|
|
18
|
+
<Grid
|
|
19
|
+
path="/contacts"
|
|
20
|
+
columns={[
|
|
21
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
22
|
+
{ key: 'email', label: 'Email' },
|
|
23
|
+
{ key: 'company', label: 'Company', sortable: true },
|
|
24
|
+
{ key: 'phone', label: 'Phone' },
|
|
25
|
+
{
|
|
26
|
+
key: 'status',
|
|
27
|
+
label: 'Status',
|
|
28
|
+
render: ({ value }) => (
|
|
29
|
+
<Badge variant={statusVariant[String(value)] ?? 'secondary'}>{String(value)}</Badge>
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
]}
|
|
33
|
+
actions={['add', 'edit', 'delete']}
|
|
34
|
+
view="table"
|
|
35
|
+
sortable
|
|
36
|
+
pagination={{ limit: 20 }}
|
|
37
|
+
form={
|
|
38
|
+
<>
|
|
39
|
+
<Input name="name" label="Full name" required />
|
|
40
|
+
<Input name="email" label="Email" type="email" />
|
|
41
|
+
<Input name="phone" label="Phone" />
|
|
42
|
+
<Input name="company" label="Company" />
|
|
43
|
+
<Select name="status" label="Status" options={statusOptions} />
|
|
44
|
+
</>
|
|
45
|
+
}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Badge, Input, Select, type BadgeType } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const stageOptions = [
|
|
5
|
+
{ label: 'Prospect', value: 'prospect' },
|
|
6
|
+
{ label: 'Qualified', value: 'qualified' },
|
|
7
|
+
{ label: 'Proposal', value: 'proposal' },
|
|
8
|
+
{ label: 'Won', value: 'won' },
|
|
9
|
+
{ label: 'Lost', value: 'lost' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const stageVariant: Record<string, BadgeType> = {
|
|
13
|
+
prospect: 'secondary',
|
|
14
|
+
qualified: 'info',
|
|
15
|
+
proposal: 'warning',
|
|
16
|
+
won: 'success',
|
|
17
|
+
lost: 'danger',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function DealsPage() {
|
|
21
|
+
return (
|
|
22
|
+
<Grid
|
|
23
|
+
path="/deals"
|
|
24
|
+
columns={[
|
|
25
|
+
{ key: 'title', label: 'Deal', sortable: true },
|
|
26
|
+
{ key: 'contact', label: 'Contact' },
|
|
27
|
+
{
|
|
28
|
+
key: 'value',
|
|
29
|
+
label: 'Value',
|
|
30
|
+
render: ({ value }) => `$ ${Number(value).toLocaleString()}`,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'stage',
|
|
34
|
+
label: 'Stage',
|
|
35
|
+
render: ({ value }) => (
|
|
36
|
+
<Badge variant={stageVariant[String(value)] ?? 'secondary'}>{String(value)}</Badge>
|
|
37
|
+
),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: 'closedAt',
|
|
41
|
+
label: 'Closed',
|
|
42
|
+
render: ({ value }) => (value ? String(value) : '-'),
|
|
43
|
+
},
|
|
44
|
+
]}
|
|
45
|
+
actions={['add', 'edit', 'delete']}
|
|
46
|
+
view="table"
|
|
47
|
+
groupBy="stage"
|
|
48
|
+
sortable
|
|
49
|
+
pagination={{ limit: 25 }}
|
|
50
|
+
form={
|
|
51
|
+
<>
|
|
52
|
+
<Input name="title" label="Deal title" required />
|
|
53
|
+
<Input name="contact" label="Contact name" />
|
|
54
|
+
<Input name="value" label="Value ($)" type="number" />
|
|
55
|
+
<Select name="stage" label="Stage" options={stageOptions} />
|
|
56
|
+
<Input name="closedAt" label="Closed date" type="date" />
|
|
57
|
+
</>
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '@llmnative/react';
|
|
3
|
+
import { mockData } from '../../data/mockData';
|
|
4
|
+
|
|
5
|
+
function StatCard({ label, value, sub }: { label: string; value: number | string; sub?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<Card title={label}>
|
|
8
|
+
<p className="text-2xl font-semibold">{value}</p>
|
|
9
|
+
{sub && <p className="text-xs text-muted-foreground">{sub}</p>}
|
|
10
|
+
</Card>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function HomePage() {
|
|
15
|
+
const contacts = Object.values(mockData['/contacts']);
|
|
16
|
+
const deals = Object.values(mockData['/deals']);
|
|
17
|
+
const openDeals = deals.filter((d) => d.stage !== 'won' && d.stage !== 'lost');
|
|
18
|
+
const pipeline = openDeals.reduce((sum, d) => sum + d.value, 0);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="mx-auto max-w-5xl space-y-8 px-2 py-4">
|
|
22
|
+
<div className="space-y-1">
|
|
23
|
+
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
|
24
|
+
<p className="text-sm text-muted-foreground">Overview of your CRM activity.</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
28
|
+
<StatCard label="Contacts" value={contacts.length} />
|
|
29
|
+
<StatCard label="Active" value={contacts.filter(c => c.status === 'active').length} sub="active contacts" />
|
|
30
|
+
<StatCard label="Open deals" value={openDeals.length} />
|
|
31
|
+
<StatCard label="Pipeline" value={`$ ${pipeline.toLocaleString()}`} sub="total open value" />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MenuConfig } from '@llmnative/react';
|
|
2
|
+
import HomePage from '../pages/home/HomePage';
|
|
3
|
+
import ProductsPage from '../pages/products/ProductsPage';
|
|
4
|
+
import CategoriesPage from '../pages/categories/CategoriesPage';
|
|
5
|
+
|
|
6
|
+
export const menu: MenuConfig = {
|
|
7
|
+
main: [
|
|
8
|
+
{ path: '/', title: 'Overview', icon: 'layout-dashboard', page: HomePage, end: true },
|
|
9
|
+
{ path: '/products', title: 'Products', icon: 'package', page: ProductsPage, group: 'Catalog' },
|
|
10
|
+
{ path: '/categories', title: 'Categories', icon: 'tag', page: CategoriesPage, group: 'Catalog' },
|
|
11
|
+
],
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const mockData = {
|
|
2
|
+
'/categories': {
|
|
3
|
+
'cat-1': { name: 'Electronics', slug: 'electronics' },
|
|
4
|
+
'cat-2': { name: 'Clothing', slug: 'clothing' },
|
|
5
|
+
'cat-3': { name: 'Home & Garden',slug: 'home-garden' },
|
|
6
|
+
'cat-4': { name: 'Sports', slug: 'sports' },
|
|
7
|
+
},
|
|
8
|
+
'/products': {
|
|
9
|
+
'p-1': { name: 'Wireless Headphones', category: 'Electronics', price: 79.00, stock: 42, sku: 'EL-001', active: true },
|
|
10
|
+
'p-2': { name: 'Running Shoes', category: 'Sports', price: 120.00, stock: 18, sku: 'SP-002', active: true },
|
|
11
|
+
'p-3': { name: 'Desk Lamp', category: 'Home & Garden', price: 35.00, stock: 0, sku: 'HG-003', active: false },
|
|
12
|
+
'p-4': { name: 'Cotton T-Shirt', category: 'Clothing', price: 15.00, stock: 130, sku: 'CL-004', active: true },
|
|
13
|
+
'p-5': { name: 'Mechanical Keyboard', category: 'Electronics', price: 99.00, stock: 25, sku: 'EL-005', active: true },
|
|
14
|
+
'p-6': { name: 'Yoga Mat', category: 'Sports', price: 45.00, stock: 60, sku: 'SP-006', active: true },
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Input } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
export default function CategoriesPage() {
|
|
5
|
+
return (
|
|
6
|
+
<Grid
|
|
7
|
+
path="/categories"
|
|
8
|
+
columns={[
|
|
9
|
+
{ key: 'name', label: 'Category', sortable: true },
|
|
10
|
+
{ key: 'slug', label: 'Slug' },
|
|
11
|
+
]}
|
|
12
|
+
actions={['add', 'edit', 'delete']}
|
|
13
|
+
view="table"
|
|
14
|
+
sortable
|
|
15
|
+
form={
|
|
16
|
+
<>
|
|
17
|
+
<Input name="name" label="Category name" required />
|
|
18
|
+
<Input name="slug" label="Slug (e.g. home-garden)" />
|
|
19
|
+
</>
|
|
20
|
+
}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '@llmnative/react';
|
|
3
|
+
import { mockData } from '../../data/mockData';
|
|
4
|
+
|
|
5
|
+
function StatCard({ label, value, sub }: { label: string; value: number | string; sub?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<Card title={label}>
|
|
8
|
+
<p className="text-2xl font-semibold">{value}</p>
|
|
9
|
+
{sub && <p className="text-xs text-muted-foreground">{sub}</p>}
|
|
10
|
+
</Card>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function HomePage() {
|
|
15
|
+
const products = Object.values(mockData['/products']);
|
|
16
|
+
const totalStock = products.reduce((sum, p) => sum + p.stock, 0);
|
|
17
|
+
const outOfStock = products.filter(p => p.stock === 0).length;
|
|
18
|
+
const totalValue = products.reduce((sum, p) => sum + p.price * p.stock, 0);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="mx-auto max-w-5xl space-y-8 px-2 py-4">
|
|
22
|
+
<div className="space-y-1">
|
|
23
|
+
<h1 className="text-2xl font-semibold">Inventory</h1>
|
|
24
|
+
<p className="text-sm text-muted-foreground">Stock overview across all categories.</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
28
|
+
<StatCard label="Products" value={products.length} />
|
|
29
|
+
<StatCard label="Total stock" value={totalStock.toLocaleString()} sub="units" />
|
|
30
|
+
<StatCard label="Out of stock" value={outOfStock} sub="products" />
|
|
31
|
+
<StatCard label="Stock value" value={`$ ${(totalValue / 100).toLocaleString()}`} />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Badge, Input, Select } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const categoryOptions = [
|
|
5
|
+
{ label: 'Electronics', value: 'Electronics' },
|
|
6
|
+
{ label: 'Clothing', value: 'Clothing' },
|
|
7
|
+
{ label: 'Home & Garden', value: 'Home & Garden' },
|
|
8
|
+
{ label: 'Sports', value: 'Sports' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export default function ProductsPage() {
|
|
12
|
+
return (
|
|
13
|
+
<Grid
|
|
14
|
+
path="/products"
|
|
15
|
+
columns={[
|
|
16
|
+
{ key: 'name', label: 'Product', sortable: true },
|
|
17
|
+
{ key: 'sku', label: 'SKU' },
|
|
18
|
+
{ key: 'category', label: 'Category', sortable: true },
|
|
19
|
+
{
|
|
20
|
+
key: 'price',
|
|
21
|
+
label: 'Price',
|
|
22
|
+
render: ({ value }) => `$ ${Number(value).toFixed(2)}`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'stock',
|
|
26
|
+
label: 'Stock',
|
|
27
|
+
render: ({ value }) => (
|
|
28
|
+
<Badge variant={Number(value) === 0 ? 'danger' : Number(value) < 10 ? 'warning' : 'success'}>
|
|
29
|
+
{String(value)}
|
|
30
|
+
</Badge>
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'active',
|
|
35
|
+
label: 'Active',
|
|
36
|
+
render: ({ value }) => (
|
|
37
|
+
<Badge variant={value ? 'success' : 'secondary'}>{value ? 'Yes' : 'No'}</Badge>
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
actions={['add', 'edit', 'delete']}
|
|
42
|
+
view="table"
|
|
43
|
+
sortable
|
|
44
|
+
pagination={{ limit: 25 }}
|
|
45
|
+
form={
|
|
46
|
+
<>
|
|
47
|
+
<Input name="name" label="Product name" required />
|
|
48
|
+
<Input name="sku" label="SKU" />
|
|
49
|
+
<Select name="category" label="Category" options={categoryOptions} />
|
|
50
|
+
<Input name="price" label="Price ($)" type="number" min={0} step={0.01} />
|
|
51
|
+
<Input name="stock" label="Stock (units)" type="number" />
|
|
52
|
+
</>
|
|
53
|
+
}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MenuConfig } from '@llmnative/react';
|
|
2
|
+
import HomePage from '../pages/home/HomePage';
|
|
3
|
+
import ProjectsPage from '../pages/projects/ProjectsPage';
|
|
4
|
+
import TasksPage from '../pages/tasks/TasksPage';
|
|
5
|
+
import TeamPage from '../pages/team/TeamPage';
|
|
6
|
+
|
|
7
|
+
export const menu: MenuConfig = {
|
|
8
|
+
main: [
|
|
9
|
+
{ path: '/', title: 'Overview', icon: 'layout-dashboard', page: HomePage, end: true },
|
|
10
|
+
{ path: '/projects', title: 'Projects', icon: 'folder-open', page: ProjectsPage, group: 'Work' },
|
|
11
|
+
{ path: '/tasks', title: 'Tasks', icon: 'check-square', page: TasksPage, group: 'Work' },
|
|
12
|
+
{ path: '/team', title: 'Team', icon: 'users', page: TeamPage, group: 'Work' },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const mockData = {
|
|
2
|
+
'/team': {
|
|
3
|
+
'tm-1': { name: 'Alice Martin', role: 'Lead developer', email: 'alice@example.com' },
|
|
4
|
+
'tm-2': { name: 'Bob Chen', role: 'Designer', email: 'bob@example.com' },
|
|
5
|
+
'tm-3': { name: 'Clara Rossi', role: 'Project manager', email: 'clara@example.com' },
|
|
6
|
+
},
|
|
7
|
+
'/projects': {
|
|
8
|
+
'pr-1': { name: 'Website redesign', status: 'active', owner: 'Alice Martin', deadline: '2025-08-31', description: 'Full UX and branding overhaul.' },
|
|
9
|
+
'pr-2': { name: 'Mobile app v2', status: 'active', owner: 'Bob Chen', deadline: '2025-10-15', description: 'React Native rebuild.' },
|
|
10
|
+
'pr-3': { name: 'API migration', status: 'done', owner: 'Alice Martin', deadline: '2025-04-01', description: 'Move from REST to GraphQL.' },
|
|
11
|
+
'pr-4': { name: 'Analytics dashboard',status: 'planning', owner: 'Clara Rossi', deadline: '2025-12-01', description: 'Realtime metrics for ops team.' },
|
|
12
|
+
},
|
|
13
|
+
'/tasks': {
|
|
14
|
+
't-1': { title: 'Define color palette', project: 'Website redesign', assignee: 'Bob Chen', status: 'done', priority: 'high' },
|
|
15
|
+
't-2': { title: 'Implement auth flow', project: 'Mobile app v2', assignee: 'Alice Martin', status: 'in-progress', priority: 'high' },
|
|
16
|
+
't-3': { title: 'Write API docs', project: 'API migration', assignee: 'Alice Martin', status: 'done', priority: 'medium' },
|
|
17
|
+
't-4': { title: 'Design dashboard layout', project: 'Analytics dashboard',assignee: 'Bob Chen', status: 'todo', priority: 'medium' },
|
|
18
|
+
't-5': { title: 'Homepage wireframes', project: 'Website redesign', assignee: 'Bob Chen', status: 'in-progress', priority: 'medium' },
|
|
19
|
+
't-6': { title: 'Data model planning', project: 'Analytics dashboard',assignee: 'Clara Rossi', status: 'todo', priority: 'low' },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '@llmnative/react';
|
|
3
|
+
import { mockData } from '../../data/mockData';
|
|
4
|
+
|
|
5
|
+
function StatCard({ label, value, sub }: { label: string; value: number | string; sub?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<Card title={label}>
|
|
8
|
+
<p className="text-2xl font-semibold">{value}</p>
|
|
9
|
+
{sub && <p className="text-xs text-muted-foreground">{sub}</p>}
|
|
10
|
+
</Card>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function HomePage() {
|
|
15
|
+
const projects = Object.values(mockData['/projects']);
|
|
16
|
+
const tasks = Object.values(mockData['/tasks']);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="mx-auto max-w-5xl space-y-8 px-2 py-4">
|
|
20
|
+
<div className="space-y-1">
|
|
21
|
+
<h1 className="text-2xl font-semibold">Project overview</h1>
|
|
22
|
+
<p className="text-sm text-muted-foreground">Track projects, tasks and team progress.</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
26
|
+
<StatCard label="Projects" value={projects.length} />
|
|
27
|
+
<StatCard label="Active" value={projects.filter(p => p.status === 'active').length} sub="projects" />
|
|
28
|
+
<StatCard label="Open tasks" value={tasks.filter(t => t.status !== 'done').length} />
|
|
29
|
+
<StatCard label="Completed" value={tasks.filter(t => t.status === 'done').length} sub="tasks" />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Badge, Input, Select, type BadgeType } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const statusOptions = [
|
|
5
|
+
{ label: 'Planning', value: 'planning' },
|
|
6
|
+
{ label: 'Active', value: 'active' },
|
|
7
|
+
{ label: 'On hold', value: 'on-hold' },
|
|
8
|
+
{ label: 'Done', value: 'done' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const statusVariant: Record<string, BadgeType> = {
|
|
12
|
+
planning: 'secondary',
|
|
13
|
+
active: 'primary',
|
|
14
|
+
'on-hold': 'warning',
|
|
15
|
+
done: 'success',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function ProjectsPage() {
|
|
19
|
+
return (
|
|
20
|
+
<Grid
|
|
21
|
+
path="/projects"
|
|
22
|
+
columns={[
|
|
23
|
+
{ key: 'name', label: 'Project', sortable: true },
|
|
24
|
+
{ key: 'owner', label: 'Owner' },
|
|
25
|
+
{
|
|
26
|
+
key: 'status',
|
|
27
|
+
label: 'Status',
|
|
28
|
+
render: ({ value }) => (
|
|
29
|
+
<Badge variant={statusVariant[String(value)] ?? 'secondary'}>{String(value)}</Badge>
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
{ key: 'deadline', label: 'Deadline' },
|
|
33
|
+
{ key: 'description', label: 'Description' },
|
|
34
|
+
]}
|
|
35
|
+
actions={['add', 'edit', 'delete']}
|
|
36
|
+
view="table"
|
|
37
|
+
sortable
|
|
38
|
+
pagination={{ limit: 20 }}
|
|
39
|
+
form={
|
|
40
|
+
<>
|
|
41
|
+
<Input name="name" label="Project name" required />
|
|
42
|
+
<Input name="owner" label="Owner" />
|
|
43
|
+
<Select name="status" label="Status" options={statusOptions} />
|
|
44
|
+
<Input name="deadline" label="Deadline" type="date" />
|
|
45
|
+
<Input name="description" label="Description" />
|
|
46
|
+
</>
|
|
47
|
+
}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|