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:
@@ -265,6 +265,16 @@ const migrations: Migration[] = [
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 15,
|
||||
name: "add_max_participants_to_open_day_events",
|
||||
up: (db) => {
|
||||
const cols = db.prepare("PRAGMA table_info(open_day_events)").all() as { name: string }[];
|
||||
if (!cols.some((c) => c.name === "max_participants")) {
|
||||
db.exec("ALTER TABLE open_day_events ADD COLUMN max_participants INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function runMigrations(db: Database.Database) {
|
||||
@@ -958,6 +968,7 @@ interface OpenDayEventRow {
|
||||
discount_price: number;
|
||||
discount_threshold: number;
|
||||
min_bookings: number;
|
||||
max_participants: number;
|
||||
active: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -972,6 +983,7 @@ export interface OpenDayEvent {
|
||||
discountPrice: number;
|
||||
discountThreshold: number;
|
||||
minBookings: number;
|
||||
maxParticipants: number;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
@@ -1051,6 +1063,7 @@ function mapEventRow(r: OpenDayEventRow): OpenDayEvent {
|
||||
discountPrice: r.discount_price,
|
||||
discountThreshold: r.discount_threshold,
|
||||
minBookings: r.min_bookings,
|
||||
maxParticipants: r.max_participants ?? 0,
|
||||
active: !!r.active,
|
||||
};
|
||||
}
|
||||
@@ -1160,6 +1173,7 @@ export function updateOpenDayEvent(
|
||||
discountPrice: number;
|
||||
discountThreshold: number;
|
||||
minBookings: number;
|
||||
maxParticipants: number;
|
||||
active: boolean;
|
||||
}>
|
||||
): void {
|
||||
@@ -1173,6 +1187,7 @@ export function updateOpenDayEvent(
|
||||
if (data.discountPrice !== undefined) { sets.push("discount_price = ?"); vals.push(data.discountPrice); }
|
||||
if (data.discountThreshold !== undefined) { sets.push("discount_threshold = ?"); vals.push(data.discountThreshold); }
|
||||
if (data.minBookings !== undefined) { sets.push("min_bookings = ?"); vals.push(data.minBookings); }
|
||||
if (data.maxParticipants !== undefined) { sets.push("max_participants = ?"); vals.push(data.maxParticipants); }
|
||||
if (data.active !== undefined) { sets.push("active = ?"); vals.push(data.active ? 1 : 0); }
|
||||
if (sets.length === 0) return;
|
||||
sets.push("updated_at = datetime('now')");
|
||||
@@ -1204,6 +1219,17 @@ export function addOpenDayClass(
|
||||
return result.lastInsertRowid as number;
|
||||
}
|
||||
|
||||
export function getOpenDayClassById(classId: number): OpenDayClass | null {
|
||||
const db = getDb();
|
||||
const row = db.prepare(
|
||||
`SELECT c.*, COALESCE(b.cnt, 0) as booking_count
|
||||
FROM open_day_classes c
|
||||
LEFT JOIN (SELECT class_id, COUNT(*) as cnt FROM open_day_bookings GROUP BY class_id) b ON b.class_id = c.id
|
||||
WHERE c.id = ?`
|
||||
).get(classId) as OpenDayClassRow | undefined;
|
||||
return row ? mapClassRow(row) : null;
|
||||
}
|
||||
|
||||
export function getOpenDayClasses(eventId: number): OpenDayClass[] {
|
||||
const db = getDb();
|
||||
const rows = db
|
||||
|
||||
Reference in New Issue
Block a user