A
AdminDeveloper

Installation & Setup

Get started with AtithiOS UI Theme in your Next.js project.

1. Install Dependencies
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome clsx
Project Credits

Developed By Shulyn Technologies Private Limited

// Reusable source:
import { THEME_DEVELOPER_CREDIT } from '@/lib/credits';

// The global StatusBar uses this constant so every AppShell screen carries the same credit line.
2. Add Theme CSS

Add the theme variables to your globals.css:

/* In src/app/globals.css */
@import './theme/variables.css';
3. Configure Layout
/* In src/app/layout.tsx */
import './globals.css';
import { ThemeProvider } from '@/theme';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

AppShell Layout

The main layout component that provides header, sidebar, content area, and status bar.

Basic Usage
import { AppShell } from '@/components';

const NAV_SECTIONS = [
  {
    id: 'modules',
    label: 'Modules',
    items: [
      { id: 'items', label: 'Items', href: '/items', shortcut: 'Alt+5' },
      { id: 'vendors', label: 'Vendors', href: '/vendors' },
    ],
  },
];

export default function Page() {
  return (
    <AppShell
      navSections={NAV_SECTIONS}
      currentPath="/items"
      breadcrumbs={[
        { label: 'Dashboard', href: '/' },
        { label: 'Items' }
      ]}
      user={{ name: 'Rahul Kumar', role: 'Manager' }}
      info="Items Module"
      mode="ITEMS"
    >
      {/* Page content here */}
    </AppShell>
  );
}
AppShell Props
navSectionsNavSection[]Local menu structure; shared ERP menu is used by default
useProvidedNavSectionsbooleanUse custom page navigation instead of the shared sidebar
currentPathstring?Optional active path override
onNavigate((path: string) => void)?Legacy integration callback; AppShell uses client routing by default
breadcrumbsArray<{label, href?}>Header breadcrumb trail
user{name, role?}User info in header
modestringStatus bar mode label
showShortcutsbooleanShow shortcuts panel
loadingbooleanShow the thin loader above the status bar
loadingLabelstringAccessible label for save and data loading states
autoFocusMainContentbooleanRetry focus on the first active main-content control after route changes
flashProgressOnActionbooleanFlash the loader for button/link clicks and keyboard actions
progressFlashDurationnumberMinimum action flash duration in milliseconds
enableHealthRecoverybooleanShow recovery messages for offline, slow, failed, or overloaded states
healthCheckDelaynumberDelay before a save/load/navigation is treated as stalled
autoReloadDelaynumberCountdown before automatic reload for recoverable failures
responsivenessLagThresholdnumberMain-thread pause threshold for busy app detection
Progress Loader
Thin action feedback above the status bar
// Automatic flash behavior is enabled by default.
<AppShell
  loading={saving}
  loadingLabel="Saving purchase order"
  flashProgressOnAction
  progressFlashDuration={720}
>
  {children}
</AppShell>

// AppShell flashes progress for:
// - shell navigation
// - browser back/forward navigation
// - button/link clicks
// - shortcut-triggered Button and ActionTile clicks
// - form submits
// - explicit loading={true}

// For custom controls:
<div role="button" data-progress-action onClick={runImport}>Import</div>

// To suppress automatic flash:
<button data-progress-ignore="true">Local Toggle</button>
App Health Recovery
Calm on-screen recovery for slow, offline, or overloaded states
// Enabled by default for every AppShell screen.
<AppShell
  enableHealthRecovery
  healthCheckDelay={8000}
  autoReloadDelay={15000}
  responsivenessLagThreshold={5000}
>
  {children}
</AppShell>

// The shell watches for:
// - browser offline/online events
// - long loading={true} or navigation states
// - failed script and stylesheet chunks
// - unhandled application errors and promise failures
// - high browser JS memory pressure when the browser exposes it
// - main-thread stalls after the browser becomes responsive again

// Recoverable failures show a reason and reload automatically.
// Offline states wait for the connection to return instead of reloading in a loop.
// Auto reload is capped at two attempts per route/reason per minute.
Responsive Mobile & Tablet Menus
AppShell automatically changes navigation behavior by viewport
Desktop> 900pxLeft sidebar, header breadcrumbs, right shortcut panel, footer status bar
Tablet< 1180pxReduced sidebar width and hidden shortcut overflow so grids and document screens keep space
Mobile< 900pxDesktop sidebar/status bar collapse into a bottom app bar and slide-up application menu
SearchBottom tabOpens the mobile menu and focuses the menu search input for fast module lookup
EscapeGlobalCloses the mobile menu first, then follows the standard AppShell Escape behavior

Core Components

Reusable UI components with consistent styling.

Foundation Primitives
Modal, Drawer, ConfirmDialog, Toast, and reusable state screens
import {
  Modal,
  Drawer,
  ConfirmDialog,
  EmptyState,
  LoadingState,
  ErrorState,
  PermissionDeniedState,
  useToast,
} from '@/components';

function Page() {
  const [modalOpen, setModalOpen] = useState(false);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [confirmOpen, setConfirmOpen] = useState(false);
  const { addToast } = useToast();

  return (
    <>
      <Button onClick={() => setModalOpen(true)}>Open Modal</Button>
      <Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button>
      <Button onClick={() => setConfirmOpen(true)}>Delete</Button>
      <Button onClick={() => addToast({ title: 'Saved', variant: 'success' })}>
        Toast
      </Button>

      <Modal open={modalOpen} onClose={() => setModalOpen(false)} title="Edit Item">
        Modal content
      </Modal>

      <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title="Filters">
        Drawer content
      </Drawer>

      <ConfirmDialog
        open={confirmOpen}
        onClose={() => setConfirmOpen(false)}
        onConfirm={deleteRecord}
        title="Delete this record?"
        intent="danger"
      />

      <EmptyState />
      <LoadingState />
      <ErrorState />
      <PermissionDeniedState />
    </>
  );
}

// ThemeProvider wraps the app with ToastProvider, so useToast works anywhere.
// Modal, Drawer, and ConfirmDialog trap focus and close with Escape by default.
Card
Container with header, body, and optional footer
import { Card, CardHeader, CardBody, CardFooter } from '@/components';

// Default card
<Card>
  <CardHeader title="Card Title" subtitle="Optional subtitle" />
  <CardBody>Content here</CardBody>
</Card>

// Variants - colored left border
<Card variant="primary">...</Card>
<Card variant="success">...</Card>
<Card variant="warning">...</Card>
<Card variant="danger">...</Card>
<Card variant="info">...</Card>

// With actions
<Card>
  <CardHeader
    title="Title"
    action={<Button size="sm">Edit</Button>}
  />
  <CardBody>Content</CardBody>
</Card>
Button
Multiple variants and sizes
import { Button } from '@/components';

// Variants
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
<Button variant="success">Success</Button>

// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>

// States
<Button loading={true}>Loading...</Button>
<Button disabled>Disabled</Button>

// With click handler and functional shortcut
<Button shortcut="Alt+S" onClick={() => console.log('saved')}>Save</Button>
<Button shortcut="Esc" onClick={() => console.log('cancelled')}>Cancel</Button>
Icon Registry
Theme-level FontAwesome icons for pages and shared controls
import { Icon, getNavIcon } from '@/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

// Use the theme registry for page and control icons.
<Icon.Search size={14} />
<Icon.Plus size={14} />
<Icon.ChartLine size={20} />

// Navigation and module cards use nav item ids.
<FontAwesomeIcon icon={getNavIcon('reports')} />

// Theme rule: use FontAwesome icons only.
// Do not use inline SVG, emoji icons, generated icon strings, or mixed icon libraries.
ActionTile
Reusable shortcut tiles with FontAwesome icons
import { faListCheck, faPlus } from '@fortawesome/free-solid-svg-icons';
import { ActionTile } from '@/components';

// FontAwesome solid icons only, no emoji or generated icon strings.
// Light color variants: blue, emerald, amber, rose, violet, cyan, slate.
<ActionTile
  title="Items Master"
  description="View and manage all item records"
  icon={faListCheck}
  shortcut="Alt+M"
  color="blue"
  href="/items/master"
/>

<ActionTile
  title="New Item"
  description="Add a new item to the master list"
  icon={faPlus}
  shortcut="Alt+N"
  color="emerald"
  href="/items/new"
/>
ReportCard
Reusable report launcher cards with searchable pages and visible shortcuts
import { ReportCard } from '@/components';

<ReportCard
  title="Sales Summary"
  description="Daily, weekly, and monthly sales overview"
  icon="ChartLine"
  shortcut="Alt+S"
  lastGenerated="08 May 2026"
  color="blue"
  href="/reports/sales-summary"
/>

// Put report cards in a normal responsive grid.
// Use one unique shortcut per report and include those shortcuts in AppShell.shortcuts.
// Avoid visually ambiguous or reserved keys like Alt+O/Alt+0, Alt+L ledger, and Alt+T trial balance.
// The page search input should use data-search-input so / focuses it.
Input, Select, Textarea
Form controls
import { Input, Select, Textarea } from '@/components';

<Input
  label="Name"
  placeholder="Enter name"
  value={value}
  onChange={e => setValue(e.target.value)}
  required
  error="This field is required"
  hint="Helpful information"
/>

<Select
  label="Country"
  value={country}
  onChange={e => setCountry(e.target.value)}
  options={[
    { value: 'in', label: 'India' },
    { value: 'us', label: 'United States' },
  ]}
/>

<Textarea
  label="Description"
  value={desc}
  onChange={e => setDesc(e.target.value)}
  rows={4}
/>
Badge
Status indicators
import { Badge } from '@/components';

<Badge variant="default">Default</Badge>
<Badge variant="primary">Primary</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="danger">Danger</Badge>
<Badge variant="info">Info</Badge>
KPIStrip
Key metrics display
import { KPIStrip } from '@/components';

const items = [
  { label: 'Total Revenue', value: '₹2.4L', change: '+12%', trend: 'up' },
  { label: 'Pending Orders', value: '8', trend: 'neutral' },
  { label: 'Low Stock', value: '3', trend: 'down' },
];

<KPIStrip items={items} />
Pagination & ViewTabs
Reusable paging, lazy-load, and tabbed multi-view controls
import { Pagination, ViewTabs } from '@/components';

const [view, setView] = useState<'cards' | 'table'>('cards');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);

<ViewTabs
  variant="segmented"
  value={view}
  onChange={setView}
  tabs={[
    { id: 'cards', label: 'Cards', count: 84 },
    { id: 'table', label: 'Table', count: 84 },
  ]}
/>

<Pagination
  page={page}
  pageSize={pageSize}
  totalItems={84}
  pageSizeOptions={[10, 25, 50, 100]}
  onPageChange={setPage}
  onPageSizeChange={setPageSize}
  onLoadMore={loadNextChunk}
  hasMore={hasMoreRows}
/>

// Use ViewTabs for cards/table/split/chart modes.
// Use Pagination directly for custom report cards and list pages.
// DataGrid can render Pagination internally with the pagination prop.
DataGrid
Keyboard navigable data table with pagination and lazy-load support
import { DataGrid, Column } from '@/components';

const columns: Column<Item>[] = [
  { key: 'id', header: '#', width: 60 },
  { key: 'name', header: 'Name', sortable: true },
  { key: 'category', header: 'Category', filter: 'select',
    filterOptions: [{ value: 'all', label: 'All' }, { value: 'food', label: 'Food' }] },
  { key: 'price', header: 'Price', sortable: true,
    render: (val) => <span style={{ color: 'green' }}>₹{val}</span> },
];

<DataGrid
  data={items}
  columns={columns}
  rowKey="id"
  compact
  searchable
  onRowClick={(row) => console.log(row)}
  pagination={{
    defaultPageSize: 25,
    pageSizeOptions: [10, 25, 50, 100],
  }}
/>

// Manual/server paging.
<DataGrid
  data={serverRows}
  columns={columns}
  rowKey="id"
  pagination={{
    mode: 'manual',
    page,
    pageSize,
    totalItems,
    loading,
    hasMore,
    onPageChange: fetchPage,
    onPageSizeChange: setPageSize,
    onLoadMore: loadNextChunk,
  }}
/>

// First cell is auto-focused on page load.
// Active row and active cell are both highlighted.
// Arrow keys move rows and columns.
// Enter opens the active row when onRowClick is set.
// Select cells keep native up/down option navigation.
EditableGrid
Inline editable grid for BOM, quantity, and transaction entry screens
import { EditableGrid, type ColumnDef } from '@/components';

const columns: ColumnDef[] = [
  { key: 'ingredient', header: 'Ingredient', editable: true },
  { key: 'qty', header: 'Qty', type: 'number', align: 'right', step: 0.1 },
  {
    key: 'unit',
    header: 'Unit',
    type: 'select',
    options: [
      { value: 'kg', label: 'kg' },
      { value: 'ltr', label: 'ltr' },
      { value: 'pcs', label: 'pcs' },
    ],
  },
  { key: 'cost', header: 'Cost', type: 'readonly',
    render: value => `Rs. ${value}` },
];

<EditableGrid
  data={bomRows}
  columns={columns}
  rowKey="id"
  addLabel="Add Ingredient"
  minRows={1}
  maxRows={100}
  onChange={setBomRows}
/>

// EditableGrid is built for spreadsheet-like ERP entry:
// - Tab moves across editable cells and creates a row at the end.
// - Arrow keys move between cells unless a select needs native option navigation.
// - Alt+A adds a row when the grid is focused.
// - Alt+Delete removes the active row.
// - active row and active cell are both highlighted.
// - set autoFocus={false} inside docs or preview surfaces.
MultiLevelGridView
Nested shortcut-driven grid drilldowns
import { MultiLevelGridView, type MultiLevelGridLevel } from '@/components';

const levels: MultiLevelGridLevel[] = [
  {
    id: 'brands',
    title: 'Ingredient Brands',
    rowKey: 'brandId',
    columns: brandColumns,
    getRows: context => brandStock[String(context.root?.ingredient ?? '')] ?? [],
    getRowLabel: row => String(row.brand ?? ''),
    next: { levelId: 'vendors', shortcut: 'Alt+V', label: 'Open vendors' },
  },
  {
    id: 'vendors',
    title: 'Vendors',
    rowKey: 'vendorId',
    columns: vendorColumns,
    getRows: context => vendorsByBrand[String(context.selectedRows.brands?.brandId ?? '')] ?? [],
    getRowLabel: row => String(row.vendor ?? ''),
    next: { levelId: 'ledger', shortcut: 'Alt+L', label: 'Open ledger' },
  },
  {
    id: 'ledger',
    title: 'Vendor Ledger',
    rowKey: 'ledgerId',
    columns: ledgerColumns,
    getRows: context => ledgerByVendor[String(context.selectedRows.vendors?.vendorId ?? '')] ?? [],
  },
];

<InlineGrid
  data={rows}
  columns={columns}
  rowKey="id"
  onActiveRowChange={row => setActiveIngredient(row)}
/>

<MultiLevelGridView
  title="Ingredient Drilldown"
  levels={levels}
  rootContext={activeIngredient}
  openShortcut="Alt+I"
  openLabel="Brands"
/>

// Example flow:
// 1. Select an ingredient row in the BOM grid.
// 2. Press Alt+I to open brand stock.
// 3. Select a brand and press Alt+V to open vendors.
// 4. Select a vendor and press Alt+L to open account ledger.
// 5. Press Alt+Backspace to move back one level.

The BOM module uses this pattern for editable recipes: Bill of Materials rows open `/bom/[id]`, ingredient rows remain editable in `InlineGrid`, and the selected ingredient drives the nested brand, vendor, and ledger grids.

Dashboard Components

Reusable dashboard building blocks for ERP landing screens, control rooms, and module launch pages.

ModuleGrid
Keyboard-aware dashboard module launcher
import { ModuleGrid, type Module } from '@/components';

const modules: Module[] = [
  { id: 'items', name: 'Items', description: 'Manage items', shortcut: 'Alt+5', href: '/items' },
  { id: 'items-master', name: 'Items Master', description: 'Master data', shortcut: 'Alt+M', href: '/items/master' },
  { id: 'po', name: 'Purchase Orders', description: 'Procurement workflow', shortcut: 'Alt+9', href: '/po' },
  { id: 'reports', name: 'Reports', description: 'ERP analysis', shortcut: 'Alt+R', href: '/reports' },
];

const [focusedIndex, setFocusedIndex] = useState(0);

<ModuleGrid
  modules={modules}
  columns={4}
  focusedIndex={focusedIndex}
  onFocusChange={setFocusedIndex}
  onClick={module => router.push(module.href ?? `/${module.id}`)}
/>

// Keyboard behavior:
// - Arrow keys move between module cards.
// - Enter and Space trigger the focused module.
// - Home and End jump to first and last module.
// - Inputs and data grid cells keep their own keyboard behavior.
ActivityFeed
Compact timeline for recent ERP activity
import { ActivityFeed } from '@/components';

const activities = [
  { id: '1', title: 'Order Created', description: 'PO/2025-26/48', time: '10:24', type: 'success' },
  { id: '2', title: 'Low Stock Alert', time: '09:00', type: 'warning' },
  { id: '3', title: 'Invoice Exported', time: '08:42', type: 'info' },
];

<ActivityFeed activities={activities} maxItems={5} />
KPICard
Single dashboard metric card used inside KPIStrip or custom layouts
import { KPICard } from '@/components';

<KPICard
  label="Revenue Today"
  value="Rs. 45,280"
  change="+12%"
  trend="up"
  color="success"
  icon={<Icon.MoneyTrend size={14} />}
/>

// Use KPIStrip for normal multi-card metric rows.
// Use KPICard directly when you need a custom dashboard grid,
// a sidebar metric, or a compact summary inside another component.

ERP Documents

Reusable printable document architecture for Invoice, GRN, Purchase Order, and similar ERP documents.

What ERPDocument Solves

Invoice, GRN, and Purchase Order screens share the same document skeleton: toolbar actions, branded header, metadata grid, parties, item table, totals, supporting boxes, checklist cards, signatures, and print-safe CSS. Use ERPDocument to configure that structure instead of rebuilding each page manually.

import {
  ERPDocument,
  DocumentStatusBadge,
  type ERPDocumentParty,
  type ERPDocumentSection,
} from '@/components';
Step 1: Prepare Document Data
// Keep business data in a typed data/service layer.
type Party = {
  name: string;
  gstin: string;
  pan: string;
  address: string[];
  state: string;
  stateCode: string;
  phone: string;
  email: string;
};

type InvoiceLine = {
  id: string;
  hsnSac: string;
  description: string;
  qty: number;
  unit: string;
  rate: number;
  discount: number;
  cgstRate: number;
  sgstRate: number;
  igstRate: number;
};

const invoice = getInvoiceDocument(params.id);
const totals = getInvoiceTotals(invoice);
Step 2: Convert Parties
function toDocumentParty(title: string, party: Party): ERPDocumentParty {
  return {
    title,
    name: party.name,
    address: party.address,
    fields: [
      { label: 'GSTIN', value: party.gstin },
      { label: 'PAN', value: party.pan },
      { label: 'State', value: `${party.stateCode} - ${party.state}` },
      { label: 'Phone', value: party.phone },
      { label: 'Email', value: party.email },
    ],
  };
}
Step 3: Configure Table, Totals, Cards, and Signatures
const sections: ERPDocumentSection<InvoiceLine>[] = [
  {
    type: 'table',
    title: 'Itemised Tax Details',
    rows: invoice.lines,
    columns: [
      { header: '#', render: (_line, index) => index + 1 },
      { header: 'Description', render: line => <strong>{line.description}</strong> },
      { header: 'HSN/SAC', render: line => line.hsnSac },
      { header: 'Qty', align: 'right', render: line => line.qty.toLocaleString('en-IN') },
      { header: 'Rate', align: 'right', render: line => formatINR(line.rate) },
      { header: 'Taxable', align: 'right', render: line => formatINR(getInvoiceLineAmounts(line).taxable) },
      { header: 'CGST', align: 'right', render: line => <>{line.cgstRate}%<br />{formatINR(getInvoiceLineAmounts(line).cgst)}</> },
      { header: 'SGST', align: 'right', render: line => <>{line.sgstRate}%<br />{formatINR(getInvoiceLineAmounts(line).sgst)}</> },
      { header: 'Total', align: 'right', render: line => <strong>{formatINR(getInvoiceLineAmounts(line).total)}</strong> },
    ],
  },
  {
    type: 'totals',
    summary: {
      title: 'GST & E-Invoice Reference',
      lines: [
        <><strong>IRN:</strong> {invoice.irn}</>,
        <><strong>Amount in words:</strong> {invoice.amountInWords}</>,
      ],
    },
    totals: [
      { label: 'Taxable Value', value: formatINR(totals.taxable) },
      { label: 'CGST', value: formatINR(totals.cgst) },
      { label: 'SGST', value: formatINR(totals.sgst) },
      { label: 'Invoice Total', value: formatINR(totals.total), highlight: true },
    ],
  },
  {
    type: 'twoColumn',
    boxes: [
      { title: 'Terms & Conditions', lines: invoice.terms },
      { title: 'Notes', lines: invoice.notes },
    ],
  },
  {
    type: 'signatures',
    signatures: [
      { title: 'Prepared By', name: 'Accounts Executive' },
      { title: 'Authorised Signatory', name: invoice.authorizedBy },
    ],
  },
];
Step 4: Render Inside AppShell
<AppShell
  navSections={NAV_SECTIONS}
  currentPath={`/invoices/${params.id}`}
  breadcrumbs={[
    { label: 'Dashboard', href: '/' },
    { label: 'Invoices', href: '/invoices' },
    { label: invoice.invoiceNo },
  ]}
  mode="TAX INVOICE"
  shortcuts={[
    { key: 'Alt+P', label: 'Print' },
    { key: 'Alt+D', label: 'Export PDF' },
    { key: 'Alt+C', label: 'Copy Link' },
    { key: 'Esc', label: 'Back' },
  ]}
>
  <ERPDocument
    toolbar={{
      title: `Tax Invoice: ${invoice.invoiceNo}`,
      onBack: () => navigate('/invoices'),
    }}
    brandMark="AOS"
    documentTitle="Tax Invoice"
    documentBadge={invoice.copyType}
    company={{
      name: invoice.seller.name,
      address: invoice.seller.address.join(', '),
      details: [
        `GSTIN: ${invoice.seller.gstin} | PAN: ${invoice.seller.pan}`,
        `${invoice.seller.phone} | ${invoice.seller.email}`,
      ],
    }}
    meta={[
      { label: 'Invoice No.', value: invoice.invoiceNo },
      { label: 'Invoice Date', value: invoice.date },
      { label: 'Place of Supply', value: invoice.placeOfSupply },
      { label: 'Reverse Charge', value: invoice.reverseCharge },
    ]}
    parties={[
      toDocumentParty('Bill To', invoice.buyer),
      toDocumentParty('Ship To', invoice.shipTo),
    ]}
    sections={sections}
    footerNote="Validate statutory values before production use."
  />
</AppShell>
Available Section Types
metafieldsAdditional metadata grid, useful for transport details or statutory references
partiespartiesExtra party blocks such as bill-to, ship-to, supplier, receiving location
tablerows + columnsLine-item table for invoice items, PO items, GRN received quantities, or custom rows
totalssummary + totalsLeft summary box and right totals panel with optional highlighted grand total
cardscardsChecklist, delivery schedule, QC checks, approval milestones
twoColumnboxesTerms, bank details, notes, compliance text, operational instructions
signaturessignaturesPrepared by, checked by, authorised signatory, supplier/driver sign-off
customchildrenEscape hatch for charts, QR codes, stamps, or any document-specific block
How Invoice, GRN, and PO Differ
InvoiceTax documentUse seller/buyer/ship-to parties, GST tax columns, e-invoice/IRN summary, bank details, terms, signatory
GRNStock receiptUse supplier/receiving-location parties, transport metadata, received/accepted/rejected columns, QC checklist, stock posting, signatures
POProcurement orderUse supplier/bill-to/ship-to parties, HSN/spec/rate/tax estimate columns, delivery schedule, terms, approvals
Reuse Checklist
  • Step 1: Define the typed document data in a service or library file.
  • Step 2: Convert business parties into ERPDocumentParty.
  • Step 3: Build ERPDocumentSection[] for the line table, totals, notes, cards, and signatures.
  • Step 4: Place ERPDocument inside AppShell.
  • Step 5: Add Alt+P, Alt+D, Alt+C, and Esc shortcuts to the shell panel.
  • Step 6: Use browser print destination “Save as PDF” for export; print CSS is already handled by the component and AppShell.
  • Step 7: Use the custom section type only when the standard blocks cannot represent a requirement.

Enterprise Features

Reusable higher-level ERP capabilities for layout personalization, analytics, document intake, cloud storage, OCR, and RBAC.

Draggable Dashboard Layout
dnd-kit based layout builder for user-configurable ERP dashboards
import { DashboardLayoutBuilder, type DashboardLayoutItem } from '@/components';

const [widgets, setWidgets] = useState<DashboardLayoutItem[]>([
  { id: 'sales', title: 'Sales Today', size: 'sm', metrics: [{ label: 'Sales', value: 'Rs. 8.4L' }] },
  { id: 'kitchen', title: 'Kitchen Load', size: 'md', metrics: [{ label: 'Open KOT', value: 48 }] },
]);

<DashboardLayoutBuilder
  items={widgets}
  onChange={setWidgets}
  columns={3}
/>;

// Persist widgets after onChange to save each user's dashboard layout.
Charts & Graphical Analytics
Responsive Recharts wrapper with ERP-friendly chart modes
import { EnterpriseChart } from '@/components';

<EnterpriseChart
  kind="composed"
  data={salesData}
  xKey="day"
  currency
  series={[
    { key: 'sales', label: 'Sales', type: 'bar' },
    { key: 'margin', label: 'Margin', type: 'area' },
    { key: 'orders', label: 'Orders', type: 'line' },
  ]}
/>;

// Supported kinds: line, bar, area, pie, composed.
File Upload, Image Upload & Preview
Reusable drop zone for invoices, item photos, contracts, KYC, and attachments
import { FileUploadZone, type UploadedAsset } from '@/components';

const [assets, setAssets] = useState<UploadedAsset[]>([]);

<FileUploadZone
  accept="image/*,.pdf,.doc,.docx,.xls,.xlsx"
  maxFiles={8}
  maxSizeMb={10}
  onAssetsChange={setAssets}
/>;

// Use the returned asset metadata to upload to S3/R2, attach to GRN/PO/Invoice,
// or pass selected images into OCRCapturePanel.
OCR Capture
Tesseract.js powered text capture with common ERP field extraction
import { OCRCapturePanel } from '@/components';

<OCRCapturePanel
  language="eng"
  onCapture={({ text, fields }) => {
    saveCapturedDocument({
      rawText: text,
      documentNo: fields.documentNo,
      gstin: fields.gstin,
      amount: fields.amount,
    });
  }}
/>;

// Built-in field hints: documentNo, date, GSTIN, amount, vendor.
// Keep validation in your ERP workflow before posting financial entries.
S3 / Cloudflare R2 Storage
Server-side presigned upload, download, delete, and list operations
// Client
<CloudStoragePanel />

// Server route: POST /api/cloud-storage/presign
{
  "provider": "r2",
  "operation": "upload",
  "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
  "region": "auto",
  "bucket": "atithios-documents",
  "accessKeyId": "...",
  "secretAccessKey": "...",
  "key": "grn/GRN-00042/invoice.pdf",
  "contentType": "application/pdf"
}

// The route uses @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner.
// Store real credentials in a secure vault or server env for production flows.
User Management & RBAC
Role-based access control matrix for standard ERP permissions
import { RBACMatrix, type RBACPermissions } from '@/components';

const [permissions, setPermissions] = useState<RBACPermissions>(initialPermissions);

<RBACMatrix
  roles={roles}
  modules={erpModules}
  value={permissions}
  onChange={setPermissions}
/>;

// Standard actions: view, create, edit, approve, delete, export.
// Use module.actions to restrict actions per module, e.g. user management has no approve/export.
Example Pages Added
/enterprise/dashboard-builderDashboard layoutDrag/drop widgets with saved order preview
/enterprise/analyticsChartsSales, purchase, and inventory chart views
/enterprise/filesDocumentsFile preview, OCR capture, and S3/R2 signing
/usersUser ManagementUser list, role cards, and RBAC matrix

Application Screens

Finished ERP screens and route-level states included with the theme.

Items Master Page
Complete master-data screen at /items/master
// Route: src/app/items/master/page.tsx
// Purpose: reusable ERP master-data screen pattern.

<AppShell
  mode="ITEMS MASTER"
  shortcuts={[
    { key: '/', label: 'Search' },
    { key: 'Alt+N', label: 'New Item' },
    { key: 'Alt+E', label: 'Edit Selected' },
    { key: 'Alt+D', label: 'Delete Selected' },
    { key: 'Alt+X', label: 'Export CSV' },
    { key: 'Enter', label: 'Open/Edit' },
    { key: 'Esc', label: 'Back' },
  ]}
>
  <Input data-search-input placeholder="Search code, item, category, HSN..." />
  <ViewTabs value={stockView} onChange={setStockView} tabs={stockTabs} />
  <DataGrid
    data={filteredItems}
    columns={columns}
    rowKey="id"
    selectedKey={activeItem?.id}
    onActiveRowChange={setActiveItem}
    onRowClick={openItemEditor}
    pagination={{ defaultPageSize: 10, pageSizeOptions: [10, 25, 50, 100] }}
  />
  <ConfirmDialog intent="danger" title="Delete item from master?" />
</AppShell>

// Included behavior:
// - category, search, and stock-state filters
// - active row context panel
// - keyboard row navigation and Enter edit
// - Alt+N, Alt+E, Alt+D, Alt+X actions
// - reusable DataGrid pagination
// - CSV export and delete confirmation
Error Pages
Documented route states and recovery screens
// Route-level error surfaces included in the theme:
src/app/error.tsx              // global runtime error boundary
src/app/not-found.tsx          // 404 route fallback
src/app/error/400/page.tsx     // bad request
src/app/error/403/page.tsx     // access forbidden
src/app/error/500/page.tsx     // server error
src/app/error/503/page.tsx     // service unavailable

// Use cases:
// - 400: invalid request or bad route payload
// - 403: permission issue; includes login recovery
// - 404: unknown route; auto returns to dashboard
// - 500: server/runtime failure with retry and dashboard fallback
// - 503: maintenance or overloaded service
// - AppShell health recovery: offline, slow operation, failed asset, memory pressure

// Rule for production apps:
// keep error pages branded, calm, keyboard-visible, and action-oriented.
// Always provide a primary recovery action and a dashboard fallback.
Final Documentation Coverage
Checklist for releasing this theme as reusable ERP UI
  • AppShell: desktop layout, responsive mobile menu, status bar, loader, health recovery, and shortcuts are documented.
  • Core components: cards, buttons, icons, forms, badges, KPIs, grids, editable grids, multilevel grids, pagination, tabs, modals, drawers, toasts, and state blocks are documented with previews.
  • ERP documents: invoice, GRN, PO, parties, sections, totals, print, export, and reuse steps are documented.
  • Enterprise features: DND layout builder, charts, file/image upload preview, OCR, S3/R2 signing, RBAC, and example pages are documented.
  • Application screens: dashboard, Items Master, reports, documents, user management, lazy-load customer balances, and route-level error pages are represented.
  • Keyboard system: reusable shortcuts, grid navigation, Mac Command/Option compatibility, Escape rules, and conflict rules are documented.
  • Credits: developer credit is centralized in src/lib/credits.ts and shown globally in the status bar.
  • Project graph: architecture, route, component, logic, data, and documentation graphs are included for final project handover.

Project Graphs

Graphified documentation for architecture, routes, components, logic, data services, and documentation coverage.

Graphified Project Overview
Use visual nodes and edges to inspect the project without reading every file first
// Visit the full graphified project map.
/project-graph

// Included graph maps:
// 1. Application Architecture
// 2. Route & Module Graph
// 3. Component Dependency Graph
// 4. Logic & Interaction Graph
// 5. Data, Document & Service Flow
// 6. Documentation Coverage Graph

// Each map has:
// - visual nodes
// - relationship edges
// - selected-node details
// - searchable node inventory
// - searchable relationship inventory
// - coverage matrix
Reusable ProjectGraphView API
import { ProjectGraphView, type ProjectGraphMap } from '@/components';

const graph: ProjectGraphMap = {
  id: 'workflow',
  title: 'Approval Workflow',
  description: 'PO to approval to GRN to ledger posting.',
  nodes: [
    { id: 'po', title: 'Purchase Order', type: 'route', description: 'Creates order', x: 20, y: 50 },
    { id: 'approval', title: 'Approval', type: 'logic', description: 'Manager approves', x: 50, y: 50 },
    { id: 'grn', title: 'Goods Receipt', type: 'route', description: 'Receives stock', x: 80, y: 50 },
  ],
  edges: [
    { from: 'po', to: 'approval', label: 'submits' },
    { from: 'approval', to: 'grn', label: 'releases receiving' },
  ],
};

<ProjectGraphView graph={graph} />;
Coverage Rule
  • Architecture: AppShell, providers, navigation, responsive menus, loaders, and recovery logic must be mapped.
  • Routes: Every page family, dynamic route, API route, and error page class must be represented.
  • Components: Layout, UI, dashboard, document, enterprise, and icon registry components must be grouped.
  • Logic: Keyboard, focus, grid movement, edit flow, progress, health recovery, report shortcuts, and mobile menu behavior must be visible.
  • Data: Libraries, ERP documents, reports, BOM drilldown, customer balances, OCR, and storage flows must be connected.
  • Documentation: The docs page, component previews, enterprise pages, graph page, and scripts must be linked.

Theming

Customize colors, typography, and spacing via CSS variables.

CSS Variables
/* In src/theme/variables.css */

:root {
  /* Primary Color - Amber */
  --primary: #f59e0b;
  --primary-hover: #d97706;
  --primary-active: #b45309;
  --primary-subtle: #fef3c7;

  /* Semantic Colors */
  --success: #16a34a;
  --success-subtle: #dcfce7;
  --warning: #ea580c;
  --warning-subtle: #fff7ed;
  --danger: #dc2626;
  --danger-subtle: #fee2e2;
  --info: #0284c7;
  --info-subtle: #e0f2fe;

  /* Typography */
  --font-sans: 'Inter', sans-serif;
  --font-mono: 'JetBrains Mono', monospace;

  /* Spacing */
  --radius-sm: 3px;
  --radius: 4px;
  --radius-md: 6px;

  /* Layout */
  --sidebar-width: 220px;
  --header-height: 48px;
  --status-height: 32px;
}

Keyboard Shortcuts

Reusable keyboard architecture for ERP screens.

Shortcut System Architecture
AppShellGlobalOwns shell shortcuts at window capture so grids cannot trap Escape, search, dashboard, alerts, or login
ButtonActionUse the shortcut prop for Save, Cancel, Approve, Delete, Print, and other commands
ActionTileLauncherUse the shortcut prop for tile-style module launchers and large page actions
DataGridTableOwns arrow-key cell movement and Enter row action through onRowClick
EditableGridEntry TableOwns inline cell editing, Tab row creation, Alt+A add row, and Alt+Delete remove row
MultiLevelGridViewDrilldownOpens nested grid levels from a selected source row using openShortcut and next.shortcut
useShortcutsCustomUse only for page-specific behavior that is not naturally a button, tile, or grid action
Functional vs Visual Shortcuts
// Visual hint only: shows in shortcut panel and status bar.
<AppShell shortcuts={[{ key: 'Alt+S', label: 'Save' }]} />

// Functional shortcut: renders a hint and triggers this click action.
<Button shortcut="Alt+S" onClick={save}>Save</Button>

// Functional launcher shortcut.
<ActionTile
  title="New Item"
  icon={faPlus}
  shortcut="Alt+N"
  href="/items/new"
/>

// Functional row shortcut: Enter opens the active row.
<DataGrid
  data={items}
  columns={columns}
  rowKey="id"
  onRowClick={item => router.push(`/items/${item.id}`)}
/>

// Functional drilldown shortcut: selected ingredient -> brands -> vendors -> ledger.
<MultiLevelGridView
  levels={levels}
  rootContext={activeIngredient}
  openShortcut="Alt+I"
/>
Mac Compatibility
One shortcut definition works across Windows, Linux, and macOS
CtrlPrimary modifierKeep writing Ctrl+B or Ctrl+L in code. On Mac the theme displays and listens for Command.
AltAlternate modifierAlt shortcuts display and work as Option on Mac, so Alt+S becomes Option+S.
useShortcutLabelCustom UIUse this hook when a shortcut appears inside custom text, tooltips, or manually written kbd tags.
formatShortcutForPlatformUtilityUse this helper for data-driven menus, shortcut tables, and non-component renderers.
parseShortcutAccepted aliasesParses Ctrl, Control, Cmd, Command, Meta, Mod, Alt, Option, Opt, Shift, and common Mac symbols.
// Preferred: keep canonical shortcut strings in reusable theme components.
<Button shortcut="Ctrl+B" onClick={openAlerts}>Alerts</Button>
<Button shortcut="Alt+S" onClick={save}>Save</Button>

// Custom surfaces should format labels through the theme hook.
const saveLabel = useShortcutLabel('Alt+S');
return <span>Press <kbd>{saveLabel}</kbd> to save</span>;

// Functional matching also uses the shared parser.
const shortcut = parseShortcut('Ctrl+L');
if (shortcutMatchesEvent(event, shortcut)) router.push('/login');
Global Shortcuts
Alt+1-9Navigate to visible nav item by order when no explicit shortcut match exists
Alt+MNavigate to Items Master when declared in navigation
Alt+EOpen BOM Editor when declared in navigation
/Focus sidebar search when not typing in a field
GGo to dashboard when not typing in a text field
EscAlways handled by AppShell: close recovery/alerts, run page Escape action, blur editing, or go to dashboard
EnterOpen the active grid row when the grid has onRowClick
Ctrl+BToggle alerts
Ctrl+LGo to login
ERP Shortcut Standards
Alt+NNew record on the current page
Alt+EOpen editor workspace or edit current record when page-owned
Alt+SSave or update current record
EscCancel, close, blur field, or go to dashboard
EnterOpen/edit selected row or confirm focused primary action
Alt+AAdd grid row
Alt+DeleteRemove grid row
Alt+IRecommended first-level ingredient drilldown shortcut
Alt+VRecommended vendor drilldown shortcut
Alt+LRecommended ledger drilldown shortcut

Avoid assigning one shortcut to two different actions on the same page unless both actions intentionally do the same thing.

Master Page Pattern

Two-panel layout with list on left, details/form on right. Perfect for CRUD operations.

Structure
/* Layout: Left (list) + Right (detail/form) */
.masterLayout {
  display: flex;
  gap: 12px;
  height: calc(100vh - 140px);
}

.leftPanel {
  width: 320px;  /* Fixed width list */
}

.rightPanel {
  flex: 1;       /* Expands for detail/form */
}
Features
  • Search bar - Filter list items
  • Selectable list - Click to view details
  • Detail view - View mode with Edit/Delete actions
  • Form view - Create/Edit with validation
  • Toast notifications - Success/error feedback
  • Keyboard shortcuts - Alt+N (new), Alt+E (edit), Alt+D (delete), Alt+S (save), Esc (cancel)
Keyboard Handling
// Prefer theme-owned shortcuts over page-level keydown listeners.
<AppShell
  shortcuts={[
    { key: '/', label: 'Search' },
    { key: 'Alt+N', label: 'New' },
    { key: 'Enter', label: 'Edit' },
    { key: 'Esc', label: 'Back' },
  ]}
>
  <Button shortcut="Alt+N" onClick={openNew}>New</Button>

  <DataGrid
    data={rows}
    columns={columns}
    rowKey="id"
    onRowClick={row => openEdit(row.id)}
  />

  <EditableGrid
    data={detailRows}
    columns={detailColumns}
    rowKey="id"
    onChange={setDetailRows}
  />

  <Button shortcut="Esc" onClick={goBack}>Cancel</Button>
</AppShell>

// Use useShortcuts only for behavior that is not naturally a button,
// tile, or grid action.
useShortcuts([
  { key: 'd', alt: true, label: 'Delete', action: deleteSelected },
]);

API Reference

Component props and type definitions.

Type Exports
// Button
type ButtonProps = {
  variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'success';
  size?: 'sm' | 'md';
  loading?: boolean;
  shortcut?: string;
  showShortcut?: boolean;
  disabled?: boolean;
  onClick?: () => void;
  children: React.ReactNode;
};

// ActionTile
type ActionTileColor = 'blue' | 'emerald' | 'amber' | 'rose' | 'violet' | 'cyan' | 'slate';
type ActionTileProps = {
  title: string;
  description?: string;
  icon: IconDefinition;
  shortcut?: string;
  color?: ActionTileColor;
  href?: string;
  showShortcut?: boolean;
  onClick?: () => void;
};

// ReportCard
type ReportCardColor = 'blue' | 'emerald' | 'amber' | 'rose' | 'violet' | 'cyan' | 'slate';
type ReportCardProps = {
  title: string;
  description: string;
  icon: IconName;
  shortcut: string;
  lastGenerated?: string;
  actionLabel?: string;
  color?: ReportCardColor;
  href?: string;
  showShortcut?: boolean;
  enableShortcut?: boolean;
  onClick?: () => void;
};

// Icons
type IconName = keyof typeof Icon;
const navIcon = getNavIcon('reports');

// Card
type CardVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'purple';
type CardProps = { children: React.ReactNode; variant?: CardVariant; className?: string; };
type CardHeaderProps = { title: string; subtitle?: string; action?: React.ReactNode; };

// Foundation primitives
type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
type ModalProps = {
  open: boolean;
  onClose: () => void;
  title?: React.ReactNode;
  description?: React.ReactNode;
  children: React.ReactNode;
  footer?: React.ReactNode;
  size?: ModalSize;
  closeOnOverlayClick?: boolean;
  closeOnEscape?: boolean;
};

type DrawerSide = 'left' | 'right' | 'bottom';
type DrawerSize = 'sm' | 'md' | 'lg';
type DrawerProps = Omit<ModalProps, 'size'> & { side?: DrawerSide; size?: DrawerSize };

type ConfirmIntent = 'default' | 'danger' | 'success' | 'warning';
type ConfirmDialogProps = {
  open: boolean;
  onClose: () => void;
  onConfirm: () => void | Promise<void>;
  title: React.ReactNode;
  description?: React.ReactNode;
  confirmLabel?: React.ReactNode;
  cancelLabel?: React.ReactNode;
  intent?: ConfirmIntent;
  loading?: boolean;
};

type ToastVariant = 'info' | 'success' | 'warning' | 'danger';
type ToastOptions = {
  title: React.ReactNode;
  description?: React.ReactNode;
  variant?: ToastVariant;
  duration?: number;
  action?: React.ReactNode;
};

type StateTone = 'default' | 'info' | 'success' | 'warning' | 'danger';
type StateBlockProps = {
  title: React.ReactNode;
  description?: React.ReactNode;
  tone?: StateTone;
  actions?: React.ReactNode;
  compact?: boolean;
};

// Dashboard components
type KPIItem = {
  label: string;
  value: string;
  icon?: string;
  change?: string;
  trend?: 'up' | 'down' | 'neutral';
  color?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'purple';
};
type KPIStripProps = { items: KPIItem[]; columns?: number };
type KPICardProps = Omit<KPIItem, 'icon'> & { icon?: React.ReactNode };

type Module = {
  id: string;
  name: string;
  description: string;
  icon?: string;
  shortcut?: string;
  href?: string;
};
type ModuleGridProps = {
  modules: Module[];
  columns?: number;
  onClick?: (module: Module) => void;
  focusedIndex?: number;
  onFocusChange?: (index: number) => void;
  enableKeyboard?: boolean;
};

type Activity = {
  id: string;
  title: string;
  description?: string;
  time: string;
  type?: 'success' | 'warning' | 'danger' | 'info';
};
type ActivityFeedProps = { activities: Activity[]; maxItems?: number };

// Pagination and views
type PaginationProps = {
  page: number;
  pageSize: number;
  totalItems: number;
  pageSizeOptions?: number[];
  loading?: boolean;
  hasMore?: boolean;
  showPageControls?: boolean;
  showPageSize?: boolean;
  onPageChange: (page: number) => void;
  onPageSizeChange?: (pageSize: number) => void;
  onLoadMore?: () => void;
};
type ViewTab<T extends string = string> = {
  id: T;
  label: React.ReactNode;
  description?: React.ReactNode;
  icon?: React.ReactNode;
  count?: number;
  disabled?: boolean;
};
type ViewTabsProps<T extends string = string> = {
  tabs: ViewTab<T>[];
  value: T;
  onChange: (value: T) => void;
  variant?: 'tabs' | 'segmented';
};

// DataGrid
type Column<T> = {
  key: string;
  header: string;
  width?: string | number;
  align?: 'left' | 'center' | 'right';
  sortable?: boolean;
  filter?: 'text' | 'select';
  filterOptions?: Array<{ value: string; label: string }>;
  render?: (value: unknown, row: T) => React.ReactNode;
};
type DataGridPagination = {
  mode?: 'client' | 'manual';
  page?: number;
  defaultPage?: number;
  pageSize?: number;
  defaultPageSize?: number;
  pageSizeOptions?: number[];
  totalItems?: number;
  loading?: boolean;
  hasMore?: boolean;
  showPageControls?: boolean;
  showPageSize?: boolean;
  onPageChange?: (page: number) => void;
  onPageSizeChange?: (pageSize: number) => void;
  onLoadMore?: () => void;
};

// EditableGrid
type ColumnDef = {
  key: string;
  header: string;
  width?: string | number;
  align?: 'left' | 'center' | 'right';
  editable?: boolean;
  type?: 'text' | 'number' | 'select' | 'readonly';
  step?: number;
  min?: number;
  max?: number;
  options?: Array<{ value: string; label: string }>;
  render?: (value: unknown, row: Record<string, unknown>) => React.ReactNode;
};
type EditableGridProps = {
  data: Record<string, unknown>[];
  columns: ColumnDef[];
  rowKey?: string;
  onChange?: (data: Record<string, unknown>[]) => void;
  addLabel?: string;
  maxRows?: number;
  minRows?: number;
  headerColor?: string;
  showRowNumbers?: boolean;
  tabStartIndex?: number;
  autoFocus?: boolean;
};

// MultiLevelGridView
type MultiLevelGridRow = Record<string, unknown>;
type MultiLevelGridContext = {
  root: MultiLevelGridRow | null;
  selectedRows: Record<string, MultiLevelGridRow | undefined>;
};
type MultiLevelGridLevel = {
  id: string;
  title: string;
  rowKey: string;
  columns: Column<MultiLevelGridRow>[] | ((context: MultiLevelGridContext) => Column<MultiLevelGridRow>[]);
  getRows: (context: MultiLevelGridContext) => MultiLevelGridRow[];
  getRowLabel?: (row: MultiLevelGridRow) => string;
  next?: { levelId: string; shortcut: string; label: string };
};

// ERPDocument
type ERPDocumentField = { label: string; value: React.ReactNode };
type ERPDocumentParty = {
  title: string;
  name: React.ReactNode;
  address?: React.ReactNode[];
  fields?: ERPDocumentField[];
  lines?: React.ReactNode[];
};
type ERPDocumentTableColumn<T> = {
  header: React.ReactNode;
  align?: 'left' | 'center' | 'right';
  render: (row: T, index: number) => React.ReactNode;
};
type ERPDocumentTotalRow = { label: React.ReactNode; value: React.ReactNode; highlight?: boolean };
type ERPDocumentInfoBox = { title: React.ReactNode; lines?: React.ReactNode[]; children?: React.ReactNode };
type ERPDocumentStatusCard = {
  title: React.ReactNode;
  status?: React.ReactNode;
  tone?: 'done' | 'review' | 'neutral';
  lines?: React.ReactNode[];
};
type ERPDocumentSignature = {
  title: React.ReactNode;
  name?: React.ReactNode;
  meta?: React.ReactNode[];
  status?: React.ReactNode;
};
type ERPDocumentSection<T> =
  | { type: 'meta'; title?: React.ReactNode; fields: ERPDocumentField[] }
  | { type: 'parties'; parties: ERPDocumentParty[] }
  | { type: 'table'; title?: React.ReactNode; rows: T[]; columns: ERPDocumentTableColumn<T>[] }
  | { type: 'totals'; summary?: ERPDocumentInfoBox; totals: ERPDocumentTotalRow[] }
  | { type: 'cards'; title?: React.ReactNode; cards: ERPDocumentStatusCard[] }
  | { type: 'twoColumn'; boxes: ERPDocumentInfoBox[] }
  | { type: 'signatures'; title?: React.ReactNode; signatures: ERPDocumentSignature[] }
  | { type: 'custom'; title?: React.ReactNode; children: React.ReactNode };

// AppShell
type NavItem = { id: string; label: string; href?: string; shortcut?: string; };
type NavSection = { id: string; label: string; items: NavItem[] };
type ShortcutDef = { key: string; label: string; };
type AppShellProps = {
  autoFocusMainContent?: boolean; // default true
  flashProgressOnAction?: boolean; // default true
  progressFlashDuration?: number; // default 720
  enableHealthRecovery?: boolean; // default true
  healthCheckDelay?: number; // default 8000
  autoReloadDelay?: number; // default 15000
  responsivenessLagThreshold?: number; // default 5000
  loading?: boolean;
  loadingLabel?: string;
  shortcuts?: ShortcutDef[];
};
DOCS
↑↓←→Navigate
EnterSelect
GDashboard
/Search
Ctrl+BAlerts
EscBack/Close
AtithiOS v2.0Developed By Shulyn Technologies Private Limited