From be69b402376d8ce966b9738664b10f6434920647 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 20 Nov 2025 10:44:48 +0700 Subject: [PATCH] fix: OrderForm variable product issues - empty colors, desktop dialog, duplicate handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Issues Fixed:** 1. **Empty Color Values** - Problem: Variation attributes showed 'Color:' with no value - Cause: Backend returned empty strings for some attributes - Fix: Filter empty values with .filter(([_, value]) => value) - Result: Only non-empty attributes displayed 2. **Desktop Should Use Dialog** - Problem: Both desktop and mobile used Drawer (bottom sheet) - Expected: Desktop = Dialog (modal), Mobile = Drawer - Fix: Added useMediaQuery hook, conditional rendering - Pattern: Same as Settings pages (Payments, Shipping, etc.) 3. **Duplicate Product+Variation Handling** - Problem: Same product+variation created new row each time - Expected: Should increment quantity of existing row - Fix: Check for existing item before adding - Logic: findIndex by product_id + variation_id, then increment qty **Changes to OrderForm.tsx:** - Added Dialog and useMediaQuery imports - Added isDesktop detection - Split variation selector into Desktop (Dialog) and Mobile (Drawer) - Fixed variationLabel to filter empty values - Added duplicate check logic before adding to cart - If exists: increment qty, else: add new item **Changes to PROJECT_SOP.md:** - Added Responsive Modal Pattern section - Documented Dialog/Drawer pattern with code example - Added rule 3: Same product+variation = increment qty - Added rule 6: Filter empty attribute values - Added rule 7: Responsive modals (Dialog/Drawer) **Result:** ✅ Color values display correctly (empty values filtered) ✅ Desktop uses Dialog (centered modal) ✅ Mobile uses Drawer (bottom sheet) ✅ Duplicate product+variation increments quantity ✅ UX matches Tokopedia/Shopee pattern ✅ Follows Settings page modal pattern --- PROJECT_SOP.md | 37 ++++- admin-spa/src/routes/Orders/index.tsx | 4 +- .../src/routes/Orders/partials/OrderForm.tsx | 156 +++++++++++++++--- admin-spa/src/routes/Products/index.tsx | 3 +- 4 files changed, 176 insertions(+), 24 deletions(-) diff --git a/PROJECT_SOP.md b/PROJECT_SOP.md index 66d8090..95507a9 100644 --- a/PROJECT_SOP.md +++ b/PROJECT_SOP.md @@ -412,6 +412,40 @@ All CRUD list pages MUST follow these consistent UI patterns: When adding products to orders, variable products MUST follow the Tokopedia/Shopee pattern: +**Responsive Modal Pattern:** +- **Desktop:** Use `Dialog` component (centered modal) +- **Mobile:** Use `Drawer` component (bottom sheet) +- **Detection:** Use `useMediaQuery("(min-width: 768px)")` + +**Implementation:** +```tsx +const isDesktop = useMediaQuery("(min-width: 768px)"); + +{/* Desktop: Dialog */} +{selectedProduct && isDesktop && ( + + + + {product.name} + + {/* Variation list */} + + +)} + +{/* Mobile: Drawer */} +{selectedProduct && !isDesktop && ( + + + + {product.name} + + {/* Variation list */} + + +)} +``` + **Desktop Pattern:** ``` [Search Product...] @@ -438,7 +472,6 @@ When adding products to orders, variable products MUST follow the Tokopedia/Shop ✓ Anker Earbuds Black Rp296,000 [-] 1 [+] [🗑️] -``` **Rules:** 1. ✅ Each variation is a **separate line item** @@ -447,6 +480,8 @@ When adding products to orders, variable products MUST follow the Tokopedia/Shop 4. ✅ Mobile: Click variation to open drawer for selection 5. ❌ Don't auto-select first variation 6. ❌ Don't hide variation selector +7. ✅ **Duplicate Handling**: Same product + same variation = increment quantity (NOT new row) +8. ✅ **Empty Attribute Values**: Filter empty attribute values - Use `.filter()` to remove empty strings **Implementation:** - Product search shows variable products diff --git a/admin-spa/src/routes/Orders/index.tsx b/admin-spa/src/routes/Orders/index.tsx index 316ac7a..3d75b2a 100644 --- a/admin-spa/src/routes/Orders/index.tsx +++ b/admin-spa/src/routes/Orders/index.tsx @@ -267,7 +267,7 @@ export default function Orders() {
- + { setPage(1); setStatus(v === 'all' ? undefined : v); }}>