feat: BookingsPage rental tab — connect to API (replace mock data)
This commit is contained in:
@@ -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)
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user