Material Selection Page¶
Route: Part of /history/create flow (accessed by navigating from CreateRequestPage)
Component: src/pages/requester/MaterialSelectionPage.tsx
Auth: Protected
Entered from: Create Request (Order Material zone)
Purpose¶
Allows operators to browse all available Sub-SKU types, set desired quantities, and add them to the cart. Supports two view layouts (grid and list) and live search.
Layout¶
┌─────────────────────────────────────────┐
│ ← Order Material [⊞][☰] [Cart(N)]│ ← Sub-header (fixed)
├─────────────────────────────────────────┤
│ 🔍 [Search by material name or code…] │ ← Search bar (fixed)
├─────────────────────────────────────────┤
│ │
│ [Card] [Card] [Card] ← Grid view │
│ [Card] [Card] [Card] or │ ← Scrollable content
│ List view │
│ │
└─────────────────────────────────────────┘
UI Components¶
Sub-header¶
- Back arrow (
useNavigate(-1)) - Title "Order Material" + caption "Select Sub-SKU types and quantities"
- View toggle:
ToggleButtonGroupwith grid icon and list icon — switches between'grid'and'list'view modes - Checkout button: Visible only when
cart.length > 0— shows "Checkout (N)" count and navigates to/history/checkout
Search Bar¶
- Full-width
OutlinedInputwithSearchIconadornment - Background
#F8F9FA, padded9pxvertically - Filters across: sub-type
name,code, and parentskuName(case-insensitive) - Instant filtering on every keystroke
Material Data (Flat List)¶
All materials are flattened from mockMaterials into a single FlatSST[] array:
type FlatSST = SubSKUType & { skuName: string; skuCode: string };
const allSubSkus: FlatSST[] = mockMaterials.flatMap((sku) =>
sku.subSkuTypes.map((sst) => ({ ...sst, skuName: sku.name, skuCode: sku.code }))
);
This gives 9 items total (from the mock). Each item carries its parent SKU name for display.
Grid View¶
Column width: minmax(280px, 1fr) — auto-fill responsive grid. On 1024px width with 20px padding each side, this renders 3 columns.
Card Structure¶
┌─────────────────────────────┐
│ Widget A [12 avail]│
│ Sub-SKU Type 1 │
│ WGT-A-T1 │
│─────────────────────────────│
│ [−] 1 [+] max 5 [Add] │
└─────────────────────────────┘
- Border: teal when item is in cart, light grey otherwise
- Background: teal tint when in cart, white otherwise
- Opacity: 0.6 when blocked (unavailable or pre-processing)
Status Chip¶
| State | Chip label | Colour |
|---|---|---|
inPreProcessing |
Pre-Processing | Amber #E65100 |
available === 0 |
Unavailable | Grey #9E9E9E |
| orderable | {available} avail. |
Teal |
Quantity Stepper¶
[−]and[+]icon buttons flanking the current quantity value−disabled whenqty <= 1+disabled whenqty >= sst.maxQty- Both disabled when
blocked - Button size: 36×36px with
borderRadius: 0(for seamless inline stepper appearance) - Default quantity per item: 1 (managed by
quantitiesstate asRecord<string, number>)
Add / Added Button¶
- Not in cart:
variant="contained"teal button with+icon, disabled when blocked - In cart:
variant="outlined"red button with checkmark icon, label "Added" — clicking removes from cart (toggle behaviour)
List View¶
A compact table-like grid layout inside a Paper card.
Columns: 1fr 110px 80px 140px
| Col 1 | Col 2 | Col 3 | Col 4 |
|---|---|---|---|
| Material name + code | Status chip | Qty stepper (compact) | Add/Added button |
- Each row has a divider between it and the next
- Row background: teal tint if in cart,
#FAFAFAif blocked, white otherwise - Added items have a 6px teal dot indicator next to the name
- Quantity stepper in list view is more compact:
28pxbuttons,32pxheight
State¶
| Variable | Type | Initial | Description |
|---|---|---|---|
search |
string |
'' |
Search filter input |
quantities |
Record<string, number> |
{} |
Pending quantities per sub-SKU ID; default 1 |
viewMode |
'grid' \| 'list' |
'grid' |
Display layout mode |
Store reads/writes: cartStore.cart, cartStore.addToCart, cartStore.removeFromCart
UX Logic¶
Blocking Logic¶
Both conditions result in the same disabled state. inPreProcessing items may have available > 0 in the data but are still not orderable — this is a business rule (the material is ready in quantity but not physically ready for dispatch).
Add to Cart¶
const handleAdd = (sst: FlatSST) => {
const item: CartItem = {
subSkuTypeId: sst.id,
subSkuTypeName: `${sst.skuName} – ${sst.name}`,
quantity: getQty(sst.id), // current pending quantity from local state
maxQty: sst.maxQty,
skuName: sst.skuName,
};
addToCart(item);
};
The subSkuTypeName is pre-formatted as "Parent SKU – Sub-type name" (e.g. "Widget A – Sub-SKU Type 1") for display in the checkout review.
Cart Toggle¶
Tapping "Added" immediately calls removeFromCart(sst.id). The button reverts to "Add" instantly. Quantities in local state (quantities) are preserved — if the operator re-adds the item, the previous quantity is still set.
Search Empty State¶
When filtered.length === 0, a centred empty state text is shown: "No materials match your search".
Edge Cases¶
| Scenario | Behaviour |
|---|---|
| All items blocked | Empty-looking cards, all steppers/buttons disabled |
| Cart has items, user searches | Cart button still visible in header (cart state persists) |
| Max qty reached | + button disabled; cannot exceed maxQty |
| Item added then search clears it | Still in cart (cart is separate from display filter) |
| Back navigation | useNavigate(-1) returns to previous route (Create Request) |