Replace dropdown filters with tab buttons across admin pages

Update all admin pages to use tab button filters instead of dropdown selects, following the pattern from MemberAccess.tsx:

Changes:
- AdminProducts: Tab buttons for product type (only bootcamp/webinar shown) and status (active/inactive)
- AdminConsulting: Added status filter with tab buttons (pending payment, confirmed, completed, cancelled)
- AdminOrders: Tab buttons for status filter (all, paid, pending, refunded)
- AdminMembers: Tab buttons for role filter (all, admin, member)
- AdminReviews: Tab buttons for both type (all, consulting, bootcamp, webinar, general) and status (all, pending, approved)

Features added:
- Clear button (X) on search input when text is present
- Reset button appears when any filter is active
- Consistent styling with shadow-sm for active tabs and border-2 for outline tabs
- All filters in vertical stack layout for better mobile responsiveness
- Active state visually distinct with default variant and shadow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-28 00:33:43 +07:00
parent c993abe1e9
commit 0a299466d8
5 changed files with 414 additions and 123 deletions

View File

@@ -10,9 +10,8 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Skeleton } from "@/components/ui/skeleton";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { formatDateTime } from "@/lib/format";
import { Eye, Shield, ShieldOff, Search } from "lucide-react";
import { Eye, Shield, ShieldOff, Search, X } from "lucide-react";
import { toast } from "@/hooks/use-toast";
interface Member {
@@ -78,6 +77,11 @@ export default function AdminMembers() {
return matchesSearch && matchesRole;
});
const clearFilters = () => {
setSearchQuery('');
setFilterRole('all');
};
const viewMemberDetails = async (member: Member) => {
setSelectedMember(member);
const { data } = await supabase.from("user_access").select("*, product:products(title)").eq("user_id", member.id);
@@ -123,7 +127,7 @@ export default function AdminMembers() {
{/* Search & Filter */}
<Card className="border-2 border-border mb-6">
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
@@ -132,20 +136,58 @@ export default function AdminMembers() {
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 border-2"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<X className="w-4 h-4" />
</button>
)}
</div>
<Select value={filterRole} onValueChange={setFilterRole}>
<SelectTrigger className="border-2">
<SelectValue placeholder="Filter role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Semua Role</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mt-2 text-sm text-muted-foreground">
Menampilkan {filteredMembers.length} dari {members.length} member
<div className="flex flex-wrap gap-2 items-center">
<span className="text-sm font-medium text-muted-foreground">Role:</span>
<Button
variant={filterRole === 'all' ? 'default' : 'outline'}
size="sm"
onClick={() => setFilterRole('all')}
className={filterRole === 'all' ? 'shadow-sm' : 'border-2'}
>
Semua
</Button>
<Button
variant={filterRole === 'admin' ? 'default' : 'outline'}
size="sm"
onClick={() => setFilterRole('admin')}
className={filterRole === 'admin' ? 'shadow-sm' : 'border-2'}
>
Admin
</Button>
<Button
variant={filterRole === 'member' ? 'default' : 'outline'}
size="sm"
onClick={() => setFilterRole('member')}
className={filterRole === 'member' ? 'shadow-sm' : 'border-2'}
>
Member
</Button>
{(searchQuery || filterRole !== 'all') && (
<Button
variant="ghost"
size="sm"
onClick={clearFilters}
className="text-muted-foreground hover:text-destructive"
>
<X className="w-4 h-4 mr-1" />
Reset
</Button>
)}
</div>
<p className="text-sm text-muted-foreground">
Menampilkan {filteredMembers.length} dari {members.length} member
</p>
</div>
</CardContent>
</Card>