feat: full Room Service backend integration

- Migrations 086-088: room_service_settings, menu_items, orders+order_items
- Backend: /api/hotels/:slug/room-service/* routes (settings, menu CRUD,
  orders CRUD, public config, public order status)
- push.ts: sendPushToHotel() — push to all hotel staff on new order
- On new order: in-app notification + push to hotel staff
- api.ts: RoomService types + roomService API methods
- RoomServicePage: replaced mocked state with real API, 30s polling,
  add/edit/delete menu items modal, real advance/cancel order
- GuestRoomServicePage: fetches real menu + config, uses hotel payment
  methods, submits real order, polls status every 15s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 11:44:28 +03:00
parent 0e2180e3bc
commit 1975663cdd
9 changed files with 1169 additions and 418 deletions

View File

@@ -982,6 +982,118 @@ export const api = {
return json as { bookingId: string; status: string }
}),
},
// ── Room Service ──────────────────────────────────────────────────────────
roomService: {
getPublicConfig: (slug: string) =>
req<RoomServicePublicConfig>('GET', `/api/hotels/${slug}/room-service/public-config`),
getMenu: (slug: string) =>
req<RoomServiceMenuItem[]>('GET', `/api/hotels/${slug}/room-service/menu`),
getMenuAll: (slug: string) =>
req<RoomServiceMenuItem[]>('GET', `/api/hotels/${slug}/room-service/menu/all`),
createMenuItem: (slug: string, data: {
name: string; category?: string; price: number; emoji?: string
prepTimeMin?: number; isPopular?: boolean; sortOrder?: number
}) =>
req<RoomServiceMenuItem>('POST', `/api/hotels/${slug}/room-service/menu`, data),
updateMenuItem: (slug: string, itemId: string, data: Partial<{
name: string; category: string; price: number; emoji: string
prepTimeMin: number; isPopular: boolean; isStopList: boolean
isActive: boolean; sortOrder: number
}>) =>
req<RoomServiceMenuItem>('PATCH', `/api/hotels/${slug}/room-service/menu/${itemId}`, data),
deleteMenuItem: (slug: string, itemId: string) =>
req<void>('DELETE', `/api/hotels/${slug}/room-service/menu/${itemId}`),
getSettings: (slug: string) =>
req<RoomServiceSettings>('GET', `/api/hotels/${slug}/room-service/settings`),
updateSettings: (slug: string, data: { isActive?: boolean; serviceChargePct?: number }) =>
req<RoomServiceSettings>('PATCH', `/api/hotels/${slug}/room-service/settings`, data),
listOrders: (slug: string, params?: { status?: string; date?: string }) => {
const q = new URLSearchParams()
if (params?.status) q.set('status', params.status)
if (params?.date) q.set('date', params.date)
const qs = q.toString() ? `?${q.toString()}` : ''
return req<RoomServiceOrder[]>('GET', `/api/hotels/${slug}/room-service/orders${qs}`)
},
createOrder: (slug: string, data: {
roomNumber: string; guestName?: string; orderType?: 'room' | 'restaurant'
scheduledTime?: string; paymentMethod?: string; notes?: string
items: Array<{ menuItemId: string; quantity: number }>
}) =>
req<RoomServiceOrder>('POST', `/api/hotels/${slug}/room-service/orders`, data),
updateOrder: (slug: string, orderId: string, data: { status?: string; paymentMethod?: string }) =>
req<RoomServiceOrder>('PATCH', `/api/hotels/${slug}/room-service/orders/${orderId}`, data),
},
}
// ── Room Service types ────────────────────────────────────────────────────────
export interface RoomServicePublicConfig {
hotelName: string
hotelSlug: string
serviceChargePct: number
paymentMethods: Array<{ id: string; name: string; type: string }>
}
export interface RoomServiceMenuItem {
id: string
hotelId: string
name: string
category: string
price: number
emoji: string
prepTimeMin: number
isPopular: boolean
isStopList: boolean
isActive: boolean
sortOrder: number
createdAt: string
}
export interface RoomServiceSettings {
hotelId: string
isActive: boolean
serviceChargePct: number
updatedAt: string
}
export type RoomServiceOrderStatus = 'new' | 'preparing' | 'ready' | 'delivered' | 'cancelled'
export interface RoomServiceOrderItem {
id: string
menuItemId: string | null
name: string
emoji: string
price: number
quantity: number
}
export interface RoomServiceOrder {
id: string
hotelId: string
roomNumber: string
guestName: string
orderType: 'room' | 'restaurant'
scheduledTime: string | null
status: RoomServiceOrderStatus
paymentMethod: string | null
subtotal: number
serviceCharge: number
total: number
notes: string | null
items: RoomServiceOrderItem[]
createdAt: string
updatedAt: string
}
// ── Schedule ─────────────────────────────────────────────────────────────────