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,6 +1,8 @@
|
||||
import { useState } from 'react'
|
||||
import { Save, Building2, Bell, Shield, Globe, CreditCard, BedDouble, Mail, MessageSquare, Users, Plus, X as XIcon, Send, CheckCircle2, Copy } from 'lucide-react'
|
||||
import { MOCK_HOTELS } from '../data/mockData'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Save, Building2, Bell, Shield, Globe, CreditCard, BedDouble, Mail, MessageSquare, Plus, X as XIcon, Send, CheckCircle2, Copy } from 'lucide-react'
|
||||
import { api } from '../lib/api'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import type { Hotel } from '../types'
|
||||
import { cn, PLAN_COLORS, PLAN_LABELS } from '../lib/utils'
|
||||
import { Badge } from '../components/ui/Badge'
|
||||
import { useTheme } from '../contexts/ThemeContext'
|
||||
@@ -26,11 +28,18 @@ function Toggle({ on, onChange }: { on: boolean; onChange: () => void }) {
|
||||
}
|
||||
|
||||
export function SettingsPage() {
|
||||
const { user } = useAuth()
|
||||
const slug = user?.hotelSlug ?? ''
|
||||
const [section, setSection] = useState('general')
|
||||
const [saved, setSaved] = useState(false)
|
||||
const hotel = MOCK_HOTELS[0]
|
||||
const [hotel, setHotel] = useState<Hotel | null>(null)
|
||||
const { theme, toggle } = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (!slug) return
|
||||
api.hotels.get(slug).then(setHotel).catch(console.error)
|
||||
}, [slug])
|
||||
|
||||
const [calendarCompact, setCalendarCompact] = useState(
|
||||
() => localStorage.getItem('calendarCompact') === 'true',
|
||||
)
|
||||
@@ -43,14 +52,26 @@ export function SettingsPage() {
|
||||
}
|
||||
|
||||
const [form, setForm] = useState({
|
||||
name: hotel.name,
|
||||
address: hotel.address,
|
||||
timezone: hotel.timezone,
|
||||
currency: hotel.currency,
|
||||
name: '',
|
||||
address: '',
|
||||
timezone: 'Europe/Moscow',
|
||||
currency: 'RUB',
|
||||
checkInTime: '14:00',
|
||||
checkOutTime: '12:00',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!hotel) return
|
||||
setForm({
|
||||
name: hotel.name ?? '',
|
||||
address: hotel.address ?? '',
|
||||
timezone: hotel.timezone ?? 'Europe/Moscow',
|
||||
currency: hotel.currency ?? 'RUB',
|
||||
checkInTime: hotel.checkInTime ?? '14:00',
|
||||
checkOutTime: hotel.checkOutTime ?? '12:00',
|
||||
})
|
||||
}, [hotel])
|
||||
|
||||
// Booking / assignment settings
|
||||
const [assignmentStrategy, setAssignmentStrategy] = useState<'spread' | 'together' | 'sequential' | 'manual'>('spread')
|
||||
const [showBookingSource, setShowBookingSource] = useState(false)
|
||||
@@ -126,7 +147,22 @@ export function SettingsPage() {
|
||||
setTimeout(() => setCopiedToken(false), 2000)
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
if (section === 'general') {
|
||||
try {
|
||||
const updated = await api.hotels.update(slug, {
|
||||
name: form.name,
|
||||
address: form.address,
|
||||
timezone: form.timezone,
|
||||
currency: form.currency,
|
||||
checkInTime: form.checkInTime,
|
||||
checkOutTime: form.checkOutTime,
|
||||
})
|
||||
setHotel(updated)
|
||||
} catch (err) {
|
||||
console.error('Failed to save hotel settings', err)
|
||||
}
|
||||
}
|
||||
setSaved(true)
|
||||
setTimeout(() => setSaved(false), 2000)
|
||||
}
|
||||
@@ -135,7 +171,7 @@ export function SettingsPage() {
|
||||
<div className="p-4 md:p-6 max-w-4xl mx-auto">
|
||||
<div className="mb-5">
|
||||
<h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">Настройки</h1>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">{hotel.name}</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">{hotel?.name ?? '...'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-5">
|
||||
@@ -617,7 +653,7 @@ export function SettingsPage() {
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">Текущий план</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge className={PLAN_COLORS[hotel.plan]}>{PLAN_LABELS[hotel.plan]}</Badge>
|
||||
<Badge className={PLAN_COLORS[hotel?.plan ?? 'starter']}>{PLAN_LABELS[hotel?.plan ?? 'starter']}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn-primary">Улучшить план</button>
|
||||
@@ -630,11 +666,11 @@ export function SettingsPage() {
|
||||
] as const).map(p => (
|
||||
<div key={p.plan} className={cn(
|
||||
'p-4 rounded-xl border-2 transition-all',
|
||||
hotel.plan === p.plan ? 'border-brand-500 bg-brand-50/50 dark:bg-brand-900/10' : 'border-slate-200 dark:border-slate-600',
|
||||
hotel?.plan === p.plan ? 'border-brand-500 bg-brand-50/50 dark:bg-brand-900/10' : 'border-slate-200 dark:border-slate-600',
|
||||
)}>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Badge className={PLAN_COLORS[p.plan]}>{PLAN_LABELS[p.plan]}</Badge>
|
||||
{hotel.plan === p.plan && <span className="text-xs text-brand-600 dark:text-brand-400 font-medium">Текущий</span>}
|
||||
{hotel?.plan === p.plan && <span className="text-xs text-brand-600 dark:text-brand-400 font-medium">Текущий</span>}
|
||||
</div>
|
||||
<p className="text-lg font-bold text-slate-900 dark:text-slate-100 mb-3">{p.price}</p>
|
||||
<ul className="space-y-1 text-xs text-slate-600 dark:text-slate-400">
|
||||
|
||||
Reference in New Issue
Block a user