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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user