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

83 lines
5.5 KiB
Markdown

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