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:
@@ -317,6 +317,15 @@ export const api = {
|
||||
listObjects: (slug: string) =>
|
||||
req<RentalObjectApi[]>('GET', `/api/hotels/${slug}/rental-objects`),
|
||||
|
||||
createObject: (slug: string, data: RentalObjectPayload) =>
|
||||
req<RentalObjectApi>('POST', `/api/hotels/${slug}/rental-objects`, data),
|
||||
|
||||
updateObject: (slug: string, id: string, data: Partial<RentalObjectPayload>) =>
|
||||
req<RentalObjectApi>('PATCH', `/api/hotels/${slug}/rental-objects/${id}`, data),
|
||||
|
||||
deleteObject: (slug: string, id: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/rental-objects/${id}`),
|
||||
|
||||
listBookings: (slug: string, from?: string, to?: string) => {
|
||||
const qs = new URLSearchParams()
|
||||
if (from) qs.set('from', from)
|
||||
@@ -591,6 +600,20 @@ export interface RentalBookingPayload {
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface RentalObjectPayload {
|
||||
name: string
|
||||
icon?: string
|
||||
color?: string
|
||||
text_color?: string
|
||||
price_per_hour?: number
|
||||
price_per_day?: number
|
||||
open_hour?: number
|
||||
close_hour?: number
|
||||
max_hours_per_slot?: number | null
|
||||
buffer_minutes?: number
|
||||
sort_order?: number
|
||||
}
|
||||
|
||||
function toHotelPayload(h: HotelPayload): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {}
|
||||
if (h.name !== undefined) out.name = h.name
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user