feat: min/max participants — shared ParticipantLimits component

- New ParticipantLimits component in FormField.tsx (reusable)
- Used in both Open Day settings and MC editor — identical layout
- Open Day: event-level min/max (DB migration 15)
- MC: per-event min/max (JSON fields)
- Public: waiting list when full, spots counter, amber success modal
This commit is contained in:
2026-03-24 22:11:10 +03:00
parent 4acc88c1ab
commit d08905ee93
13 changed files with 484 additions and 50 deletions

View File

@@ -93,6 +93,7 @@ export function OpenDay({ data }: OpenDayProps) {
<ClassCard
key={cls.id}
cls={cls}
maxParticipants={event.maxParticipants}
onSignup={setSignup}
/>
))}
@@ -137,9 +138,11 @@ export function OpenDay({ data }: OpenDayProps) {
function ClassCard({
cls,
maxParticipants = 0,
onSignup,
}: {
cls: OpenDayClass;
maxParticipants?: number;
onSignup: (info: { classId: number; label: string }) => void;
}) {
const label = `${cls.style} · ${cls.trainer} · ${cls.startTime}${cls.endTime}`;
@@ -161,8 +164,10 @@ function ClassCard({
);
}
const isFull = maxParticipants > 0 && cls.bookingCount >= maxParticipants;
return (
<div className="rounded-xl border border-white/10 bg-neutral-900 p-4 transition-all hover:border-gold/20">
<div className={`rounded-xl border p-4 transition-all ${isFull ? "border-white/5 bg-neutral-900/50" : "border-white/10 bg-neutral-900 hover:border-gold/20"}`}>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<span className="text-xs text-gold font-medium">{cls.startTime}{cls.endTime}</span>
@@ -171,12 +176,21 @@ function ClassCard({
<Users size={10} />
{cls.trainer}
</p>
{maxParticipants > 0 && (
<p className={`text-[10px] mt-1 ${isFull ? "text-amber-400" : "text-neutral-500"}`}>
{cls.bookingCount}/{maxParticipants} мест
</p>
)}
</div>
<button
onClick={() => onSignup({ classId: cls.id, label })}
className="shrink-0 rounded-full bg-gold/10 border border-gold/20 px-4 py-2 text-xs font-medium text-gold hover:bg-gold/20 transition-colors cursor-pointer"
className={`shrink-0 rounded-full px-4 py-2 text-xs font-medium transition-colors cursor-pointer ${
isFull
? "bg-amber-500/10 border border-amber-500/20 text-amber-400 hover:bg-amber-500/20"
: "bg-gold/10 border border-gold/20 text-gold hover:bg-gold/20"
}`}
>
Записаться
{isFull ? "Лист ожидания" : "Записаться"}
</button>
</div>
</div>