ae30be8f9d
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
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { MapPin } from "lucide-react";
|
|
import { shortAddress } from "@/components/sections/schedule/constants";
|
|
import { ScheduleBadge } from "@/components/ui/ScheduleBadge";
|
|
|
|
export interface GroupCardSlot {
|
|
days: string[];
|
|
times: string[];
|
|
}
|
|
|
|
export interface GroupCardProps {
|
|
type: string;
|
|
level?: string;
|
|
recruiting?: boolean;
|
|
hasSlots?: boolean;
|
|
status?: string;
|
|
statusLabel?: string;
|
|
address?: string;
|
|
location?: string;
|
|
merged: GroupCardSlot[];
|
|
dotColor?: string;
|
|
/** Compact mode for small cards (e.g. trainer bio scroll row) */
|
|
compact?: boolean;
|
|
/** Show location badge */
|
|
showLocation?: boolean;
|
|
/** Extra badges (e.g. "Сегодня") rendered after level badge */
|
|
extraBadges?: React.ReactNode;
|
|
/** Click handler for type name (e.g. filter toggle in schedule) */
|
|
onTypeClick?: () => void;
|
|
/** Click handler for book button */
|
|
onBook?: () => void;
|
|
}
|
|
|
|
export function GroupCard({
|
|
type,
|
|
level,
|
|
recruiting,
|
|
hasSlots,
|
|
status,
|
|
statusLabel,
|
|
address,
|
|
location,
|
|
merged,
|
|
dotColor = "bg-gold",
|
|
compact = false,
|
|
showLocation = true,
|
|
extraBadges,
|
|
onTypeClick,
|
|
onBook,
|
|
}: GroupCardProps) {
|
|
const dot = compact ? "h-2 w-2" : "h-2.5 w-2.5";
|
|
const typeCls = compact ? "text-xs" : "text-sm";
|
|
const dayPad = compact ? "px-1.5 py-px text-[10px] min-w-[40px]" : "px-2 py-0.5 text-[11px] min-w-[52px]";
|
|
const timeCls = compact ? "text-xs" : "text-sm font-medium";
|
|
const locSize = compact ? "px-2 py-0.5 text-[9px]" : "px-2.5 py-0.5 text-[10px]";
|
|
const locIcon = compact ? 8 : 9;
|
|
|
|
const levelBadge = level ? (
|
|
<ScheduleBadge size={compact ? "sm" : "md"}>{level}</ScheduleBadge>
|
|
) : null;
|
|
|
|
const typeContent = (
|
|
<>
|
|
<span className={`${dot} shrink-0 rounded-full ${dotColor}`} />
|
|
<span className={`${typeCls} font-semibold text-white/90`}>{type}</span>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<div className="flex flex-col flex-1 gap-1.5">
|
|
{/* Type + address + level + status badges */}
|
|
<div className="flex items-center gap-1.5 flex-wrap">
|
|
{onTypeClick ? (
|
|
<button onClick={onTypeClick} className="flex items-center gap-1.5 cursor-pointer">
|
|
{typeContent}
|
|
</button>
|
|
) : (
|
|
<span className="inline-flex items-center gap-1.5">{typeContent}</span>
|
|
)}
|
|
{showLocation && (address || location) && (
|
|
<span className={`inline-flex items-center gap-1 rounded-full bg-gold/15 border border-gold/25 ${locSize} font-medium text-white`}>
|
|
<MapPin size={locIcon} className="text-gold" />
|
|
{shortAddress(address || location || "")}
|
|
</span>
|
|
)}
|
|
{status && (
|
|
<ScheduleBadge size={compact ? "sm" : "md"}>{statusLabel || status}</ScheduleBadge>
|
|
)}
|
|
{levelBadge}
|
|
{extraBadges}
|
|
</div>
|
|
|
|
{/* Schedule rows */}
|
|
<div className={compact ? "space-y-0.5" : "space-y-1"}>
|
|
{merged.map((m, i) => (
|
|
<div key={i} className="flex items-center gap-1.5">
|
|
<span className={`rounded-md bg-gold/10 ${dayPad} font-bold text-gold text-center`}>
|
|
{m.days.join(", ")}
|
|
</span>
|
|
<span className={`${timeCls} tabular-nums text-white/60`}>
|
|
{m.times.join(", ")}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Book button */}
|
|
{onBook && (
|
|
compact ? (
|
|
<button
|
|
onClick={onBook}
|
|
className="w-full mt-auto rounded-lg bg-gold/15 border border-gold/25 py-1.5 text-[11px] font-semibold text-gold hover:bg-gold/25 transition-colors cursor-pointer"
|
|
>
|
|
Записаться
|
|
</button>
|
|
) : null
|
|
)}
|
|
</div>
|
|
);
|
|
}
|