Frontend Architecture¶
Overview¶
The LCO DMA (Data Management Application) frontend is a modern React-based single-page application (SPA) built with TypeScript, focusing on construction project estimation and management. The application follows a clean, modular architecture with clear separation of concerns.
Tech Stack¶
Core Technologies¶
- React 18.3.1 - UI framework with hooks and modern patterns
- TypeScript 5.8.3 - Type-safe JavaScript with strict mode enabled
- Vite 6.3.6 - Fast build tool and dev server with HMR
- React Router DOM 6.30.1 - Client-side routing with nested routes
- Axios 1.11.0 - HTTP client for API communication with interceptors
UI Framework & Styling¶
- Tailwind CSS 3.4.17 - Utility-first CSS framework with custom configuration
- Radix UI (v1.x - v2.x) - Headless, accessible UI component primitives:
- @radix-ui/react-accordion 1.2.12
- @radix-ui/react-alert-dialog 1.1.15
- @radix-ui/react-avatar 1.1.10
- @radix-ui/react-checkbox 1.3.3
- @radix-ui/react-dialog 1.1.15
- @radix-ui/react-dropdown-menu 2.1.16
- @radix-ui/react-label 2.1.7
- @radix-ui/react-popover 1.1.15
- @radix-ui/react-select 2.2.6
- @radix-ui/react-slot 1.2.3
- @radix-ui/react-switch 1.2.6
- @radix-ui/react-tabs 1.1.13
- @radix-ui/react-toast 1.2.15
- @radix-ui/react-tooltip 1.2.8
- cmdk 1.1.1 - Command menu component for comboboxes
- Lucide React 0.542.0 - Icon library with consistent design
- tailwindcss-animate 1.0.7 - Animation utilities for Tailwind
- shadcn/ui patterns - Pre-built component patterns (no package, code copied)
Data & State Management¶
- Zustand 5.0.8 - Lightweight state management library
- @tanstack/react-table 8.21.3 - Headless table library for complex data grids
- @dnd-kit - Drag and drop functionality:
- @dnd-kit/core 6.3.1
- @dnd-kit/sortable 10.0.0
- @dnd-kit/modifiers 9.0.0
- @dnd-kit/utilities 3.2.2
Development Tools¶
- Node.js: 20.19.0+ (engine requirement)
- ESLint 9.32.0 - Code linting with TypeScript support
- eslint-plugin-react-hooks 5.2.0
- eslint-plugin-react-refresh 0.4.20
- typescript-eslint 8.39.0
- PostCSS 8.5.6 & Autoprefixer 10.4.21 - CSS processing
- Class Variance Authority 0.7.1 - Component variant management
- clsx 2.1.1 & tailwind-merge 3.3.1 - Utility class composition
Project Structure¶
frontend/
├── src/
│ ├── assets/ # Static assets (images, logos)
│ ├── components/ # React components
│ │ ├── ui/ # Reusable UI components (shadcn/ui)
│ │ │ ├── button.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── table.tsx
│ │ │ ├── combobox.tsx
│ │ │ └── ... (42 UI components)
│ │ ├── table/ # Advanced table components
│ │ │ ├── DataTable.tsx
│ │ │ ├── EditableCell.tsx
│ │ │ ├── BulkEditPopover.tsx
│ │ │ ├── ColumnVisibilityDialog.tsx
│ │ │ ├── DataTableFilterBuilder.tsx
│ │ │ └── ... (table utilities)
│ │ ├── virtualized/ # Virtualization components
│ │ │ └── VirtualizedTableBody.tsx
│ │ ├── estimation-tabs/ # Estimation tab components
│ │ │ ├── EstimationMasterTab.tsx
│ │ │ ├── MTODisciplineTab.tsx
│ │ │ ├── CrewsTab.tsx
│ │ │ ├── EquipmentTagsTab.tsx
│ │ │ ├── SubcontractorsTab.tsx
│ │ │ ├── MaterialCostTab.tsx
│ │ │ └── LaborSettingsTab.tsx
│ │ ├── docs/ # Documentation components
│ │ ├── AppSidebar.tsx # Main navigation (shadcn Sidebar)
│ │ ├── COATable.tsx # Hierarchical COA tree
│ │ ├── CrewsTable.tsx
│ │ ├── DisciplineCard.tsx
│ │ ├── WBSTable.tsx
│ │ └── ... (dialogs, forms)
│ ├── config/ # Configuration files
│ ├── constants/ # Application constants
│ │ └── locations.ts
│ ├── hooks/ # Custom React hooks (modular architecture)
│ │ ├── index.ts # Central exports
│ │ ├── factories/ # Hook factory patterns
│ │ │ ├── createInfiniteResourceHook.ts
│ │ │ ├── createLookupMapHook.ts
│ │ │ └── usePaginatedResource.ts
│ │ ├── foundation/ # Low-level utility hooks
│ │ │ ├── useDebouncedValue.ts
│ │ │ ├── useLocalStorageState.ts
│ │ │ ├── useSessionBackedState.ts
│ │ │ ├── usePagination.ts
│ │ │ └── useModifiedTracker.ts
│ │ ├── infinite/ # Infinite scroll hooks
│ │ │ ├── useInfiniteMTOs.ts
│ │ │ ├── useInfiniteCOAs.ts
│ │ │ ├── useInfiniteEquipmentTags.ts
│ │ │ ├── useInfiniteMaterialCosts.ts
│ │ │ └── useInfiniteSubcontractors.ts
│ │ ├── crews/ # Crew business logic
│ │ │ ├── useServiceCrews.ts
│ │ │ ├── useServiceCrewsCalculations.ts
│ │ │ ├── useAvailableCrews.ts
│ │ │ ├── useCrewMembers.ts
│ │ │ ├── useCrewRates.ts
│ │ │ └── useEquipment.ts
│ │ ├── mto/ # MTO domain hooks
│ │ │ ├── useEstimationMaster.ts
│ │ │ ├── useMTOManagement.ts
│ │ │ └── useMTOCalculations.ts
│ │ ├── resources/ # Reference data hooks
│ │ │ ├── useCOAs.ts
│ │ │ ├── useSubcontractors.ts
│ │ │ └── useEquipmentTags.ts
│ │ ├── table/ # Table state hooks
│ │ │ └── useTableViewState.ts
│ │ └── ui/ # UI hooks
│ │ ├── useDialogManager.ts
│ │ └── use-toast.tsx
│ ├── lib/ # Utility libraries
│ │ └── utils.ts # cn() function for class composition
│ ├── pages/ # Page-level components (route handlers)
│ │ ├── HomePage.tsx
│ │ ├── ProjectsPage.tsx
│ │ ├── ProjectDetailPage.tsx
│ │ ├── EstimationServicePage.tsx
│ │ ├── CrewsTablePage.tsx
│ │ ├── CrewMemberTablePage.tsx
│ │ ├── CrewTradesTablePage.tsx
│ │ ├── EquipmentTablePage.tsx
│ │ ├── MaterialTablePage.tsx
│ │ ├── WBSTablePage.tsx
│ │ ├── WBSBuilderPage.tsx
│ │ ├── CreateEditCrewPage.tsx
│ │ ├── EditServiceCrewPage.tsx
│ │ ├── CreateProjectPage.tsx
│ │ ├── EditProjectPage.tsx
│ │ └── DocsPage.tsx
│ ├── services/ # API integration layer
│ │ └── api/
│ │ ├── config.ts # Axios client configuration
│ │ ├── projects.ts # Project API endpoints
│ │ ├── services.ts # Service API endpoints
│ │ ├── crews.ts # Crew API endpoints
│ │ ├── serviceCrews.ts # Service Crew API endpoints
│ │ ├── equipment.ts # Equipment API endpoints
│ │ ├── equipmentTags.ts # Equipment Tags API endpoints
│ │ ├── material.ts # Material API endpoints
│ │ ├── materialCost.ts # Material Cost API endpoints
│ │ ├── mto.ts # MTO API endpoints
│ │ ├── wbs.ts # WBS API endpoints
│ │ ├── coas.ts # COA API endpoints
│ │ ├── subcontractors.ts # Subcontractor API endpoints
│ │ └── laborSettings.ts # Labor Settings API endpoints
│ ├── stores/ # Zustand global state stores (3 stores)
│ │ ├── README.md
│ │ ├── authStore.ts
│ │ ├── projectStore.ts
│ │ └── demoDataStore.ts
│ ├── types/ # TypeScript type definitions
│ │ ├── project.ts
│ │ ├── service.ts
│ │ ├── crew.ts
│ │ ├── serviceCrew.ts
│ │ ├── equipment.ts
│ │ ├── equipmentTag.ts
│ │ ├── material.ts
│ │ ├── mto.ts
│ │ ├── wbs.ts
│ │ ├── coa.ts
│ │ └── subcontractor.ts
│ ├── utils/ # Utility functions
│ │ ├── mtoCalculations/
│ │ │ ├── __tests__/
│ │ │ └── ... (calculation utilities)
│ │ ├── coaHelpers.ts # COA tree utilities
│ │ └── tanstackTableHelpers.ts # Table column creation
│ ├── App.tsx # Main app component with routing
│ ├── main.tsx # Application entry point
│ ├── index.css # Global styles with Tailwind directives
│ └── App.css # App-specific styles
├── public/ # Static public assets
├── package.json # Dependencies and scripts
├── vite.config.ts # Vite configuration
├── tailwind.config.js # Tailwind CSS configuration
├── tsconfig.json # TypeScript configuration
├── tsconfig.app.json # App-specific TypeScript config
├── tsconfig.node.json # Node-specific TypeScript config
└── eslint.config.js # ESLint configuration
Architecture Patterns¶
1. Component Architecture¶
Component Hierarchy¶
flowchart TD
App["App (Router Provider)"]
Sidebar["Sidebar (Global Navigation)"]
Main["Main Content Area"]
Pages["Route-based Pages"]
Header["PageHeader (Breadcrumbs, Title, Actions)"]
Content["Page Content"]
Tabs["Tabs (optional)"]
DataTable["DataTable with advanced features"]
Forms["Forms"]
Dialogs["Dialogs/Modals"]
Filtering["Filtering"]
Sorting["Sorting"]
ColVis["Column visibility"]
BulkEdit["Bulk editing"]
EditCells["Editable cells"]
App --> Sidebar
App --> Main
Main --> Pages
Pages --> Header
Pages --> Content
Content --> Tabs
Content --> DataTable
Content --> Forms
Content --> Dialogs
DataTable --> Filtering
DataTable --> Sorting
DataTable --> ColVis
DataTable --> BulkEdit
DataTable --> EditCells
Component Types¶
UI Components (components/ui/)
- Pure, reusable components based on Radix UI primitives
- No business logic, highly composable
- Forwarded refs for accessibility
- Examples: Button, Dialog, Table, Input, Select, Combobox
Table Components (components/table/)
- Advanced data table functionality built on @tanstack/react-table
- VirtualizedDataTable.tsx (798 lines) - Performance table with virtualization and infinite scroll
- DataTable.tsx - Standard table component with filtering, sorting, search
- EditableCell.tsx - Inline cell editing with validation
- BulkEditPopover.tsx - Bulk edit multiple rows
- ColumnVisibilityDialog.tsx - Show/hide columns with drag-and-drop
- DataTableFilterBuilder.tsx - Complex filter builder with AND/OR logic
- SelectionBanner.tsx - Select all/page rows banner
- FloatingActionBar.tsx - Bulk action toolbar
- Context-based data sharing via TableDataContext.tsx
Virtualized Components (components/virtualized/)
- VirtualizedTableBody.tsx (267 lines) - Reusable virtualized table body
Feature Components (components/)
- Business-specific components
- Contain application logic
- AppSidebar.tsx - Main navigation with shadcn Sidebar component
- COATable.tsx (686 lines) - Hierarchical COA tree structure
- CrewsTable.tsx - Crew selection and rate display
- DisciplineCard.tsx - Discipline navigation cards
- WBSTable.tsx - Work Breakdown Structure table
Estimation Tab Components (components/estimation-tabs/)
- Tab content for EstimationServicePage
- EstimationMasterTab.tsx - Aggregated estimation view
- MTODisciplineTab.tsx - MTO data by discipline with virtualized table
- CrewsTab.tsx - Service crew management
- EquipmentTagsTab.tsx - Equipment tag CRUD
- SubcontractorsTab.tsx - Subcontractor management
- MaterialCostTab.tsx - Material cost entries
- LaborSettingsTab.tsx - Labor configuration
- LoadingSpinner.tsx - Shared loading indicator
Page Components (pages/)
- Route handlers
- Orchestrate data fetching, state management, and layout
- Examples: ProjectDetailPage, EstimationServicePage
2. Routing Architecture¶
The application uses React Router v6 with nested routes:
<Router>
<Sidebar />
<Routes>
// Main routes
<Route path="/" element={<HomePage />} />
<Route path="/projects" element={<ProjectsPage />} />
// Project hierarchy
<Route path="/projects/:projectId" element={<ProjectDetailPage />} />
<Route path="/projects/:projectId/services/:serviceId"
element={<EstimationServicePage />} />
<Route path="/projects/:projectId/services/:serviceId/crews/:serviceCrewId/edit"
element={<EditServiceCrewPage />} />
<Route path="/projects/:projectId/edit" element={<EditProjectPage />} />
// Reference tables
<Route path="/reference-tables/crew-member-table" element={<CrewMemberTablePage />} />
<Route path="/reference-tables/crews" element={<CrewsTablePage />} />
<Route path="/reference-tables/crew-trades" element={<CrewTradesTablePage />} />
<Route path="/reference-tables/equipment" element={<EquipmentTablePage />} />
<Route path="/reference-tables/material" element={<MaterialTablePage />} />
<Route path="/reference-tables/wbs" element={<WBSTablePage />} />
<Route path="/reference-tables/wbs/:wbsId/edit" element={<WBSBuilderPage />} />
<Route path="/reference-tables/crews/create" element={<CreateEditCrewPage />} />
<Route path="/reference-tables/crews/:id/edit" element={<CreateEditCrewPage />} />
// Project creation
<Route path="/create-project" element={<CreateProjectPage />} />
// Documentation
<Route path="/docs" element={<DocsPage />} />
</Routes>
</Router>
Route Parameters:
- projectId - Unique project identifier
- serviceId - Service identifier within a project
- serviceCrewId - Service crew identifier for editing
- wbsId - WBS identifier for editing
- id - Generic ID for crews
Query Parameters:
- clientId - Required for project-related API calls (passed via Zustand store)
3. State Management¶
The application uses a hybrid approach combining Zustand for global state and React hooks for local state:
Zustand Stores (stores/)¶
Project Store (projectStore.ts)
- Purpose: Manages project context (projectId, serviceId, clientId) across the application
- Eliminates: Prop drilling through multiple component layers
- Features:
- Redux DevTools integration for debugging
- Helper methods: hasContext(), getQueryParams()
- Actions: setContext(), clearContext()
Store Pattern:
export const useProjectStore = create<ProjectStore>()(
devtools(
(set, get) => ({
projectId: null,
serviceId: null,
clientId: null,
setContext: (context) => set(context, false, 'setContext'),
clearContext: () => set({ projectId: null, serviceId: null, clientId: null }, false, 'clearContext'),
hasContext: () => !!(get().projectId && get().serviceId && get().clientId),
getQueryParams: () => { /* ... */ },
}),
{ name: 'ProjectStore' }
)
);
Custom Hooks Architecture (hooks/)¶
The application uses a modular hooks architecture organized by domain:
hooks/
├── factories/ # Hook factory patterns for code reuse
│ ├── createInfiniteResourceHook.ts # Generic infinite scroll factory
│ ├── createLookupMapHook.ts # O(1) Map-based lookup factory
│ └── usePaginatedResource.ts # Paginated data fetching
│
├── foundation/ # Low-level utility hooks
│ ├── useDebouncedValue.ts # Debounced state updates
│ ├── useLocalStorageState.ts # Persistent localStorage state
│ ├── useSessionBackedState.ts # Session-persistent state
│ ├── usePagination.ts # Pagination state management
│ └── useModifiedTracker.ts # Track modified items for bulk save
│
├── infinite/ # Infinite scroll data hooks (built from factories)
│ ├── useInfiniteMTOs.ts # Infinite MTO loading
│ ├── useInfiniteCOAs.ts # Infinite COA loading
│ ├── useInfiniteEquipmentTags.ts # Infinite equipment tags
│ ├── useInfiniteMaterialCosts.ts # Infinite material costs
│ └── useInfiniteSubcontractors.ts # Infinite subcontractor loading
│
├── crews/ # Crew-related business logic
│ ├── useServiceCrews.ts # Orchestrator for service crews
│ ├── useServiceCrewsCalculations.ts # Rate calculations
│ ├── useServiceCrewsSync.ts # Sync with backend
│ ├── useAvailableCrews.ts # Available crew selection
│ ├── useCrewMembers.ts # Crew member lookup with Map
│ ├── useCrewRates.ts # Indirect cost calculations
│ ├── useCrewRows.ts # Crew row state
│ └── useEquipment.ts # Equipment lookup with Map
│
├── mto/ # MTO domain hooks
│ ├── useEstimationMaster.ts # Estimation master data
│ ├── useMTOManagement.ts # MTO CRUD operations
│ ├── useMTOCalculations.ts # Field calculations/formulas
│ └── useAsyncImportTask.ts # Async import polling
│
├── resources/ # Reference data hooks
│ ├── useCOAs.ts # COA lookup
│ ├── useSubcontractors.ts # Subcontractor lookup
│ └── useEquipmentTags.ts # Equipment tag lookup
│
├── table/ # Table state management
│ └── useTableViewState.ts # Persistent filter/sort/columns
│
├── ui/ # UI-related hooks
│ ├── useDialogManager.ts # Dialog open/close state
│ └── use-toast.tsx # Toast notification system
│
└── utility/ # Miscellaneous utilities
└── useMarkdownDoc.ts # Load markdown documentation
Hook Categories:
| Category | Purpose | Example |
|---|---|---|
| Factories | Eliminate duplication via generic patterns | createInfiniteResourceHook |
| Foundation | Low-level primitives for building hooks | useLocalStorageState |
| Infinite | Infinite scroll with deduplication | useInfiniteMTOs |
| Crews | Service crew business logic | useServiceCrews |
| MTO | Material Take-Off operations | useMTOManagement |
| Resources | Reference data lookups | useCOAs |
| Table | Table view persistence | useTableViewState |
| UI | User interface utilities | useDialogManager |
Pattern Benefits: - Code reusability across components - Separation of concerns (logic vs. UI) - Testability in isolation - Type safety with TypeScript - Performance optimization with memoization - Consistent API across similar hooks via factories
Local Component State¶
// Standard data fetching pattern
const [data, setData] = useState<Type | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const result = await api.getData();
setData(result);
} catch (err) {
setError('Error message');
} finally {
setLoading(false);
}
};
fetchData();
}, [dependencies]);
4. API Integration Layer¶
API Client Configuration (services/api/config.ts)¶
// Axios instance with interceptors
const apiClient = axios.create({
baseURL: API_BASE_URL, // from env: VITE_API_URL
timeout: 30000,
headers: { 'Content-Type': 'application/json' }
});
// Request interceptor (for auth tokens - placeholder)
apiClient.interceptors.request.use(...)
// Response interceptor (for error handling)
apiClient.interceptors.response.use(...)
API Service Modules¶
Each domain has its own API module:
Example: Projects API (services/api/projects.ts)
export const projectsApi = {
getProjects(filters?: ProjectFilters): Promise<PaginatedResponse<Project>>
getProject(projectId: string, clientId: string): Promise<Project>
createProject(project: Partial<Project>): Promise<Project>
updateProject(projectId: string, updates: Partial<Project>): Promise<Project>
deleteProject(projectId: string, clientId: string): Promise<void>
}
API Modules:
- projects.ts - Project CRUD operations
- services.ts - Service management
- crews.ts - Crew and crew member management
- serviceCrews.ts - Service-specific crew assignments
- equipment.ts - Equipment catalog
- equipmentTags.ts - Equipment tags management
- material.ts - Material catalog
- materialCost.ts - Material cost entries per project
- mto.ts - Material Take-Off operations
- wbs.ts - Work Breakdown Structure
- coas.ts - Chart of Accounts (COA) with hierarchical structure
- subcontractors.ts - Subcontractor management
- laborSettings.ts - Labor settings and indirect costs configuration
5. Type System¶
Strong TypeScript typing throughout the application:
Domain Types¶
Project Types (types/project.ts)
- Project - Full project entity
- Location - Geographic information
- PaginatedResponse<T> - Generic pagination wrapper
- ProjectFilters - Query parameters
Service Types (types/service.ts)
- Service - Service entity
- ServiceType - Union type: 'estimation' | 'loan-monitoring' | 'project-controls' | 'claims'
Crew Types (types/crew.ts)
- Crew - Crew template with composition
- CrewMember - Individual trade labor rates
- CrewComposition - Manpower and equipment breakdown
- SkillLevel - Enum for trade skill levels
Service Crew Types (types/serviceCrew.ts)
- ServiceCrew - Project-specific crew instance
- IndirectCosts - Nested cost structure (labor/equipment)
- Helper functions: createDefaultIndirectCosts(), migrateIndirectCosts()
MTO Types (types/mto.ts)
- MaterialTakeoff - MTO entry with calculations
- DisciplineType - Union of discipline categories
- EstimationMasterMTO - Aggregated estimation data
Equipment Types (types/equipmentTag.ts, types/equipment.ts)
- EquipmentTag - Equipment categorization tags
- Equipment - Equipment catalog items
WBS Types (types/wbs.ts)
- WBS - Work Breakdown Structure with hierarchical levels
6. Data Flow Architecture¶
Example: MTO Management in Estimation Service¶
flowchart TD
Open["User Opens Discipline Sheet"]
Comp["Component (EstimationServicePage)"]
Hook["useMTOManagement Hook"]
Fetch["1. Fetch MTO Items (mtoApi.getMTOsByProject)"]
Calc["2. Calculate Fields (useMTOCalculations hook)<br/>- Apply formulas based on discipline type<br/>- Calculate derived fields"]
Update["3. Update Local State (setMtoItems)"]
Edit["User Edits Cell"]
Handle["4. Handle Cell Edit (handleCellEdit)<br/>- Update item in state<br/>- Recalculate dependent fields<br/>- Mark as modified"]
Save["User Clicks "Save Changes""]
APICall["5. API Call (mtoApi.bulkUpdateMTO)"]
UpdateResp["6. Update State with Response"]
Clear["7. Clear Modified Items"]
Toast["Success Toast Notification"]
Open --> Comp --> Hook --> Fetch --> Calc --> Update --> Edit --> Handle --> Save --> APICall --> UpdateResp --> Clear --> Toast
Example: Crew Rate Calculation¶
flowchart TD
Select["User Selects Crew"]
Handler["Component Event Handler"]
Details["1. Fetch Crew Details (crewsApi.getCrew)"]
Members["2. Fetch Crew Members (useCrewMembers hook)<br/>- Returns Map for O(1) lookup"]
Labour["3. Calculate Labour Rate<br/>- Match crew composition to crew members by location/trade<br/>- Weighted average based on quantities"]
Equip["4. Calculate Equipment Rate (useEquipment hook)<br/>- Sum equipment costs"]
Row["5. Update Row State (via useCrewRows hook)"]
EditIndirect["User Edits Indirect Costs"]
Final["6. Calculate Final Rates (via useCrewRates hook)<br/>- Apply indirect cost percentages<br/>- Calculate grand total"]
SaveClick["User Clicks "Save""]
APICall["7. API Call (serviceCrewsApi.createServiceCrew)"]
Toast["Success Toast"]
Select --> Handler --> Details --> Members --> Labour --> Equip --> Row --> EditIndirect --> Final --> SaveClick --> APICall --> Toast
7. Performance Optimizations¶
-
Lookup Maps: O(1) access for crew members and equipment
-
Abort Controllers: Cancel in-flight requests on component unmount
-
Batch Fetching: Load all paginated data efficiently
-
Memoization: useMemo and useCallback in custom hooks
- Table Virtualization: @tanstack/react-virtual for row virtualization (see Section 8)
8. Hook Factory Patterns¶
The application uses factory functions to eliminate duplicated pagination and data fetching logic.
createInfiniteResourceHook Factory¶
Generic factory for infinite scroll hooks with continuation token or offset-based pagination.
Problem Solved: Each infinite scroll hook (MTOs, COAs, Equipment Tags, etc.) had ~200 lines of duplicated logic for: - Continuation token handling - Offset-based pagination fallback - Deduplication of items across pages - AbortController management - Loading/error states
Factory API:
interface InfiniteHookConfig<T, F> {
fetchPage: (params: F & { skip?: number; limit?: number; continuationToken?: string; signal?: AbortSignal }) =>
Promise<{ data: T[]; total?: number; hasMore?: boolean; continuationToken?: string }>;
getId: (item: T) => string; // Unique ID for deduplication
errorMessage: string;
pageSize?: number; // Default: 100
maxItems?: number; // Safety cap, default: 10000
}
// Creates a hook with this return signature:
interface InfiniteHookResult<T> {
items: T[];
total: number | null;
loadingInitial: boolean;
isFetchingNext: boolean;
hasNextPage: boolean;
error: string | null;
loadMore: () => Promise<void>;
refetch: () => Promise<void>;
reset: () => void;
}
Usage Example:
// hooks/infinite/useInfiniteMTOs.ts
export const useInfiniteMTOs = createInfiniteResourceHook<MaterialTakeoff, MTOFilters>({
fetchPage: mtoApi.getMTOsByProject,
getId: (mto) => mto.id,
errorMessage: 'Failed to load MTOs',
pageSize: 100,
});
createLookupMapHook Factory¶
Generic factory for fetching all paginated data and creating a Map for O(1) lookups.
Problem Solved: Crew members and equipment both needed: - Full data fetch across all pages - Map creation for O(1) lookups by composite key - AbortController cleanup - Mounted state tracking
Factory API:
interface LookupMapHookConfig<T, F> {
fetchFn: (filters: F) => Promise<{ data: T[]; hasMore: boolean }>;
createMap: (items: T[]) => Map<string, T>;
errorMessage: string;
batchSize?: number; // Default: 100
maxItems?: number; // Default: 10000
}
// Returns:
interface LookupMapHookResult<T> {
map: Map<string, T>;
items: T[];
loading: boolean;
error: string | null;
refetch: () => void;
}
Usage Example:
// hooks/crews/useCrewMembers.ts
const useCrewMembersData = createLookupMapHook<CrewMember, CrewMemberFilters>({
fetchFn: crewMembersApi.getCrewMembers,
createMap: (items) => {
const map = new Map<string, CrewMember>();
items.forEach(cm => {
const key = `${cm.country}|${cm.province}|${cm.tradeCode}|${cm.laborDesignation}`;
map.set(key, cm);
});
return map;
},
errorMessage: 'Failed to load crew members',
});
9. Virtualization Architecture¶
The application uses @tanstack/react-virtual for row virtualization in large tables.
Component Hierarchy¶
flowchart TD
Root["VirtualizedDataTable (798 lines)"]
Toolbar["DataTableToolbar (filters, search, sort, column visibility)"]
Header["TableHeader (column headers with sort indicators)"]
Body["VirtualizedTableBody (267 lines)"]
Virtualizer["@tanstack/react-virtual virtualizer"]
Visible["Visible rows only (configurable overscan)"]
Loader["Loader row (when hasNextPage)"]
FAB["FloatingActionBar (bulk selection actions)"]
Banner["SelectionBanner (all-items selection)"]
BulkEdit["BulkEditPopover (bulk edit dialog)"]
Root --> Toolbar
Root --> Header
Root --> Body
Body --> Virtualizer
Virtualizer --> Visible
Virtualizer --> Loader
Root --> FAB
Root --> Banner
Root --> BulkEdit
VirtualizedDataTable (components/table/VirtualizedDataTable.tsx)¶
Entry point combining DataTable toolbar features with virtualized body.
Key Features:
- Full toolbar: filters, search, sort, column visibility dialog
- Row selection with checkbox column
- Inline editing via EditableCell
- Bulk operations (edit, delete) with selection context
- Infinite scroll integration via loadMore callback
- Column windowing (40-column chunks for wide tables)
Props Interface:
interface VirtualizedDataTableProps<TData> {
// Data from infinite hooks
data: TData[];
total: number | null;
loadingInitial: boolean;
isFetchingNext: boolean;
hasNextPage: boolean;
error: string | null;
loadMore: () => Promise<void>;
// Column configuration
columns: ColumnConfig[];
// State (controlled by parent)
filterGroup: FilterGroup;
onFiltersChange: (filters: FilterGroup) => void;
sorting: SortingState;
onSortingChange: (sorting: SortingState) => void;
columnVisibility: VisibilityState;
columnOrder: string[];
onColumnConfigChange: (visibility: VisibilityState, order: string[]) => void;
// Callbacks
onCellEdit?: (rowId: string, columnId: string, value: unknown) => void;
onBulkEdit?: (rows: TData[], updates: Record<string, unknown>, context: SelectionContext) => Promise<void>;
onBulkDelete?: (rows: TData[], context: SelectionContext) => Promise<void>;
}
VirtualizedTableBody (components/virtualized/VirtualizedTableBody.tsx)¶
Reusable virtualized <tbody> component.
Key Features: - Renders only visible rows plus overscan buffer - Estimated row height with optional measurement refinement - Prefetch-ahead triggering for infinite scroll - Empty state rendering - Loader row for "loading more" indicator
Performance Optimizations: 1. Reduced overscan during editing: Fewer off-screen rows when cells are being edited 2. Height caching: Avoids repeated DOM measurements 3. requestIdleCallback: Non-urgent work deferred to idle periods 4. Stable row keys: Prevents unnecessary re-renders
Configuration:
<VirtualizedTableBody
parentRef={scrollContainerRef}
data={rows}
renderRow={(item, index) => <TableRow>...</TableRow>}
estimateSize={44} // Estimated row height in px
overscan={12} // Extra rows above/below viewport
prefetchAhead={10} // Rows from end to trigger loadMore
colSpan={columns.length} // For spacer/loader cells
onEndReached={loadMore}
hasNextPage={hasNextPage}
isLoadingMore={isFetchingNext}
/>
10. Table State Persistence¶
The useTableViewState hook persists table configuration to localStorage.
Purpose¶
Users customize table views (filters, sorting, column visibility, column order) and expect these to persist across sessions. The hook handles: - Storing state per-table, per-project - Validating persisted state against current column schema - Gracefully handling added/removed columns
Implementation¶
// Usage in component
const {
filterGroup,
sorting,
columnVisibility,
columnOrder,
setFilterGroup,
setSorting,
setColumnConfig,
clearState,
} = useTableViewState(
'mto-piping', // Table key
projectId, // Project scope
PIPING_COLUMNS // Column definitions
);
Storage Key Format: lco_table_{projectId}_{tableKey}
Persisted State:
interface TableViewState {
filterGroup: FilterGroup; // AND/OR filter conditions
sorting: SortingState; // Column sort state
columnVisibility: VisibilityState; // Which columns are visible
columnOrder: string[]; // Column display order
}
Schema Change Handling¶
When columns are added or removed from the codebase:
- Removed columns: Filtered out of visibility, sorting, filters, and order
- New columns: Inserted at their natural position in column definitions
- Invalid filters: Conditions referencing removed columns are dropped
This prevents crashes when loading persisted state after schema changes.
11. Environment Configuration¶
Environment Variables¶
# .env.development (local development)
VITE_API_URL=http://localhost:8000/api/v1
VITE_ENVIRONMENT=development
# .env.production (Azure deployment)
VITE_API_URL=https://lco-function-app-hggkctgbhhhghgef.canadacentral-01.azurewebsites.net/api/v1
VITE_ENVIRONMENT=production
Vite Configuration Features (vite.config.ts)¶
- Path Aliases:
@/maps tosrc/for clean imports - Dev Server Proxy:
- Build Optimization:
- Source maps disabled in production
- Asset optimization
- React Plugin:
@vitejs/plugin-reactwith Fast Refresh
12. Error Handling Strategy¶
API Error Handling¶
Interceptor Level (services/api/config.ts)
- 401: Unauthorized (auth redirect - placeholder)
- 403: Forbidden
- 404: Not Found
- 500: Server Error
- Network errors: Logged to console
Component Level
try {
// API call
} catch (err) {
toast({
title: 'Error',
description: 'User-friendly message',
variant: 'destructive'
});
}
Loading States¶
All async operations show loading indicators:
- Loading spinners for page loads
- isLoading states for buttons
- Disabled states during operations
Key Features & Workflows¶
1. Project Management¶
- Create, view, edit, delete projects
- Project metadata: location, budget, dates, working hours
- Hierarchical navigation: Projects → Services → Estimation
2. Estimation Service¶
- MTO Discipline Sheets: Material Take-Off by discipline (Electrical, Mechanical, Civil, etc.)
- Inline cell editing
- Automatic formula calculations
- Bulk edit and delete operations
- Advanced filtering and sorting
- Crew Management:
- Select crews from templates
- Auto-calculate labour and equipment rates
- Edit indirect costs with real-time calculations
- Save crews to service
- Estimation Master: Aggregated view of all estimation data
- Equipment Tags: Manage and assign equipment tags to MTO items
3. Advanced Table Features¶
- Inline Editing: Edit cells directly with validation
- Bulk Operations: Edit or delete multiple rows
- Column Management: Show/hide columns with drag-and-drop reordering
- Advanced Filtering: Complex filter builder with AND/OR logic
- Sorting: Multi-column sorting
- Search: Global search across all visible columns
4. Reference Data Management¶
- Crew Templates: Reusable crew compositions
- Crew Members: Trade labor rates by location, quarter, year
- Equipment Catalog: Equipment with base unit prices
- Equipment Tags: Categorization and grouping
- Crew Trades: Trade definitions and categories
- Material Catalog: Material items
- WBS (Work Breakdown Structure): Hierarchical task breakdown
Design Patterns¶
1. Compound Components¶
Example: Tabs component with TabsList, TabsTrigger, TabsContent
2. Render Props Pattern¶
Used in dialogs and modals for controlled rendering
3. Custom Hooks Pattern¶
Extensive use of custom hooks for business logic separation
- See hooks/README.md for detailed documentation
4. Composition over Inheritance¶
All components use composition for flexibility
5. Controlled Components¶
Forms use controlled inputs with React state
6. Provider Pattern¶
Context providers for table data sharing
Navigation Flow¶
flowchart TD
Home["Home"]
Projects["Projects List"]
Detail["Project Detail"]
Services["Services List"]
Estimation["Estimation Service (Tabs)"]
MTO["MTO Discipline Sheets (by discipline)"]
Crews["Crews (with rate calculations)"]
EditSC["Edit Service Crew"]
Master["Estimation Master (aggregated view)"]
EditProj["Edit Project"]
RefTables["Reference Tables"]
Trades["Crew Trades"]
Members["Crew Members"]
Equipment["Equipment"]
EquipTags["Equipment Tags (via dialog)"]
Material["Material"]
WBS["WBS"]
WBSBuilder["WBS Builder"]
RefCrews["Crews"]
CreateCrew["Create Crew"]
EditCrew["Edit Crew"]
Docs["Documentation"]
Home --> Projects
Projects --> Detail
Detail --> Services
Services --> Estimation
Estimation --> MTO
Estimation --> Crews
Crews --> EditSC
Estimation --> Master
Detail --> EditProj
Home --> RefTables
RefTables --> Trades
RefTables --> Members
RefTables --> Equipment
RefTables --> EquipTags
RefTables --> Material
RefTables --> WBS
WBS --> WBSBuilder
RefTables --> RefCrews
RefCrews --> CreateCrew
RefCrews --> EditCrew
Home --> Docs
Build & Deployment¶
Development¶
Production Build¶
npm run build # TypeScript type check (tsc -b) + Vite build
npm run preview # Preview production build locally
Output: dist/ directory with optimized assets:
- HTML entry point with injected scripts
- Chunked JavaScript bundles
- Optimized CSS files
- Static assets with content hashing
Scripts¶
| Script | Description |
|---|---|
npm run dev |
Start Vite dev server |
npm run build |
Production build with type checking |
npm run lint |
Run ESLint on codebase |
npm run preview |
Preview production build |
Security Considerations¶
- Environment Variables: API URLs in
.envfiles (not committed to git) - CORS: Handled by backend Azure Function App
- Authentication: Placeholder for token-based auth (not yet implemented)
- Input Validation: Client-side validation + backend validation
- XSS Prevention: React's built-in escaping
Related Documentation¶
- Developer Guide - Setup and development workflows
- Component Patterns - UI component guidelines
- Custom Hooks (
frontend/src/hooks/README.md) - Hook documentation - Stores (
frontend/src/stores/README.md) - Store documentation - Backend Architecture - API and database design