feat: connect RentalPage to API (objects + bookings persisted in DB)

- Add createObject/updateObject/deleteObject to api.rental
- Add RentalObjectPayload interface
- RentalPage now loads objects and bookings from API on mount
- Save/delete object operations call the API instead of only local state
- Save booking operations call the API (create or update)
- bufferMinutes from DB is now correctly passed to booking modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 22:23:16 +03:00
parent 3961585196
commit fe4b9aa6eb
2 changed files with 103 additions and 16 deletions

View File

@@ -1,12 +1,14 @@
import { useState } from 'react'
import { Plus, Pencil, Trash2, Clock, CalendarDays, X, Save } from 'lucide-react'
import { useState, useEffect } from 'react'
import { Plus, Pencil, Trash2, CalendarDays, X, Save } from 'lucide-react'
import { format } from 'date-fns'
import { ru } from 'date-fns/locale'
import { cn, formatCurrency } from '../lib/utils'
import { Badge } from '../components/ui/Badge'
import { RENTAL_OBJECTS, MOCK_RENTAL_BOOKINGS } from '../data/rentalData'
import type { RentalObject, RentalBooking } from '../data/rentalData'
import { RentalBookingModal } from '../components/rental/RentalBookingModal'
import { api } from '../lib/api'
import type { RentalObjectApi, RentalBookingApi } from '../lib/api'
import { useAuth } from '../contexts/AuthContext'
// ── Object form ───────────────────────────────────────────────────────────────
@@ -257,9 +259,34 @@ function ObjectFormModal({
// ── Main page ─────────────────────────────────────────────────────────────────
// Convert API response to local type
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,
}
}
export function RentalPage() {
const [objects, setObjects] = useState<RentalObject[]>(RENTAL_OBJECTS)
const [bookings, setBookings] = useState<RentalBooking[]>(MOCK_RENTAL_BOOKINGS)
const { user } = useAuth()
const slug = user?.hotelSlug ?? ''
const [objects, setObjects] = useState<RentalObject[]>([])
const [bookings, setBookings] = useState<RentalBooking[]>([])
const [editObj, setEditObj] = useState<RentalObject | null>(null)
const [createModal, setCreateModal] = useState(false)
const [selectedObj, setSelectedObj] = useState<RentalObject | null>(null)
@@ -267,25 +294,62 @@ export function RentalPage() {
const today = format(new Date(), 'yyyy-MM-dd')
const handleSaveObject = (obj: RentalObject) => {
setObjects(prev => {
const exists = prev.find(o => o.id === obj.id)
return exists ? prev.map(o => o.id === obj.id ? obj : o) : [...prev, obj]
})
// Load from API on mount
useEffect(() => {
if (!slug) return
Promise.all([
api.rental.listObjects(slug),
api.rental.listBookings(slug),
]).then(([objs, bks]) => {
setObjects(objs.map(fromObjApi))
setBookings(bks.map(fromBookingApi))
}).catch(console.error)
}, [slug])
const handleSaveObject = async (obj: RentalObject) => {
if (!slug) return
const payload = {
name: obj.name, icon: obj.icon, color: obj.color, text_color: obj.textColor,
price_per_hour: obj.pricePerHour, price_per_day: obj.pricePerDay,
open_hour: obj.openHour, close_hour: obj.closeHour,
max_hours_per_slot: obj.maxHoursPerSlot ?? null,
buffer_minutes: obj.bufferMinutes ?? 0,
}
const isNew = !objects.find(o => o.id === obj.id)
const saved = isNew
? await api.rental.createObject(slug, payload)
: await api.rental.updateObject(slug, obj.id, payload)
const converted = fromObjApi(saved)
setObjects(prev => isNew ? [...prev, converted] : prev.map(o => o.id === obj.id ? converted : o))
// Update selectedObj if it was the edited one
setSelectedObj(prev => prev?.id === obj.id ? converted : prev)
setEditObj(null)
setCreateModal(false)
}
const handleDeleteObject = (id: string) => {
const handleDeleteObject = async (id: string) => {
if (!slug) return
await api.rental.deleteObject(slug, id)
setObjects(prev => prev.filter(o => o.id !== id))
if (selectedObj?.id === id) setSelectedObj(null)
}
const handleBookingSave = (b: RentalBooking) => {
setBookings(prev => {
const exists = prev.find(x => x.id === b.id)
return exists ? prev.map(x => x.id === b.id ? b : x) : [...prev, b]
})
const handleBookingSave = async (b: RentalBooking) => {
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 = !bookings.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)
setBookings(prev => isNew ? [...prev, converted] : prev.map(x => x.id === b.id ? converted : x))
setRentalModal(null)
}