a080ef5a8e
- Admin forms, dialogs, and page editors: light-mode borders, text contrast - YandexMap: theme-aware map styles - Layout: theme init script adjustments
56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useRef } from "react";
|
|
import { Search, X } from "lucide-react";
|
|
import { adminFetch } from "@/lib/csrf";
|
|
import type { SearchResult } from "./types";
|
|
|
|
export function SearchBar({
|
|
onResults,
|
|
onClear,
|
|
}: {
|
|
onResults: (results: SearchResult[]) => void;
|
|
onClear: () => void;
|
|
}) {
|
|
const [query, setQuery] = useState("");
|
|
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
|
|
function handleChange(q: string) {
|
|
setQuery(q);
|
|
clearTimeout(timerRef.current);
|
|
if (q.trim().length < 2) {
|
|
onClear();
|
|
return;
|
|
}
|
|
timerRef.current = setTimeout(() => {
|
|
adminFetch(`/api/admin/bookings/search?q=${encodeURIComponent(q.trim())}`)
|
|
.then((r) => r.json())
|
|
.then((data: SearchResult[]) => onResults(data))
|
|
.catch(() => {});
|
|
}, 400);
|
|
}
|
|
|
|
function clear() {
|
|
setQuery("");
|
|
onClear();
|
|
}
|
|
|
|
return (
|
|
<div className="relative">
|
|
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-500" />
|
|
<input
|
|
type="text"
|
|
value={query}
|
|
onChange={(e) => handleChange(e.target.value)}
|
|
placeholder="Поиск по имени или телефону..."
|
|
className="w-full rounded-lg border border-neutral-200 bg-neutral-100 py-2 pl-9 pr-8 text-sm text-neutral-900 placeholder-neutral-400 outline-none focus:border-gold/40 dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500"
|
|
/>
|
|
{query && (
|
|
<button onClick={clear} className="absolute right-2 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-900 dark:hover:text-white">
|
|
<X size={14} />
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|