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:
+23
-19
@@ -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 */
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
+20
-3
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+1
-3
@@ -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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -55,4 +55,3 @@ export const bookingServices = new CommonRemoteDataServices<BookingEntity, Booki
|
||||
moduleKey: 'BOOKING',
|
||||
transformer: new BookingTransformer(),
|
||||
});
|
||||
|
||||
|
||||
+13
-8
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+101
-67
@@ -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>
|
||||
|
||||
+84
-61
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Vendored
-1
@@ -25,7 +25,6 @@
|
||||
import type {} from '@repo/core-events';
|
||||
|
||||
declare module '@repo/core-events' {
|
||||
|
||||
// ─── Payload Types ──────────────────────────────────────────
|
||||
|
||||
interface ReceiptItem {
|
||||
|
||||
Reference in New Issue
Block a user