refactor: improve code formatting and consistency across multiple files

- Standardized import statements and removed unnecessary line breaks for better readability in various components.
- Enhanced error handling and logging in the useElectronPrinter hook.
- Updated sample data formatting in AgGridShowcase for improved clarity.
- Refactored JSX elements for consistent indentation and structure in LandingSample, AuthPage, and EventsPage components.
- Consolidated and simplified conditional rendering logic in several components.

These changes aim to enhance code maintainability and readability throughout the project.
This commit is contained in:
shancheas
2026-08-25 17:50:48 +07:00
parent f2f0be111a
commit 67ae5b6c11
94 changed files with 1742 additions and 849 deletions
+23 -19
View File
@@ -21,25 +21,29 @@ export default function App() {
const colorScheme = useThemeStore((s) => s.colorScheme);
const [density, setDensity] = useState<DensityType>('compact');
const router = useMemo(() => createBrowserRouter([
{
path: "/",
element: <ShowcaseLayout density={density} setDensity={setDensity} />,
children: [
{ index: true, element: <Navigate to="/ui-components" replace /> },
{ path: "ui-components", element: <UiComponentsPage /> },
{ path: "forms", element: <FormsPage /> },
{ path: "storage", element: <StoragePage /> },
{ path: "events", element: <EventsPage /> },
{ path: "hardware", element: <HardwarePage /> },
{ path: "rbac", element: <RbacPage /> },
{ path: "auth", element: <AuthPage /> },
{ path: "action-tools", element: <ActionToolsPage /> },
{ path: "ag-grid", element: <AgGridPage /> },
]
},
{ path: "/shell-demo", element: <ShellDemoPage /> }
]), [density]);
const router = useMemo(
() =>
createBrowserRouter([
{
path: '/',
element: <ShowcaseLayout density={density} setDensity={setDensity} />,
children: [
{ index: true, element: <Navigate to="/ui-components" replace /> },
{ path: 'ui-components', element: <UiComponentsPage /> },
{ path: 'forms', element: <FormsPage /> },
{ path: 'storage', element: <StoragePage /> },
{ path: 'events', element: <EventsPage /> },
{ path: 'hardware', element: <HardwarePage /> },
{ path: 'rbac', element: <RbacPage /> },
{ path: 'auth', element: <AuthPage /> },
{ path: 'action-tools', element: <ActionToolsPage /> },
{ path: 'ag-grid', element: <AgGridPage /> },
],
},
{ path: '/shell-demo', element: <ShellDemoPage /> },
]),
[density],
);
return (
<ThemeProvider colorScheme={colorScheme} density={density}>
@@ -74,30 +74,27 @@ export function useElectronPrinter(): UseElectronPrinterReturn {
}
}, []);
const print = useCallback(
async (options?: ElectronPrintOptions): Promise<ElectronPrintResult> => {
if (!window.electronAPI) {
return { success: false, failureReason: 'Not running in Electron' };
}
const print = useCallback(async (options?: ElectronPrintOptions): Promise<ElectronPrintResult> => {
if (!window.electronAPI) {
return { success: false, failureReason: 'Not running in Electron' };
}
setLoading(true);
setError(null);
try {
const result = await window.electronAPI.print(options);
if (!result.success && result.failureReason) {
setError(result.failureReason);
}
return result;
} catch (err) {
const message = err instanceof Error ? err.message : 'Print failed';
setError(message);
return { success: false, failureReason: message };
} finally {
setLoading(false);
setLoading(true);
setError(null);
try {
const result = await window.electronAPI.print(options);
if (!result.success && result.failureReason) {
setError(result.failureReason);
}
},
[],
);
return result;
} catch (err) {
const message = err instanceof Error ? err.message : 'Print failed';
setError(message);
return { success: false, failureReason: message };
} finally {
setLoading(false);
}
}, []);
return {
printers,
@@ -3,14 +3,7 @@ import { useIsElectron } from './use-is-electron';
// ─── Types ──────────────────────────────────────────────────────
export type UpdateStatus =
| 'idle'
| 'checking'
| 'available'
| 'not-available'
| 'downloading'
| 'ready'
| 'error';
export type UpdateStatus = 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'ready' | 'error';
export interface UseElectronUpdaterReturn {
/** Current status of the auto-updater lifecycle */
+38 -16
View File
@@ -34,17 +34,28 @@ export default function ShowcaseLayout({ density, setDensity }: ShowcaseLayoutPr
const currentPath = location.pathname.replace('/', '');
switch (currentPath) {
case 'rbac': return 'Role-Based Access Control and Permissions';
case 'storage': return 'Offline-First PouchDB Synchronization';
case 'auth': return 'Authentication & Security Layers';
case 'ui-components': return 'Theme, Typography, Forms & Data Grids';
case 'forms': return 'Enterprise Form Engine & Zod Validation';
case 'events': return 'Global Event Bus Synchronization';
case 'hardware': return 'Hardware Integration & Printers';
case 'layout-engine': return 'Core Layout Engine & Variants';
case 'action-tools': return 'Showcase for PageActions and RowActions components';
case 'ag-grid': return 'Enterprise Data Grid with Mantine Theme Integration';
default: return 'Architecture Showcase';
case 'rbac':
return 'Role-Based Access Control and Permissions';
case 'storage':
return 'Offline-First PouchDB Synchronization';
case 'auth':
return 'Authentication & Security Layers';
case 'ui-components':
return 'Theme, Typography, Forms & Data Grids';
case 'forms':
return 'Enterprise Form Engine & Zod Validation';
case 'events':
return 'Global Event Bus Synchronization';
case 'hardware':
return 'Hardware Integration & Printers';
case 'layout-engine':
return 'Core Layout Engine & Variants';
case 'action-tools':
return 'Showcase for PageActions and RowActions components';
case 'ag-grid':
return 'Enterprise Data Grid with Mantine Theme Integration';
default:
return 'Architecture Showcase';
}
};
@@ -58,8 +69,12 @@ export default function ShowcaseLayout({ density, setDensity }: ShowcaseLayoutPr
>
<AppShell.Navbar p="md">
<Box mb="xl">
<Title order={3} c="brand.7">Eigen ERP</Title>
<Text size="xs" c="dimmed">Architecture Showcase</Text>
<Title order={3} c="brand.7">
Eigen ERP
</Title>
<Text size="xs" c="dimmed">
Architecture Showcase
</Text>
</Box>
<Box style={{ flex: 1 }}>
{navItems.map((item) => (
@@ -74,7 +89,7 @@ export default function ShowcaseLayout({ density, setDensity }: ShowcaseLayoutPr
/>
))}
</Box>
<Box mt="auto" pt="md" style={{ borderTop: '1px solid var(--mantine-color-default-border)' }}>
<Select
label="Color Scheme"
@@ -101,11 +116,18 @@ export default function ShowcaseLayout({ density, setDensity }: ShowcaseLayoutPr
</AppShell.Navbar>
<AppShell.Main style={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }}>
<Paper p="md" radius={0} withBorder style={{ borderTop: 0, borderLeft: 0, borderRight: 0, zIndex: 10, flexShrink: 0 }}>
<Paper
p="md"
radius={0}
withBorder
style={{ borderTop: 0, borderLeft: 0, borderRight: 0, zIndex: 10, flexShrink: 0 }}
>
<Group justify="space-between">
<Box>
<Title order={3}>Architecture Showcase</Title>
<Text size="sm" c="dimmed">{getSubtitle()}</Text>
<Text size="sm" c="dimmed">
{getSubtitle()}
</Text>
</Box>
<Select
w={180}
+160 -17
View File
@@ -20,18 +20,162 @@ interface OrderRow {
}
const SAMPLE_DATA: OrderRow[] = [
{ id: '1', orderCode: 'ORD-2026-001', customer: 'Acme Corp', email: 'acme@example.com', product: 'Widget A', quantity: 120, unitPrice: 24.5, total: 2940, status: 'Delivered', createdAt: '2026-01-15', region: 'North America' },
{ id: '2', orderCode: 'ORD-2026-002', customer: 'Globex Inc', email: 'info@globex.com', product: 'Widget B', quantity: 50, unitPrice: 89.99, total: 4499.5, status: 'Shipped', createdAt: '2026-02-03', region: 'Europe' },
{ id: '3', orderCode: 'ORD-2026-003', customer: 'Initech', email: 'orders@initech.com', product: 'Gadget Pro', quantity: 200, unitPrice: 15.0, total: 3000, status: 'Processing', createdAt: '2026-03-21', region: 'Asia Pacific' },
{ id: '4', orderCode: 'ORD-2026-004', customer: 'Umbrella Ltd', email: 'sales@umbrella.co', product: 'Widget A', quantity: 75, unitPrice: 24.5, total: 1837.5, status: 'Pending', createdAt: '2026-04-10', region: 'Europe' },
{ id: '5', orderCode: 'ORD-2026-005', customer: 'Stark Industries', email: 'tony@stark.io', product: 'Gadget Elite', quantity: 10, unitPrice: 499.0, total: 4990, status: 'Delivered', createdAt: '2026-04-18', region: 'North America' },
{ id: '6', orderCode: 'ORD-2026-006', customer: 'Wayne Enterprises', email: 'bruce@wayne.com', product: 'Widget C', quantity: 300, unitPrice: 12.75, total: 3825, status: 'Draft', createdAt: '2026-05-02', region: 'North America' },
{ id: '7', orderCode: 'ORD-2026-007', customer: 'Cyberdyne', email: 'info@cyberdyne.jp', product: 'Gadget Pro', quantity: 150, unitPrice: 15.0, total: 2250, status: 'Cancelled', createdAt: '2026-05-15', region: 'Asia Pacific' },
{ id: '8', orderCode: 'ORD-2026-008', customer: 'Oscorp', email: 'orders@oscorp.com', product: 'Widget B', quantity: 90, unitPrice: 89.99, total: 8099.1, status: 'Shipped', createdAt: '2026-06-01', region: 'North America' },
{ id: '9', orderCode: 'ORD-2026-009', customer: 'LexCorp', email: 'lex@lexcorp.com', product: 'Gadget Elite', quantity: 5, unitPrice: 499.0, total: 2495, status: 'Processing', createdAt: '2026-06-12', region: 'Europe' },
{ id: '10', orderCode: 'ORD-2026-010', customer: 'Pied Piper', email: 'richard@piedpiper.io', product: 'Widget A', quantity: 500, unitPrice: 24.5, total: 12250, status: 'Pending', createdAt: '2026-06-20', region: 'North America' },
{ id: '11', orderCode: 'ORD-2026-011', customer: 'Hooli', email: 'gavin@hooli.com', product: 'Gadget Pro', quantity: 1000, unitPrice: 15.0, total: 15000, status: 'Delivered', createdAt: '2026-07-01', region: 'North America' },
{ id: '12', orderCode: 'ORD-2026-012', customer: 'Massive Dynamic', email: 'nina@massive.com', product: 'Widget C', quantity: 60, unitPrice: 12.75, total: 765, status: 'Shipped', createdAt: '2026-07-08', region: 'Europe' },
{
id: '1',
orderCode: 'ORD-2026-001',
customer: 'Acme Corp',
email: 'acme@example.com',
product: 'Widget A',
quantity: 120,
unitPrice: 24.5,
total: 2940,
status: 'Delivered',
createdAt: '2026-01-15',
region: 'North America',
},
{
id: '2',
orderCode: 'ORD-2026-002',
customer: 'Globex Inc',
email: 'info@globex.com',
product: 'Widget B',
quantity: 50,
unitPrice: 89.99,
total: 4499.5,
status: 'Shipped',
createdAt: '2026-02-03',
region: 'Europe',
},
{
id: '3',
orderCode: 'ORD-2026-003',
customer: 'Initech',
email: 'orders@initech.com',
product: 'Gadget Pro',
quantity: 200,
unitPrice: 15.0,
total: 3000,
status: 'Processing',
createdAt: '2026-03-21',
region: 'Asia Pacific',
},
{
id: '4',
orderCode: 'ORD-2026-004',
customer: 'Umbrella Ltd',
email: 'sales@umbrella.co',
product: 'Widget A',
quantity: 75,
unitPrice: 24.5,
total: 1837.5,
status: 'Pending',
createdAt: '2026-04-10',
region: 'Europe',
},
{
id: '5',
orderCode: 'ORD-2026-005',
customer: 'Stark Industries',
email: 'tony@stark.io',
product: 'Gadget Elite',
quantity: 10,
unitPrice: 499.0,
total: 4990,
status: 'Delivered',
createdAt: '2026-04-18',
region: 'North America',
},
{
id: '6',
orderCode: 'ORD-2026-006',
customer: 'Wayne Enterprises',
email: 'bruce@wayne.com',
product: 'Widget C',
quantity: 300,
unitPrice: 12.75,
total: 3825,
status: 'Draft',
createdAt: '2026-05-02',
region: 'North America',
},
{
id: '7',
orderCode: 'ORD-2026-007',
customer: 'Cyberdyne',
email: 'info@cyberdyne.jp',
product: 'Gadget Pro',
quantity: 150,
unitPrice: 15.0,
total: 2250,
status: 'Cancelled',
createdAt: '2026-05-15',
region: 'Asia Pacific',
},
{
id: '8',
orderCode: 'ORD-2026-008',
customer: 'Oscorp',
email: 'orders@oscorp.com',
product: 'Widget B',
quantity: 90,
unitPrice: 89.99,
total: 8099.1,
status: 'Shipped',
createdAt: '2026-06-01',
region: 'North America',
},
{
id: '9',
orderCode: 'ORD-2026-009',
customer: 'LexCorp',
email: 'lex@lexcorp.com',
product: 'Gadget Elite',
quantity: 5,
unitPrice: 499.0,
total: 2495,
status: 'Processing',
createdAt: '2026-06-12',
region: 'Europe',
},
{
id: '10',
orderCode: 'ORD-2026-010',
customer: 'Pied Piper',
email: 'richard@piedpiper.io',
product: 'Widget A',
quantity: 500,
unitPrice: 24.5,
total: 12250,
status: 'Pending',
createdAt: '2026-06-20',
region: 'North America',
},
{
id: '11',
orderCode: 'ORD-2026-011',
customer: 'Hooli',
email: 'gavin@hooli.com',
product: 'Gadget Pro',
quantity: 1000,
unitPrice: 15.0,
total: 15000,
status: 'Delivered',
createdAt: '2026-07-01',
region: 'North America',
},
{
id: '12',
orderCode: 'ORD-2026-012',
customer: 'Massive Dynamic',
email: 'nina@massive.com',
product: 'Widget C',
quantity: 60,
unitPrice: 12.75,
total: 765,
status: 'Shipped',
createdAt: '2026-07-08',
region: 'Europe',
},
];
/* ─── Status Badge Renderer ─────────────────────────────────────── */
@@ -164,11 +308,10 @@ export default function AgGridShowcase() {
AG Grid Enterprise Mantine Integration
</Title>
<Text size="sm" c="dimmed" mb="md">
This showcase demonstrates AG Grid integrated with Mantine's theme system. The grid
automatically adapts to dark/light mode changes, inherits brand colors, typography, and
border radius from the Mantine theme — all via AG Grid's built-in CSS variable system with
zero custom stylesheets. Toggle the color scheme in the Theme Controls above to see it in
action.
This showcase demonstrates AG Grid integrated with Mantine's theme system. The grid automatically adapts to
dark/light mode changes, inherits brand colors, typography, and border radius from the Mantine theme — all
via AG Grid's built-in CSS variable system with zero custom stylesheets. Toggle the color scheme in the
Theme Controls above to see it in action.
</Text>
<Group>
<NumberInput
+3 -1
View File
@@ -5,7 +5,9 @@ export default function AuthPage() {
<Container size="xl" m={0} p={0}>
<Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Auth & Security</Title>
<Title order={4} mb="md">
Auth & Security
</Title>
<Text c="dimmed">Auth Demo Component Coming Soon...</Text>
</Card>
</Stack>
@@ -1,12 +1,5 @@
import { useState, useRef, useEffect } from 'react';
import {
Card,
Title,
Text,
Stack,
Badge,
Divider,
} from '@repo/ui/components';
import { Card, Title, Text, Stack, Badge, Divider } from '@repo/ui/components';
// ── Showcase Components ──────────────────────────────────────────
import { CashierUI } from './printer/cashier.ui';
@@ -65,8 +58,8 @@ export default function EventsDemoPage() {
🖨 Showcase 1: Cross-Platform Printer Abstraction
</Title>
<Text size="sm" c="dimmed" mb="md">
The CashierUI publishes a <code>{DEVICE_EVENTS.PRINT_RECEIPT}</code> event.
The PrinterListener listens for it and simulates interacting with a physical printer.
The CashierUI publishes a <code>{DEVICE_EVENTS.PRINT_RECEIPT}</code> event. The PrinterListener listens for it
and simulates interacting with a physical printer.
</Text>
{/* Headless listener — renders nothing visible */}
@@ -110,9 +103,8 @@ export default function EventsDemoPage() {
📈 Showcase 2: High-Frequency Real-Time Data (50 updates/sec)
</Title>
<Text size="sm" c="dimmed" mb="md">
A mock WebSocket fires <code>WS:STOCK_UPDATE</code> every 20ms.
Each StockRow subscribes to the global event but only updates when{' '}
<code>payload.id === row.id</code>. The parent grid never re-renders.
A mock WebSocket fires <code>WS:STOCK_UPDATE</code> every 20ms. Each StockRow subscribes to the global event
but only updates when <code>payload.id === row.id</code>. The parent grid never re-renders.
</Text>
<LiveStockGrid />
@@ -126,8 +118,8 @@ export default function EventsDemoPage() {
💾 Showcase 3: Auth/Profile Sync with <code>@repo/core-storage</code>
</Title>
<Text size="sm" c="dimmed" mb="md">
ProfileSettingsUI publishes <code>{AUTH_EVENTS.PROFILE_UPDATED}</code>.
StorageSyncListener silently catches it in the background and saves to IndexedDB via <code>secureIndexedDB</code>.
ProfileSettingsUI publishes <code>{AUTH_EVENTS.PROFILE_UPDATED}</code>. StorageSyncListener silently catches
it in the background and saves to IndexedDB via <code>secureIndexedDB</code>.
</Text>
{/* Headless listener — renders nothing visible */}
@@ -1,15 +1,7 @@
import { useState } from 'react';
import { usePublishEvent } from '@repo/core-events';
import type { ReceiptItem } from '@repo/core-events';
import {
Button,
Group,
Stack,
Text,
TextInput,
Table,
Badge,
} from '@repo/ui/components';
import { Button, Group, Stack, Text, TextInput, Table, Badge } from '@repo/ui/components';
import { DEVICE_EVENTS } from '../../../../../core/constants/events';
// ─── Mock Receipt Data ──────────────────────────────────────────
@@ -38,8 +38,8 @@ export function PrinterListener({ onLog }: PrinterListenerProps) {
// Uses the preload-exposed API. The Electron main process
// handles the actual OS-level print job via `webContents.print()`.
onLog(`[Electron] Sending receipt ${payload.receiptId} to OS printer via IPC bridge...`);
window.electronAPI!
.print({ silent: true, printBackground: true })
window
.electronAPI!.print({ silent: true, printBackground: true })
.then((result) => {
if (result.success) {
onLog(`[Electron] ✅ Receipt ${payload.receiptId} printed successfully.`);
@@ -55,7 +55,9 @@ export function PrinterListener({ onLog }: PrinterListenerProps) {
// Opens the native browser print dialog. In production, you'd
// likely render a hidden print-optimized iframe first.
onLog(`[Browser] 🖨️ Receipt ${payload.receiptId} — opening browser print dialog...`);
onLog(` → Cashier: ${payload.cashierName} | Items: ${payload.items.length} | Total: $${payload.total.toFixed(2)}`);
onLog(
` → Cashier: ${payload.cashierName} | Items: ${payload.items.length} | Total: $${payload.total.toFixed(2)}`,
);
window.print();
}
});
@@ -8,9 +8,26 @@ import { WS_EVENTS } from '../../../../../core/constants/events';
* We generate 1000 unique IDs from these base tickers + numeric suffix.
*/
const BASE_TICKERS = [
'AAPL', 'GOOG', 'MSFT', 'AMZN', 'META', 'NVDA', 'TSLA', 'AMD',
'NFLX', 'ORCL', 'CRM', 'INTC', 'PYPL', 'ADBE', 'CSCO', 'QCOM',
'AVGO', 'TXN', 'MU', 'SHOP',
'AAPL',
'GOOG',
'MSFT',
'AMZN',
'META',
'NVDA',
'TSLA',
'AMD',
'NFLX',
'ORCL',
'CRM',
'INTC',
'PYPL',
'ADBE',
'CSCO',
'QCOM',
'AVGO',
'TXN',
'MU',
'SHOP',
];
/**
@@ -58,18 +58,12 @@ export const StockRow = memo(function StockRow({ stockId }: StockRowProps) {
: '#fa5252' // red
: undefined;
const changeArrow = data
? data.change >= 0
? '▲'
: '▼'
: '';
const changeArrow = data ? (data.change >= 0 ? '▲' : '▼') : '';
return (
<tr style={{ fontSize: 12, fontFamily: 'monospace' }}>
<td style={{ padding: '2px 8px', fontWeight: 600 }}>{stockId}</td>
<td style={{ padding: '2px 8px', textAlign: 'right' }}>
{data ? `$${data.price.toFixed(2)}` : '—'}
</td>
<td style={{ padding: '2px 8px', textAlign: 'right' }}>{data ? `$${data.price.toFixed(2)}` : '—'}</td>
<td
style={{
padding: '2px 8px',
@@ -80,12 +74,8 @@ export const StockRow = memo(function StockRow({ stockId }: StockRowProps) {
>
{data ? `${changeArrow} ${data.change >= 0 ? '+' : ''}${data.change.toFixed(2)}` : '—'}
</td>
<td style={{ padding: '2px 8px', textAlign: 'right' }}>
{data ? data.volume.toLocaleString() : ''}
</td>
<td style={{ padding: '2px 8px', textAlign: 'right', color: '#868e96' }}>
{renderCountRef.current}
</td>
<td style={{ padding: '2px 8px', textAlign: 'right' }}>{data ? data.volume.toLocaleString() : '—'}</td>
<td style={{ padding: '2px 8px', textAlign: 'right', color: '#868e96' }}>{renderCountRef.current}</td>
</tr>
);
});
@@ -151,9 +151,7 @@ export class AdvancedBookingTransformer extends BookingTransformer {
return super.transformGetManyResponse(dtos).map((entity) => ({
...entity,
// Normalize 'cancelled' vs 'canceled' from different API versions
status: entity.status === ('canceled' as BookingEntity['status'])
? 'cancelled'
: entity.status,
status: entity.status === ('canceled' as BookingEntity['status']) ? 'cancelled' : entity.status,
}));
}
}
@@ -55,4 +55,3 @@ export const bookingServices = new CommonRemoteDataServices<BookingEntity, Booki
moduleKey: 'BOOKING',
transformer: new BookingTransformer(),
});
@@ -30,10 +30,10 @@ export default function BookingSample() {
telemetryContext: {
customSpanName: 'booking.list.fetch',
tags: {
'feature': 'booking',
feature: 'booking',
'ui.component': 'BookingSample',
'ui.action': 'list_fetch',
'page': 1,
page: 1,
},
pushEventOnSuccess: 'booking_list_loaded',
},
@@ -76,14 +76,19 @@ export default function BookingSample() {
{loading ? 'Fetching…' : 'Test Fetch Bookings'}
</button>
{error && (
<pre style={{ color: '#ef4444', marginTop: 16, whiteSpace: 'pre-wrap' }}>
{error}
</pre>
)}
{error && <pre style={{ color: '#ef4444', marginTop: 16, whiteSpace: 'pre-wrap' }}> {error}</pre>}
{result && (
<pre style={{ marginTop: 16, background: '#1e1e2e', color: '#a6e3a1', padding: 16, borderRadius: 8, overflow: 'auto' }}>
<pre
style={{
marginTop: 16,
background: '#1e1e2e',
color: '#a6e3a1',
padding: 16,
borderRadius: 8,
overflow: 'auto',
}}
>
{JSON.stringify(result, null, 2)}
</pre>
)}
+3 -1
View File
@@ -7,7 +7,9 @@ export default function EventsPage() {
<Container size="xl" m={0} p={0}>
<Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Nested Showcase Example</Title>
<Title order={4} mb="md">
Nested Showcase Example
</Title>
<Text mb="md">This is an example of a nested showcase component.</Text>
<ExamplePage />
</Card>
@@ -1,13 +1,31 @@
import { useForm } from 'react-hook-form';
import { Button, Paper, Title, Group, Stack, Code, Divider, Text, Chip, Radio } from '@repo/ui/components';
import {
FieldTextInput, FieldPasswordInput, FieldTextarea, FieldNumberInput,
FieldJsonInput, FieldPinInput, FieldAutocomplete, FieldSelect,
FieldMultiSelect, FieldNativeSelect, FieldTagsInput, FieldCheckbox,
FieldRadioGroup, FieldSwitch, FieldChipGroup, FieldSegmentedControl,
FieldSlider, FieldRangeSlider, FieldRating, FieldColorInput,
FieldColorPicker, FieldFileInput, FieldLocalSelect, FieldAsyncSelect,
FieldRichTextEditor
import {
FieldTextInput,
FieldPasswordInput,
FieldTextarea,
FieldNumberInput,
FieldJsonInput,
FieldPinInput,
FieldAutocomplete,
FieldSelect,
FieldMultiSelect,
FieldNativeSelect,
FieldTagsInput,
FieldCheckbox,
FieldRadioGroup,
FieldSwitch,
FieldChipGroup,
FieldSegmentedControl,
FieldSlider,
FieldRangeSlider,
FieldRating,
FieldColorInput,
FieldColorPicker,
FieldFileInput,
FieldLocalSelect,
FieldAsyncSelect,
FieldRichTextEditor,
} from '@repo/ui/form';
import type { LoadOptionsFn } from '@repo/ui/form';
import { useFormDemoTranslation } from '../i18n/useFormDemoTranslation';
@@ -48,13 +66,15 @@ const MOCK_VENDORS = [
const loadMockVendorsOptions: LoadOptionsFn<any> = async (search, _page) => {
await new Promise((resolve) => setTimeout(resolve, 500));
const filtered = MOCK_VENDORS.filter(v => v.name.toLowerCase().includes(search.toLowerCase()) || v.code.toLowerCase().includes(search.toLowerCase()));
const filtered = MOCK_VENDORS.filter(
(v) => v.name.toLowerCase().includes(search.toLowerCase()) || v.code.toLowerCase().includes(search.toLowerCase()),
);
return { options: filtered, hasMore: false };
};
export default function AllFieldsDemo() {
const t = useFormDemoTranslation();
const { control, handleSubmit, watch } = useForm<any>({
defaultValues: {
customerName: '',
@@ -86,17 +106,18 @@ export default function AllFieldsDemo() {
asyncSelectPrefilled: { id: 999, code: 'ASYNC-99', name: 'Pre-loaded Async Vendor' },
localMultiPrefilled: [
{ id: 'V1', code: 'VN-01', name: 'Vendor One' },
{ id: 'V2', code: 'VN-02', name: 'Vendor Two' }
{ id: 'V2', code: 'VN-02', name: 'Vendor Two' },
],
asyncMultiPrefilled: [
{ id: 888, code: 'ASYNC-88', name: 'Ghost Async Vendor 1' },
{ id: 999, code: 'ASYNC-99', name: 'Ghost Async Vendor 2' }
{ id: 999, code: 'ASYNC-99', name: 'Ghost Async Vendor 2' },
],
richTextEmpty: "",
richTextPrefilled: "<h2 style=\"text-align: center\">ERP Release Notes</h2><p>This is a <b>highly important</b> update. Please observe the following:</p><ul><li>System maintenance at <i>midnight</i>.</li><li><u style=\"text-align: justify\">All users must log out.</u></li></ul><p style=\"text-align: justify\">Thank you for your cooperation.</p>",
richTextEmpty: '',
richTextPrefilled:
'<h2 style="text-align: center">ERP Release Notes</h2><p>This is a <b>highly important</b> update. Please observe the following:</p><ul><li>System maintenance at <i>midnight</i>.</li><li><u style="text-align: justify">All users must log out.</u></li></ul><p style="text-align: justify">Thank you for your cooperation.</p>',
realPokeSelect: null,
multiRealPokeSelect: []
}
multiRealPokeSelect: [],
},
});
const onSubmit = (data: any) => console.log('All Fields Submitted:', data);
@@ -109,7 +130,9 @@ export default function AllFieldsDemo() {
<Stack gap="xl">
{/* --- Text & Numbers --- */}
<div>
<Title order={5} mb="sm" c="brand">{t.sections.textAndNumbers}</Title>
<Title order={5} mb="sm" c="brand">
{t.sections.textAndNumbers}
</Title>
<Divider mb="md" />
<Group grow align="flex-start" mb="md">
<FieldTextInput name="customerName" control={control} label={t.fields.customerName} />
@@ -124,41 +147,40 @@ export default function AllFieldsDemo() {
<FieldJsonInput name="jsonConfig" control={control} label={t.fields.jsonConfig} formatOnBlur />
</Group>
<div>
<Text size="sm" fw={500} mb={3}>{t.fields.pin}</Text>
<Text size="sm" fw={500} mb={3}>
{t.fields.pin}
</Text>
<FieldPinInput name="pin" control={control} length={6} />
</div>
</div>
{/* --- Selections --- */}
<div>
<Title order={5} mb="sm" c="brand">{t.sections.selections}</Title>
<Title order={5} mb="sm" c="brand">
{t.sections.selections}
</Title>
<Divider mb="md" />
<Group grow align="flex-start" mb="md">
<FieldSelect
name="orderType"
control={control}
label={t.fields.orderType}
data={['BULK', 'RETAIL']}
/>
<FieldNativeSelect
name="nativeOrderType"
control={control}
label={`Native ${t.fields.orderType}`}
data={['BULK', 'RETAIL']}
<FieldSelect name="orderType" control={control} label={t.fields.orderType} data={['BULK', 'RETAIL']} />
<FieldNativeSelect
name="nativeOrderType"
control={control}
label={`Native ${t.fields.orderType}`}
data={['BULK', 'RETAIL']}
/>
</Group>
<Group grow align="flex-start" mb="md">
<FieldAutocomplete
name="country"
control={control}
label={t.fields.country}
data={['Indonesia', 'Singapore', 'Malaysia']}
<FieldAutocomplete
name="country"
control={control}
label={t.fields.country}
data={['Indonesia', 'Singapore', 'Malaysia']}
/>
<FieldMultiSelect
name="categories"
control={control}
label={t.fields.categories}
data={['Electronics', 'Fashion', 'Food']}
<FieldMultiSelect
name="categories"
control={control}
label={t.fields.categories}
data={['Electronics', 'Fashion', 'Food']}
/>
</Group>
<Group grow align="flex-start" mb="md">
@@ -170,7 +192,7 @@ export default function AllFieldsDemo() {
options={[
{ id: 1, name: 'Apple', type: 'Fruit' },
{ id: 2, name: 'Carrot', type: 'Vegetable' },
{ id: 3, name: 'Banana', type: 'Fruit' }
{ id: 3, name: 'Banana', type: 'Fruit' },
]}
valueKey="id"
labelKey="name"
@@ -185,7 +207,7 @@ export default function AllFieldsDemo() {
options={[
{ id: 1, name: 'Red', hex: '#f00' },
{ id: 2, name: 'Green', hex: '#0f0' },
{ id: 3, name: 'Blue', hex: '#00f' }
{ id: 3, name: 'Blue', hex: '#00f' },
]}
valueKey="id"
renderLabel={(item) => `${item.name} (${item.hex})`}
@@ -240,7 +262,9 @@ export default function AllFieldsDemo() {
</Group>
<FieldTagsInput name="tags" control={control} label={t.fields.tags} />
<Title order={5} mb="sm" mt="lg" c="brand">{t.sections.advancedObjectSelects}</Title>
<Title order={5} mb="sm" mt="lg" c="brand">
{t.sections.advancedObjectSelects}
</Title>
<Divider mb="md" />
<Group grow align="flex-start" mb="md">
<FieldLocalSelect
@@ -284,7 +308,9 @@ export default function AllFieldsDemo() {
/>
</Group>
<Title order={5} mb="sm" mt="lg" c="brand">{t.sections.multiSelectEditMode}</Title>
<Title order={5} mb="sm" mt="lg" c="brand">
{t.sections.multiSelectEditMode}
</Title>
<Divider mb="md" />
<Group grow align="flex-start" mb="md">
<FieldLocalSelect
@@ -309,7 +335,9 @@ export default function AllFieldsDemo() {
/>
</Group>
<Title order={5} mb="sm" mt="lg" c="brand">{t.sections.richTextEditor}</Title>
<Title order={5} mb="sm" mt="lg" c="brand">
{t.sections.richTextEditor}
</Title>
<Divider mb="md" />
<FieldRichTextEditor
name="richTextEmpty"
@@ -329,39 +357,35 @@ export default function AllFieldsDemo() {
{/* --- Toggles & Choices --- */}
<div>
<Title order={5} mb="sm" c="brand">{t.sections.togglesAndChoices}</Title>
<Title order={5} mb="sm" c="brand">
{t.sections.togglesAndChoices}
</Title>
<Divider mb="md" />
<Group mb="md">
<FieldCheckbox name="terms" control={control} label={t.fields.terms} />
<FieldSwitch name="receiveEmails" control={control} label={t.fields.receiveEmails} />
</Group>
<FieldRadioGroup
name="priority"
control={control}
label={t.fields.priority}
mb="md"
>
<FieldRadioGroup name="priority" control={control} label={t.fields.priority} mb="md">
<Group mt="xs">
<Radio value="low" label="Low" />
<Radio value="high" label="High" />
</Group>
</FieldRadioGroup>
<FieldSegmentedControl
name="segmentedPriority"
control={control}
<FieldSegmentedControl
name="segmentedPriority"
control={control}
label={t.fields.priority}
data={[
{ label: 'Normal', value: 'normal' },
{ label: 'Urgent', value: 'urgent' }
{ label: 'Urgent', value: 'urgent' },
]}
mb="md"
/>
<div>
<Text size="sm" fw={500} mb={3}>Chip Selection</Text>
<FieldChipGroup
name="chipSelection"
control={control}
>
<Text size="sm" fw={500} mb={3}>
Chip Selection
</Text>
<FieldChipGroup name="chipSelection" control={control}>
<Group>
<Chip value="1">Option 1</Chip>
<Chip value="2">Option 2</Chip>
@@ -372,7 +396,9 @@ export default function AllFieldsDemo() {
{/* --- Ranges & Specialized --- */}
<div>
<Title order={5} mb="sm" c="brand">{t.sections.rangesAndSpecialized}</Title>
<Title order={5} mb="sm" c="brand">
{t.sections.rangesAndSpecialized}
</Title>
<Divider mb="md" />
<Group grow align="flex-start" mb="md">
<FieldSlider name="satisfaction" control={control} label={t.fields.rating} />
@@ -384,23 +410,31 @@ export default function AllFieldsDemo() {
</Group>
<Group grow align="flex-start" mb="md">
<div>
<Text size="sm" fw={500} mb={3}>{t.fields.themeColor} Picker</Text>
<Text size="sm" fw={500} mb={3}>
{t.fields.themeColor} Picker
</Text>
<FieldColorPicker name="colorPicker" control={control} />
</div>
<div>
<Text size="sm" fw={500} mb={3}>{t.fields.rating}</Text>
<Text size="sm" fw={500} mb={3}>
{t.fields.rating}
</Text>
<FieldRating name="rating" control={control} />
</div>
</Group>
</div>
<Button type="submit" mt="md">{t.common.submit}</Button>
<Button type="submit" mt="md">
{t.common.submit}
</Button>
</Stack>
</form>
</Paper>
<Paper p="md" withBorder radius="md" bg="var(--mantine-color-gray-0)">
<Title order={6} mb="xs">{t.common.submittedData}</Title>
<Title order={6} mb="xs">
{t.common.submittedData}
</Title>
<Code block>{JSON.stringify(data, null, 2)}</Code>
</Paper>
</Stack>
@@ -2,7 +2,14 @@ import { useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button, Paper, Title, Divider, Stack, Code, Alert, TypographyStylesProvider } from '@repo/ui/components';
import { FieldTextInput, FieldSelect, FieldSwitch, FieldLocalSelect, FieldAsyncSelect, FieldRichTextEditor } from '@repo/ui/form';
import {
FieldTextInput,
FieldSelect,
FieldSwitch,
FieldLocalSelect,
FieldAsyncSelect,
FieldRichTextEditor,
} from '@repo/ui/form';
import { useConditionalField } from '@repo/ui/hooks';
import { compose, required, emailValidator } from '@repo/ui/validators';
import { useFormDemoTranslation } from '../i18n/useFormDemoTranslation';
@@ -23,7 +30,7 @@ interface Warehouse {
const REGIONS: Region[] = [
{ id: 'R1', code: 'APAC', taxRate: 0.1 },
{ id: 'R2', code: 'EMEA', taxRate: 0.2 }
{ id: 'R2', code: 'EMEA', taxRate: 0.2 },
];
const mockFetchWarehouses = async (regionIds: string[], search: string, page: number) => {
@@ -34,15 +41,17 @@ const mockFetchWarehouses = async (regionIds: string[], search: string, page: nu
{ id: 'W3', regionId: 'R2', name: 'London Central' },
{ id: 'W4', regionId: 'R2', name: 'Berlin Storage' },
];
const filtered = allWarehouses.filter(w => regionIds.includes(w.regionId) && w.name.toLowerCase().includes(search.toLowerCase()));
const filtered = allWarehouses.filter(
(w) => regionIds.includes(w.regionId) && w.name.toLowerCase().includes(search.toLowerCase()),
);
const pageSize = 10;
const start = (page - 1) * pageSize;
const paginated = filtered.slice(start, start + pageSize);
return {
options: paginated,
hasMore: start + pageSize < filtered.length
hasMore: start + pageSize < filtered.length,
};
};
@@ -55,44 +64,48 @@ export default function ReactiveWatchDemo() {
const newsletterEmailValidator = compose(z.string(), required(t.fields.email), emailValidator());
const roleValidator = compose(z.string(), required(t.fields.role));
const reactiveSchema = useMemo(() => z
.object({
userType: z.enum(['PERSONAL', 'CORPORATE']),
corporateTaxId: z.string().optional(),
hasSpouse: z.boolean(),
spouseName: z.string().optional(),
newsletter: z.boolean(),
newsletterEmail: z.string().optional(),
department: z.string().optional(),
role: z.string().optional(),
regions: z.array(z.object({ id: z.string(), code: z.string(), taxRate: z.number() })).optional(),
warehouses: z.array(z.object({ id: z.string(), regionId: z.string(), name: z.string() })).optional(),
richTextLive: z.string().optional(),
})
.and(
z.discriminatedUnion('userType', [
z.object({ userType: z.literal('PERSONAL') }),
z.object({ userType: z.literal('CORPORATE'), corporateTaxId: taxIdValidator }),
]),
)
.and(
z.union([
z.object({ hasSpouse: z.literal(false) }),
z.object({ hasSpouse: z.literal(true), spouseName: spouseNameValidator }),
]),
)
.and(
z.union([
z.object({ newsletter: z.literal(false) }),
z.object({ newsletter: z.literal(true), newsletterEmail: newsletterEmailValidator }),
]),
)
.and(
z.union([
z.object({ department: z.string().min(1), role: roleValidator }),
z.object({ department: z.union([z.literal(''), z.undefined(), z.null()]).optional() }),
]),
), [t, taxIdValidator, spouseNameValidator, newsletterEmailValidator, roleValidator]);
const reactiveSchema = useMemo(
() =>
z
.object({
userType: z.enum(['PERSONAL', 'CORPORATE']),
corporateTaxId: z.string().optional(),
hasSpouse: z.boolean(),
spouseName: z.string().optional(),
newsletter: z.boolean(),
newsletterEmail: z.string().optional(),
department: z.string().optional(),
role: z.string().optional(),
regions: z.array(z.object({ id: z.string(), code: z.string(), taxRate: z.number() })).optional(),
warehouses: z.array(z.object({ id: z.string(), regionId: z.string(), name: z.string() })).optional(),
richTextLive: z.string().optional(),
})
.and(
z.discriminatedUnion('userType', [
z.object({ userType: z.literal('PERSONAL') }),
z.object({ userType: z.literal('CORPORATE'), corporateTaxId: taxIdValidator }),
]),
)
.and(
z.union([
z.object({ hasSpouse: z.literal(false) }),
z.object({ hasSpouse: z.literal(true), spouseName: spouseNameValidator }),
]),
)
.and(
z.union([
z.object({ newsletter: z.literal(false) }),
z.object({ newsletter: z.literal(true), newsletterEmail: newsletterEmailValidator }),
]),
)
.and(
z.union([
z.object({ department: z.string().min(1), role: roleValidator }),
z.object({ department: z.union([z.literal(''), z.undefined(), z.null()]).optional() }),
]),
),
[t, taxIdValidator, spouseNameValidator, newsletterEmailValidator, roleValidator],
);
const { control, handleSubmit, setValue, unregister, clearErrors } = useForm<any>({
resolver: zodResolver(reactiveSchema as any),
@@ -108,9 +121,9 @@ export default function ReactiveWatchDemo() {
regions: [{ id: 'R1', code: 'APAC', taxRate: 0.1 }],
warehouses: [
{ id: 'W-99', regionId: 'R1', name: 'APAC Central Hub' },
{ id: 'W-98', regionId: 'R1', name: 'APAC Backup Hub' }
{ id: 'W-98', regionId: 'R1', name: 'APAC Backup Hub' },
],
richTextLive: "<p>Start typing to see the live preview...</p>",
richTextLive: '<p>Start typing to see the live preview...</p>',
},
});
@@ -184,18 +197,18 @@ export default function ReactiveWatchDemo() {
const isMounted = useRef(false);
const prevRegionIds = useRef<string[]>(regions?.map((r: Region) => r.id) || []);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
return;
}
const currentIds = regions?.map((r: Region) => r.id) || [];
const prevIds = prevRegionIds.current;
const hasChanged = currentIds.length !== prevIds.length || currentIds.some((id: string) => !prevIds.includes(id));
if (hasChanged) {
setValue('warehouses', []);
clearErrors('warehouses');
@@ -297,7 +310,7 @@ export default function ReactiveWatchDemo() {
{t.sections.reactiveWatchCascading}
</Title>
<Divider mb="sm" />
<FieldLocalSelect<Region>
multiple
name="regions"
@@ -308,7 +321,7 @@ export default function ReactiveWatchDemo() {
labelKey="code"
clearable
/>
<FieldAsyncSelect<Warehouse>
multiple
key={`warehouse-select-${regions?.map((r: any) => r.id).join(',')}`}
@@ -316,18 +329,26 @@ export default function ReactiveWatchDemo() {
control={control as any}
label={t.fields.warehouses}
disabled={!regions || regions.length === 0}
loadOptions={useCallback(async (search, page) => {
if (!regions || regions.length === 0) return { options: [], hasMore: false };
return mockFetchWarehouses(regions.map((r: any) => r.id), search, page);
}, [regions])}
loadOptions={useCallback(
async (search, page) => {
if (!regions || regions.length === 0) return { options: [], hasMore: false };
return mockFetchWarehouses(
regions.map((r: any) => r.id),
search,
page,
);
},
[regions],
)}
valueKey="id"
renderLabel={(item) => `[${item.id}] ${item.name}`}
clearable
/>
{regions && regions.length > 0 && (
<Alert mt="sm" color="teal">
{t.descriptions.selectedRegionsTax} {regions.map((r: any) => `${r.code} (${(r.taxRate * 100).toFixed(0)}%)`).join(', ')}
{t.descriptions.selectedRegionsTax}{' '}
{regions.map((r: any) => `${r.code} (${(r.taxRate * 100).toFixed(0)}%)`).join(', ')}
</Alert>
)}
@@ -335,16 +356,18 @@ export default function ReactiveWatchDemo() {
{t.sections.reactiveRichTextPreview}
</Title>
<Divider mb="sm" />
<FieldRichTextEditor
name="richTextLive"
control={control as any}
label={t.fields.liveEditor}
description={t.descriptions.typeToSeePreview}
/>
<Paper p="md" withBorder radius="md" mt="sm">
<Title order={6} mb="xs">{t.sections.liveHtmlPreview}</Title>
<Title order={6} mb="xs">
{t.sections.liveHtmlPreview}
</Title>
<TypographyStylesProvider>
<div dangerouslySetInnerHTML={{ __html: watchedRichTextLive }} />
</TypographyStylesProvider>
+3 -1
View File
@@ -5,7 +5,9 @@ export default function RbacPage() {
<Container size="xl" m={0} p={0}>
<Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">RBAC Engine</Title>
<Title order={4} mb="md">
RBAC Engine
</Title>
<Text c="dimmed">RBAC Demo Component Coming Soon...</Text>
</Card>
</Stack>
+67 -16
View File
@@ -1,4 +1,24 @@
import { Stack, Card, Title, Group, Select, Text, Badge, Divider, Button, TextInput, NumberInput, PasswordInput, Textarea, Checkbox, Switch, Table, StatusBadge, STATUS_DATA, Container } from '@repo/ui/components';
import {
Stack,
Card,
Title,
Group,
Select,
Text,
Badge,
Divider,
Button,
TextInput,
NumberInput,
PasswordInput,
Textarea,
Checkbox,
Switch,
Table,
StatusBadge,
STATUS_DATA,
Container,
} from '@repo/ui/components';
import { useThemeStore } from '../../core/stores/theme.store';
import { ColorSchemeType } from '@repo/ui/provider';
@@ -16,7 +36,9 @@ export default function UiComponentsPage() {
<Stack gap="xl">
{/* Control Panel */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Theme Controls</Title>
<Title order={4} mb="md">
Theme Controls
</Title>
<Group grow align="flex-end">
<Select
label="Color Scheme"
@@ -34,18 +56,30 @@ export default function UiComponentsPage() {
<Card withBorder shadow="sm" radius="md" p="md">
<Stack gap="lg">
<div>
<Title order={4} mb="xs">Typography & Badges</Title>
<Text size="sm" c="dimmed">This is dimmed small text indicating a subtitle.</Text>
<Title order={4} mb="xs">
Typography & Badges
</Title>
<Text size="sm" c="dimmed">
This is dimmed small text indicating a subtitle.
</Text>
<Group mt="md">
<Badge color="brand">Brand Badge</Badge>
<Badge color="success" variant="light">Success Status</Badge>
<Badge color="error" variant="outline">Error State</Badge>
<Badge color="success" variant="light">
Success Status
</Badge>
<Badge color="error" variant="outline">
Error State
</Badge>
</Group>
</div>
<Divider />
<div>
<Title order={4} mb="xs">Enterprise Status Badges</Title>
<Text size="sm" c="dimmed" mb="md">Pre-configured status badges for transaction and master data.</Text>
<Title order={4} mb="xs">
Enterprise Status Badges
</Title>
<Text size="sm" c="dimmed" mb="md">
Pre-configured status badges for transaction and master data.
</Text>
<Group>
<StatusBadge status={STATUS_DATA.DRAFT} />
<StatusBadge status={STATUS_DATA.PENDING} />
@@ -59,12 +93,22 @@ export default function UiComponentsPage() {
</div>
<Divider />
<div>
<Title order={4} mb="md">Buttons</Title>
<Title order={4} mb="md">
Buttons
</Title>
<Group>
<Button variant="filled" color="brand">Filled Button</Button>
<Button variant="outline" color="brand">Outline Button</Button>
<Button variant="light" color="info">Light Info</Button>
<Button variant="subtle" color="error">Cancel</Button>
<Button variant="filled" color="brand">
Filled Button
</Button>
<Button variant="outline" color="brand">
Outline Button
</Button>
<Button variant="light" color="info">
Light Info
</Button>
<Button variant="subtle" color="error">
Cancel
</Button>
</Group>
</div>
</Stack>
@@ -72,7 +116,9 @@ export default function UiComponentsPage() {
{/* Forms */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Form Elements</Title>
<Title order={4} mb="md">
Form Elements
</Title>
<Stack gap="md">
<Group grow align="flex-start">
<TextInput label="First Name" placeholder="Enter your first name" withAsterisk />
@@ -92,7 +138,9 @@ export default function UiComponentsPage() {
{/* Data Grid */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Data Grid</Title>
<Title order={4} mb="md">
Data Grid
</Title>
<Table striped highlightOnHover withTableBorder withColumnBorders>
<Table.Thead>
<Table.Tr>
@@ -108,7 +156,10 @@ export default function UiComponentsPage() {
<Table.Td>{row.id}</Table.Td>
<Table.Td>{row.customer}</Table.Td>
<Table.Td>
<Badge size="sm" color={row.status === 'Delivered' ? 'success' : row.status === 'Shipped' ? 'info' : 'warning'}>
<Badge
size="sm"
color={row.status === 'Delivered' ? 'success' : row.status === 'Shipped' ? 'info' : 'warning'}
>
{row.status}
</Badge>
</Table.Td>
-1
View File
@@ -25,7 +25,6 @@
import type {} from '@repo/core-events';
declare module '@repo/core-events' {
// ─── Payload Types ──────────────────────────────────────────
interface ReceiptItem {