- {tab === "reminders" &&
}
+
+ {tab === "reminders" &&
}
{tab === "classes" &&
}
{tab === "master-classes" &&
}
{tab === "open-day" &&
}
@@ -933,7 +933,7 @@ function BookingsPageInner() {
setAddOpen(false)}
- onAdded={() => setRefreshKey((k) => k + 1)}
+ onAdded={() => { setRefreshKey((k) => k + 1); refreshDashboard(); }}
/>
);
diff --git a/src/app/admin/bookings/types.ts b/src/app/admin/bookings/types.ts
index 37210bb..85ef10d 100644
--- a/src/app/admin/bookings/types.ts
+++ b/src/app/admin/bookings/types.ts
@@ -36,7 +36,10 @@ export function countStatuses(items: { status: string }[]): Record
(items: T[]): T[] {
const order: Record = { new: 0, contacted: 1, confirmed: 2, declined: 3 };
- return [...items].sort((a, b) => (order[a.status] ?? 0) - (order[b.status] ?? 0));
+ const UNKNOWN_STATUS_ORDER = 99;
+ return [...items].sort((a, b) =>
+ (order[a.status] ?? UNKNOWN_STATUS_ORDER) - (order[b.status] ?? UNKNOWN_STATUS_ORDER)
+ );
}
export interface BookingGroup {
diff --git a/src/app/admin/master-classes/page.tsx b/src/app/admin/master-classes/page.tsx
index 1f7f68f..7e8535f 100644
--- a/src/app/admin/master-classes/page.tsx
+++ b/src/app/admin/master-classes/page.tsx
@@ -35,6 +35,7 @@ function PriceField({ label, value, onChange, placeholder }: { label: string; va
interface MasterClassesData {
title: string;
successMessage?: string;
+ waitingListText?: string;
items: MasterClassItem[];
}
diff --git a/src/app/api/master-class-register/route.ts b/src/app/api/master-class-register/route.ts
index 90d87e3..6a3de0e 100644
--- a/src/app/api/master-class-register/route.ts
+++ b/src/app/api/master-class-register/route.ts
@@ -38,7 +38,8 @@ export async function POST(request: Request) {
let isWaiting = false;
if (mcItem?.maxParticipants && mcItem.maxParticipants > 0) {
const currentRegs = getMcRegistrations(cleanTitle);
- isWaiting = currentRegs.length >= mcItem.maxParticipants;
+ const confirmedCount = currentRegs.filter((r) => r.status === "confirmed").length;
+ isWaiting = confirmedCount >= mcItem.maxParticipants;
}
const id = addMcRegistration(
@@ -46,7 +47,8 @@ export async function POST(request: Request) {
cleanName,
sanitizeHandle(instagram) ?? "",
sanitizeHandle(telegram),
- cleanPhone
+ cleanPhone,
+ isWaiting ? "Лист ожидания" : undefined
);
return NextResponse.json({ ok: true, id, isWaiting });
diff --git a/src/app/api/open-day-register/route.ts b/src/app/api/open-day-register/route.ts
index 6d63e0c..325ff88 100644
--- a/src/app/api/open-day-register/route.ts
+++ b/src/app/api/open-day-register/route.ts
@@ -4,6 +4,7 @@ import {
getPersonOpenDayBookings,
getOpenDayEvent,
getOpenDayClassById,
+ getConfirmedOpenDayBookingCount,
} from "@/lib/db";
import { checkRateLimit, getClientIp } from "@/lib/rateLimit";
import { sanitizeName, sanitizePhone, sanitizeHandle } from "@/lib/validation";
@@ -35,17 +36,18 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "Телефон обязателен" }, { status: 400 });
}
- // Check if class is full (event-level max) — if so, booking goes to waiting list
- const cls = getOpenDayClassById(classId);
+ // Check if class is full (event-level max, confirmed only) — if so, booking goes to waiting list
const event = getOpenDayEvent(eventId);
const maxP = event?.maxParticipants ?? 0;
- const isWaiting = maxP > 0 && cls ? cls.bookingCount >= maxP : false;
+ const confirmedCount = maxP > 0 ? getConfirmedOpenDayBookingCount(classId) : 0;
+ const isWaiting = maxP > 0 && confirmedCount >= maxP;
const id = addOpenDayBooking(classId, eventId, {
name: cleanName,
phone: cleanPhone,
instagram: sanitizeHandle(instagram),
telegram: sanitizeHandle(telegram),
+ notes: isWaiting ? "Лист ожидания" : undefined,
});
// Return total bookings for this person (for discount calculation)
diff --git a/src/lib/db.ts b/src/lib/db.ts
index 14d4320..f209c9d 100644
--- a/src/lib/db.ts
+++ b/src/lib/db.ts
@@ -593,15 +593,16 @@ export function addMcRegistration(
name: string,
instagram: string,
telegram?: string,
- phone?: string
+ phone?: string,
+ notes?: string
): number {
const db = getDb();
const result = db
.prepare(
- `INSERT INTO mc_registrations (master_class_title, name, instagram, telegram, phone)
- VALUES (?, ?, ?, ?, ?)`
+ `INSERT INTO mc_registrations (master_class_title, name, instagram, telegram, phone, notes)
+ VALUES (?, ?, ?, ?, ?, ?)`
)
- .run(masterClassTitle, name, instagram, telegram || null, phone || null);
+ .run(masterClassTitle, name, instagram, telegram || null, phone || null, notes || null);
return result.lastInsertRowid as number;
}
@@ -1103,6 +1104,14 @@ function mapClassRow(r: OpenDayClassRow): OpenDayClass {
};
}
+export function getConfirmedOpenDayBookingCount(classId: number): number {
+ const db = getDb();
+ const row = db.prepare(
+ "SELECT COUNT(*) as cnt FROM open_day_bookings WHERE class_id = ? AND status = 'confirmed'"
+ ).get(classId) as { cnt: number };
+ return row.cnt;
+}
+
export function setOpenDayBookingStatus(id: number, status: string): void {
const db = getDb();
db.prepare("UPDATE open_day_bookings SET status = ?, notified_confirm = 1 WHERE id = ?").run(status, id);
@@ -1297,15 +1306,15 @@ export function deleteOpenDayClass(id: number): void {
export function addOpenDayBooking(
classId: number,
eventId: number,
- data: { name: string; phone: string; instagram?: string; telegram?: string }
+ data: { name: string; phone: string; instagram?: string; telegram?: string; notes?: string }
): number {
const db = getDb();
const result = db
.prepare(
- `INSERT INTO open_day_bookings (class_id, event_id, name, phone, instagram, telegram)
- VALUES (?, ?, ?, ?, ?, ?)`
+ `INSERT INTO open_day_bookings (class_id, event_id, name, phone, instagram, telegram, notes)
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
)
- .run(classId, eventId, data.name, data.phone, data.instagram || null, data.telegram || null);
+ .run(classId, eventId, data.name, data.phone, data.instagram || null, data.telegram || null, data.notes || null);
return result.lastInsertRowid as number;
}