Rental: connect to API, improve cell UI; remove price from room labels

Backend:
- Migration 015_rental.sql: rental_objects + rental_bookings tables, seed demo data
- New routes: GET/POST/PATCH/DELETE rental-objects and rental-bookings per hotel
- Register rentalRoutes in app.ts

Frontend:
- api.ts: add rental.listObjects, listBookings, createBooking, updateBooking, deleteBooking
- CalendarPage: fetch rental objects + bookings from API instead of mock data
- BookingCalendar: rental cell UI redesigned — colored squares + working hours + time slots (matches design spec)
- BookingCalendar: remove base rate price from desktop room label column (irrelevant with dynamic pricing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 15:46:01 +03:00
parent 3f4d0dac4c
commit b48e50e62b
6 changed files with 396 additions and 43 deletions

View File

@@ -312,6 +312,29 @@ export const api = {
req<void>('DELETE', `/api/hotels/${slug}/guests/${id}`),
},
// ── Rental ────────────────────────────────────────────────────────────────
rental: {
listObjects: (slug: string) =>
req<RentalObjectApi[]>('GET', `/api/hotels/${slug}/rental-objects`),
listBookings: (slug: string, from?: string, to?: string) => {
const qs = new URLSearchParams()
if (from) qs.set('from', from)
if (to) qs.set('to', to)
const q = qs.toString()
return req<RentalBookingApi[]>('GET', `/api/hotels/${slug}/rental-bookings${q ? `?${q}` : ''}`)
},
createBooking: (slug: string, data: RentalBookingPayload) =>
req<RentalBookingApi>('POST', `/api/hotels/${slug}/rental-bookings`, data),
updateBooking: (slug: string, id: string, data: Partial<RentalBookingPayload>) =>
req<RentalBookingApi>('PATCH', `/api/hotels/${slug}/rental-bookings/${id}`, data),
deleteBooking: (slug: string, id: string) =>
req<void>('DELETE', `/api/hotels/${slug}/rental-bookings/${id}`),
},
// ── NetUP IPTV ────────────────────────────────────────────────────────────
netup: {
getSettings: (slug: string) =>
@@ -515,6 +538,56 @@ export interface GuestPayload {
rating?: number
}
export interface RentalObjectApi {
id: string
hotelId: string
name: string
icon: string
color: string
textColor: string
pricePerHour: number
pricePerDay: number
openHour: number
closeHour: number
maxHoursPerSlot: number | null
bufferMinutes: number
sortOrder: number
createdAt: string
}
export interface RentalBookingApi {
id: string
hotelId: string
objectId: string
date: string
isFullDay: boolean
startHour: number
endHour: number
guestName: string
guestPhone: string
linkedRoomId: string | null
totalAmount: number
paidAmount: number
status: 'confirmed' | 'cancelled'
notes: string | null
createdAt: string
}
export interface RentalBookingPayload {
object_id: string
date: string
is_full_day?: boolean
start_hour?: number
end_hour?: number
guest_name: string
guest_phone?: string
linked_room_id?: string
total_amount?: number
paid_amount?: number
status?: string
notes?: string
}
function toHotelPayload(h: HotelPayload): Record<string, unknown> {
const out: Record<string, unknown> = {}
if (h.name !== undefined) out.name = h.name