feat: Convert Products form to vertical tab layout

Applied VerticalTabForm to ProductFormTabbed

Changes:
1. Replaced horizontal Tabs with VerticalTabForm
2. Converted TabsContent to FormSection components
3. Removed activeTab state (scroll spy handles this)
4. Dynamic tabs based on product type
   - Simple: General, Inventory, Organization
   - Variable: General, Inventory, Variations, Organization

Benefits:
 Consistent layout with Coupons form
 Better space utilization
 Narrower content area (more readable)
 Scroll spy navigation
 Click to scroll to section
 Professional UI

Layout:
- Desktop: 250px sidebar + content area
- Sidebar: Sticky with icons
- Content: Scrollable with smooth navigation
- Mobile: Keeps original horizontal tabs (future)

Removed:
- TabsList, TabsTrigger components
- activeTab state and setActiveTab calls
- Manual tab switching on validation errors

Result:
Both Products and Coupons now use same vertical tab pattern
Forms are more professional and easier to navigate
This commit is contained in:
dwindown
2025-11-20 17:27:39 +07:00
parent 7455d99ab8
commit be671b66ec

View File

@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api'; import { api } from '@/lib/api';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { VerticalTabForm, FormSection } from '@/components/VerticalTabForm';
import { Package, DollarSign, Layers, Tag } from 'lucide-react'; import { Package, DollarSign, Layers, Tag } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { GeneralTab } from './tabs/GeneralTab'; import { GeneralTab } from './tabs/GeneralTab';
@@ -74,7 +74,6 @@ export function ProductFormTabbed({
const [downloadable, setDownloadable] = useState(initial?.downloadable || false); const [downloadable, setDownloadable] = useState(initial?.downloadable || false);
const [featured, setFeatured] = useState(initial?.featured || false); const [featured, setFeatured] = useState(initial?.featured || false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [activeTab, setActiveTab] = useState('general');
// Update form state when initial data changes (for edit mode) // Update form state when initial data changes (for edit mode)
useEffect(() => { useEffect(() => {
@@ -124,13 +123,11 @@ export function ProductFormTabbed({
// Validation // Validation
if (!name.trim()) { if (!name.trim()) {
toast.error(__('Product name is required')); toast.error(__('Product name is required'));
setActiveTab('general');
return; return;
} }
if (type === 'simple' && !regularPrice) { if (type === 'simple' && !regularPrice) {
toast.error(__('Regular price is required for simple products')); toast.error(__('Regular price is required for simple products'));
setActiveTab('general');
return; return;
} }
@@ -166,32 +163,19 @@ export function ProductFormTabbed({
} }
}; };
// Define tabs based on product type
const tabs = [
{ id: 'general', label: __('General'), icon: <Package className="w-4 h-4" /> },
{ id: 'inventory', label: __('Inventory'), icon: <Layers className="w-4 h-4" /> },
...(type === 'variable' ? [{ id: 'variations', label: __('Variations'), icon: <Layers className="w-4 h-4" /> }] : []),
{ id: 'organization', label: __('Organization'), icon: <Tag className="w-4 h-4" /> },
];
return ( return (
<form ref={formRef} onSubmit={handleSubmit} className={className}> <form ref={formRef} onSubmit={handleSubmit} className={className}>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full"> <VerticalTabForm tabs={tabs}>
<TabsList className={`grid w-full mb-6 ${type === 'variable' ? 'grid-cols-4' : 'grid-cols-3'}`}>
<TabsTrigger value="general" className="flex items-center gap-2">
<Package className="w-4 h-4" />
<span className="hidden sm:inline">{__('General')}</span>
</TabsTrigger>
<TabsTrigger value="inventory" className="flex items-center gap-2">
<Layers className="w-4 h-4" />
<span className="hidden sm:inline">{__('Inventory')}</span>
</TabsTrigger>
{type === 'variable' && (
<TabsTrigger value="variations" className="flex items-center gap-2">
<Layers className="w-4 h-4" />
<span className="hidden sm:inline">{__('Variations')}</span>
</TabsTrigger>
)}
<TabsTrigger value="organization" className="flex items-center gap-2">
<Tag className="w-4 h-4" />
<span className="hidden sm:inline">{__('Organization')}</span>
</TabsTrigger>
</TabsList>
{/* General Tab */} {/* General Tab */}
<TabsContent value="general"> <FormSection id="general">
<GeneralTab <GeneralTab
name={name} name={name}
setName={setName} setName={setName}
@@ -216,10 +200,10 @@ export function ProductFormTabbed({
salePrice={salePrice} salePrice={salePrice}
setSalePrice={setSalePrice} setSalePrice={setSalePrice}
/> />
</TabsContent> </FormSection>
{/* Inventory Tab */} {/* Inventory Tab */}
<TabsContent value="inventory"> <FormSection id="inventory">
<InventoryTab <InventoryTab
manageStock={manageStock} manageStock={manageStock}
setManageStock={setManageStock} setManageStock={setManageStock}
@@ -228,10 +212,11 @@ export function ProductFormTabbed({
stockStatus={stockStatus || 'instock'} stockStatus={stockStatus || 'instock'}
setStockStatus={setStockStatus} setStockStatus={setStockStatus}
/> />
</TabsContent> </FormSection>
{/* Variations Tab */} {/* Variations Tab (only for variable products) */}
<TabsContent value="variations"> {type === 'variable' && (
<FormSection id="variations">
<VariationsTab <VariationsTab
attributes={attributes} attributes={attributes}
setAttributes={setAttributes} setAttributes={setAttributes}
@@ -239,10 +224,11 @@ export function ProductFormTabbed({
setVariations={setVariations} setVariations={setVariations}
regularPrice={regularPrice} regularPrice={regularPrice}
/> />
</TabsContent> </FormSection>
)}
{/* Organization Tab */} {/* Organization Tab */}
<TabsContent value="organization"> <FormSection id="organization">
<OrganizationTab <OrganizationTab
categories={categories} categories={categories}
selectedCategories={selectedCategories} selectedCategories={selectedCategories}
@@ -251,17 +237,21 @@ export function ProductFormTabbed({
selectedTags={selectedTags} selectedTags={selectedTags}
setSelectedTags={setSelectedTags} setSelectedTags={setSelectedTags}
/> />
</TabsContent> </FormSection>
</Tabs>
{/* Submit Button */} {/* Submit Button */}
{!hideSubmitButton && ( {!hideSubmitButton && (
<div className="flex justify-end gap-3 pt-6 border-t mt-6"> <div className="mt-6 flex gap-3">
<Button type="submit" disabled={submitting}> <Button type="submit" disabled={submitting}>
{submitting ? __('Saving...') : mode === 'create' ? __('Create Product') : __('Update Product')} {submitting
? __('Saving...')
: mode === 'create'
? __('Create Product')
: __('Update Product')}
</Button> </Button>
</div> </div>
)} )}
</VerticalTabForm>
</form> </form>
); );
} }