BookingModal: replace guestName with FIO+phone fields, remove passport tab

- Add lastName/firstName/middleName/phone fields to booking form
- Remove 'Документы гостя' tab entirely from BookingModal
- Auto-parse existing guestName into FIO parts when editing
- Add guestPhone to Booking type and propagate through CalendarPage/BookingsPage API calls
- Add middle_name to guests table (migration 011)
- Update guests backend route to include middle_name in all queries
- Add middleName to GuestApiType in api.ts
- Fix selectAcGuest in BookingDetailPanel to also fill middle_name
- Auto-open guest form pre-filled with booking FIO when no guests exist
- Add passport series+number lookup in BookingDetailPanel guest form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 21:15:37 +03:00
parent 873a9a4fc4
commit 84cfca500c
8 changed files with 127 additions and 259 deletions

View File

@@ -96,6 +96,17 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
setBgGuests(guests)
setRequireDocs(Boolean((settings as { require_guest_docs?: boolean }).require_guest_docs))
setBgLoaded(true)
// Auto-open add form pre-filled with booking FIO when no guests yet
if (guests.length === 0 && booking.status !== 'cancelled') {
const parts = (booking.guestName ?? '').split(' ')
setBgForm({
last_name: parts[0] ?? '',
first_name: parts[1] ?? '',
middle_name: parts.slice(2).join(' ') || undefined,
is_main: true,
})
setShowGuestForm(true)
}
})
.catch(console.error)
.finally(() => setBgLoading(false))
@@ -130,15 +141,34 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
...f,
first_name: g.firstName,
last_name: g.lastName,
middle_name: g.middleName ?? undefined,
birth_date: g.birthDate ?? undefined,
passport_series: g.passportSeries ?? undefined,
passport_number: g.passportNumber ?? undefined,
nationality: g.nationality ?? undefined,
}))
setAcQuery('')
setAcQuery(g.lastName)
setAcResults([])
}
const passportTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const handlePassportInput = (field: 'passport_series' | 'passport_number', val: string) => {
setBgForm(f => ({ ...f, [field]: val }))
if (passportTimerRef.current) clearTimeout(passportTimerRef.current)
const series = field === 'passport_series' ? val : (bgForm.passport_series ?? '')
const number = field === 'passport_number' ? val : (bgForm.passport_number ?? '')
const query = (series + number).trim()
if (query.length < 4 || !slug) return
passportTimerRef.current = setTimeout(() => {
api.guests.list(slug, query)
.then(results => {
if (results.length === 1) selectAcGuest(results[0])
})
.catch(() => {/* ignore */})
}, 400)
}
const openAddForm = () => {
setEditingBg(null)
setBgForm({ is_main: bgGuests.length === 0 })
@@ -704,11 +734,11 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
<div className="grid grid-cols-2 gap-2">
<div>
<label className={lbl}>Серия</label>
<input className={`${fld} font-mono uppercase`} value={bgForm.passport_series ?? ''} onChange={e => setBgForm(f => ({ ...f, passport_series: e.target.value.toUpperCase() }))} placeholder="4510" maxLength={10} />
<input className={`${fld} font-mono uppercase`} value={bgForm.passport_series ?? ''} onChange={e => handlePassportInput('passport_series', e.target.value.toUpperCase())} placeholder="4510" maxLength={10} />
</div>
<div>
<label className={lbl}>Номер</label>
<input className={`${fld} font-mono`} value={bgForm.passport_number ?? ''} onChange={e => setBgForm(f => ({ ...f, passport_number: e.target.value }))} placeholder="123456" maxLength={10} />
<input className={`${fld} font-mono`} value={bgForm.passport_number ?? ''} onChange={e => handlePassportInput('passport_number', e.target.value)} placeholder="123456" maxLength={10} />
</div>
</div>
<div>