feat: Implement variable product handling in OrderForm (Tokopedia pattern)

Following PROJECT_SOP.md section 5.7 - Variable Product Handling:

**Backend (OrdersController.php):**
- Updated /products/search endpoint to return:
  - Product type (simple/variable)
  - Variations array with attributes, prices, stock
  - Formatted attribute names (Color, Size, etc.)

**Frontend (OrderForm.tsx):**
- Updated ProductSearchItem type to include variations
- Updated LineItem type to support variation_id and variation_name
- Added variation selector drawer (mobile + desktop)
- Each variation = separate cart item row
- Display variation name below product name
- Fixed remove button to work with variations (by index)

**UX Pattern:**
1. Search product → If variable, show variation drawer
2. Select variation → Add as separate line item
3. Can add same product with different variations
4. Each variation shown clearly: 'Product Name' + 'Color: Red'

**Result:**
 Tokopedia/Shopee pattern implemented
 No auto-selection of first variation
 Each variation is independent cart item
 Works on mobile and desktop

**Next:** Fix PageHeader max-w-5xl to only apply on settings pages
This commit is contained in:
dwindown
2025-11-20 09:47:14 +07:00
parent 746148cc5f
commit 9a6a434c48
2 changed files with 158 additions and 7 deletions

View File

@@ -1138,9 +1138,10 @@ class OrdersController {
$prods = wc_get_products( $args );
$rows = array_map( function( $p ) {
return [
$data = [
'id' => $p->get_id(),
'name' => $p->get_name(),
'type' => $p->get_type(),
'price' => (float) $p->get_price(),
'regular_price' => (float) $p->get_regular_price(),
'sale_price' => $p->get_sale_price() ? (float) $p->get_sale_price() : null,
@@ -1148,7 +1149,41 @@ class OrdersController {
'stock' => $p->get_stock_quantity(),
'virtual' => $p->is_virtual(),
'downloadable' => $p->is_downloadable(),
'variations' => [],
];
// If variable product, include variations
if ( $p->get_type() === 'variable' ) {
$variation_ids = $p->get_children();
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
if ( ! $variation ) continue;
// Get variation attributes
$attributes = [];
foreach ( $variation->get_variation_attributes() as $attr_name => $attr_value ) {
// Remove 'attribute_' prefix and format name
$clean_name = str_replace( 'attribute_', '', $attr_name );
$clean_name = str_replace( 'pa_', '', $clean_name ); // Remove taxonomy prefix if present
$clean_name = ucfirst( str_replace( [ '-', '_' ], ' ', $clean_name ) );
$attributes[ $clean_name ] = $attr_value;
}
$data['variations'][] = [
'id' => $variation->get_id(),
'attributes' => $attributes,
'price' => (float) $variation->get_price(),
'regular_price' => (float) $variation->get_regular_price(),
'sale_price' => $variation->get_sale_price() ? (float) $variation->get_sale_price() : null,
'sku' => $variation->get_sku(),
'stock' => $variation->get_stock_quantity(),
'in_stock' => $variation->is_in_stock(),
];
}
}
return $data;
}, $prods );
return new WP_REST_Response( [ 'rows' => $rows ], 200 );