From f7dca7bc28555879fc1e40055ffe74da2f3c8912 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 20 Nov 2025 11:11:06 +0700 Subject: [PATCH] docs: Critical metabox & custom fields compatibility gap identified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Issue: Third-Party Plugin Compatibility** Current Status: ❌ NOT IMPLEMENTED Priority: 🔴 CRITICAL - Blocks production readiness **Problem:** Our SPA admin does NOT expose: - Custom meta fields from third-party plugins - WordPress metaboxes (add_meta_box) - WooCommerce custom fields - ACF/CMB2/Pods fields - Plugin-injected data (e.g., Tracking Number) **Example Use Case:** Plugin: WooCommerce Shipment Tracking - Adds 'Tracking Number' metabox to order edit page - Stores: _tracking_number meta field - Current: ❌ NOT visible in WooNooW admin - Expected: ✅ Should be visible and editable **Impact:** 1. Breaks compatibility with popular plugins 2. Users cannot see/edit custom fields 3. Data exists but not accessible in SPA 4. Forces users back to classic admin 5. BLOCKS production readiness **Solution Architecture:** Phase 1: API Layer (2-3 days) - Expose meta_data in OrdersController::show() - Expose meta_data in ProductsController::get_product() - Add filters: woonoow/order_api_data, woonoow/product_api_data - Add filters: woonoow/order_allowed_private_meta - Add actions: woonoow/order_updated, woonoow/product_updated Phase 2: Frontend Components (3-4 days) - Create MetaFields.tsx component - Create useMetaFields.ts hook - Update Orders/Edit.tsx to include meta fields - Update Products/Edit.tsx to include meta fields - Add meta fields to detail pages Phase 3: Plugin Integration (2-3 days) - Create MetaFieldsRegistry.php - Add woonoow/register_meta_fields action - Localize fields to JavaScript - Create example: ShipmentTracking.php integration - Document integration pattern **Documentation Created:** - METABOX_COMPAT.md - Complete implementation guide - Includes code examples for all phases - Includes third-party integration guide - Includes testing checklist **Updated:** - PROJECT_SOP.md - Added metabox compat reference - Marked as CRITICAL requirement - Noted as blocking production readiness **Timeline:** Total: 1-2 weeks implementation **Blocking:** - ✅ Coupons CRUD (can proceed) - ✅ Customers CRUD (can proceed) - ❌ Production readiness (BLOCKED) **Next Steps:** 1. Review METABOX_COMPAT.md 2. Prioritize implementation 3. Start with Phase 1 (API layer) 4. Test with popular plugins (Shipment Tracking, ACF) --- METABOX_COMPAT.md | 552 ++++++++++++++++++++++++++++++++++++++++++++++ PROJECT_SOP.md | 6 + 2 files changed, 558 insertions(+) create mode 100644 METABOX_COMPAT.md diff --git a/METABOX_COMPAT.md b/METABOX_COMPAT.md new file mode 100644 index 0000000..ce46878 --- /dev/null +++ b/METABOX_COMPAT.md @@ -0,0 +1,552 @@ +# WooNooW Metabox & Custom Fields Compatibility + +## Current Status: ❌ NOT IMPLEMENTED + +**Critical Gap:** Our SPA admin does NOT currently expose custom meta fields, metaboxes, or third-party plugin data injected via WordPress/WooCommerce hooks. + +### Example Use Case: +``` +Plugin: WooCommerce Shipment Tracking +- Adds "Tracking Number" metabox to order edit page +- Uses: add_meta_box('wc_shipment_tracking', ...) +- Stores: update_post_meta($order_id, '_tracking_number', $value) + +Current Behavior: ❌ Field NOT visible in WooNooW admin +Expected Behavior: ✅ Field should be visible and editable +``` + +--- + +## Problem Analysis + +### 1. Orders API (`OrdersController.php`) + +**Current Implementation:** +```php +public static function show(WP_REST_Request $req) { + $order = wc_get_order($id); + + $data = [ + 'id' => $order->get_id(), + 'status' => $order->get_status(), + 'billing' => [...], + 'shipping' => [...], + 'items' => [...], + // ... hardcoded fields only + ]; + + return new WP_REST_Response($data, 200); +} +``` + +**Missing:** +- ❌ No `get_meta_data()` exposure +- ❌ No `apply_filters('woonoow/order_data', $data, $order)` +- ❌ No metabox hook listening +- ❌ No custom field groups + +### 2. Products API (`ProductsController.php`) + +**Current Implementation:** +```php +public static function get_product(WP_REST_Request $request) { + $product = wc_get_product($id); + + return new WP_REST_Response([ + 'id' => $product->get_id(), + 'name' => $product->get_name(), + // ... hardcoded fields only + ], 200); +} +``` + +**Missing:** +- ❌ No custom product meta exposure +- ❌ No `apply_filters('woonoow/product_data', $data, $product)` +- ❌ No ACF/CMB2/Pods integration +- ❌ No custom tabs/panels + +--- + +## Solution Architecture + +### Phase 1: Meta Data Exposure (API Layer) + +#### 1.1 Orders API Enhancement + +**Add to `OrdersController::show()`:** +```php +public static function show(WP_REST_Request $req) { + $order = wc_get_order($id); + + // ... existing data ... + + // Expose all meta data + $meta_data = []; + foreach ($order->get_meta_data() as $meta) { + $key = $meta->key; + + // Skip internal/private meta (starts with _) + // unless explicitly allowed + if (strpos($key, '_') === 0) { + $allowed_private = apply_filters('woonoow/order_allowed_private_meta', [ + '_tracking_number', + '_tracking_provider', + '_shipment_tracking_items', + '_wc_shipment_tracking_items', + // Add more as needed + ], $order); + + if (!in_array($key, $allowed_private, true)) { + continue; + } + } + + $meta_data[$key] = $meta->value; + } + + $data['meta'] = $meta_data; + + // Allow plugins to add/modify data + $data = apply_filters('woonoow/order_api_data', $data, $order, $req); + + return new WP_REST_Response($data, 200); +} +``` + +**Add to `OrdersController::update()`:** +```php +public static function update(WP_REST_Request $req) { + $order = wc_get_order($id); + $data = $req->get_json_params(); + + // ... existing update logic ... + + // Update custom meta fields + if (isset($data['meta']) && is_array($data['meta'])) { + foreach ($data['meta'] as $key => $value) { + // Validate meta key is allowed + $allowed = apply_filters('woonoow/order_updatable_meta', [ + '_tracking_number', + '_tracking_provider', + // Add more as needed + ], $order); + + if (in_array($key, $allowed, true)) { + $order->update_meta_data($key, $value); + } + } + } + + $order->save(); + + // Allow plugins to perform additional updates + do_action('woonoow/order_updated', $order, $data, $req); + + return new WP_REST_Response(['success' => true], 200); +} +``` + +#### 1.2 Products API Enhancement + +**Add to `ProductsController::get_product()`:** +```php +public static function get_product(WP_REST_Request $request) { + $product = wc_get_product($id); + + // ... existing data ... + + // Expose all meta data + $meta_data = []; + foreach ($product->get_meta_data() as $meta) { + $key = $meta->key; + + // Skip internal meta unless allowed + if (strpos($key, '_') === 0) { + $allowed_private = apply_filters('woonoow/product_allowed_private_meta', [ + '_custom_field_example', + // Add more as needed + ], $product); + + if (!in_array($key, $allowed_private, true)) { + continue; + } + } + + $meta_data[$key] = $meta->value; + } + + $data['meta'] = $meta_data; + + // Allow plugins to add/modify data + $data = apply_filters('woonoow/product_api_data', $data, $product, $request); + + return new WP_REST_Response($data, 200); +} +``` + +--- + +### Phase 2: Frontend Rendering (React Components) + +#### 2.1 Dynamic Meta Fields Component + +**Create: `admin-spa/src/components/MetaFields.tsx`** +```tsx +interface MetaField { + key: string; + label: string; + type: 'text' | 'textarea' | 'number' | 'select' | 'date'; + options?: Array<{value: string; label: string}>; + section?: string; // Group fields into sections +} + +interface MetaFieldsProps { + meta: Record; + fields: MetaField[]; + onChange: (key: string, value: any) => void; + readOnly?: boolean; +} + +export function MetaFields({ meta, fields, onChange, readOnly }: MetaFieldsProps) { + // Group fields by section + const sections = fields.reduce((acc, field) => { + const section = field.section || 'Other'; + if (!acc[section]) acc[section] = []; + acc[section].push(field); + return acc; + }, {} as Record); + + return ( +
+ {Object.entries(sections).map(([section, sectionFields]) => ( + + + {section} + + + {sectionFields.map(field => ( +
+ + {field.type === 'text' && ( + onChange(field.key, e.target.value)} + disabled={readOnly} + /> + )} + {field.type === 'textarea' && ( +