fix: comprehensive bookings admin UX improvements

- #1 Delete confirmation dialog before removing bookings
- #2 Error toasts instead of silent .catch(() => {})
- #3 Optimistic rollback — UI reverts on API failure
- #4 Loading indicator on reminder status buttons
- #5 Search results are now actionable (status change + delete)
- #6 New bookings banner instead of full tab remount
- #7 Error states for failed data loads
- #8 InlineNotes only saves on blur when value changed
- #9 AddBookingModal supports Instagram/Telegram fields
- #10 Polling pauses when browser tab is hidden
- #11 Enter key submits ConfirmModal
This commit is contained in:
2026-03-24 15:54:22 +03:00
parent 669c4a3023
commit aa0cfe35c3
7 changed files with 315 additions and 75 deletions

View File

@@ -25,6 +25,8 @@ export function AddBookingModal({
const [eventType, setEventType] = useState<EventType>("master-class");
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [instagram, setInstagram] = useState("");
const [telegram, setTelegram] = useState("");
const [mcTitle, setMcTitle] = useState("");
const [mcOptions, setMcOptions] = useState<McOption[]>([]);
const [odClasses, setOdClasses] = useState<OdClass[]>([]);
@@ -34,7 +36,7 @@ export function AddBookingModal({
useEffect(() => {
if (!open) return;
setName(""); setPhone(""); setMcTitle(""); setOdClassId("");
setName(""); setPhone(""); setInstagram(""); setTelegram(""); setMcTitle(""); setOdClassId("");
// Fetch upcoming MCs (filter out expired)
adminFetch("/api/admin/sections/masterClasses").then((r) => r.json()).then((data: { items?: { title: string; slots: { date: string }[] }[] }) => {
@@ -80,7 +82,12 @@ export function AddBookingModal({
await adminFetch("/api/admin/group-bookings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: name.trim(), phone: phone.trim() }),
body: JSON.stringify({
name: name.trim(),
phone: phone.trim(),
...(instagram.trim() && { instagram: instagram.trim() }),
...(telegram.trim() && { telegram: telegram.trim() }),
}),
});
} else if (eventType === "master-class") {
const title = mcTitle || mcOptions[0]?.title || "Мастер-класс";
@@ -193,6 +200,10 @@ export function AddBookingModal({
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Имя" className={inputClass} />
<input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="Телефон" className={inputClass} />
<div className="flex gap-2">
<input type="text" value={instagram} onChange={(e) => setInstagram(e.target.value)} placeholder="Instagram" className={inputClass} />
<input type="text" value={telegram} onChange={(e) => setTelegram(e.target.value)} placeholder="Telegram" className={inputClass} />
</div>
</div>
<button