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:
2026-03-30 22:57:36 +03:00
parent 06be6b48ce
commit ae30be8f9d
19 changed files with 286 additions and 129 deletions
+37 -8
View File
@@ -2,12 +2,13 @@
import { useState, useMemo } from "react";
import Image from "next/image";
import { Calendar, Sparkles, User } from "lucide-react";
import { Calendar, Sparkles, User, MapPin } from "lucide-react";
import { SectionHeading } from "@/components/ui/SectionHeading";
import { Reveal } from "@/components/ui/Reveal";
import { SignupModal } from "@/components/ui/SignupModal";
import type { OpenDayEvent, OpenDayClass } from "@/lib/openDay";
import type { SiteContent } from "@/types";
import type { SiteContent, ScheduleLocation } from "@/types";
import { formatMarkup } from "@/lib/markup";
interface OpenDayProps {
data: {
@@ -16,6 +17,7 @@ interface OpenDayProps {
};
popups?: SiteContent["popups"];
teamMembers?: { name: string; image: string }[];
locations?: ScheduleLocation[];
}
function formatDateRu(dateStr: string): string {
@@ -27,7 +29,7 @@ function formatDateRu(dateStr: string): string {
});
}
export function OpenDay({ data, popups, teamMembers }: OpenDayProps) {
export function OpenDay({ data, popups, teamMembers, locations }: OpenDayProps) {
const { event, classes } = data;
const [signup, setSignup] = useState<{ classId: number; label: string } | null>(null);
@@ -57,6 +59,17 @@ export function OpenDay({ data, popups, teamMembers }: OpenDayProps) {
const halls = Object.keys(hallGroups);
// Map hall name → address from schedule locations
const hallAddress = useMemo(() => {
const map: Record<string, string> = {};
if (locations) {
for (const loc of locations) {
if (loc.name && loc.address) map[loc.name] = loc.address;
}
}
return map;
}, [locations]);
if (classes.length === 0) return null;
return (
@@ -93,9 +106,9 @@ export function OpenDay({ data, popups, teamMembers }: OpenDayProps) {
{event.description && (
<Reveal>
<p className="mt-4 text-center text-sm text-neutral-400 max-w-2xl mx-auto">
{event.description}
</p>
<div className="mt-4 text-center text-sm text-neutral-400 max-w-2xl mx-auto">
{formatMarkup(event.description)}
</div>
</Reveal>
)}
@@ -105,7 +118,15 @@ export function OpenDay({ data, popups, teamMembers }: OpenDayProps) {
// Single hall — simple list
<Reveal>
<div className="max-w-lg mx-auto space-y-3">
<h3 className="text-sm font-medium text-neutral-400 text-center">{halls[0]}</h3>
<div className="text-center mb-4">
<h3 className="text-base font-semibold text-white">{halls[0]}</h3>
{hallAddress[halls[0]] && (
<p className="text-sm text-gold/70 mt-0.5 flex items-center justify-center gap-1.5">
<MapPin size={13} />
{hallAddress[halls[0]]}
</p>
)}
</div>
{hallGroups[halls[0]].map((cls) => (
<ClassCard
key={cls.id}
@@ -123,7 +144,15 @@ export function OpenDay({ data, popups, teamMembers }: OpenDayProps) {
{halls.map((hall) => (
<Reveal key={hall}>
<div>
<h3 className="text-sm font-medium text-neutral-400 mb-3 text-center">{hall}</h3>
<div className="text-center mb-4 rounded-lg bg-white/[0.03] border border-white/[0.06] py-3 px-4">
<h3 className="text-base font-semibold text-white">{hall}</h3>
{hallAddress[hall] && (
<p className="text-sm text-gold/70 mt-0.5 flex items-center justify-center gap-1.5">
<MapPin size={13} />
{hallAddress[hall]}
</p>
)}
</div>
<div className="space-y-3">
{hallGroups[hall].map((cls) => (
<ClassCard