Skip to content

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 &quot;Save Changes&quot;"]
    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 &quot;Save&quot;"]
    APICall["7. API Call (serviceCrewsApi.createServiceCrew)"]
    Toast["Success Toast"]

    Select --> Handler --> Details --> Members --> Labour --> Equip --> Row --> EditIndirect --> Final --> SaveClick --> APICall --> Toast

7. Performance Optimizations

  1. Lookup Maps: O(1) access for crew members and equipment

    const crewMembersMap = new Map<string, CrewMember>();
    // Key format: "country|province|tradeCode|laborDesignation"
    

  2. Abort Controllers: Cancel in-flight requests on component unmount

    useEffect(() => {
      const abortController = new AbortController();
      fetchData({ signal: abortController.signal });
      return () => abortController.abort();
    }, []);
    

  3. Batch Fetching: Load all paginated data efficiently

    while (hasMore && skip < 10000) {
      const response = await api.get({ skip, limit: 100 });
      allItems.push(...response.data);
      skip += 100;
    }
    

  4. Memoization: useMemo and useCallback in custom hooks

  5. 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:

  1. Removed columns: Filtered out of visibility, sorting, filters, and order
  2. New columns: Inserted at their natural position in column definitions
  3. 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 to src/ for clean imports
  • Dev Server Proxy:
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
        secure: false,
      },
    }
    
  • Build Optimization:
  • Source maps disabled in production
  • Asset optimization
  • React Plugin: @vitejs/plugin-react with 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

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

npm run dev     # Vite dev server with HMR on http://localhost:5173

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

  1. Environment Variables: API URLs in .env files (not committed to git)
  2. CORS: Handled by backend Azure Function App
  3. Authentication: Placeholder for token-based auth (not yet implemented)
  4. Input Validation: Client-side validation + backend validation
  5. XSS Prevention: React's built-in escaping
  • 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