feat: booking panel upgrade — refactor, notes, search, manual add, polling
Phase 1 — Refactor: - Split monolith _shared.tsx into types.ts, BookingComponents, InlineNotes, GenericBookingsList, AddBookingModal, SearchBar (no more _ prefix) - All 3 tabs use GenericBookingsList — shared status workflow, filters, archive Phase 2 — Features: - DB migration 13: add notes column to all booking tables - Inline notes with amber highlight, auto-save 800ms debounce - Confirm modal comment saves to notes field - Manual add: 2 tabs (Занятие / Мероприятие), filters expired MCs, Open Day support - Search bar: cross-table search by name/phone - 10s polling for real-time updates (bookings page + sidebar badge) - Status change marks booking as seen (fixes unread count on reset) - Confirm modal stores human-readable group label instead of raw groupId - Confirmed group bookings appear in Reminders tab Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ const DB_PATH =
|
||||
|
||||
let _db: Database.Database | null = null;
|
||||
|
||||
function getDb(): Database.Database {
|
||||
export function getDb(): Database.Database {
|
||||
if (!_db) {
|
||||
_db = new Database(DB_PATH);
|
||||
_db.pragma("journal_mode = WAL");
|
||||
@@ -242,6 +242,18 @@ const migrations: Migration[] = [
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 13,
|
||||
name: "add_booking_notes",
|
||||
up: (db) => {
|
||||
for (const table of ["mc_registrations", "group_bookings", "open_day_bookings"]) {
|
||||
const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];
|
||||
if (!cols.some((c) => c.name === "notes")) {
|
||||
db.exec(`ALTER TABLE ${table} ADD COLUMN notes TEXT`);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function runMigrations(db: Database.Database) {
|
||||
@@ -524,6 +536,7 @@ interface McRegistrationRow {
|
||||
notified_reminder: number;
|
||||
reminder_status: string | null;
|
||||
status: string;
|
||||
notes: string | null;
|
||||
}
|
||||
|
||||
export interface McRegistration {
|
||||
@@ -538,6 +551,7 @@ export interface McRegistration {
|
||||
notifiedReminder: boolean;
|
||||
reminderStatus?: string;
|
||||
status: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export function addMcRegistration(
|
||||
@@ -588,12 +602,13 @@ function mapMcRow(r: McRegistrationRow): McRegistration {
|
||||
notifiedReminder: !!r.notified_reminder,
|
||||
reminderStatus: r.reminder_status ?? undefined,
|
||||
status: r.status || "new",
|
||||
notes: r.notes ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function setMcRegistrationStatus(id: number, status: string): void {
|
||||
const db = getDb();
|
||||
db.prepare("UPDATE mc_registrations SET status = ? WHERE id = ?").run(status, id);
|
||||
db.prepare("UPDATE mc_registrations SET status = ?, notified_confirm = 1 WHERE id = ?").run(status, id);
|
||||
}
|
||||
|
||||
export function updateMcRegistration(
|
||||
@@ -640,6 +655,7 @@ interface GroupBookingRow {
|
||||
confirmed_date: string | null;
|
||||
confirmed_group: string | null;
|
||||
confirmed_comment: string | null;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -659,6 +675,7 @@ export interface GroupBooking {
|
||||
confirmedDate?: string;
|
||||
confirmedGroup?: string;
|
||||
confirmedComment?: string;
|
||||
notes?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@@ -697,6 +714,7 @@ export function getGroupBookings(): GroupBooking[] {
|
||||
confirmedDate: r.confirmed_date ?? undefined,
|
||||
confirmedGroup: r.confirmed_group ?? undefined,
|
||||
confirmedComment: r.confirmed_comment ?? undefined,
|
||||
notes: r.notes ?? undefined,
|
||||
createdAt: r.created_at,
|
||||
}));
|
||||
}
|
||||
@@ -709,11 +727,11 @@ export function setGroupBookingStatus(
|
||||
const db = getDb();
|
||||
if (status === "confirmed" && confirmation) {
|
||||
db.prepare(
|
||||
"UPDATE group_bookings SET status = ?, confirmed_date = ?, confirmed_group = ?, confirmed_comment = ? WHERE id = ?"
|
||||
"UPDATE group_bookings SET status = ?, confirmed_date = ?, confirmed_group = ?, confirmed_comment = ?, notified_confirm = 1 WHERE id = ?"
|
||||
).run(status, confirmation.date, confirmation.group, confirmation.comment || null, id);
|
||||
} else {
|
||||
db.prepare(
|
||||
"UPDATE group_bookings SET status = ?, confirmed_date = NULL, confirmed_group = NULL, confirmed_comment = NULL WHERE id = ?"
|
||||
"UPDATE group_bookings SET status = ?, confirmed_date = NULL, confirmed_group = NULL, confirmed_comment = NULL, notified_confirm = 1 WHERE id = ?"
|
||||
).run(status, id);
|
||||
}
|
||||
}
|
||||
@@ -769,6 +787,15 @@ export function setReminderStatus(
|
||||
db.prepare(`UPDATE ${table} SET reminder_status = ? WHERE id = ?`).run(status, id);
|
||||
}
|
||||
|
||||
export function updateBookingNotes(
|
||||
table: "mc_registrations" | "group_bookings" | "open_day_bookings",
|
||||
id: number,
|
||||
notes: string
|
||||
): void {
|
||||
const db = getDb();
|
||||
db.prepare(`UPDATE ${table} SET notes = ? WHERE id = ?`).run(notes || null, id);
|
||||
}
|
||||
|
||||
export interface ReminderItem {
|
||||
id: number;
|
||||
type: "class" | "master-class" | "open-day";
|
||||
@@ -838,6 +865,27 @@ export function getUpcomingReminders(): ReminderItem[] {
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
// Group bookings — confirmed with date today/tomorrow
|
||||
try {
|
||||
const gbRows = db.prepare(
|
||||
"SELECT * FROM group_bookings WHERE status = 'confirmed' AND confirmed_date IN (?, ?)"
|
||||
).all(today, tomorrow) as GroupBookingRow[];
|
||||
for (const r of gbRows) {
|
||||
items.push({
|
||||
id: r.id,
|
||||
type: "class",
|
||||
table: "group_bookings",
|
||||
name: r.name,
|
||||
phone: r.phone ?? undefined,
|
||||
instagram: r.instagram ?? undefined,
|
||||
telegram: r.telegram ?? undefined,
|
||||
reminderStatus: r.reminder_status ?? undefined,
|
||||
eventLabel: r.confirmed_group || "Занятие",
|
||||
eventDate: r.confirmed_date!,
|
||||
});
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
// Open Day bookings — check event date
|
||||
try {
|
||||
const events = db.prepare(
|
||||
@@ -1007,7 +1055,7 @@ function mapClassRow(r: OpenDayClassRow): OpenDayClass {
|
||||
|
||||
export function setOpenDayBookingStatus(id: number, status: string): void {
|
||||
const db = getDb();
|
||||
db.prepare("UPDATE open_day_bookings SET status = ? WHERE id = ?").run(status, id);
|
||||
db.prepare("UPDATE open_day_bookings SET status = ?, notified_confirm = 1 WHERE id = ?").run(status, id);
|
||||
}
|
||||
|
||||
function mapBookingRow(r: OpenDayBookingRow): OpenDayBooking {
|
||||
|
||||
Reference in New Issue
Block a user