fix: schedule status labels, Open Day halls, unsaved data guards
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
This commit is contained in:
@@ -32,11 +32,12 @@ export default function TeamEditorPage() {
|
||||
|
||||
// Auto-save section title with debounce (skip initial load)
|
||||
const titleChangeCount = useRef(0);
|
||||
const pendingSaveRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!titleLoadedRef.current) return;
|
||||
titleChangeCount.current++;
|
||||
// Skip the first change (initial load setting the value)
|
||||
if (titleChangeCount.current <= 1) return;
|
||||
pendingSaveRef.current = true;
|
||||
if (titleTimerRef.current) clearTimeout(titleTimerRef.current);
|
||||
titleTimerRef.current = setTimeout(async () => {
|
||||
const res = await adminFetch("/api/admin/sections/team", {
|
||||
@@ -45,11 +46,40 @@ export default function TeamEditorPage() {
|
||||
body: JSON.stringify({ title: sectionTitle }),
|
||||
});
|
||||
setSaveStatus(res.ok ? "saved" : "error");
|
||||
if (res.ok) pendingSaveRef.current = false;
|
||||
setTimeout(() => setSaveStatus("idle"), 2000);
|
||||
}, 800);
|
||||
return () => { if (titleTimerRef.current) clearTimeout(titleTimerRef.current); };
|
||||
}, [sectionTitle]);
|
||||
|
||||
// Warn before leaving with unsaved title
|
||||
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("#") || href.startsWith("/admin/team/")) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (titleTimerRef.current) clearTimeout(titleTimerRef.current);
|
||||
adminFetch("/api/admin/sections/team", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title: sectionTitle }),
|
||||
}).finally(() => { window.location.href = href; });
|
||||
}
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
document.addEventListener("click", onLinkClick, true);
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
document.removeEventListener("click", onLinkClick, true);
|
||||
};
|
||||
}, [sectionTitle]);
|
||||
|
||||
const saveOrder = useCallback(async (updated: Member[]) => {
|
||||
setMembers(updated);
|
||||
const res = await adminFetch("/api/admin/team/reorder", {
|
||||
|
||||
Reference in New Issue
Block a user