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
158 lines
5.3 KiB
TypeScript
158 lines
5.3 KiB
TypeScript
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
|
|
export interface MetaField {
|
|
key: string;
|
|
label: string;
|
|
type: 'text' | 'textarea' | 'number' | 'select' | 'date' | 'checkbox';
|
|
options?: Array<{ value: string; label: string }>;
|
|
section?: string;
|
|
description?: string;
|
|
placeholder?: string;
|
|
}
|
|
|
|
interface MetaFieldsProps {
|
|
meta: Record<string, any>;
|
|
fields: MetaField[];
|
|
onChange: (key: string, value: any) => void;
|
|
readOnly?: boolean;
|
|
}
|
|
|
|
/**
|
|
* MetaFields Component
|
|
*
|
|
* Generic component to display/edit custom meta fields from plugins.
|
|
* Part of Level 1 compatibility - allows plugins using standard WP/WooCommerce
|
|
* meta storage to have their fields displayed automatically.
|
|
*
|
|
* Zero coupling with specific plugins - renders any registered fields.
|
|
*/
|
|
export function MetaFields({ meta, fields, onChange, readOnly = false }: MetaFieldsProps) {
|
|
// Don't render if no fields registered
|
|
if (fields.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// Group fields by section
|
|
const sections = fields.reduce((acc, field) => {
|
|
const section = field.section || 'Additional Fields';
|
|
if (!acc[section]) acc[section] = [];
|
|
acc[section].push(field);
|
|
return acc;
|
|
}, {} as Record<string, MetaField[]>);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{Object.entries(sections).map(([section, sectionFields]) => (
|
|
<Card key={section}>
|
|
<CardHeader>
|
|
<CardTitle>{section}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{sectionFields.map((field) => (
|
|
<div key={field.key} className="space-y-2">
|
|
<Label htmlFor={field.key}>
|
|
{field.label}
|
|
{field.description && (
|
|
<span className="text-xs text-muted-foreground ml-2">
|
|
{field.description}
|
|
</span>
|
|
)}
|
|
</Label>
|
|
|
|
{field.type === 'text' && (
|
|
<Input
|
|
id={field.key}
|
|
value={meta[field.key] || ''}
|
|
onChange={(e) => onChange(field.key, e.target.value)}
|
|
disabled={readOnly}
|
|
placeholder={field.placeholder}
|
|
/>
|
|
)}
|
|
|
|
{field.type === 'textarea' && (
|
|
<Textarea
|
|
id={field.key}
|
|
value={meta[field.key] || ''}
|
|
onChange={(e) => onChange(field.key, e.target.value)}
|
|
disabled={readOnly}
|
|
placeholder={field.placeholder}
|
|
rows={4}
|
|
/>
|
|
)}
|
|
|
|
{field.type === 'number' && (
|
|
<Input
|
|
id={field.key}
|
|
type="number"
|
|
value={meta[field.key] || ''}
|
|
onChange={(e) => onChange(field.key, e.target.value)}
|
|
disabled={readOnly}
|
|
placeholder={field.placeholder}
|
|
/>
|
|
)}
|
|
|
|
{field.type === 'date' && (
|
|
<Input
|
|
id={field.key}
|
|
type="date"
|
|
value={meta[field.key] || ''}
|
|
onChange={(e) => onChange(field.key, e.target.value)}
|
|
disabled={readOnly}
|
|
/>
|
|
)}
|
|
|
|
{field.type === 'select' && field.options && (
|
|
<Select
|
|
value={meta[field.key] || ''}
|
|
onValueChange={(value) => onChange(field.key, value)}
|
|
disabled={readOnly}
|
|
>
|
|
<SelectTrigger id={field.key}>
|
|
<SelectValue placeholder={field.placeholder || 'Select...'} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{field.options.map((opt) => (
|
|
<SelectItem key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
|
|
{field.type === 'checkbox' && (
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id={field.key}
|
|
checked={!!meta[field.key]}
|
|
onCheckedChange={(checked) => onChange(field.key, checked)}
|
|
disabled={readOnly}
|
|
/>
|
|
<label
|
|
htmlFor={field.key}
|
|
className="text-sm cursor-pointer leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
>
|
|
{field.placeholder || 'Enable'}
|
|
</label>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|