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:
2026-03-17 14:21:45 +03:00
parent 57162c55c6
commit 55a34dc0dc
9 changed files with 277 additions and 109 deletions

View File

@@ -1,24 +1,42 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { format, addDays } from 'date-fns'
import { Pencil, Check } from 'lucide-react'
import { MOCK_ROOMS, MOCK_BOOKINGS } from '../data/mockData'
import { api } from '../lib/api'
import { useAuth } from '../contexts/AuthContext'
import { FloorMap } from '../components/floormap/FloorMap'
import { BookingModal } from '../components/bookings/BookingModal'
import { cn } from '../lib/utils'
import type { Booking } from '../types'
import type { Room, Booking } from '../types'
import type { BookingPayload } from '../lib/api'
export function FloorMapPage() {
const [bookings, setBookings] = useState<Booking[]>(MOCK_BOOKINGS)
const { user } = useAuth()
const slug = user?.hotelSlug ?? ''
const [rooms, setRooms] = useState<Room[]>([])
const [bookings, setBookings] = useState<Booking[]>([])
const [loading, setLoading] = useState(true)
const [createForRoom, setCreateForRoom] = useState<string | null>(null)
const [editMode, setEditMode] = useState(false)
useEffect(() => {
if (!slug) return
Promise.all([api.rooms.list(slug), api.bookings.list(slug)])
.then(([r, b]) => { setRooms(r); setBookings(b) })
.catch(console.error)
.finally(() => setLoading(false))
}, [slug])
const stats = {
total: MOCK_ROOMS.length,
occupied: MOCK_ROOMS.filter(r => r.status === 'occupied').length,
available: MOCK_ROOMS.filter(r => r.status === 'available').length,
maintenance: MOCK_ROOMS.filter(r => r.status === 'maintenance' || r.status === 'blocked').length,
total: rooms.length,
occupied: rooms.filter(r => r.status === 'occupied').length,
available: rooms.filter(r => r.status === 'available').length,
maintenance: rooms.filter(r => r.status === 'maintenance' || r.status === 'blocked').length,
}
if (loading) return (
<div className="p-6 flex items-center justify-center text-slate-400 text-sm">Загрузка...</div>
)
return (
<div className="p-4 md:p-6 space-y-4">
{/* Header */}
@@ -63,7 +81,7 @@ export function FloorMapPage() {
{/* Floor map */}
<div className="card overflow-hidden p-0">
<FloorMap
rooms={MOCK_ROOMS}
rooms={rooms}
bookings={bookings}
editMode={editMode}
onSelectRoom={editMode ? undefined : (id) => setCreateForRoom(id)}
@@ -79,11 +97,15 @@ export function FloorMapPage() {
checkIn: format(new Date(), 'yyyy-MM-dd'),
checkOut: format(addDays(new Date(), 1), 'yyyy-MM-dd'),
}}
rooms={MOCK_ROOMS}
rooms={rooms}
onClose={() => setCreateForRoom(null)}
onSave={(data) => {
setBookings(prev => [...prev, data as Booking])
setCreateForRoom(null)
onSave={async (data) => {
try {
const created = await api.bookings.create(slug, data as BookingPayload)
setBookings(prev => [...prev, created])
} finally {
setCreateForRoom(null)
}
}}
/>
)}