feat: Product New/Edit pages with comprehensive form
Implemented full Product CRUD create/edit functionality. Product New Page (New.tsx): ✅ Create new products ✅ Page header with back/create buttons ✅ Form submission with React Query mutation ✅ Success toast & navigation ✅ Error handling Product Edit Page (Edit.tsx): ✅ Load existing product data ✅ Update product with PUT request ✅ Loading & error states ✅ Page header with back/save buttons ✅ Query invalidation on success ProductForm Component (partials/ProductForm.tsx - 600+ lines): ✅ Basic Information (name, type, status, descriptions) ✅ Product Types: Simple, Variable, Grouped, External ✅ Pricing (regular, sale, SKU) for simple products ✅ Inventory Management (stock tracking, quantity, status) ✅ Categories & Tags (multi-select with checkboxes) ✅ Attributes & Variations (for variable products) - Add/remove attributes - Define attribute options - Generate all variations automatically - Per-variation pricing & stock ✅ Additional Options (virtual, downloadable, featured) ✅ Form validation ✅ Reusable for create/edit modes ✅ Full i18n support Features: - Dynamic category/tag fetching from API - Variation generator from attributes - Manage stock toggle - Stock status badges - Form ref for external submit - Hide submit button option (for page header buttons) - Comprehensive validation - Toast notifications Pattern: - Follows PROJECT_SOP.md CRUD template - Consistent with Orders module - Clean separation of concerns - Type-safe with TypeScript
This commit is contained in:
@@ -1,11 +1,76 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '@/lib/api';
|
||||
import { __ } from '@/lib/i18n';
|
||||
import { toast } from 'sonner';
|
||||
import { useFABConfig } from '@/hooks/useFABConfig';
|
||||
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
||||
import { ProductForm, ProductFormData } from './partials/ProductForm';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export default function ProductNew() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const { setPageHeader, clearPageHeader } = usePageHeader();
|
||||
|
||||
// Hide FAB on new product page
|
||||
useFABConfig('none');
|
||||
|
||||
// Create mutation
|
||||
const createMutation = useMutation({
|
||||
mutationFn: async (data: ProductFormData) => {
|
||||
return api.post('/products', data);
|
||||
},
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(__('Product created successfully'));
|
||||
queryClient.invalidateQueries({ queryKey: ['products'] });
|
||||
|
||||
// Navigate to product detail or edit page
|
||||
if (response?.id) {
|
||||
navigate(`/products/${response.id}`);
|
||||
} else {
|
||||
navigate('/products');
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(error.message || __('Failed to create product'));
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (data: ProductFormData) => {
|
||||
await createMutation.mutateAsync(data);
|
||||
};
|
||||
|
||||
// Set page header with back button and create button
|
||||
useEffect(() => {
|
||||
const actions = (
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={() => navigate('/products')}>
|
||||
{__('Back')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => formRef.current?.requestSubmit()}
|
||||
disabled={createMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending ? __('Creating...') : __('Create')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
setPageHeader(__('New Product'), actions);
|
||||
return () => clearPageHeader();
|
||||
}, [createMutation.isPending, setPageHeader, clearPageHeader, navigate]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold mb-3">{__('New Product')}</h1>
|
||||
<p className="opacity-70">{__('Coming soon — SPA product create form.')}</p>
|
||||
<div className="space-y-4">
|
||||
<ProductForm
|
||||
mode="create"
|
||||
onSubmit={handleSubmit}
|
||||
formRef={formRef}
|
||||
hideSubmitButton={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user