From 5fe2500dbe79351ea118b21aacf26ecf3f6d2d2e Mon Sep 17 00:00:00 2001 From: "diana.dolgolyova" Date: Thu, 12 Mar 2026 15:25:32 +0300 Subject: [PATCH] feat: searchable select dropdowns + updated levels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace native select with custom searchable dropdown - Search matches word starts (type 'а' finds 'Анна' or 'Мария Андреева') - Search input shows for lists with 4+ options - Updated levels: Начинающий/Без опыта, Продвинутый Co-Authored-By: Claude Opus 4.6 --- src/app/admin/_components/FormField.tsx | 96 ++++++++++++++++++++----- src/app/admin/schedule/page.tsx | 2 +- 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/app/admin/_components/FormField.tsx b/src/app/admin/_components/FormField.tsx index a5ab1fa..57367c8 100644 --- a/src/app/admin/_components/FormField.tsx +++ b/src/app/admin/_components/FormField.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from "react"; +import { useRef, useEffect, useState } from "react"; interface InputFieldProps { label: string; @@ -94,25 +94,87 @@ export function SelectField({ options, placeholder, }: SelectFieldProps) { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + const containerRef = useRef(null); + const inputRef = useRef(null); + + const selectedLabel = options.find((o) => o.value === value)?.label || ""; + const filtered = search + ? options.filter((o) => { + const q = search.toLowerCase(); + // Match any word that starts with the search query + return o.label.toLowerCase().split(/\s+/).some((word) => word.startsWith(q)); + }) + : options; + + // Close on outside click + useEffect(() => { + if (!open) return; + function handle(e: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) { + setOpen(false); + setSearch(""); + } + } + document.addEventListener("mousedown", handle); + return () => document.removeEventListener("mousedown", handle); + }, [open]); + return ( -
+
- + {selectedLabel || placeholder || "Выберите..."} + + + {open && ( +
+ {options.length > 3 && ( +
+ setSearch(e.target.value)} + placeholder="Поиск..." + className="w-full rounded-md border border-white/10 bg-neutral-900 px-3 py-1.5 text-sm text-white outline-none focus:border-gold/50 placeholder:text-neutral-600" + /> +
+ )} +
+ {filtered.length === 0 && ( +
Ничего не найдено
+ )} + {filtered.map((opt) => ( + + ))} +
+
+ )}
); } diff --git a/src/app/admin/schedule/page.tsx b/src/app/admin/schedule/page.tsx index f814125..2900bd5 100644 --- a/src/app/admin/schedule/page.tsx +++ b/src/app/admin/schedule/page.tsx @@ -27,7 +27,7 @@ const DAY_ORDER: Record = Object.fromEntries( const LEVELS = [ { value: "", label: "Без уровня" }, - { value: "Начинающий", label: "Начинающий" }, + { value: "Начинающий/Без опыта", label: "Начинающий/Без опыта" }, { value: "Продвинутый", label: "Продвинутый" }, ];