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
+15 -11
View File
@@ -1,5 +1,5 @@
import { Clock, User, MapPin } from "lucide-react";
import { shortAddress } from "./constants";
import { shortAddress, findStatusConfig } from "./constants";
import { ScheduleBadge } from "@/components/ui/ScheduleBadge";
import type { ScheduleDayMerged, ScheduleClassWithLocation } from "./constants";
@@ -11,6 +11,7 @@ interface DayCardProps {
toggleFilterTrainer: (trainer: string | null) => void;
filterTypes: Set<string>;
toggleFilterType: (type: string) => void;
statuses?: { key: string; label: string; description: string }[];
}
function ClassRow({
@@ -20,6 +21,7 @@ function ClassRow({
toggleFilterTrainer,
filterTypes,
toggleFilterType,
statuses,
}: {
cls: ScheduleClassWithLocation;
typeDots: Record<string, string>;
@@ -27,19 +29,22 @@ function ClassRow({
toggleFilterTrainer: (trainer: string | null) => void;
filterTypes: Set<string>;
toggleFilterType: (type: string) => void;
statuses?: { key: string; label: string; description: string }[];
}) {
return (
<div className={`px-5 py-3.5 ${cls.hasSlots ? "bg-emerald-500/5" : cls.recruiting ? "bg-sky-500/5" : ""}`}>
<div className="px-5 py-3.5">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 text-sm text-neutral-500 dark:text-white/40">
<Clock size={13} />
<span className="font-semibold">{cls.time}</span>
</div>
{cls.hasSlots && <ScheduleBadge>есть места</ScheduleBadge>}
{cls.recruiting && <ScheduleBadge>набор</ScheduleBadge>}
{cls.status && cls.status !== "hasSlots" && cls.status !== "recruiting" && (
<ScheduleBadge>{cls.status}</ScheduleBadge>
)}
<div className="flex items-center gap-1.5">
{cls.status && (() => {
const cfg = findStatusConfig(statuses, cls.status);
return <ScheduleBadge>{cfg?.label || cls.status}</ScheduleBadge>;
})()}
{cls.level && <ScheduleBadge>{cls.level}</ScheduleBadge>}
</div>
</div>
<button
onClick={() => window.dispatchEvent(new CustomEvent("openTrainerProfile", { detail: cls.trainer }))}
@@ -56,13 +61,12 @@ function ClassRow({
<span className={`h-2 w-2 shrink-0 rounded-full ${typeDots[cls.type] ?? "bg-white/30"}`} />
<span className="text-xs text-neutral-500 dark:text-white/40">{cls.type}</span>
</button>
{cls.level && <ScheduleBadge>{cls.level}</ScheduleBadge>}
</div>
</div>
);
}
export function DayCard({ day, typeDots, showLocation, filterTrainerSet, toggleFilterTrainer, filterTypes, toggleFilterType }: DayCardProps) {
export function DayCard({ day, typeDots, showLocation, filterTrainerSet, toggleFilterTrainer, filterTypes, toggleFilterType, statuses }: DayCardProps) {
// Group classes by location when showLocation is true
const locationGroups = showLocation
? Array.from(
@@ -109,7 +113,7 @@ export function DayCard({ day, typeDots, showLocation, filterTrainerSet, toggleF
</div>
<div className="divide-y divide-neutral-100 dark:divide-white/[0.04]">
{classes.map((cls, i) => (
<ClassRow key={i} cls={cls} typeDots={typeDots} filterTrainerSet={filterTrainerSet} toggleFilterTrainer={toggleFilterTrainer} filterTypes={filterTypes} toggleFilterType={toggleFilterType} />
<ClassRow key={i} cls={cls} typeDots={typeDots} filterTrainerSet={filterTrainerSet} toggleFilterTrainer={toggleFilterTrainer} filterTypes={filterTypes} toggleFilterType={toggleFilterType} statuses={statuses} />
))}
</div>
</div>
@@ -119,7 +123,7 @@ export function DayCard({ day, typeDots, showLocation, filterTrainerSet, toggleF
// Single location — no sub-headers
<div className="divide-y divide-neutral-100 dark:divide-white/[0.04]">
{day.classes.map((cls, i) => (
<ClassRow key={i} cls={cls} typeDots={typeDots} filterTrainerSet={filterTrainerSet} toggleFilterTrainer={toggleFilterTrainer} filterTypes={filterTypes} toggleFilterType={toggleFilterType} />
<ClassRow key={i} cls={cls} typeDots={typeDots} filterTrainerSet={filterTrainerSet} toggleFilterTrainer={toggleFilterTrainer} filterTypes={filterTypes} toggleFilterType={toggleFilterType} statuses={statuses} />
))}
</div>
)}