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' && ( +