Connect ChannelsPage, FloorMapPage, SettingsPage, UsersPage to real API
- ChannelsPage: load from API, toggle/sync call real endpoints; normalize enabled→isEnabled, lastSyncedAt→lastSyncAt, add displayName/mappings defaults - FloorMapPage: load rooms+bookings from API; create booking via API - SettingsPage: load hotel via GET /api/hotels/:slug; save general section via PATCH (name, address, timezone, currency, check_in/out times) - UsersPage: load users from API; create/update/delete via API; map backend User (name/role) → StaffUser (firstName/lastName/StaffRole) - api.ts: add hotels.get/update, users.delete, channel normalization, HotelPayload/toHotelPayload - types/index.ts: extend Hotel with phone/checkInTime/checkOutTime/optional fields; add User.createdAt/updatedAt - backend/routes/hotels.ts: extend PATCH to allow address/phone/check_in_time/check_out_time - backend/migrations/004_hotels_contacts.sql: add address/phone/check_in/out_time to hotels Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { Room, Booking, HousekeepingTask, Channel, User } from '../types'
|
||||
import type { Room, Booking, HousekeepingTask, Channel, ChannelName, Hotel, User } from '../types'
|
||||
|
||||
// ── Base URL ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -104,6 +104,34 @@ async function req<T>(
|
||||
return transformKeys(data) as T
|
||||
}
|
||||
|
||||
// ── Channel normalization ────────────────────────────────────────────────────
|
||||
|
||||
const CHANNEL_DISPLAY_NAMES: Record<string, string> = {
|
||||
booking_com: 'Booking.com',
|
||||
airbnb: 'Airbnb',
|
||||
expedia: 'Expedia',
|
||||
vrbo: 'VRBO',
|
||||
yandex_travel: 'Яндекс Путешествия',
|
||||
ostrovok: 'Островок',
|
||||
sutochno: 'Суточно.ру',
|
||||
onetwotrip: 'OneTwoTrip',
|
||||
}
|
||||
|
||||
function normalizeChannel(raw: Record<string, unknown>): Channel {
|
||||
const name = raw.name as ChannelName
|
||||
return {
|
||||
id: raw.id as string,
|
||||
hotelId: (raw.hotelId as string) ?? '',
|
||||
name,
|
||||
displayName: CHANNEL_DISPLAY_NAMES[name as string] ?? String(name),
|
||||
isEnabled: Boolean(raw.enabled),
|
||||
lastSyncAt: (raw.lastSyncedAt as string | null) ?? null,
|
||||
lastSyncStatus: raw.lastSyncedAt ? 'success' : 'idle',
|
||||
bookingsImported: 0,
|
||||
mappings: [],
|
||||
}
|
||||
}
|
||||
|
||||
// ── API methods ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const api = {
|
||||
@@ -177,14 +205,20 @@ export const api = {
|
||||
|
||||
// ── Channels ──────────────────────────────────────────────────────────────
|
||||
channels: {
|
||||
list: (slug: string) =>
|
||||
req<Channel[]>('GET', `/api/hotels/${slug}/channels`),
|
||||
list: async (slug: string): Promise<Channel[]> => {
|
||||
const raw = await req<Record<string, unknown>[]>('GET', `/api/hotels/${slug}/channels`)
|
||||
return raw.map(normalizeChannel)
|
||||
},
|
||||
|
||||
update: (slug: string, id: string, data: { enabled?: boolean; api_key?: string }) =>
|
||||
req<Channel>('PATCH', `/api/hotels/${slug}/channels/${id}`, data),
|
||||
update: async (slug: string, id: string, data: { enabled?: boolean; api_key?: string }): Promise<Channel> => {
|
||||
const raw = await req<Record<string, unknown>>('PATCH', `/api/hotels/${slug}/channels/${id}`, data)
|
||||
return normalizeChannel(raw)
|
||||
},
|
||||
|
||||
sync: (slug: string, id: string) =>
|
||||
req<{ channel: Channel; synced_bookings: number }>('POST', `/api/hotels/${slug}/channels/${id}/sync`),
|
||||
sync: async (slug: string, id: string): Promise<{ channel: Channel; syncedBookings: number }> => {
|
||||
const raw = await req<Record<string, unknown>>('POST', `/api/hotels/${slug}/channels/${id}/sync`)
|
||||
return { channel: normalizeChannel(raw), syncedBookings: (raw.syncedBookings as number) ?? 0 }
|
||||
},
|
||||
},
|
||||
|
||||
// ── Users ─────────────────────────────────────────────────────────────────
|
||||
@@ -197,6 +231,18 @@ export const api = {
|
||||
|
||||
update: (slug: string, id: string, data: Partial<{ name: string; email: string; password: string }>) =>
|
||||
req<User>('PATCH', `/api/hotels/${slug}/users/${id}`, data),
|
||||
|
||||
delete: (slug: string, id: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/users/${id}`),
|
||||
},
|
||||
|
||||
// ── Hotels ────────────────────────────────────────────────────────────────
|
||||
hotels: {
|
||||
get: (slug: string) =>
|
||||
req<Hotel>('GET', `/api/hotels/${slug}`),
|
||||
|
||||
update: (slug: string, data: HotelPayload) =>
|
||||
req<Hotel>('PATCH', `/api/hotels/${slug}`, toHotelPayload(data)),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -264,3 +310,21 @@ export interface HkPayload {
|
||||
room_id?: string; type?: string; priority?: string
|
||||
status?: string; assignee_id?: string; notes?: string; due_date?: string
|
||||
}
|
||||
|
||||
export interface HotelPayload {
|
||||
name?: string; address?: string; phone?: string
|
||||
timezone?: string; currency?: string
|
||||
checkInTime?: string; checkOutTime?: string
|
||||
}
|
||||
|
||||
function toHotelPayload(h: HotelPayload): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {}
|
||||
if (h.name !== undefined) out.name = h.name
|
||||
if (h.address !== undefined) out.address = h.address
|
||||
if (h.phone !== undefined) out.phone = h.phone
|
||||
if (h.timezone !== undefined) out.timezone = h.timezone
|
||||
if (h.currency !== undefined) out.currency = h.currency
|
||||
if (h.checkInTime !== undefined) out.check_in_time = h.checkInTime
|
||||
if (h.checkOutTime !== undefined) out.check_out_time = h.checkOutTime
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user