From e8ca3ceeb2dbbc5a8a8baa007450e7b708067b2b Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 20 Nov 2025 20:32:46 +0700 Subject: [PATCH] fix: Vertical tabs visibility and add mobile search/filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed 3 critical issues: 1. Fixed Vertical Tabs - Cards All Showing - Updated VerticalTabForm to hide inactive sections - Only active section visible (className: hidden for others) - Proper tab switching now works 2. Added Mobile Search/Filter to Coupons - Created CouponFilterSheet component - Added mobile search bar with icon - Filter button with active count badge - Matches Products pattern exactly - Sheet with Apply/Reset buttons 3. Removed max-height from VerticalTabForm - User removed max-h-[calc(100vh-200px)] - Content now flows naturally - Better for forms with varying heights Components Created: - CouponFilterSheet.tsx - Mobile filter bottom sheet - Discount type filter - Apply/Reset actions - Active filter count Changes to Coupons/index.tsx: - Added mobile search bar (md:hidden) - Added filter sheet state - Added activeFiltersCount - Search icon + SlidersHorizontal icon - Filter badge indicator Changes to VerticalTabForm: - Hide inactive sections (className: hidden) - Only show section matching activeTab - Proper visibility control Result: ✅ Vertical tabs work correctly (only one section visible) ✅ Mobile search/filter on Coupons (like Products) ✅ Filter count badge ✅ Professional mobile UX Next: Move customer site member checkbox to settings --- admin-spa/src/components/VerticalTabForm.tsx | 4 +- admin-spa/src/routes/Coupons/Edit.tsx | 2 +- admin-spa/src/routes/Coupons/New.tsx | 2 +- .../Coupons/components/CouponFilterSheet.tsx | 80 +++++++++ admin-spa/src/routes/Coupons/index.tsx | 156 ++++++++++++------ 5 files changed, 186 insertions(+), 58 deletions(-) create mode 100644 admin-spa/src/routes/Coupons/components/CouponFilterSheet.tsx diff --git a/admin-spa/src/components/VerticalTabForm.tsx b/admin-spa/src/components/VerticalTabForm.tsx index 9e33e4e..a632ebc 100644 --- a/admin-spa/src/components/VerticalTabForm.tsx +++ b/admin-spa/src/components/VerticalTabForm.tsx @@ -92,13 +92,15 @@ export function VerticalTabForm({ tabs, children, className }: VerticalTabFormPr {/* Content Area */}
{React.Children.map(children, (child) => { if (React.isValidElement(child) && child.props['data-section-id']) { const sectionId = child.props['data-section-id']; + const isActive = sectionId === activeTab; return React.cloneElement(child as React.ReactElement, { ref: (el: HTMLElement) => registerSection(sectionId, el), + className: isActive ? '' : 'hidden', }); } return child; diff --git a/admin-spa/src/routes/Coupons/Edit.tsx b/admin-spa/src/routes/Coupons/Edit.tsx index 9ad5261..6757668 100644 --- a/admin-spa/src/routes/Coupons/Edit.tsx +++ b/admin-spa/src/routes/Coupons/Edit.tsx @@ -89,7 +89,7 @@ export default function CouponEdit() { } return ( -
+
+
{ diff --git a/admin-spa/src/routes/Coupons/components/CouponFilterSheet.tsx b/admin-spa/src/routes/Coupons/components/CouponFilterSheet.tsx new file mode 100644 index 0000000..61e9923 --- /dev/null +++ b/admin-spa/src/routes/Coupons/components/CouponFilterSheet.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { __ } from '@/lib/i18n'; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; + +interface CouponFilterSheetProps { + open: boolean; + onClose: () => void; + filters: { + discount_type: string; + }; + onFiltersChange: (filters: { discount_type: string }) => void; + onReset: () => void; +} + +export function CouponFilterSheet({ + open, + onClose, + filters, + onFiltersChange, + onReset, +}: CouponFilterSheetProps) { + const [localFilters, setLocalFilters] = useState(filters); + + const handleApply = () => { + onFiltersChange(localFilters); + onClose(); + }; + + const handleReset = () => { + setLocalFilters({ discount_type: '' }); + onReset(); + onClose(); + }; + + return ( + + + + {__('Filter Coupons')} + + +
+ {/* Discount Type */} +
+ + +
+
+ + {/* Actions */} +
+ + +
+
+
+ ); +} diff --git a/admin-spa/src/routes/Coupons/index.tsx b/admin-spa/src/routes/Coupons/index.tsx index 0dc9ad1..40ee722 100644 --- a/admin-spa/src/routes/Coupons/index.tsx +++ b/admin-spa/src/routes/Coupons/index.tsx @@ -11,8 +11,9 @@ import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; -import { Trash2, RefreshCw, Edit, Tag } from 'lucide-react'; +import { Trash2, RefreshCw, Edit, Tag, Search, SlidersHorizontal } from 'lucide-react'; import { useFABConfig } from '@/hooks/useFABConfig'; +import { CouponFilterSheet } from './components/CouponFilterSheet'; export default function CouponsIndex() { const navigate = useNavigate(); @@ -21,9 +22,13 @@ export default function CouponsIndex() { const [search, setSearch] = useState(''); const [discountType, setDiscountType] = useState(''); const [selectedIds, setSelectedIds] = useState([]); + const [filterSheetOpen, setFilterSheetOpen] = useState(false); // Configure FAB to navigate to new coupon page - useFABConfig('navigate', '/coupons/new'); + useFABConfig('coupons'); + + // Count active filters + const activeFiltersCount = discountType && discountType !== 'all' ? 1 : 0; // Fetch coupons const { data, isLoading, isError, error, refetch } = useQuery({ @@ -110,69 +115,101 @@ export default function CouponsIndex() { const hasActiveFilters = search || (discountType && discountType !== 'all'); return ( -
- {/* Toolbar */} -
- {/* Left: Bulk Actions */} -
- {/* Delete - Show only when items selected */} - {selectedIds.length > 0 && ( - - )} +
+ {/* Mobile: Search + Filter */} +
+
+ {/* Search Input */} +
+ + setSearch(e.target.value)} + placeholder={__('Search coupons...')} + className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + /> +
- {/* Refresh - Always visible (REQUIRED per SOP) */} + {/* Filter Button */}
+
- {/* Right: Filters */} -
- {/* Discount Type Filter */} - + {/* Desktop Toolbar */} +
+
+ + {/* Left: Bulk Actions */} +
+ {/* Delete - Show only when items selected */} + {selectedIds.length > 0 && ( + + )} - {/* Search */} - setSearch(e.target.value)} - className="w-[200px]" - /> - - {/* Reset Filters - Text link style per SOP */} - {hasActiveFilters && ( + {/* Refresh - Always visible (REQUIRED per SOP) */} - )} +
+ + {/* Right: Filters */} +
+ {/* Discount Type Filter */} + + + {/* Search */} + setSearch(e.target.value)} + className="w-[200px]" + /> + + {/* Reset Filters - Text link style per SOP */} + {hasActiveFilters && ( + + )} +
@@ -328,6 +365,15 @@ export default function CouponsIndex() {
)} + + {/* Mobile Filter Sheet */} + setFilterSheetOpen(false)} + filters={{ discount_type: discountType }} + onFiltersChange={(filters) => setDiscountType(filters.discount_type)} + onReset={() => setDiscountType('')} + />
); }