# 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` 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 ```php 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