diff --git a/.vscode/settings.json b/.vscode/settings.json index 1b53be8..16bfb9c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,5 @@ "mode": "auto" } ], - "cSpell.words": ["mantine", "Menlo", "mgmt", "Millis", "Pandang", "Segoe", "Ujung", "VITE", "WITA"] + "cSpell.words": ["dtos", "mantine", "Menlo", "mgmt", "Millis", "Pandang", "Segoe", "TDTO", "Ujung", "VITE", "WITA"] } diff --git a/apps/web/src/apps/index.tsx b/apps/web/src/apps/index.tsx index b132d29..7d0920d 100644 --- a/apps/web/src/apps/index.tsx +++ b/apps/web/src/apps/index.tsx @@ -1,7 +1,7 @@ import { lazy, Suspense, useEffect, useState } from 'react'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { ThemeProvider, DensityType } from '@repo/ui/provider'; -import { NotFound, Forbidden, Maintenance, ComingSoon } from '@repo/ui/components'; +import { NotFound, Forbidden, Maintenance, ComingSoon, AgGridProvider } from '@repo/ui/components'; import { LoadingScreen } from '../core/components/loading-screen'; import { useThemeStore } from '../core/stores/theme.store'; import { initializeAndPurgeHistoryBackground } from './modules/layouts/hooks/useHistoryTracker'; @@ -23,22 +23,24 @@ export default function App() { return ( - - }> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - + + + }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); } diff --git a/apps/web/src/apps/modules/example/full-page/domain/constants/full-page.constants.ts b/apps/web/src/apps/modules/example/full-page/domain/constants/full-page.constants.ts index 9c03319..d892a85 100644 --- a/apps/web/src/apps/modules/example/full-page/domain/constants/full-page.constants.ts +++ b/apps/web/src/apps/modules/example/full-page/domain/constants/full-page.constants.ts @@ -4,7 +4,7 @@ import { ModuleConfigEntity } from '@repo/ui/foundations'; * Core configuration and constants for the Full Page module. * Used across Domain, Data, and Presentation layers. */ -export const FullPageModuleConfig: ModuleConfigEntity = { +export const fullPageModuleConfig: ModuleConfigEntity = { /** Unique identifier for permissions, caching, and i18n */ moduleKey: 'EXAMPLE_FULL_PAGE', @@ -21,6 +21,5 @@ export const FullPageModuleConfig: ModuleConfigEntity = { moduleCategory: 'FULL_PAGE', /** */ - // moduleType: 'MASTER_DATA', - moduleType: 'TRANSACTION', + moduleType: 'MASTER_DATA', } as const; diff --git a/apps/web/src/apps/modules/example/full-page/domain/factories/index.ts b/apps/web/src/apps/modules/example/full-page/domain/factories/index.ts index 45bf752..126e93d 100644 --- a/apps/web/src/apps/modules/example/full-page/domain/factories/index.ts +++ b/apps/web/src/apps/modules/example/full-page/domain/factories/index.ts @@ -9,7 +9,7 @@ import { apiClient } from '../../../../../../core/lib/api-client'; import { FullPageRemoteDataServices } from '../../data/full-page.remote.service'; -import { FullPageModuleConfig } from '../constants/full-page.constants'; +import { fullPageModuleConfig } from '../constants/full-page.constants'; import { FullPageRemoteDataTransformer } from '../transformers/full-page.remote.transformer'; /** @@ -24,7 +24,7 @@ export const fullPageDataTransformer = new FullPageRemoteDataTransformer(); * It is fully wired with the HTTP client and automatically handles data mapping via the injected transformer. */ export const fullPageDataService = new FullPageRemoteDataServices(apiClient, { - apiUrl: FullPageModuleConfig.apiUrl, - moduleKey: FullPageModuleConfig.moduleKey, + apiUrl: fullPageModuleConfig.apiUrl, + moduleKey: fullPageModuleConfig.moduleKey, transformer: fullPageDataTransformer, }); diff --git a/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx b/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx index 78878c2..a1415d8 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx +++ b/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx @@ -2,9 +2,10 @@ import { lazy } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { EnterpriseModuleProvider } from '@repo/ui/foundations'; import { registerModuleNamespace } from '@repo/core-i18n'; -import { FullPageModuleConfig } from '../../domain/constants'; +import { fullPageModuleConfig } from '../../domain/constants'; import { fullPageDataService } from '../../domain/factories'; import { FullPageEntity } from '../../domain/entities'; +import { fullPageStore } from '../store'; import fullPageId from '../languages/id/full-page.json'; import fullPageEn from '../languages/en/full-page.json'; @@ -18,7 +19,7 @@ const DetailPage = lazy(() => import('../pages/full-page.page.detail')); // --------------------------------------------------------------------------- // Called once at import time — safe, idempotent, outside React render cycle. // The namespace 'full-page' must match config.translationNamespace. -registerModuleNamespace(FullPageModuleConfig.translationNamespace, { +registerModuleNamespace(fullPageModuleConfig.translationNamespace, { id: fullPageId, en: fullPageEn, }); @@ -28,14 +29,18 @@ registerModuleNamespace(FullPageModuleConfig.translationNamespace, { // --------------------------------------------------------------------------- export default function FullPageModule() { return ( - config={FullPageModuleConfig} dataServices={fullPageDataService}> + + config={fullPageModuleConfig} + dataServices={fullPageDataService} + store={fullPageStore} + > } /> } /> } /> } /> } /> - } /> + } /> } /> diff --git a/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.detail.tsx b/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.detail.tsx index 2fc576b..9917e82 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.detail.tsx +++ b/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.detail.tsx @@ -1,19 +1,18 @@ import { EnterpriseDetailPageProvider, useEnterpriseModuleTranslationContext } from '@repo/ui/foundations'; -import { FullPageModuleConfig } from '../../domain/constants'; +import { fullPageModuleConfig } from '../../domain/constants'; export default function FullPagePageDetail() { const { t } = useEnterpriseModuleTranslationContext(); return ( diff --git a/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts b/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts index e69de29..55527ad 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts +++ b/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts @@ -0,0 +1,19 @@ +import { create } from 'zustand'; +import { EnterpriseModuleState } from '@repo/ui/foundations'; +import { FullPageEntity } from '../../domain/entities'; + +export interface FullPageStoreState extends EnterpriseModuleState {} + +export const fullPageStore = create((set) => ({ + metaData: null, + setMetaData: (data) => set({ metaData: data }), + + filterData: { page: 1, limit: 10 }, + setFilterData: (data) => set({ filterData: data }), + + selectedRows: [], + setSelectedRows: (rows) => set({ selectedRows: rows }), + + privileges: [], + setPrivileges: (privileges) => set({ privileges }), +})); diff --git a/apps/web/src/apps/modules/example/single-page/domain/constants/single-page.constants.ts b/apps/web/src/apps/modules/example/single-page/domain/constants/single-page.constants.ts index 663f7fc..7b82f75 100644 --- a/apps/web/src/apps/modules/example/single-page/domain/constants/single-page.constants.ts +++ b/apps/web/src/apps/modules/example/single-page/domain/constants/single-page.constants.ts @@ -4,7 +4,7 @@ import { ModuleConfigEntity } from '@repo/ui/foundations'; * Core configuration and constants for the Full Page module. * Used across Domain, Data, and Presentation layers. */ -export const SinglePageModuleConfig: ModuleConfigEntity = { +export const singlePageModuleConfig: ModuleConfigEntity = { /** Unique identifier for permissions, caching, and i18n */ moduleKey: 'EXAMPLE_SINGLE_PAGE', diff --git a/apps/web/src/apps/modules/example/single-page/domain/factories/index.ts b/apps/web/src/apps/modules/example/single-page/domain/factories/index.ts index 30cfe38..4b5550e 100644 --- a/apps/web/src/apps/modules/example/single-page/domain/factories/index.ts +++ b/apps/web/src/apps/modules/example/single-page/domain/factories/index.ts @@ -9,7 +9,7 @@ import { apiClient } from '../../../../../../core/lib/api-client'; import { SinglePageRemoteDataServices } from '../../data/single-page.remote.service'; -import { SinglePageModuleConfig } from '../constants/single-page.constants'; +import { singlePageModuleConfig } from '../constants/single-page.constants'; import { SinglePageRemoteDataTransformer } from '../transformers/single-page.remote.transformer'; /** @@ -24,7 +24,7 @@ export const singlePageDataTransformer = new SinglePageRemoteDataTransformer(); * It is fully wired with the HTTP client and automatically handles data mapping via the injected transformer. */ export const singlePageDataService = new SinglePageRemoteDataServices(apiClient, { - apiUrl: SinglePageModuleConfig.apiUrl, - moduleKey: SinglePageModuleConfig.moduleKey, + apiUrl: singlePageModuleConfig.apiUrl, + moduleKey: singlePageModuleConfig.moduleKey, transformer: singlePageDataTransformer, }); diff --git a/apps/web/src/apps/modules/example/single-page/presentation/factory/index.tsx b/apps/web/src/apps/modules/example/single-page/presentation/factory/index.tsx index b26258b..f2e1c9a 100644 --- a/apps/web/src/apps/modules/example/single-page/presentation/factory/index.tsx +++ b/apps/web/src/apps/modules/example/single-page/presentation/factory/index.tsx @@ -2,9 +2,10 @@ import { lazy } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { EnterpriseModuleProvider } from '@repo/ui/foundations'; import { registerModuleNamespace } from '@repo/core-i18n'; -import { SinglePageModuleConfig } from '../../domain/constants'; +import { singlePageModuleConfig } from '../../domain/constants'; import { singlePageDataService } from '../../domain/factories'; import { SinglePageEntity } from '../../domain/entities'; +import { singlePageStore } from '../store'; import singlePageId from '../languages/id/single-page.json'; import singlePageEn from '../languages/en/single-page.json'; @@ -16,7 +17,7 @@ const IndexPage = lazy(() => import('../pages/single-page.page.index')); // --------------------------------------------------------------------------- // Called once at import time — safe, idempotent, outside React render cycle. // The namespace 'single-page' must match config.translationNamespace. -registerModuleNamespace(SinglePageModuleConfig.translationNamespace, { +registerModuleNamespace(singlePageModuleConfig.translationNamespace, { id: singlePageId, en: singlePageEn, }); @@ -26,10 +27,14 @@ registerModuleNamespace(SinglePageModuleConfig.translationNamespace, { // --------------------------------------------------------------------------- export default function SinglePageModule() { return ( - config={SinglePageModuleConfig} dataServices={singlePageDataService}> + + config={singlePageModuleConfig} + dataServices={singlePageDataService} + store={singlePageStore} + > } /> - } /> + } /> } /> diff --git a/apps/web/src/apps/modules/example/single-page/presentation/store/index.ts b/apps/web/src/apps/modules/example/single-page/presentation/store/index.ts index e69de29..e5c4e8f 100644 --- a/apps/web/src/apps/modules/example/single-page/presentation/store/index.ts +++ b/apps/web/src/apps/modules/example/single-page/presentation/store/index.ts @@ -0,0 +1,19 @@ +import { create } from 'zustand'; +import { EnterpriseModuleState } from '@repo/ui/foundations'; +import { SinglePageEntity } from '../../domain/entities'; + +export interface SinglePageStoreState extends EnterpriseModuleState {} + +export const singlePageStore = create((set) => ({ + metaData: null, + setMetaData: (data) => set({ metaData: data }), + + filterData: { page: 1, limit: 10 }, + setFilterData: (data) => set({ filterData: data }), + + selectedRows: [], + setSelectedRows: (rows) => set({ selectedRows: rows }), + + privileges: [], + setPrivileges: (privileges) => set({ privileges }), +})); diff --git a/apps/web/src/apps/showcase/components/AgGridShowcase.tsx b/apps/web/src/apps/showcase/components/AgGridShowcase.tsx new file mode 100644 index 0000000..a6a0f90 --- /dev/null +++ b/apps/web/src/apps/showcase/components/AgGridShowcase.tsx @@ -0,0 +1,218 @@ +import { useState, useMemo } from 'react'; +import { Card, Title, Text, Stack, Badge, Group, Select, NumberInput } from '@repo/ui/components'; +import { AgGridProvider, DataGrid } from '@repo/ui/ag-grid'; +import type { ColDef, ValueFormatterParams } from '@repo/ui/ag-grid'; + +/* ─── Sample Data ──────────────────────────────────────────────── */ + +interface OrderRow { + id: string; + orderCode: string; + customer: string; + email: string; + product: string; + quantity: number; + unitPrice: number; + total: number; + status: 'Draft' | 'Pending' | 'Processing' | 'Shipped' | 'Delivered' | 'Cancelled'; + createdAt: string; + region: string; +} + +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' }, +]; + +/* ─── Status Badge Renderer ─────────────────────────────────────── */ + +const STATUS_COLORS: Record = { + Draft: 'gray', + Pending: 'warning', + Processing: 'info', + Shipped: 'brand', + Delivered: 'success', + Cancelled: 'error', +}; + +function StatusCellRenderer({ value }: { value: OrderRow['status'] }) { + return ( + + {value} + + ); +} + +/* ─── Currency Formatter ────────────────────────────────────────── */ + +function currencyFormatter(params: ValueFormatterParams): string { + if (params.value == null) return '-'; + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + }).format(params.value); +} + +/* ─── Showcase Component ────────────────────────────────────────── */ + +export default function AgGridShowcase() { + const [gridHeight, setGridHeight] = useState(460); + const [rowModelType, setRowModelType] = useState('clientSide'); + + const defaultColDef = useMemo( + () => ({ + flex: 1, + minWidth: 100, + sortable: true, + filter: true, + resizable: true, + }), + [], + ); + + const columnDefs = useMemo[]>( + () => [ + { + field: 'orderCode', + headerName: 'Order Code', + pinned: 'left', + minWidth: 140, + filter: 'agTextColumnFilter', + }, + { + field: 'status', + headerName: 'Status', + minWidth: 120, + cellRenderer: StatusCellRenderer, + filter: 'agSetColumnFilter', + }, + { + field: 'customer', + headerName: 'Customer', + minWidth: 160, + filter: 'agTextColumnFilter', + }, + { + field: 'email', + headerName: 'Email', + minWidth: 180, + filter: 'agTextColumnFilter', + }, + { + field: 'product', + headerName: 'Product', + minWidth: 130, + filter: 'agSetColumnFilter', + }, + { + field: 'region', + headerName: 'Region', + minWidth: 140, + filter: 'agSetColumnFilter', + enableRowGroup: true, + }, + { + field: 'quantity', + headerName: 'Qty', + minWidth: 80, + filter: 'agNumberColumnFilter', + type: 'numericColumn', + }, + { + field: 'unitPrice', + headerName: 'Unit Price', + minWidth: 110, + filter: 'agNumberColumnFilter', + type: 'numericColumn', + valueFormatter: currencyFormatter, + }, + { + field: 'total', + headerName: 'Total', + minWidth: 120, + filter: 'agNumberColumnFilter', + type: 'numericColumn', + valueFormatter: currencyFormatter, + }, + { + field: 'createdAt', + headerName: 'Created Date', + minWidth: 130, + filter: 'agDateColumnFilter', + }, + ], + [], + ); + + return ( + + + {/* ── Info Card ────────────────────────────────── */} + + + AG Grid Enterprise — Mantine Integration + + + 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. + + + setGridHeight(Number(val) || 400)} + min={300} + max={800} + step={50} + w={180} + /> +