From ae30be8f9d3969c31867c178cd7d4c3493b502fe Mon Sep 17 00:00:00 2001 From: "diana.dolgolyova" Date: Mon, 30 Mar 2026 22:57:36 +0300 Subject: [PATCH] fix: schedule status labels, Open Day halls, unsaved data guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schedule: - Status badges use admin config labels (not hardcoded text) everywhere - DayCard: level badge moved next to status badge - Single location: hide "Все студии" tab, auto-select the only hall - Group view: hide per-card address when all share same location - Filter tooltip z-index fixed (above dropdowns) - Trainer bio: status labels from config, not raw keys Open Day: - Hall name + address shown in schedule grid headers - Only one class card editable at a time (edit/create mutually exclusive) - Bigger action buttons (cancel/delete) on class cards - Create as empty draft (not pre-filled with published status) - Fix discount threshold input (allow delete to empty) - Skip auto-save during partial date input Admin: - SectionEditor: unsaved data guard (force-save before navigation) - Open Day + Team: same navigation guards - Contact: removed working hours field - TimeRangeField: allow end time hour changes - Schedule cards: visible borders, 90min default duration - Trainer bio: RichTextarea for description - Open Day: RichTextarea for description --- src/app/admin/_components/FormField.tsx | 2 +- src/app/admin/_components/SectionEditor.tsx | 37 ++++++ src/app/admin/contact/page.tsx | 6 - src/app/admin/open-day/page.tsx | 121 ++++++++++++------ src/app/admin/schedule/page.tsx | 21 +-- src/app/admin/team/[id]/page.tsx | 4 +- src/app/admin/team/page.tsx | 32 ++++- src/app/api/admin/open-day/route.ts | 4 +- src/app/page.tsx | 4 +- src/components/sections/Contact.tsx | 7 +- src/components/sections/OpenDay.tsx | 45 +++++-- src/components/sections/Schedule.tsx | 36 +++--- src/components/sections/Team.tsx | 4 +- src/components/sections/schedule/DayCard.tsx | 26 ++-- .../sections/schedule/GroupView.tsx | 23 ++-- .../sections/schedule/MobileSchedule.tsx | 19 ++- .../sections/schedule/ScheduleFilters.tsx | 2 +- src/components/sections/team/TeamProfile.tsx | 14 +- src/components/ui/GroupCard.tsx | 8 +- 19 files changed, 286 insertions(+), 129 deletions(-) diff --git a/src/app/admin/_components/FormField.tsx b/src/app/admin/_components/FormField.tsx index 9457f3e..6f5534e 100644 --- a/src/app/admin/_components/FormField.tsx +++ b/src/app/admin/_components/FormField.tsx @@ -585,7 +585,7 @@ export function TimeRangeField({ label, value, onChange, onBlur }: TimeRangeFiel } function handleEndChange(newEnd: string) { - if (start && newEnd && newEnd <= start) return; + // Always allow the change — validation handles the error display update(start, newEnd); } diff --git a/src/app/admin/_components/SectionEditor.tsx b/src/app/admin/_components/SectionEditor.tsx index d590c08..30632ff 100644 --- a/src/app/admin/_components/SectionEditor.tsx +++ b/src/app/admin/_components/SectionEditor.tsx @@ -28,6 +28,7 @@ export function SectionEditor({ const [error, setError] = useState(""); const timerRef = useRef | null>(null); const initialLoadRef = useRef(true); + const pendingSaveRef = useRef(false); useEffect(() => { adminFetch(`/api/admin/sections/${sectionKey}`) @@ -68,6 +69,7 @@ export function SectionEditor({ return; } + pendingSaveRef.current = true; if (timerRef.current) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { if (validate && !validate(data)) return; @@ -79,6 +81,41 @@ export function SectionEditor({ }; }, [data, save]); + // Clear pending flag after save completes + useEffect(() => { + if (status === "saved") pendingSaveRef.current = false; + }, [status]); + + // Warn before leaving with unsaved changes + useEffect(() => { + function onBeforeUnload(e: BeforeUnloadEvent) { + if (pendingSaveRef.current) e.preventDefault(); + } + function onLinkClick(e: MouseEvent) { + if (!pendingSaveRef.current) return; + const link = (e.target as HTMLElement).closest("a"); + if (!link || link.target === "_blank") return; + const href = link.getAttribute("href"); + if (!href || href.startsWith("#")) return; + // Force save immediately before navigating + if (timerRef.current) clearTimeout(timerRef.current); + if (data && (!validate || validate(data))) { + e.preventDefault(); + e.stopPropagation(); + save(data).then(() => { + pendingSaveRef.current = false; + window.location.href = href; + }); + } + } + window.addEventListener("beforeunload", onBeforeUnload); + document.addEventListener("click", onLinkClick, true); + return () => { + window.removeEventListener("beforeunload", onBeforeUnload); + document.removeEventListener("click", onLinkClick, true); + }; + }, [data, save, validate]); + if (loading) { return (
diff --git a/src/app/admin/contact/page.tsx b/src/app/admin/contact/page.tsx index d843399..5efe77d 100644 --- a/src/app/admin/contact/page.tsx +++ b/src/app/admin/contact/page.tsx @@ -230,12 +230,6 @@ export default function ContactEditorPage() { />
- update({ ...data, workingHours: v })} - /> - -
- -