feat: Phase 2 - Frontend meta fields components (Level 1)

Implemented: Frontend Components for Level 1 Compatibility

Created Components:
- MetaFields.tsx - Generic meta field renderer
- useMetaFields.ts - Hook for field registry

Integrated Into:
- Orders/Edit.tsx - Meta fields after OrderForm
- Products/Edit.tsx - Meta fields after ProductForm

Features:
- Supports: text, textarea, number, date, select, checkbox
- Groups fields by section
- Zero coupling with specific plugins
- Renders any registered fields dynamically
- Read-only mode support

How It Works:
1. Backend exposes meta via API (Phase 1)
2. PHP registers fields via MetaFieldsRegistry (Phase 3 - next)
3. Fields localized to window.WooNooWMetaFields
4. useMetaFields hook reads registry
5. MetaFields component renders fields
6. User edits fields
7. Form submission includes meta
8. Backend saves via update_order_meta_data()

Result:
- Generic, reusable components
- Zero plugin-specific code
- Works with any registered fields
- Clean separation of concerns

Next: Phase 3 - PHP MetaFieldsRegistry system
This commit is contained in:
dwindown
2025-11-20 12:32:06 +07:00
parent 9f731bfe0a
commit 0c5efa3efc
4 changed files with 281 additions and 4 deletions

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import OrderForm from '@/routes/Orders/partials/OrderForm';
@@ -10,6 +10,8 @@ import { __, sprintf } from '@/lib/i18n';
import { usePageHeader } from '@/contexts/PageHeaderContext';
import { Button } from '@/components/ui/button';
import { useFABConfig } from '@/hooks/useFABConfig';
import { MetaFields } from '@/components/MetaFields';
import { useMetaFields } from '@/hooks/useMetaFields';
export default function OrdersEdit() {
const { id } = useParams();
@@ -19,6 +21,10 @@ export default function OrdersEdit() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const formRef = useRef<HTMLFormElement>(null);
// Level 1 compatibility: Meta fields from plugins
const metaFields = useMetaFields('orders');
const [metaData, setMetaData] = useState<Record<string, any>>({});
// Hide FAB on edit page
useFABConfig('none');
@@ -46,6 +52,13 @@ export default function OrdersEdit() {
}, [countriesQ.data]);
const order = orderQ.data || {};
// Sync meta data from order
useEffect(() => {
if (order.meta) {
setMetaData(order.meta);
}
}, [order.meta]);
// Set page header with back button and save button
useEffect(() => {
@@ -104,11 +117,22 @@ export default function OrdersEdit() {
formRef={formRef}
hideSubmitButton={true}
onSubmit={(form) => {
const payload = { ...form } as any;
const payload = { ...form, meta: metaData } as any;
upd.mutate(payload);
}}
/>
{/* Level 1 compatibility: Custom meta fields from plugins */}
{metaFields.length > 0 && (
<MetaFields
meta={metaData}
fields={metaFields}
onChange={(key, value) => {
setMetaData(prev => ({ ...prev, [key]: value }));
}}
/>
)}
</div>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect } from 'react';
import React, { useRef, useEffect, useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate, useParams } from 'react-router-dom';
import { api } from '@/lib/api';
@@ -11,6 +11,8 @@ import { Button } from '@/components/ui/button';
import { ErrorCard } from '@/components/ErrorCard';
import { getPageLoadErrorMessage } from '@/lib/errorHandling';
import { Skeleton } from '@/components/ui/skeleton';
import { MetaFields } from '@/components/MetaFields';
import { useMetaFields } from '@/hooks/useMetaFields';
export default function ProductEdit() {
const { id } = useParams<{ id: string }>();
@@ -19,6 +21,10 @@ export default function ProductEdit() {
const formRef = useRef<HTMLFormElement>(null);
const { setPageHeader, clearPageHeader } = usePageHeader();
// Level 1 compatibility: Meta fields from plugins
const metaFields = useMetaFields('products');
const [metaData, setMetaData] = useState<Record<string, any>>({});
// Hide FAB on edit product page
useFABConfig('none');
@@ -48,7 +54,9 @@ export default function ProductEdit() {
});
const handleSubmit = async (data: ProductFormData) => {
await updateMutation.mutateAsync(data);
// Merge meta data with form data (Level 1 compatibility)
const payload = { ...data, meta: metaData };
await updateMutation.mutateAsync(payload);
};
// Set page header with back button and save button
@@ -95,6 +103,13 @@ export default function ProductEdit() {
const product = productQ.data;
// Sync meta data from product
useEffect(() => {
if (product?.meta) {
setMetaData(product.meta);
}
}, [product?.meta]);
return (
<div className="space-y-4">
<ProductForm
@@ -104,6 +119,17 @@ export default function ProductEdit() {
formRef={formRef}
hideSubmitButton={true}
/>
{/* Level 1 compatibility: Custom meta fields from plugins */}
{metaFields.length > 0 && (
<MetaFields
meta={metaData}
fields={metaFields}
onChange={(key, value) => {
setMetaData(prev => ({ ...prev, [key]: value }));
}}
/>
)}
</div>
);
}