diff --git a/src/app/admin/bookings/page.tsx b/src/app/admin/bookings/page.tsx index 6e5f5d3..8ac709c 100644 --- a/src/app/admin/bookings/page.tsx +++ b/src/app/admin/bookings/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useMemo, useCallback, useRef } from "react"; import { createPortal } from "react-dom"; import { Phone, Instagram, Send, ChevronDown, ChevronRight, Bell, CheckCircle2, XCircle, Clock, Star, Calendar, DoorOpen, X, Plus } from "lucide-react"; import { adminFetch } from "@/lib/csrf"; +import { MS_PER_DAY } from "@/lib/constants"; import { type BookingStatus, type BookingFilter, type SearchResult, BOOKING_STATUSES, SHORT_DAYS, fmtDate } from "./types"; import { LoadingSpinner, ContactLinks, BookingCard, StatusBadge, StatusActions, DeleteBtn } from "./BookingComponents"; import { GenericBookingsList } from "./GenericBookingsList"; @@ -40,6 +41,8 @@ function ConfirmModal({ open, bookingName, groupInfo, + existingDate, + existingGroup, allClasses, onConfirm, onClose, @@ -47,6 +50,8 @@ function ConfirmModal({ open: boolean; bookingName: string; groupInfo?: string; + existingDate?: string; + existingGroup?: string; allClasses: ScheduleClassInfo[]; onConfirm: (data: { group: string; date: string; comment?: string }) => void; onClose: () => void; @@ -59,10 +64,12 @@ function ConfirmModal({ useEffect(() => { if (!open) return; - setDate(""); setComment(""); - // Try to match groupInfo against schedule to pre-fill - if (groupInfo && allClasses.length > 0) { - const info = groupInfo.toLowerCase(); + const tomorrow = new Date(Date.now() + MS_PER_DAY).toISOString().split("T")[0]; + setDate(existingDate && existingDate.length === 10 ? existingDate : tomorrow); setComment(""); + // Try to match groupInfo or existingGroup against schedule to pre-fill + const matchText = existingGroup || groupInfo; + if (matchText && allClasses.length > 0) { + const info = matchText.toLowerCase(); // Score each class against groupInfo, pick best match let bestMatch: ScheduleClassInfo | null = null; let bestScore = 0; @@ -86,7 +93,7 @@ function ConfirmModal({ } } setHall(""); setTrainer(""); setGroup(""); - }, [open, groupInfo, allClasses]); + }, [open, groupInfo, existingDate, existingGroup, allClasses]); // Cascading options const halls = useMemo(() => [...new Set(allClasses.map((c) => c.hall))], [allClasses]); @@ -132,7 +139,8 @@ function ConfirmModal({ useEffect(() => { if (!open) initRef.current = false; }, [open]); // #11: Keyboard submit - const canSubmit = group && date; + const today = open ? new Date().toISOString().split("T")[0] : ""; + const canSubmit = group && date && date.length === 10 && date >= today; const handleSubmit = useCallback(() => { if (canSubmit) { const groupLabel = groups.find((g) => g.value === group)?.label || group; @@ -152,7 +160,6 @@ function ConfirmModal({ if (!open) return null; - const today = new Date().toISOString().split("T")[0]; const selectClass = "w-full rounded-lg border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-sm text-white outline-none focus:border-gold/40 [color-scheme:dark] disabled:opacity-30 disabled:cursor-not-allowed"; return createPortal( @@ -194,10 +201,14 @@ function ConfirmModal({ type="date" value={date} min={today} + max={new Date(Date.now() + MS_PER_DAY * 365).toISOString().split("T")[0]} disabled={!group} onChange={(e) => setDate(e.target.value)} - className={selectClass} + className={`${selectClass} ${date && (date < today || date.length !== 10) ? "!border-red-500/50" : ""}`} /> + {date && (date < today || date.length !== 10) && ( +

{date < today ? "Дата не может быть в прошлом" : "Неверный формат даты"}

+ )}
@@ -303,10 +314,15 @@ function GroupBookingsTab({ filter, onDataChange }: { filter: BookingFilter; onD <> {b.groupInfo && {b.groupInfo}} {(b.confirmedGroup || b.confirmedDate) && ( - + )} )} @@ -316,6 +332,8 @@ function GroupBookingsTab({ filter, onDataChange }: { filter: BookingFilter; onD open={confirmingId !== null} bookingName={confirmingBooking?.name ?? ""} groupInfo={confirmingBooking?.groupInfo} + existingDate={confirmingBooking?.confirmedDate} + existingGroup={confirmingBooking?.confirmedGroup} allClasses={allClasses} onClose={() => setConfirmingId(null)} onConfirm={handleConfirm} @@ -572,7 +590,7 @@ function DashboardSummary({ onNavigate }: { onNavigate: (tab: Tab) => void }) { useEffect(() => { const today = new Date().toISOString().split("T")[0]; - const tomorrow = new Date(Date.now() + 86400000).toISOString().split("T")[0]; + const tomorrow = new Date(Date.now() + MS_PER_DAY).toISOString().split("T")[0]; Promise.all([ adminFetch("/api/admin/group-bookings").then((r) => r.json()), diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 42f3a08..73badc5 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,5 +1,7 @@ import type { NavLink } from "@/types"; +export const MS_PER_DAY = 24 * 60 * 60 * 1000; + export const BRAND = { name: "BLACK HEART DANCE HOUSE", shortName: "Blackheart", diff --git a/src/lib/db.ts b/src/lib/db.ts index 0fa3700..c6d76a7 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,6 +1,7 @@ import Database from "better-sqlite3"; import path from "path"; import type { SiteContent, TeamMember, RichListItem, VictoryItem } from "@/types/content"; +import { MS_PER_DAY } from "@/lib/constants"; const DB_PATH = process.env.DATABASE_PATH || @@ -728,7 +729,7 @@ export function setGroupBookingStatus( if (status === "confirmed" && confirmation) { // Auto-set reminder to 'coming' only if confirmed for today/tomorrow const today = new Date().toISOString().split("T")[0]; - const tomorrow = new Date(Date.now() + 86400000).toISOString().split("T")[0]; + const tomorrow = new Date(Date.now() + MS_PER_DAY).toISOString().split("T")[0]; const reminderStatus = (confirmation.date === today || confirmation.date === tomorrow) ? "coming" : null; db.prepare( "UPDATE group_bookings SET status = ?, confirmed_date = ?, confirmed_group = ?, confirmed_comment = ?, notified_confirm = 1, reminder_status = ? WHERE id = ?"