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
+13 -8
View File
@@ -221,10 +221,10 @@ function ClassBlock({
? { backgroundImage: "repeating-linear-gradient(135deg, transparent, transparent 4px, rgba(239,68,68,0.35) 4px, rgba(239,68,68,0.35) 8px)" }
: {}),
}}
className={`absolute left-1 right-1 rounded-md border-l-3 px-2 py-0.5 text-left text-xs text-white cursor-grab active:cursor-grabbing overflow-hidden select-none ${colors} ${
className={`absolute left-1 right-1 rounded-md border border-white/20 border-l-3 px-2 py-0.5 text-left text-xs text-white cursor-grab active:cursor-grabbing overflow-hidden select-none ${colors} ${
isOverlapping ? "ring-2 ring-red-500 ring-offset-1 ring-offset-neutral-900" : ""
} ${isDragging ? "opacity-30" : "hover:opacity-90"}`}
title={`${cls.time}\n${cls.type}\n${cls.trainer}${cls.level ? ` (${cls.level})` : ""}`}
} ${isDragging ? "opacity-30" : "hover:opacity-90 hover:border-white/40"}`}
title={`${cls.time}\n${cls.type}\n${cls.trainer}${cls.level ? ` · ${cls.level}` : ""}${cls.status ? ` · ${cls.status}` : ""}`}
>
<div className="font-semibold truncate leading-tight">
{parts[0]?.trim()}{parts[1]?.trim()}
@@ -235,6 +235,11 @@ function ClassBlock({
{height > 48 && (
<div className="truncate text-white/70 leading-tight">{cls.trainer}</div>
)}
{height > 64 && (cls.level || cls.status) && (
<div className="truncate text-white/50 leading-tight text-[10px]">
{[cls.level, cls.status].filter(Boolean).join(" · ")}
</div>
)}
{isOverlapping && (
<div className="text-red-200 font-bold leading-tight"> Пересечение</div>
)}
@@ -876,10 +881,10 @@ function CalendarGrid({
const y = e.clientY - rect.top;
const rawMin = yToMinutes(y);
snapped = Math.round((rawMin - 30) / SNAP_MINUTES) * SNAP_MINUTES;
snapped = Math.max(HOUR_START * 60, Math.min(snapped, HOUR_END * 60 - 60));
snapped = Math.max(HOUR_START * 60, Math.min(snapped, HOUR_END * 60 - 90));
}
const startTime = formatMinutes(snapped);
const endTime = formatMinutes(snapped + 60);
const endTime = formatMinutes(snapped + 90);
setHover(null);
setNewClass({
@@ -1005,8 +1010,8 @@ function CalendarGrid({
{sortedDays.map((day, di) => {
const showHover = hover && hover.dayIndex === di && !drag && !newClass && !editingClass;
const hoverTop = showHover ? minutesToY(hover.startMin) : 0;
const hoverHeight = HOUR_HEIGHT; // 1 hour
const hoverEndMin = showHover ? hover.startMin + 60 : 0;
const hoverHeight = HOUR_HEIGHT * 1.5; // 1.5 hours
const hoverEndMin = showHover ? hover.startMin + 90 : 0;
return (
<div
@@ -1026,7 +1031,7 @@ function CalendarGrid({
const rawMin = yToMinutes(y);
// Snap to 15-min and offset so the block is centered on cursor
const snapped = Math.round((rawMin - 30) / SNAP_MINUTES) * SNAP_MINUTES;
const clamped = Math.max(HOUR_START * 60, Math.min(snapped, HOUR_END * 60 - 60));
const clamped = Math.max(HOUR_START * 60, Math.min(snapped, HOUR_END * 60 - 90));
setHover({ dayIndex: di, startMin: clamped });
}}
onMouseLeave={() => setHover(null)}