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>
|
||||
|
||||
<SheetFooter className="mt-6">
|
||||
{/* <SheetFooter className="mt-6">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetFooter> */}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import {
|
||||
PlusCircle,
|
||||
Search,
|
||||
|
@ -10,6 +10,14 @@ import {
|
|||
ChevronDown,
|
||||
UserPlus,
|
||||
Mail,
|
||||
SortAsc,
|
||||
SortDesc,
|
||||
Mail as MailIcon,
|
||||
Phone,
|
||||
Clock,
|
||||
Calendar,
|
||||
ShieldAlert,
|
||||
ListFilter,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
@ -18,7 +26,9 @@ import {
|
|||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuCheckboxItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchUsers } from "@/app/protected/(admin)/dashboard/user-management/action";
|
||||
|
@ -36,6 +46,21 @@ export default function UserManagement() {
|
|||
const [isAddUserOpen, setIsAddUserOpen] = 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
|
||||
const {
|
||||
data: users = [],
|
||||
|
@ -63,21 +88,150 @@ export default function UserManagement() {
|
|||
setIsSheetOpen(false);
|
||||
};
|
||||
|
||||
const filteredUsers = users.filter((user) => {
|
||||
if (!searchQuery) return true;
|
||||
const filteredUsers = useMemo(() => {
|
||||
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();
|
||||
return (
|
||||
user.email?.toLowerCase().includes(query) ||
|
||||
user.phone?.toLowerCase().includes(query) ||
|
||||
user.id.toLowerCase().includes(query)
|
||||
);
|
||||
});
|
||||
if (!matchesSearch) return false;
|
||||
}
|
||||
|
||||
// Email filter
|
||||
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 = [
|
||||
{
|
||||
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 } }) => (
|
||||
<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">
|
||||
|
@ -93,21 +247,109 @@ export default function UserManagement() {
|
|||
</div>
|
||||
</div>
|
||||
),
|
||||
filterFn: (row: any, id: string, value: string) => {
|
||||
return row.original.email?.toLowerCase().includes(value.toLowerCase());
|
||||
},
|
||||
},
|
||||
{
|
||||
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 || "-",
|
||||
filterFn: (row: any, id: string, value: string) => {
|
||||
return row.original.phone?.toLowerCase().includes(value.toLowerCase());
|
||||
},
|
||||
},
|
||||
{
|
||||
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 } }) => {
|
||||
return row.original.last_sign_in_at
|
||||
? new Date(row.original.last_sign_in_at).toLocaleString()
|
||||
|
@ -116,14 +358,129 @@ export default function UserManagement() {
|
|||
},
|
||||
{
|
||||
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 } }) => {
|
||||
return new Date(row.original.created_at).toLocaleString();
|
||||
},
|
||||
},
|
||||
{
|
||||
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 } }) => {
|
||||
if (row.original.banned_until) {
|
||||
return <Badge variant="destructive">Banned</Badge>;
|
||||
|
@ -133,14 +490,6 @@ export default function UserManagement() {
|
|||
}
|
||||
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",
|
||||
|
@ -202,8 +551,19 @@ export default function UserManagement() {
|
|||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,8 +9,6 @@ const Popover = PopoverPrimitive.Root
|
|||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverAnchor = PopoverPrimitive.Anchor
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
|
@ -30,4 +28,4 @@ const PopoverContent = React.forwardRef<
|
|||
))
|
||||
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-dropdown-menu": "^2.1.1",
|
||||
"@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-select": "^2.1.6",
|
||||
"@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": {
|
||||
"version": "1.2.2",
|
||||
"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-dropdown-menu": "^2.1.1",
|
||||
"@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-select": "^2.1.6",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
|
|
Loading…
Reference in New Issue