diff --git a/src/app/admin/bookings/page.tsx b/src/app/admin/bookings/page.tsx index 801ba65..4102ada 100644 --- a/src/app/admin/bookings/page.tsx +++ b/src/app/admin/bookings/page.tsx @@ -3,7 +3,6 @@ import { useState, useEffect, useMemo } from "react"; import { Loader2, Trash2, Phone, Instagram, Send, ChevronDown, ChevronRight, Bell, CheckCircle2, XCircle, Clock, Star, Calendar, DoorOpen } from "lucide-react"; import { adminFetch } from "@/lib/csrf"; -import { NotifyToggle } from "../_components/NotifyToggle"; // --- Types --- @@ -12,6 +11,8 @@ interface GroupBooking { name: string; phone: string; groupInfo?: string; + instagram?: string; + telegram?: string; notifiedConfirm: boolean; notifiedReminder: boolean; createdAt: string; @@ -21,6 +22,7 @@ interface McRegistration { id: number; masterClassTitle: string; name: string; + phone?: string; instagram: string; telegram?: string; notifiedConfirm: boolean; @@ -45,69 +47,13 @@ interface OpenDayBooking { classHall?: string; } -interface MasterClassSlot { - date: string; - startTime: string; - endTime: string; -} - -interface MasterClassItem { - title: string; - slots: MasterClassSlot[]; -} - type Tab = "reminders" | "classes" | "master-classes" | "open-day"; -type NotifyFilter = "all" | "new" | "no-reminder"; - -// --- Filter Chips --- - -function FilterChips({ - filter, - setFilter, - newCount, - noReminderCount, -}: { - filter: NotifyFilter; - setFilter: (f: NotifyFilter) => void; - newCount: number; - noReminderCount: number; -}) { - const FILTERS: { key: NotifyFilter; label: string; count?: number }[] = [ - { key: "all", label: "Все" }, - { key: "new", label: "Новые", count: newCount }, - { key: "no-reminder", label: "Без напоминания", count: noReminderCount }, - ]; - - return ( -
- {FILTERS.map((f) => ( - - ))} -
- ); -} // --- Group Bookings Tab --- function GroupBookingsTab() { const [bookings, setBookings] = useState([]); const [loading, setLoading] = useState(true); - const [filter, setFilter] = useState("all"); useEffect(() => { adminFetch("/api/admin/group-bookings") @@ -117,28 +63,6 @@ function GroupBookingsTab() { .finally(() => setLoading(false)); }, []); - const newCount = bookings.filter((b) => !b.notifiedConfirm).length; - const noReminderCount = bookings.filter((b) => !b.notifiedReminder).length; - - const filtered = useMemo(() => { - if (filter === "new") return bookings.filter((b) => !b.notifiedConfirm); - if (filter === "no-reminder") return bookings.filter((b) => !b.notifiedReminder); - return bookings; - }, [bookings, filter]); - - async function handleToggle(id: number, field: "notified_confirm" | "notified_reminder") { - const b = bookings.find((x) => x.id === id); - if (!b) return; - const key = field === "notified_confirm" ? "notifiedConfirm" : "notifiedReminder"; - const newValue = !b[key]; - setBookings((prev) => prev.map((x) => x.id === id ? { ...x, [key]: newValue } : x)); - await adminFetch("/api/admin/group-bookings", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "toggle-notify", id, field, value: newValue }), - }); - } - async function handleDelete(id: number) { await adminFetch(`/api/admin/group-bookings?id=${id}`, { method: "DELETE" }); setBookings((prev) => prev.filter((b) => b.id !== id)); @@ -147,41 +71,41 @@ function GroupBookingsTab() { if (loading) return ; return ( - <> - -
- {filtered.length === 0 && } - {filtered.map((b) => ( -
-
- {b.name} - - {b.phone} - - {b.groupInfo && ( - <> - · +
+ {bookings.length === 0 && } + {bookings.map((b) => ( +
+
+
+ {b.name} + + {b.phone} + + {b.instagram && ( + + {b.instagram} + + )} + {b.telegram && ( + + {b.telegram} + + )} + {b.groupInfo && ( {b.groupInfo} - - )} - {fmtDate(b.createdAt)} - handleDelete(b.id)} /> + )} +
+
+ {fmtDate(b.createdAt)} + handleDelete(b.id)} /> +
- handleToggle(b.id, "notified_confirm")} - onToggleReminder={() => handleToggle(b.id, "notified_reminder")} - />
))} -
- +
); } @@ -189,76 +113,31 @@ function GroupBookingsTab() { function McRegistrationsTab() { const [regs, setRegs] = useState([]); - const [mcItems, setMcItems] = useState([]); const [loading, setLoading] = useState(true); - const [filter, setFilter] = useState("all"); useEffect(() => { - Promise.all([ - adminFetch("/api/admin/mc-registrations").then((r) => r.json()), - adminFetch("/api/admin/sections/masterClasses").then((r) => r.json()), - ]) - .then(([regData, mcData]: [McRegistration[], { items: MasterClassItem[] }]) => { - setRegs(regData); - setMcItems(mcData.items || []); - }) + adminFetch("/api/admin/mc-registrations") + .then((r) => r.json()) + .then((data: McRegistration[]) => setRegs(data)) .catch(() => {}) .finally(() => setLoading(false)); }, []); - // Compute reminder urgency per MC title - const urgencyMap = useMemo(() => { - const map: Record = {}; - const now = Date.now(); - const twoDays = 2 * 24 * 60 * 60 * 1000; - for (const mc of mcItems) { - map[mc.title] = (mc.slots || []).some((s) => { - if (!s.date) return false; - const slotTime = new Date(s.date + "T" + (s.startTime || "23:59")).getTime(); - const diff = slotTime - now; - return diff >= 0 && diff <= twoDays; - }); - } - return map; - }, [mcItems]); - - const newCount = regs.filter((r) => !r.notifiedConfirm).length; - const noReminderCount = regs.filter((r) => !r.notifiedReminder).length; - - const filtered = useMemo(() => { - if (filter === "new") return regs.filter((r) => !r.notifiedConfirm); - if (filter === "no-reminder") return regs.filter((r) => !r.notifiedReminder); - return regs; - }, [regs, filter]); - // Group by MC title const grouped = useMemo(() => { const map: Record = {}; - for (const r of filtered) { + for (const r of regs) { if (!map[r.masterClassTitle]) map[r.masterClassTitle] = []; map[r.masterClassTitle].push(r); } return map; - }, [filtered]); + }, [regs]); const [expanded, setExpanded] = useState>({}); function toggleExpand(key: string) { setExpanded((prev) => ({ ...prev, [key]: !prev[key] })); } - async function handleToggle(id: number, field: "notified_confirm" | "notified_reminder") { - const r = regs.find((x) => x.id === id); - if (!r) return; - const key = field === "notified_confirm" ? "notifiedConfirm" : "notifiedReminder"; - const newValue = !r[key]; - setRegs((prev) => prev.map((x) => x.id === id ? { ...x, [key]: newValue } : x)); - await adminFetch("/api/admin/mc-registrations", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "toggle-notify", id, field, value: newValue }), - }); - } - async function handleDelete(id: number) { await adminFetch(`/api/admin/mc-registrations?id=${id}`, { method: "DELETE" }); setRegs((prev) => prev.filter((r) => r.id !== id)); @@ -267,74 +146,57 @@ function McRegistrationsTab() { if (loading) return ; return ( - <> - -
- {Object.keys(grouped).length === 0 && } - {Object.entries(grouped).map(([title, items]) => { - const isOpen = expanded[title] ?? false; - const groupNewCount = items.filter((r) => !r.notifiedConfirm).length; - return ( -
- - {isOpen && ( -
- {items.map((r) => ( -
+
+ {Object.keys(grouped).length === 0 && } + {Object.entries(grouped).map(([title, items]) => { + const isOpen = expanded[title] ?? false; + return ( +
+ + {isOpen && ( +
+ {items.map((r) => ( +
{r.name} - · - - - {r.instagram} - + {r.phone && ( + + {r.phone} + + )} + {r.instagram && ( + + {r.instagram} + + )} {r.telegram && ( - <> - · - - - {r.telegram} - - + + {r.telegram} + )} {fmtDate(r.createdAt)} handleDelete(r.id)} />
- handleToggle(r.id, "notified_confirm")} - onToggleReminder={() => handleToggle(r.id, "notified_reminder")} - />
))}
@@ -342,8 +204,7 @@ function McRegistrationsTab() {
); })} -
- +
); } @@ -351,12 +212,9 @@ function McRegistrationsTab() { function OpenDayBookingsTab() { const [bookings, setBookings] = useState([]); - const [eventDate, setEventDate] = useState(""); const [loading, setLoading] = useState(true); - const [filter, setFilter] = useState("all"); useEffect(() => { - // Get events to find active one adminFetch("/api/admin/open-day") .then((r) => r.json()) .then((events: { id: number; date: string }[]) => { @@ -365,7 +223,6 @@ function OpenDayBookingsTab() { return; } const ev = events[0]; - setEventDate(ev.date); return adminFetch(`/api/admin/open-day/bookings?eventId=${ev.id}`) .then((r) => r.json()) .then((data: OpenDayBooking[]) => setBookings(data)); @@ -374,28 +231,10 @@ function OpenDayBookingsTab() { .finally(() => setLoading(false)); }, []); - const reminderUrgent = useMemo(() => { - if (!eventDate) return false; - const now = Date.now(); - const twoDays = 2 * 24 * 60 * 60 * 1000; - const eventTime = new Date(eventDate + "T10:00").getTime(); - const diff = eventTime - now; - return diff >= 0 && diff <= twoDays; - }, [eventDate]); - - const newCount = bookings.filter((b) => !b.notifiedConfirm).length; - const noReminderCount = bookings.filter((b) => !b.notifiedReminder).length; - - const filtered = useMemo(() => { - if (filter === "new") return bookings.filter((b) => !b.notifiedConfirm); - if (filter === "no-reminder") return bookings.filter((b) => !b.notifiedReminder); - return bookings; - }, [bookings, filter]); - // Group by class — sorted by hall then time const grouped = useMemo(() => { const map: Record = {}; - for (const b of filtered) { + for (const b of bookings) { const key = `${b.classHall}|${b.classTime}|${b.classStyle}`; if (!map[key]) map[key] = { hall: b.classHall || "—", time: b.classTime || "—", style: b.classStyle || "—", trainer: b.classTrainer || "—", items: [] }; map[key].items.push(b); @@ -405,26 +244,13 @@ function OpenDayBookingsTab() { const hallCmp = a.hall.localeCompare(b.hall); return hallCmp !== 0 ? hallCmp : a.time.localeCompare(b.time); }); - }, [filtered]); + }, [bookings]); const [expanded, setExpanded] = useState>({}); function toggleExpand(key: string) { setExpanded((prev) => ({ ...prev, [key]: !prev[key] })); } - async function handleToggle(id: number, field: "notified_confirm" | "notified_reminder") { - const b = bookings.find((x) => x.id === id); - if (!b) return; - const key = field === "notified_confirm" ? "notifiedConfirm" : "notifiedReminder"; - const newValue = !b[key]; - setBookings((prev) => prev.map((x) => x.id === id ? { ...x, [key]: newValue } : x)); - await adminFetch("/api/admin/open-day/bookings", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "toggle-notify", id, field, value: newValue }), - }); - } - async function handleDelete(id: number) { await adminFetch(`/api/admin/open-day/bookings?id=${id}`, { method: "DELETE" }); setBookings((prev) => prev.filter((b) => b.id !== id)); @@ -433,38 +259,30 @@ function OpenDayBookingsTab() { if (loading) return ; return ( - <> - -
- {grouped.length === 0 && } - {grouped.map(([key, group]) => { - const isOpen = expanded[key] ?? false; - const groupNewCount = group.items.filter((b) => !b.notifiedConfirm).length; - return ( -
- - {isOpen && ( -
- {group.items.map((b) => ( -
+
+ {grouped.length === 0 && } + {grouped.map(([key, group]) => { + const isOpen = expanded[key] ?? false; + return ( +
+ + {isOpen && ( + @@ -507,8 +318,7 @@ function OpenDayBookingsTab() {
); })} -
- +
); }