Fix booking creation: localStorage token, conflict detection, paid_amount
- Fix api.ts getToken/saveToken to read from localStorage (matching AuthContext) — was reading sessionStorage causing all API requests to fail auth until refresh - Fix session cleanup on 401 to use localStorage - Add isConflict/availableRooms helpers to BookingModal; show amber warning when selected room is occupied with clickable free room suggestions - Pass bookings prop to BookingModal via BookingCalendar - Add paid_amount to backend POST /bookings INSERT - Show alert() when booking creation fails so user sees the error - Pass paidAmount in CalendarPage handleCreate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,12 +59,27 @@ interface BookingModalProps {
|
||||
open: boolean
|
||||
draft: DraftBooking
|
||||
rooms: Room[]
|
||||
bookings?: Booking[]
|
||||
onClose: () => void
|
||||
onSave: (data: Partial<Booking>) => void
|
||||
existing?: Booking
|
||||
}
|
||||
|
||||
export function BookingModal({ open, draft, rooms, onClose, onSave, existing }: BookingModalProps) {
|
||||
function isConflict(bookings: Booking[], roomId: string, checkIn: string, checkOut: string, excludeId?: string) {
|
||||
if (!checkIn || !checkOut || checkIn >= checkOut) return false
|
||||
return bookings.some(b =>
|
||||
b.roomId === roomId &&
|
||||
b.id !== excludeId &&
|
||||
b.status !== 'cancelled' && b.status !== 'no_show' &&
|
||||
b.checkIn < checkOut && b.checkOut > checkIn,
|
||||
)
|
||||
}
|
||||
|
||||
function availableRooms(rooms: Room[], bookings: Booking[], checkIn: string, checkOut: string, excludeId?: string) {
|
||||
return rooms.filter(r => !isConflict(bookings, r.id, checkIn, checkOut, excludeId))
|
||||
}
|
||||
|
||||
export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSave, existing }: BookingModalProps) {
|
||||
const [form, setForm] = useState({
|
||||
roomId: existing?.roomId ?? draft.roomId,
|
||||
guestName: existing?.guestName ?? '',
|
||||
@@ -252,6 +267,37 @@ export function BookingModal({ open, draft, rooms, onClose, onSave, existing }:
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Conflict warning */}
|
||||
{!isHourly && form.roomId && form.checkIn && form.checkOut && form.checkIn < form.checkOut &&
|
||||
isConflict(bookings, form.roomId, form.checkIn, form.checkOut, existing?.id) && (() => {
|
||||
const free = availableRooms(rooms, bookings, form.checkIn, form.checkOut, existing?.id)
|
||||
return (
|
||||
<div className="mt-2 p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700">
|
||||
<div className="flex items-start gap-2 text-amber-800 dark:text-amber-300 text-xs mb-2">
|
||||
<AlertCircle size={13} className="shrink-0 mt-0.5" />
|
||||
<span>Номер занят на эти даты.</span>
|
||||
</div>
|
||||
{free.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<span className="text-xs text-amber-700 dark:text-amber-400 self-center">Свободны:</span>
|
||||
{free.map(r => (
|
||||
<button
|
||||
key={r.id}
|
||||
type="button"
|
||||
onClick={() => set('roomId', r.id)}
|
||||
className="text-xs px-2 py-0.5 rounded-md bg-white dark:bg-slate-700 border border-amber-300 dark:border-amber-600 text-amber-800 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/30 font-medium transition-colors"
|
||||
>
|
||||
№{r.number} ({r.type})
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-amber-700 dark:text-amber-400">Нет свободных номеров на эти даты.</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})()
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Guest name + email */}
|
||||
|
||||
@@ -651,6 +651,7 @@ export function BookingCalendar({ rooms, bookings, onBookingCreate, onBookingUpd
|
||||
open={true}
|
||||
draft={bookingModalDraft}
|
||||
rooms={rooms}
|
||||
bookings={bookings}
|
||||
onClose={() => {
|
||||
if (bookingModalDraft) onDraftCancel?.(bookingModalDraft.roomId)
|
||||
setBookingModalDraft(null)
|
||||
|
||||
Reference in New Issue
Block a user