feat(devices): add proper filter handling and update shadcn 🔨

This commit is contained in:
2025-06-26 18:42:48 +05:00
parent 6aea54884d
commit 59adaaf281
46 changed files with 9472 additions and 1055 deletions

View 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;
}
}