add filter per kolom user
This commit is contained in:
parent
dd24481574
commit
0c16fc2be5
|
@ -370,11 +370,11 @@ export function UserDetailsSheet({ open, onOpenChange, user, onUserUpdate }: Use
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SheetFooter className="mt-6">
|
{/* <SheetFooter className="mt-6">
|
||||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</SheetFooter>
|
</SheetFooter> */}
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
Search,
|
Search,
|
||||||
|
@ -10,6 +10,14 @@ import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
Mail,
|
Mail,
|
||||||
|
SortAsc,
|
||||||
|
SortDesc,
|
||||||
|
Mail as MailIcon,
|
||||||
|
Phone,
|
||||||
|
Clock,
|
||||||
|
Calendar,
|
||||||
|
ShieldAlert,
|
||||||
|
ListFilter,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
@ -18,7 +26,9 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { fetchUsers } from "@/app/protected/(admin)/dashboard/user-management/action";
|
import { fetchUsers } from "@/app/protected/(admin)/dashboard/user-management/action";
|
||||||
|
@ -36,6 +46,21 @@ export default function UserManagement() {
|
||||||
const [isAddUserOpen, setIsAddUserOpen] = useState(false);
|
const [isAddUserOpen, setIsAddUserOpen] = useState(false);
|
||||||
const [isInviteUserOpen, setIsInviteUserOpen] = useState(false);
|
const [isInviteUserOpen, setIsInviteUserOpen] = useState(false);
|
||||||
|
|
||||||
|
// Filter states
|
||||||
|
const [filters, setFilters] = useState<{
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
lastSignIn: string;
|
||||||
|
createdAt: string;
|
||||||
|
status: string[];
|
||||||
|
}>({
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
lastSignIn: "",
|
||||||
|
createdAt: "",
|
||||||
|
status: [],
|
||||||
|
});
|
||||||
|
|
||||||
// Use React Query to fetch users
|
// Use React Query to fetch users
|
||||||
const {
|
const {
|
||||||
data: users = [],
|
data: users = [],
|
||||||
|
@ -63,21 +88,150 @@ export default function UserManagement() {
|
||||||
setIsSheetOpen(false);
|
setIsSheetOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredUsers = users.filter((user) => {
|
const filteredUsers = useMemo(() => {
|
||||||
if (!searchQuery) return true;
|
return users.filter((user) => {
|
||||||
|
// Global search
|
||||||
|
if (searchQuery) {
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
const matchesSearch =
|
||||||
|
user.email?.toLowerCase().includes(query) ||
|
||||||
|
user.phone?.toLowerCase().includes(query) ||
|
||||||
|
user.id.toLowerCase().includes(query);
|
||||||
|
|
||||||
const query = searchQuery.toLowerCase();
|
if (!matchesSearch) return false;
|
||||||
return (
|
}
|
||||||
user.email?.toLowerCase().includes(query) ||
|
|
||||||
user.phone?.toLowerCase().includes(query) ||
|
// Email filter
|
||||||
user.id.toLowerCase().includes(query)
|
if (
|
||||||
);
|
filters.email &&
|
||||||
});
|
!user.email?.toLowerCase().includes(filters.email.toLowerCase())
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phone filter
|
||||||
|
if (
|
||||||
|
filters.phone &&
|
||||||
|
!user.phone?.toLowerCase().includes(filters.phone.toLowerCase())
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last sign in filter
|
||||||
|
if (filters.lastSignIn) {
|
||||||
|
if (filters.lastSignIn === "never" && user.last_sign_in_at) {
|
||||||
|
return false;
|
||||||
|
} else if (filters.lastSignIn === "today") {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const signInDate = user.last_sign_in_at
|
||||||
|
? new Date(user.last_sign_in_at)
|
||||||
|
: null;
|
||||||
|
if (!signInDate || signInDate < today) return false;
|
||||||
|
} else if (filters.lastSignIn === "week") {
|
||||||
|
const weekAgo = new Date();
|
||||||
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||||
|
const signInDate = user.last_sign_in_at
|
||||||
|
? new Date(user.last_sign_in_at)
|
||||||
|
: null;
|
||||||
|
if (!signInDate || signInDate < weekAgo) return false;
|
||||||
|
} else if (filters.lastSignIn === "month") {
|
||||||
|
const monthAgo = new Date();
|
||||||
|
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
||||||
|
const signInDate = user.last_sign_in_at
|
||||||
|
? new Date(user.last_sign_in_at)
|
||||||
|
: null;
|
||||||
|
if (!signInDate || signInDate < monthAgo) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Created at filter
|
||||||
|
if (filters.createdAt) {
|
||||||
|
if (filters.createdAt === "today") {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const createdAt = new Date(user.created_at);
|
||||||
|
if (createdAt < today) return false;
|
||||||
|
} else if (filters.createdAt === "week") {
|
||||||
|
const weekAgo = new Date();
|
||||||
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||||
|
const createdAt = new Date(user.created_at);
|
||||||
|
if (createdAt < weekAgo) return false;
|
||||||
|
} else if (filters.createdAt === "month") {
|
||||||
|
const monthAgo = new Date();
|
||||||
|
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
||||||
|
const createdAt = new Date(user.created_at);
|
||||||
|
if (createdAt < monthAgo) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status filter
|
||||||
|
if (filters.status.length > 0) {
|
||||||
|
const userStatus = user.banned_until
|
||||||
|
? "banned"
|
||||||
|
: !user.email_confirmed_at
|
||||||
|
? "unconfirmed"
|
||||||
|
: "active";
|
||||||
|
|
||||||
|
if (!filters.status.includes(userStatus)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [users, searchQuery, filters]);
|
||||||
|
|
||||||
|
const clearFilters = () => {
|
||||||
|
setFilters({
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
lastSignIn: "",
|
||||||
|
createdAt: "",
|
||||||
|
status: [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeFilterCount = Object.values(filters).filter(
|
||||||
|
(value) =>
|
||||||
|
(typeof value === "string" && value !== "") ||
|
||||||
|
(Array.isArray(value) && value.length > 0)
|
||||||
|
).length;
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
id: "email",
|
id: "email",
|
||||||
header: "Email",
|
header: ({ column }: any) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
|
||||||
|
<span>Email</span>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-6 w-6 p-0 ml-1">
|
||||||
|
<ListFilter className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<div className="p-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Filter by email..."
|
||||||
|
value={filters.email}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFilters({ ...filters, email: e.target.value })
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setFilters({ ...filters, email: "" })}
|
||||||
|
>
|
||||||
|
Clear filter
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
cell: ({ row }: { row: { original: User } }) => (
|
cell: ({ row }: { row: { original: User } }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-primary font-medium">
|
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-primary font-medium">
|
||||||
|
@ -93,21 +247,109 @@ export default function UserManagement() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
filterFn: (row: any, id: string, value: string) => {
|
|
||||||
return row.original.email?.toLowerCase().includes(value.toLowerCase());
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "phone",
|
id: "phone",
|
||||||
header: "Phone",
|
header: ({ column }: any) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
|
||||||
|
<span>Phone</span>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-6 w-6 p-0 ml-1">
|
||||||
|
<ListFilter className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<div className="p-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Filter by phone..."
|
||||||
|
value={filters.phone}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFilters({ ...filters, phone: e.target.value })
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setFilters({ ...filters, phone: "" })}
|
||||||
|
>
|
||||||
|
Clear filter
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
cell: ({ row }: { row: { original: User } }) => row.original.phone || "-",
|
cell: ({ row }: { row: { original: User } }) => row.original.phone || "-",
|
||||||
filterFn: (row: any, id: string, value: string) => {
|
|
||||||
return row.original.phone?.toLowerCase().includes(value.toLowerCase());
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "lastSignIn",
|
id: "lastSignIn",
|
||||||
header: "Last Sign In",
|
header: ({ column }: any) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
|
||||||
|
<span>Last Sign In</span>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-6 w-6 p-0 ml-1">
|
||||||
|
<ListFilter className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.lastSignIn === "today"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
lastSignIn: filters.lastSignIn === "today" ? "" : "today",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Today
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.lastSignIn === "week"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
lastSignIn: filters.lastSignIn === "week" ? "" : "week",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Last 7 days
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.lastSignIn === "month"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
lastSignIn: filters.lastSignIn === "month" ? "" : "month",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Last 30 days
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.lastSignIn === "never"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
lastSignIn: filters.lastSignIn === "never" ? "" : "never",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Never
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setFilters({ ...filters, lastSignIn: "" })}
|
||||||
|
>
|
||||||
|
Clear filter
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
cell: ({ row }: { row: { original: User } }) => {
|
cell: ({ row }: { row: { original: User } }) => {
|
||||||
return row.original.last_sign_in_at
|
return row.original.last_sign_in_at
|
||||||
? new Date(row.original.last_sign_in_at).toLocaleString()
|
? new Date(row.original.last_sign_in_at).toLocaleString()
|
||||||
|
@ -116,14 +358,129 @@ export default function UserManagement() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "createdAt",
|
id: "createdAt",
|
||||||
header: "Created At",
|
header: ({ column }: any) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
|
||||||
|
<span>Created At</span>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-6 w-6 p-0 ml-1">
|
||||||
|
<ListFilter className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.createdAt === "today"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
createdAt: filters.createdAt === "today" ? "" : "today",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Today
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.createdAt === "week"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
createdAt: filters.createdAt === "week" ? "" : "week",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Last 7 days
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.createdAt === "month"}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
setFilters({
|
||||||
|
...filters,
|
||||||
|
createdAt: filters.createdAt === "month" ? "" : "month",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Last 30 days
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setFilters({ ...filters, createdAt: "" })}
|
||||||
|
>
|
||||||
|
Clear filter
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
cell: ({ row }: { row: { original: User } }) => {
|
cell: ({ row }: { row: { original: User } }) => {
|
||||||
return new Date(row.original.created_at).toLocaleString();
|
return new Date(row.original.created_at).toLocaleString();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "status",
|
id: "status",
|
||||||
header: "Status",
|
header: ({ column }: any) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
|
||||||
|
<span>Status</span>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-6 w-6 p-0 ml-1">
|
||||||
|
<ListFilter className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.status.includes("active")}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
const newStatus = [...filters.status];
|
||||||
|
if (newStatus.includes("active")) {
|
||||||
|
newStatus.splice(newStatus.indexOf("active"), 1);
|
||||||
|
} else {
|
||||||
|
newStatus.push("active");
|
||||||
|
}
|
||||||
|
setFilters({ ...filters, status: newStatus });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.status.includes("unconfirmed")}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
const newStatus = [...filters.status];
|
||||||
|
if (newStatus.includes("unconfirmed")) {
|
||||||
|
newStatus.splice(newStatus.indexOf("unconfirmed"), 1);
|
||||||
|
} else {
|
||||||
|
newStatus.push("unconfirmed");
|
||||||
|
}
|
||||||
|
setFilters({ ...filters, status: newStatus });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unconfirmed
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={filters.status.includes("banned")}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
const newStatus = [...filters.status];
|
||||||
|
if (newStatus.includes("banned")) {
|
||||||
|
newStatus.splice(newStatus.indexOf("banned"), 1);
|
||||||
|
} else {
|
||||||
|
newStatus.push("banned");
|
||||||
|
}
|
||||||
|
setFilters({ ...filters, status: newStatus });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Banned
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setFilters({ ...filters, status: [] })}
|
||||||
|
>
|
||||||
|
Clear filter
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
cell: ({ row }: { row: { original: User } }) => {
|
cell: ({ row }: { row: { original: User } }) => {
|
||||||
if (row.original.banned_until) {
|
if (row.original.banned_until) {
|
||||||
return <Badge variant="destructive">Banned</Badge>;
|
return <Badge variant="destructive">Banned</Badge>;
|
||||||
|
@ -133,14 +490,6 @@ export default function UserManagement() {
|
||||||
}
|
}
|
||||||
return <Badge variant="default">Active</Badge>;
|
return <Badge variant="default">Active</Badge>;
|
||||||
},
|
},
|
||||||
filterFn: (row: any, id: string, value: string) => {
|
|
||||||
const status = row.original.banned_until
|
|
||||||
? "banned"
|
|
||||||
: !row.original.email_confirmed_at
|
|
||||||
? "unconfirmed"
|
|
||||||
: "active";
|
|
||||||
return status.includes(value.toLowerCase());
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
@ -202,8 +551,19 @@ export default function UserManagement() {
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Button variant="outline" size="icon">
|
|
||||||
<Filter className="h-4 w-4" />
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className={activeFilterCount > 0 ? "relative" : ""}
|
||||||
|
onClick={clearFilters}
|
||||||
|
>
|
||||||
|
<ListFilter className="h-4 w-4" />
|
||||||
|
{activeFilterCount > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-1 bg-primary text-primary-foreground rounded-full w-4 h-4 text-xs flex items-center justify-center">
|
||||||
|
{activeFilterCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,8 +9,6 @@ const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
const PopoverAnchor = PopoverPrimitive.Anchor
|
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
const PopoverContent = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
@ -30,4 +28,4 @@ const PopoverContent = React.forwardRef<
|
||||||
))
|
))
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
export { Popover, PopoverTrigger, PopoverContent }
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.2",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||||
"@radix-ui/react-select": "^2.1.6",
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
|
@ -2044,6 +2045,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
|
"@radix-ui/react-context": "1.1.1",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.5",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.1",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.2",
|
||||||
|
"@radix-ui/react-id": "1.1.0",
|
||||||
|
"@radix-ui/react-popper": "1.2.2",
|
||||||
|
"@radix-ui/react-portal": "1.1.4",
|
||||||
|
"@radix-ui/react-presence": "1.1.2",
|
||||||
|
"@radix-ui/react-primitive": "2.0.2",
|
||||||
|
"@radix-ui/react-slot": "1.1.2",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
|
"aria-hidden": "^1.2.4",
|
||||||
|
"react-remove-scroll": "^2.6.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.2",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||||
"@radix-ui/react-select": "^2.1.6",
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
|
|
Loading…
Reference in New Issue