Add multi-guest management: per-booking roster, docs, guest autocomplete

- Migration 010: booking_guests table (roster per booking with passport data)
- Backend: /bookings/:id/guests CRUD — auto-links/creates guest profiles by passport
- Backend: /hotel-settings GET/PATCH for key-value settings (require_guest_docs)
- BookingDetailPanel: Гости tab with multi-guest list, inline add/edit form,
  guest autocomplete (debounced search in guests table), child/main badges,
  passport fields for adults, scan stub, require-docs warning
- SettingsPage: toggle "Обязательное заполнение документов гостей"
- Pass slug prop through CalendarPage → BookingCalendar → BookingDetailPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 20:53:07 +03:00
parent de4fb2126b
commit 873a9a4fc4
10 changed files with 803 additions and 11 deletions

View File

@@ -444,6 +444,7 @@ export function BookingsPage() {
room={room(selected.roomId)}
rooms={rooms}
allBookings={bookings}
slug={slug}
onClose={() => setSelected(null)}
onUpdate={handleUpdateBooking}
onBulkUpdate={handleBulkUpdate}

View File

@@ -133,6 +133,7 @@ export function CalendarPage() {
<div className="flex flex-col h-full">
<div className="flex-1 overflow-hidden">
<BookingCalendar
slug={slug}
rooms={rooms}
bookings={bookings}
onBookingCreate={handleCreate}

View File

@@ -75,6 +75,28 @@ export function SettingsPage() {
// Booking / assignment settings
const [assignmentStrategy, setAssignmentStrategy] = useState<'spread' | 'together' | 'sequential' | 'manual'>('spread')
const [showBookingSource, setShowBookingSource] = useState(false)
const [requireGuestDocs, setRequireGuestDocs] = useState(false)
const [hotelSettingsLoaded, setHotelSettingsLoaded] = useState(false)
useEffect(() => {
if (!slug || hotelSettingsLoaded) return
api.hotelSettings.get(slug)
.then(s => {
setRequireGuestDocs(Boolean(s.require_guest_docs))
setHotelSettingsLoaded(true)
})
.catch(console.error)
}, [slug, hotelSettingsLoaded])
const toggleRequireGuestDocs = async () => {
const next = !requireGuestDocs
setRequireGuestDocs(next)
try {
await api.hotelSettings.update(slug, { require_guest_docs: next })
} catch {
setRequireGuestDocs(!next)
}
}
// Guest settings
const [guestTags, setGuestTags] = useState([
@@ -266,6 +288,17 @@ export function SettingsPage() {
<Toggle on={showBookingSource} onChange={() => setShowBookingSource(p => !p)} />
</div>
{/* Require guest docs toggle */}
<div className="flex items-center justify-between p-3 rounded-lg bg-slate-50 dark:bg-slate-700/40">
<div>
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">Обязательное заполнение документов гостей при заселении</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
Показывает предупреждение, если у совершеннолетних гостей не заполнены паспортные данные
</p>
</div>
<Toggle on={requireGuestDocs} onChange={toggleRequireGuestDocs} />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Стратегия автоматического расселения