import { useRef, useEffect, useState, useMemo, useCallback } from "react"; import { Plus, X, Upload, Loader2, Link, ImageIcon, AlertCircle, Bold, Italic, List, Heading2, Pencil } from "lucide-react"; import { formatMarkup } from "@/lib/markup"; import { adminFetch } from "@/lib/csrf"; import type { RichListItem } from "@/types/content"; interface InputFieldProps { label: string; value: string; onChange: (value: string) => void; placeholder?: string; type?: "text" | "url" | "tel"; } const baseInput = "w-full rounded-lg border border-white/10 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 outline-none hover:border-gold/30 focus:border-gold transition-colors"; const textAreaInput = `${baseInput} resize-none overflow-hidden`; const smallInput = "rounded-md border border-white/10 bg-neutral-800 px-2.5 py-1.5 text-sm text-white placeholder-neutral-600 outline-none hover:border-gold/30 focus:border-gold transition-colors"; const dashedInput = "flex-1 rounded-lg border border-dashed border-white/10 bg-neutral-800/50 px-4 py-2 text-sm text-white placeholder-neutral-600 outline-none hover:border-gold/30 hover:placeholder-neutral-500 focus:border-gold/50 transition-colors"; const inputCls = baseInput; export function InputField({ label, value, onChange, placeholder, type = "text", }: InputFieldProps) { return (
onChange(e.target.value)} placeholder={placeholder} className={inputCls} />
); } export function ParticipantLimits({ min, max, onMinChange, onMaxChange, }: { min: number; max: number; onMinChange: (v: number) => void; onMaxChange: (v: number) => void; }) { const [minStr, setMinStr] = useState(String(min)); const [maxStr, setMaxStr] = useState(String(max)); const minLocal = parseInt(minStr) || 0; const maxLocal = parseInt(maxStr) || 0; const minEmpty = minStr === ""; const maxEmpty = maxStr === ""; const maxError = (maxLocal > 0 && minLocal > 0 && maxLocal < minLocal) || minEmpty || maxEmpty; function handleMin(raw: string) { setMinStr(raw); if (raw === "") return; const v = parseInt(raw) || 0; const curMax = parseInt(maxStr) || 0; if (curMax > 0 && v > curMax) return; onMinChange(v); } function handleMax(raw: string) { setMaxStr(raw); if (raw === "") return; const v = parseInt(raw) || 0; const curMin = parseInt(minStr) || 0; if (v > 0 && v < curMin) return; onMaxChange(v); } const errorMsg = minEmpty || maxEmpty ? "Поле не может быть пустым" : maxLocal > 0 && minLocal > maxLocal ? "Макс. не может быть меньше мин." : null; return (
handleMin(e.target.value)} aria-describedby="min-hint" aria-invalid={minEmpty || undefined} className={`${inputCls} ${minEmpty ? "!border-red-500/50" : ""}`} />

{minEmpty ? "Поле не может быть пустым" : "Если записей меньше — занятие можно отменить"}

handleMax(e.target.value)} aria-describedby="max-hint" aria-invalid={(maxEmpty || (maxLocal > 0 && minLocal > maxLocal)) || undefined} className={`${inputCls} ${maxEmpty || (maxLocal > 0 && minLocal > maxLocal) ? "!border-red-500/50" : ""}`} />

{maxEmpty ? "Поле не может быть пустым" : maxLocal > 0 && minLocal > maxLocal ? "Макс. не может быть меньше мин." : "0 = без лимита. При заполнении — лист ожидания"}

); } interface TextareaFieldProps { label: string; value: string; onChange: (value: string) => void; placeholder?: string; rows?: number; } export function TextareaField({ label, value, onChange, placeholder, rows = 3, }: TextareaFieldProps) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; }, [value]); useEffect(() => { function onResize() { const el = ref.current; if (!el) return; el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); }, []); return (