feat: BookingsPage rental tab — connect to API (replace mock data)

This commit is contained in:
2026-03-23 11:19:04 +03:00
parent 2475c76d45
commit b494224af0

View File

@@ -1,11 +1,11 @@
import { useState, useMemo, useEffect } from 'react'
import { Search, Plus, ArrowUp, ArrowDown, ArrowUpDown, Clock, Pencil, Loader2 } from 'lucide-react'
import { RENTAL_OBJECTS, MOCK_RENTAL_BOOKINGS } from '../data/rentalData'
import { useModules } from '../contexts/ModulesContext'
import { useAuth } from '../contexts/AuthContext'
import { api } from '../lib/api'
import type { RentalObjectApi, RentalBookingApi } from '../lib/api'
import type { Booking, BookingStatus, Room } from '../types'
import type { RentalBooking } from '../data/rentalData'
import type { RentalObject, RentalBooking } from '../data/rentalData'
import { RentalBookingModal } from '../components/rental/RentalBookingModal'
import { cn, BOOKING_STATUS_BADGE, BOOKING_STATUS_LABELS, SOURCE_COLORS, SOURCE_LABELS, formatCurrency, nightsCount } from '../lib/utils'
import { Badge } from '../components/ui/Badge'
@@ -37,6 +37,31 @@ const COLUMNS: { key: SortKey | null; label: string }[] = [
type Tab = 'rooms' | 'rental'
function fromObjApi(o: RentalObjectApi): RentalObject {
return {
id: o.id, name: o.name, icon: o.icon, color: o.color,
textColor: o.textColor, pricePerHour: o.pricePerHour, pricePerDay: o.pricePerDay,
openHour: o.openHour, closeHour: o.closeHour,
maxHoursPerSlot: o.maxHoursPerSlot ?? undefined,
bufferMinutes: o.bufferMinutes ?? undefined,
}
}
function fromBookingApi(b: RentalBookingApi): RentalBooking {
return {
id: b.id, objectId: b.objectId, date: (b.date as string).slice(0, 10),
isFullDay: b.isFullDay, startHour: b.startHour, endHour: b.endHour,
guestName: b.guestName, guestPhone: b.guestPhone ?? '',
linkedRoomId: b.linkedRoomId ?? undefined,
totalAmount: b.totalAmount, paidAmount: b.paidAmount,
status: b.status, notes: b.notes ?? undefined,
}
}
function fmtMin(m: number) {
return `${Math.floor(m / 60).toString().padStart(2, '0')}:${(m % 60).toString().padStart(2, '0')}`
}
export function BookingsPage() {
const { user } = useAuth()
const slug = user?.hotelSlug ?? ''
@@ -47,11 +72,12 @@ export function BookingsPage() {
const [rooms, setRooms] = useState<Room[]>([])
const [bookings, setBookings] = useState<Booking[]>([])
const [loading, setLoading] = useState(true)
const [rentalBookings, setRentalBookings] = useState<RentalBooking[]>(MOCK_RENTAL_BOOKINGS)
const [rentalObjects, setRentalObjects] = useState<RentalObject[]>([])
const [rentalBookings, setRentalBookings] = useState<RentalBooking[]>([])
const [newRentalStep, setNewRentalStep] = useState<'idle' | 'pick'>('idle')
const [rentalPickObj, setRentalPickObj] = useState(RENTAL_OBJECTS[0]?.id ?? '')
const [rentalPickObj, setRentalPickObj] = useState('')
const [rentalPickDate, setRentalPickDate] = useState(format(new Date(), 'yyyy-MM-dd'))
const [showRentalModal, setShowRentalModal] = useState<{ obj: typeof RENTAL_OBJECTS[0]; date: string; editBooking?: RentalBooking } | null>(null)
const [showRentalModal, setShowRentalModal] = useState<{ obj: RentalObject; date: string; editBooking?: RentalBooking } | null>(null)
const [search, setSearch] = useState('')
const [statusFilter, setStatusFilter] = useState<BookingStatus | 'all'>('all')
const [selected, setSelected] = useState<Booking | null>(null)
@@ -61,8 +87,20 @@ export function BookingsPage() {
useEffect(() => {
if (!slug) return
Promise.all([api.rooms.list(slug), api.bookings.list(slug)])
.then(([r, b]) => { setRooms(r); setBookings(b) })
Promise.all([
api.rooms.list(slug),
api.bookings.list(slug),
api.rental.listObjects(slug),
api.rental.listBookings(slug),
])
.then(([r, b, objs, rbks]) => {
setRooms(r)
setBookings(b)
const convertedObjs = objs.map(fromObjApi)
setRentalObjects(convertedObjs)
setRentalBookings(rbks.map(fromBookingApi))
setRentalPickObj(convertedObjs[0]?.id ?? '')
})
.catch(console.error)
.finally(() => setLoading(false))
}, [slug])
@@ -103,7 +141,7 @@ export function BookingsPage() {
})
const room = (id: string) => rooms.find(r => r.id === id)
const rentalObj = (id: string) => RENTAL_OBJECTS.find(o => o.id === id)
const rentalObj = (id: string) => rentalObjects.find(o => o.id === id)
const handleCreateBooking = async (data: Partial<Booking>) => {
try {
@@ -384,7 +422,7 @@ export function BookingsPage() {
<td className="px-4 py-3 text-slate-700 dark:text-slate-300">
{b.isFullDay
? <span className="text-xs font-medium px-2 py-0.5 bg-slate-100 dark:bg-slate-700 rounded-full">Весь день</span>
: `${b.startHour}:00 ${b.endHour}:00`
: `${fmtMin(b.startHour)} ${fmtMin(b.endHour)}`
}
</td>
<td className="px-4 py-3 font-semibold text-slate-900 dark:text-slate-100">
@@ -459,7 +497,7 @@ export function BookingsPage() {
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">Объект</label>
<select className="input" value={rentalPickObj} onChange={e => setRentalPickObj(e.target.value)}>
{RENTAL_OBJECTS.map(o => (
{rentalObjects.map(o => (
<option key={o.id} value={o.id}>{o.icon} {o.name}</option>
))}
</select>
@@ -473,7 +511,7 @@ export function BookingsPage() {
<button
className="btn-primary"
onClick={() => {
const obj = RENTAL_OBJECTS.find(o => o.id === rentalPickObj)
const obj = rentalObjects.find(o => o.id === rentalPickObj)
if (obj) { setShowRentalModal({ obj, date: rentalPickDate }); setNewRentalStep('idle') }
}}
>
@@ -492,11 +530,22 @@ export function BookingsPage() {
existingBookings={rentalBookings.filter(b => b.objectId === showRentalModal.obj.id && b.date === showRentalModal.date)}
editBooking={showRentalModal.editBooking}
onClose={() => setShowRentalModal(null)}
onSave={(b) => {
setRentalBookings(prev => {
const exists = prev.find(x => x.id === b.id)
return exists ? prev.map(x => x.id === b.id ? b : x) : [...prev, b]
})
onSave={async (b) => {
if (!slug) return
const payload = {
object_id: b.objectId, date: b.date, is_full_day: b.isFullDay,
start_hour: b.startHour, end_hour: b.endHour,
guest_name: b.guestName, guest_phone: b.guestPhone || undefined,
linked_room_id: b.linkedRoomId || undefined,
total_amount: b.totalAmount, paid_amount: b.paidAmount ?? 0,
status: b.status, notes: b.notes || undefined,
}
const isNew = !rentalBookings.find(x => x.id === b.id)
const saved = isNew
? await api.rental.createBooking(slug, payload)
: await api.rental.updateBooking(slug, b.id, payload)
const converted = fromBookingApi(saved)
setRentalBookings(prev => isNew ? [...prev, converted] : prev.map(x => x.id === b.id ? converted : x))
setShowRentalModal(null)
}}
/>