dstpabuaran.com/docs/2026-08-14-lazy-loading-card-table.md

5.5 KiB

Lazy Loading Card Table

Date

2026-08-14

Goal

Optimize page load performance for raw materials, products, purchases, cutting, transaction, and restock list pages by implementing lazy loading for child/variant data in card-based layouts.

Problem

Pages with 100+ records loaded all child data eagerly (variants, items, materials), causing slow initial page loads.

Solution Pattern

  1. Backend: Remove eager-loaded relationships from paginated query
  2. Backend: Add SQL subqueries for summary data (variants_count, total_qty, material_name, unit, materials_count, total_usage, product_name, cutting_result, items_count, product_names)
  3. Backend: Add new controller method returning JSON for lazy load
  4. Frontend: Use useCardTableExpand(false) for default collapsed state
  5. Frontend: Fetch child data via fetch() on expand, store in Record<number, T[]> state
  6. Frontend: Pass items + isLoading props to sub-row components

Files Modified

Restock (this session)

  • app/Services/Admin/Manage/RestockService.php - paginated() no longer eager loads restockItems; added getItems() method
  • app/Http/Controllers/Admin/Manage/RestockController.php - added items() method
  • routes/web.php - added GET restocks/{restock}/items route
  • resources/js/pages/admin/manage/restock/columns.tsx - Restock type: items_count, total_qty, product_names
  • resources/js/pages/admin/manage/restock/restock-card.tsx - uses summary data from props
  • resources/js/pages/admin/manage/restock/restock-sub-row.tsx - accepts items & isLoading props
  • resources/js/pages/admin/manage/restock/index.tsx - lazy loading with fetchItems()

Transaction (this session)

  • app/Services/Admin/Manage/TransactionService.php - paginated() no longer eager loads orderItems; added getItems() method
  • app/Http/Controllers/Admin/Manage/TransactionController.php - added items() method
  • routes/web.php - added GET transactions/{transaction}/items route
  • resources/js/pages/admin/manage/transaction/columns.tsx - Transaction type: items_count, total_qty, product_names
  • resources/js/pages/admin/manage/transaction/transaction-card.tsx - uses summary data from props
  • resources/js/pages/admin/manage/transaction/transaction-sub-row.tsx - accepts items & isLoading props
  • resources/js/pages/admin/manage/transaction/index.tsx - lazy loading with fetchItems(), print fetches items on demand

Cutting (this session)

  • app/Services/Admin/Manage/CuttingService.php - paginated() no longer eager loads cuttingResults, cuttingMaterials, cuttingMaterialCombinations; added getMaterials() and getCombinations() methods
  • app/Http/Controllers/Admin/Manage/CuttingController.php - added materials() method
  • routes/web.php - added GET cuttings/{cutting}/materials route
  • resources/js/pages/admin/manage/cutting/columns.tsx - Cutting type: materials_count, total_usage, product_name, cutting_result
  • resources/js/pages/admin/manage/cutting/cutting-card.tsx - uses summary data from props
  • resources/js/pages/admin/manage/cutting/cutting-sub-row.tsx - accepts materials, combinations, isLoading props
  • resources/js/pages/admin/manage/cutting/index.tsx - lazy loading with fetchMaterials()
  • resources/js/pages/admin/manage/cutting/show.tsx - passes full data to sub-row

Purchases (this session)

  • app/Services/Admin/Manage/PurchaseService.php - paginated() no longer eager loads purchaseItems; added getItems() method
  • app/Http/Controllers/Admin/Manage/PurchaseController.php - added items() method
  • routes/web.php - added GET purchases/{purchase}/items route
  • resources/js/pages/admin/manage/purchase/columns.tsx - Purchase type: variants_count, total_qty, material_name, unit
  • resources/js/pages/admin/manage/purchase/purchase-card.tsx - uses summary data from props
  • resources/js/pages/admin/manage/purchase/purchase-sub-row.tsx - accepts items & isLoading props
  • resources/js/pages/admin/manage/purchase/index.tsx - lazy loading with fetchItems()

Raw Materials & Products (previous sessions)

  • Same pattern applied to RawMaterialService, RawMaterialController, ProductService, ProductController
  • Frontend pages updated with lazy loading state

Routes Added

Route::get('purchases/{purchase}/items', [PurchaseController::class, 'items'])->name('purchases.items')->middleware('permission:purchases.view');
Route::get('cuttings/{cutting}/materials', [CuttingController::class, 'materials'])->name('cuttings.materials')->middleware('permission:cuttings.view');
Route::get('transactions/{transaction}/items', [TransactionController::class, 'items'])->name('transactions.items')->middleware('permission:orders.view');
Route::get('restocks/{restock}/items', [RestockController::class, 'items'])->name('restocks.items')->middleware('permission:restocks.view');

Key Implementation Details

  • SQL subqueries avoid N+1 queries while keeping summary data in the paginated response
  • getItems() / getMaterials() / getCombinations() methods handle media URL resolution for photos
  • Frontend caches loaded items to avoid re-fetching on repeated expand/collapse
  • Loading state shown while items are being fetched
  • Show pages (e.g., show.tsx) pass full data directly to sub-row components
  • Transaction print feature fetches items on demand if not cached

Verification

  • PHP syntax check: OK
  • TypeScript: Only pre-existing errors in use-*-draft.ts, create.tsx, edit.tsx files (unrelated)
  • Route cache cleared