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
139 lines
4.7 KiB
TypeScript
139 lines
4.7 KiB
TypeScript
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';
|
|
import { OrdersApi } from '@/lib/api';
|
|
import { showErrorToast, showSuccessToast, getPageLoadErrorMessage } from '@/lib/errorHandling';
|
|
import { ErrorCard } from '@/components/ErrorCard';
|
|
import { LoadingState } from '@/components/LoadingState';
|
|
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();
|
|
const orderId = Number(id);
|
|
const nav = useNavigate();
|
|
const qc = useQueryClient();
|
|
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');
|
|
|
|
const countriesQ = useQuery({ queryKey: ['countries'], queryFn: OrdersApi.countries });
|
|
const paymentsQ = useQuery({ queryKey: ['payments'], queryFn: OrdersApi.payments });
|
|
const shippingsQ = useQuery({ queryKey: ['shippings'], queryFn: OrdersApi.shippings });
|
|
const orderQ = useQuery({ queryKey: ['order', orderId], enabled: Number.isFinite(orderId), queryFn: () => OrdersApi.get(orderId) });
|
|
|
|
const upd = useMutation({
|
|
mutationFn: (payload: any) => OrdersApi.update(orderId, payload),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['orders'] });
|
|
qc.invalidateQueries({ queryKey: ['order', orderId] });
|
|
showSuccessToast(__('Order updated successfully'));
|
|
nav(`/orders/${orderId}`);
|
|
},
|
|
onError: (error: any) => {
|
|
showErrorToast(error);
|
|
}
|
|
});
|
|
|
|
const countriesData = React.useMemo(() => {
|
|
const list = countriesQ.data?.countries ?? [];
|
|
return list.map((c: any) => ({ code: String(c.code), name: String(c.name) }));
|
|
}, [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(() => {
|
|
const actions = (
|
|
<div className="flex gap-2">
|
|
<Button size="sm" variant="ghost" onClick={() => nav(`/orders/${orderId}`)}>
|
|
{__('Back')}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => formRef.current?.requestSubmit()}
|
|
disabled={upd.isPending}
|
|
>
|
|
{upd.isPending ? __('Saving...') : __('Save')}
|
|
</Button>
|
|
</div>
|
|
);
|
|
const title = order.number
|
|
? sprintf(__('Edit Order #%s'), order.number)
|
|
: __('Edit Order');
|
|
setPageHeader(title, actions);
|
|
return () => clearPageHeader();
|
|
}, [order.number, orderId, upd.isPending, setPageHeader, clearPageHeader, nav]);
|
|
|
|
if (!Number.isFinite(orderId)) {
|
|
return <div className="p-4 text-sm text-red-600">{__('Invalid order id.')}</div>;
|
|
}
|
|
|
|
if (orderQ.isLoading || countriesQ.isLoading) {
|
|
return <LoadingState message={sprintf(__('Loading order #%s...'), orderId)} />;
|
|
}
|
|
|
|
if (orderQ.isError) {
|
|
return <ErrorCard
|
|
title={__('Failed to load order')}
|
|
message={getPageLoadErrorMessage(orderQ.error)}
|
|
onRetry={() => orderQ.refetch()}
|
|
/>;
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
|
|
<OrderForm
|
|
mode="edit"
|
|
initial={order}
|
|
currency={order.currency}
|
|
currencySymbol={order.currency_symbol}
|
|
countries={countriesData}
|
|
states={countriesQ.data?.states || {}}
|
|
defaultCountry={countriesQ.data?.default_country}
|
|
payments={(paymentsQ.data || [])}
|
|
shippings={(shippingsQ.data || [])}
|
|
itemsEditable={['pending', 'on-hold', 'failed', 'draft'].includes(order.status)}
|
|
showCoupons
|
|
formRef={formRef}
|
|
hideSubmitButton={true}
|
|
onSubmit={(form) => {
|
|
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>
|
|
);
|
|
}
|