99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useMemo } from "react";
|
|
import Link from "next/link";
|
|
import { Search } from "lucide-react";
|
|
import { useRouter } from "next/navigation";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type Asset = {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
category: string;
|
|
supplier: string;
|
|
};
|
|
|
|
export function GlobalSearch({ assets }: { assets: Asset[] }) {
|
|
const [query, setQuery] = useState("");
|
|
const [open, setOpen] = useState(false);
|
|
const router = useRouter();
|
|
|
|
const matches = useMemo(() => {
|
|
const q = query.trim().toLowerCase();
|
|
if (q.length < 2) return [];
|
|
return assets
|
|
.filter(
|
|
(a) =>
|
|
a.code.toLowerCase().includes(q) ||
|
|
a.name.toLowerCase().includes(q) ||
|
|
(a.supplier || "").toLowerCase().includes(q)
|
|
)
|
|
.slice(0, 8);
|
|
}, [query, assets]);
|
|
|
|
return (
|
|
<div className="relative flex-1 max-w-md">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500 pointer-events-none" />
|
|
<input
|
|
value={query}
|
|
onChange={(e) => {
|
|
setQuery(e.target.value);
|
|
setOpen(true);
|
|
}}
|
|
onFocus={() => setOpen(true)}
|
|
onBlur={() => setTimeout(() => setOpen(false), 150)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && matches[0]) {
|
|
router.push(`/income?asset=${matches[0].id}`);
|
|
setOpen(false);
|
|
setQuery("");
|
|
}
|
|
}}
|
|
placeholder="Search plant, supplier, code…"
|
|
className="w-full h-9 pl-9 pr-3 bg-gray-100 dark:bg-white/5 border border-transparent rounded-lg text-sm placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:bg-white dark:focus:bg-[#0f1419] focus:border-[#30638E] focus:outline-none transition"
|
|
/>
|
|
</div>
|
|
|
|
{open && matches.length > 0 && (
|
|
<div className="absolute top-full left-0 right-0 mt-2 bg-white dark:bg-[#131a21] border border-gray-100 dark:border-white/10 rounded-xl shadow-lg overflow-hidden z-50">
|
|
{matches.map((a) => (
|
|
<Link
|
|
key={a.id}
|
|
href={`/income?asset=${a.id}`}
|
|
onClick={() => {
|
|
setOpen(false);
|
|
setQuery("");
|
|
}}
|
|
className={cn(
|
|
"flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 dark:hover:bg-white/5 text-sm border-b border-gray-50 dark:border-white/5 last:border-0"
|
|
)}
|
|
>
|
|
{a.code ? (
|
|
<span className="font-mono text-xs px-2 py-0.5 rounded bg-[#00798C]/10 text-[#00798C] font-semibold shrink-0">
|
|
{a.code}
|
|
</span>
|
|
) : (
|
|
<span className="text-[10px] px-2 py-0.5 rounded bg-gray-100 dark:bg-white/5 text-gray-400 shrink-0">
|
|
no code
|
|
</span>
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="font-medium text-[#003D5B] dark:text-white truncate">{a.name}</div>
|
|
{a.supplier && (
|
|
<div className="text-xs text-gray-500 dark:text-gray-400 truncate">{a.supplier}</div>
|
|
)}
|
|
</div>
|
|
{a.category && (
|
|
<span className="text-[10px] uppercase tracking-wider text-gray-400 dark:text-gray-500 shrink-0">
|
|
{a.category}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|