mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-07-01 21:28:23 +00:00
feat(devices): add proper filter handling and update shadcn 🔨
This commit is contained in:
183
components/devices/device-filter.tsx
Normal file
183
components/devices/device-filter.tsx
Normal file
@ -0,0 +1,183 @@
|
||||
"use client"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ListFilter, Loader2, X } from "lucide-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useQueryState } from "nuqs";
|
||||
import { useState, useTransition } from 'react';
|
||||
export default function DeviceFilter() {
|
||||
const { replace } = useRouter();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [disabled, startTransition] = useTransition();
|
||||
const searchParams = useSearchParams();
|
||||
const pathname = usePathname();
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
|
||||
const [urlInputName] = useQueryState("name", {
|
||||
clearOnDefault: true
|
||||
})
|
||||
const [urlInputMac] = useQueryState("mac", {
|
||||
clearOnDefault: true
|
||||
})
|
||||
const [urlInputVendor] = useQueryState("vendor", {
|
||||
clearOnDefault: true
|
||||
})
|
||||
|
||||
// Local state for input fields
|
||||
const [inputName, setInputName] = useState(urlInputName ?? "");
|
||||
const [inputMac, setInputMac] = useState(urlInputMac ?? "");
|
||||
const [inputVendor, setInputVendor] = useState(urlInputVendor ?? "");
|
||||
|
||||
// Map filter keys to their state setters
|
||||
const filterSetters: Record<string, React.Dispatch<React.SetStateAction<string>>> = {
|
||||
name: setInputName,
|
||||
mac: setInputMac,
|
||||
vendor: setInputVendor,
|
||||
};
|
||||
function handleSearch({ name, mac, vendor }: { name: string; mac: string; vendor: string }) {
|
||||
|
||||
if (name) params.set("name", name); else params.delete("name");
|
||||
if (mac) params.set("mac", mac); else params.delete("mac");
|
||||
if (vendor) params.set("vendor", vendor); else params.delete("vendor");
|
||||
params.set("page", "1");
|
||||
|
||||
startTransition(() => {
|
||||
replace(`${pathname}?${params.toString()}`);
|
||||
});
|
||||
}
|
||||
|
||||
const appliedFilters = searchParams
|
||||
.toString()
|
||||
.split("&")
|
||||
.filter((filter) => !filter.startsWith("page=") && filter)
|
||||
return (
|
||||
<div className="flex flex-col items-start justify-start gap-2 w-full">
|
||||
<Drawer open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button className="w-full sm:w-48 flex items-end justify-between" onClick={() => setIsOpen(!isOpen)} variant="outline">
|
||||
Filter
|
||||
<ListFilter />
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Device Filters</DrawerTitle>
|
||||
<DrawerDescription asChild>
|
||||
<div>
|
||||
Select your desired filters here
|
||||
</div>
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
|
||||
<div className="grid sm:grid-cols-3 gap-4 p-4">
|
||||
<Input
|
||||
placeholder="Device name ..."
|
||||
value={inputName || searchParams.get("name") || ""}
|
||||
onChange={e => setInputName(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Device Mac address ..."
|
||||
value={inputMac || searchParams.get("mac") || ""}
|
||||
onChange={e => setInputMac(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Device vendor ..."
|
||||
value={inputVendor || searchParams.get("vendor") || ""}
|
||||
onChange={e => setInputVendor(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DrawerFooter className="max-w-sm mx-auto">
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSearch({
|
||||
name: inputName ?? "",
|
||||
mac: inputMac ?? "",
|
||||
vendor: inputVendor ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{disabled ? (
|
||||
<>
|
||||
<Loader2 className="ml-2 animate-spin" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Apply Filters
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => {
|
||||
setInputName("");
|
||||
setInputMac("");
|
||||
setInputVendor("");
|
||||
replace(pathname)
|
||||
}}>
|
||||
Clear Filters
|
||||
</Button>
|
||||
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<div className="flex gap-2 w-fit flex-wrap">
|
||||
{appliedFilters.map((filter) => (
|
||||
<Badge
|
||||
aria-disabled={disabled}
|
||||
variant={"outline"}
|
||||
className={cn("flex p-2 gap-2 items-center justify-between", { "opacity-50 pointer-events-none": disabled })}
|
||||
key={filter}>
|
||||
<span className="text-md">{prettyPrintFilter(filter)}</span>
|
||||
{
|
||||
disabled ? (
|
||||
<Loader2 className="animate-spin" size={16} />
|
||||
) : (
|
||||
<X
|
||||
className="bg-sarLinkOrange/50 rounded-full p-1 hover:cursor-pointer hover:ring ring-sarLinkOrange"
|
||||
size={16}
|
||||
onClick={() => {
|
||||
const key = filter.split("=")[0];
|
||||
params.delete(key);
|
||||
|
||||
// Use the mapping to clear the correct input state
|
||||
filterSetters[key]?.("");
|
||||
|
||||
startTransition(() => {
|
||||
replace(`${pathname}?${params.toString()}`);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</X>
|
||||
)
|
||||
}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function prettyPrintFilter(filter: string) {
|
||||
const [key, value] = filter.split("=");
|
||||
switch (key) {
|
||||
case "name":
|
||||
return <p>Device Name: <span className="text-muted-foreground">{value}</span></p>;
|
||||
case "mac":
|
||||
return <p>MAC Address: <span className="text-muted-foreground">{value}</span></p>;
|
||||
case "vendor":
|
||||
return <p>Vendor: <span className="text-muted-foreground">{value}</span></p>;
|
||||
default:
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user