From d3bb43af80d15736d99ff46819b1154a5ad06a64 Mon Sep 17 00:00:00 2001 From: "diana.dolgolyova" Date: Fri, 13 Mar 2026 15:58:27 +0300 Subject: [PATCH] feat: replace free-text place input with dropdown select in victories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Predefined options: 1-6 место, Финалист, Полуфиналист, Лауреат, Номинант, Участник, Победитель, Гран-при, Best Show, Vice Champion, Champion — with emoji indicators for top places and nominations. Co-Authored-By: Claude Opus 4.6 --- src/app/admin/_components/FormField.tsx | 134 +++++++++++++++--------- 1 file changed, 82 insertions(+), 52 deletions(-) diff --git a/src/app/admin/_components/FormField.tsx b/src/app/admin/_components/FormField.tsx index a142bb1..41efee2 100644 --- a/src/app/admin/_components/FormField.tsx +++ b/src/app/admin/_components/FormField.tsx @@ -657,6 +657,80 @@ export function ValidatedLinkField({ value, onChange, onValidate, validationKey, ); } +// --- Place/Nomination Select --- +const PLACE_OPTIONS = [ + { value: "1 место", label: "1 место", icon: "🥇" }, + { value: "2 место", label: "2 место", icon: "🥈" }, + { value: "3 место", label: "3 место", icon: "🥉" }, + { value: "4 место", label: "4 место" }, + { value: "5 место", label: "5 место" }, + { value: "6 место", label: "6 место" }, + { value: "Финалист", label: "Финалист", icon: "🏅" }, + { value: "Полуфиналист", label: "Полуфиналист" }, + { value: "Лауреат", label: "Лауреат", icon: "🏆" }, + { value: "Номинант", label: "Номинант" }, + { value: "Участник", label: "Участник" }, + { value: "Победитель", label: "Победитель", icon: "🏆" }, + { value: "Гран-при", label: "Гран-при", icon: "👑" }, + { value: "Best Show", label: "Best Show", icon: "⭐" }, + { value: "Vice Champion", label: "Vice Champion" }, + { value: "Champion", label: "Champion", icon: "🏆" }, +]; + +interface PlaceSelectProps { + value: string; + onChange: (value: string) => void; +} + +function PlaceSelect({ value, onChange }: PlaceSelectProps) { + const [open, setOpen] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + if (!open) return; + function handle(e: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) setOpen(false); + } + document.addEventListener("mousedown", handle); + return () => document.removeEventListener("mousedown", handle); + }, [open]); + + const selected = PLACE_OPTIONS.find((o) => o.value === value); + + return ( +
+ + {open && ( +
+
+ {PLACE_OPTIONS.map((opt) => ( + + ))} +
+
+ )} +
+ ); +} + interface VictoryItemListFieldProps { label: string; items: VictoryItem[]; @@ -669,8 +743,6 @@ interface VictoryItemListFieldProps { } export function VictoryItemListField({ label, items, onChange, cityErrors, citySuggestions, onCitySearch, onCitySelect, onLinkValidate }: VictoryItemListFieldProps) { - const [uploadingIndex, setUploadingIndex] = useState(null); - function add() { onChange([...items, { place: "", category: "", competition: "" }]); } @@ -683,28 +755,6 @@ export function VictoryItemListField({ label, items, onChange, cityErrors, cityS onChange(items.map((item, i) => (i === index ? { ...item, [field]: value || undefined } : item))); } - function removeImage(index: number) { - onChange(items.map((item, i) => (i === index ? { ...item, image: undefined } : item))); - } - - async function handleUpload(index: number, e: React.ChangeEvent) { - const file = e.target.files?.[0]; - if (!file) return; - setUploadingIndex(index); - const formData = new FormData(); - formData.append("file", file); - formData.append("folder", "team"); - try { - const res = await fetch("/api/admin/upload", { method: "POST", body: formData }); - const result = await res.json(); - if (result.path) { - onChange(items.map((item, i) => (i === index ? { ...item, image: result.path } : item))); - } - } catch { /* upload failed */ } finally { - setUploadingIndex(null); - } - } - return (
@@ -712,12 +762,9 @@ export function VictoryItemListField({ label, items, onChange, cityErrors, cityS {items.map((item, i) => (
- update(i, "place", e.target.value)} - placeholder="Место (🥇, 1...)" - className="w-24 rounded-md border border-white/10 bg-neutral-800 px-2.5 py-1.5 text-sm text-white placeholder-neutral-600 outline-none focus:border-gold transition-colors" + onChange={(v) => update(i, "place", v)} /> update(i, "date", v)} />
-
- {item.image ? ( -
- - {item.image.split("/").pop()} - -
- ) : ( - - )} - update(i, "link", v)} - validationKey={`victory-${i}`} - onValidate={onLinkValidate} - /> -
+ update(i, "link", v)} + validationKey={`victory-${i}`} + onValidate={onLinkValidate} + />
))}