import { useState, useEffect, useCallback } from 'react' import { Search, Star, Phone, Mail, User, TrendingUp, Award, Users, Calendar, CreditCard, X, ChevronRight, MessageSquare, Tag, Repeat2, Shield, ShieldCheck, Plus, ChevronsUpDown, AlertCircle, Loader2, } from 'lucide-react' import { cn } from '../lib/utils' import { api, type GuestApiType } from '../lib/api' import { useAuth } from '../contexts/AuthContext' // ─── Types ─────────────────────────────────────────────────────────────────── type Guest = GuestApiType // ─── Helpers ────────────────────────────────────────────────────────────────── const STAY_STATUS: Record = { completed: { label: 'Завершено', cls: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400' }, checked_out: { label: 'Завершено', cls: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400' }, checked_in: { label: 'Проживает', cls: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400' }, confirmed: { label: 'Подтверждено', cls: 'bg-slate-100 text-slate-700 dark:bg-slate-700 dark:text-slate-300' }, cancelled: { label: 'Отменено', cls: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' }, no_show: { label: 'Неявка', cls: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400' }, } const TAG_CLS: Record = { VIP: 'bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-400', Постоянный: 'bg-brand-100 text-brand-700 border-brand-200 dark:bg-brand-900/30 dark:text-brand-400', Корпоративный: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400', 'Медовый месяц': 'bg-pink-100 text-pink-700 border-pink-200 dark:bg-pink-900/30 dark:text-pink-400', Проблемный: 'bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400', } const ALL_TAGS = Object.keys(TAG_CLS) const GENDER_OPTIONS = [ { value: 'male', label: 'Мужской' }, { value: 'female', label: 'Женский' }, { value: 'other', label: 'Другой' }, ] function guestName(g: Guest) { return [g.firstName, g.lastName].filter(Boolean).join(' ') || '—' } function guestInitials(g: Guest) { return `${g.firstName?.[0] ?? ''}${g.lastName?.[0] ?? ''}`.toUpperCase() } function StarRating({ value, onChange }: { value: number; onChange?: (v: number) => void }) { return (
{[1, 2, 3, 4, 5].map(i => ( onChange?.(i)} /> ))}
) } function formatDate(iso: string | null | undefined) { if (!iso) return '—' const d = new Date(iso) return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }) } function nights(checkIn: string, checkOut: string) { const n = Math.round((new Date(checkOut).getTime() - new Date(checkIn).getTime()) / 86400000) return `${n} ${n === 1 ? 'ночь' : n < 5 ? 'ночи' : 'ночей'}` } // ─── Guest Detail Modal ─────────────────────────────────────────────────────── interface GuestModalProps { guestId: string slug: string onClose: () => void onUpdated: (g: Guest) => void } function GuestModal({ guestId, slug, onClose, onUpdated }: GuestModalProps) { const [tab, setTab] = useState<'overview' | 'history' | 'payments' | 'passport'>('overview') const [guest, setGuest] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) // Edit state const [firstName, setFirstName] = useState('') const [lastName, setLastName] = useState('') const [email, setEmail] = useState('') const [phone, setPhone] = useState('') const [rating, setRating] = useState(3) const [notes, setNotes] = useState('') const [tags, setTags] = useState([]) const [isBlacklisted, setIsBlacklisted] = useState(false) // Passport const [passportSeries, setPassportSeries] = useState('') const [passportNumber, setPassportNumber] = useState('') const [birthDate, setBirthDate] = useState('') const [nationality, setNationality] = useState('') const [gender, setGender] = useState('') const [city, setCity] = useState('') useEffect(() => { setLoading(true) api.guests.get(slug, guestId) .then(g => { setGuest(g) setFirstName(g.firstName ?? '') setLastName(g.lastName ?? '') setEmail(g.email ?? '') setPhone(g.phone ?? '') setRating(g.rating) setNotes(g.notes ?? '') setTags(g.tags ?? []) setIsBlacklisted(g.isBlacklisted ?? false) setPassportSeries(g.passportSeries ?? '') setPassportNumber(g.passportNumber ?? '') setBirthDate(g.birthDate?.slice(0, 10) ?? '') setNationality(g.nationality ?? '') setGender(g.gender ?? '') setCity(g.city ?? '') }) .catch(() => setError('Не удалось загрузить данные гостя')) .finally(() => setLoading(false)) }, [slug, guestId]) const handleSave = async () => { if (!guest) return setSaving(true) try { const updated = await api.guests.update(slug, guest.id, { first_name: firstName, last_name: lastName, email: email || undefined, phone: phone || undefined, rating, notes, tags, is_blacklisted: isBlacklisted, passport_series: passportSeries || undefined, passport_number: passportNumber || undefined, birth_date: birthDate || undefined, nationality: nationality || undefined, gender: gender || undefined, city: city || undefined, }) onUpdated(updated) onClose() } catch { setError('Ошибка при сохранении') } finally { setSaving(false) } } const toggleTag = (tag: string) => setTags(prev => prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag]) const history = guest?.history ?? [] const completedStays = history.filter(s => s.status === 'checked_out' || s.status === 'completed') const totalPaid = history.reduce((s, h) => s + (h.paidAmount ?? 0), 0) const avgBill = completedStays.length ? Math.round(totalPaid / completedStays.length) : 0 return (
{/* Header */}
{loading ? (
) : guest ? (
{guestInitials(guest)}

{[firstName, lastName].filter(Boolean).join(' ')}

{tags.includes('VIP') && ( VIP )}
{phone && ( {phone} )} {email && ( {email} )}
) : null}
{error && (
{error}
)} {/* Stats strip */} {!loading && guest && (
{[ { label: 'Визитов', value: guest.totalStays }, { label: 'Потрачено', value: `${Number(guest.totalSpent).toLocaleString('ru-RU')} ₽` }, { label: 'Средний чек', value: `${avgBill.toLocaleString('ru-RU')} ₽` }, { label: 'Последний визит', value: formatDate(guest.lastVisit) }, ].map(s => (

{s.label}

{s.value}

))}
)} {/* Tabs */}
{([ { id: 'overview' as const, label: 'Обзор' }, { id: 'history' as const, label: 'История' }, { id: 'payments' as const, label: 'Платежи' }, { id: 'passport' as const, label: 'Паспорт' }, ]).map(t => ( ))}
{/* Body */}
{loading && (
)} {!loading && tab === 'overview' && (
{/* Basic info */}
setFirstName(e.target.value)} placeholder="Имя" />
setLastName(e.target.value)} placeholder="Фамилия" />
setPhone(e.target.value)} placeholder="+7 900 000-00-00" />
setEmail(e.target.value)} placeholder="email@example.com" />
{/* Tags */}

Метки

{ALL_TAGS.map(tag => ( ))}
{/* Blacklist */}
{isBlacklisted && (

Этот гость не сможет забронировать номер через виджет онлайн-бронирования.

)}
{/* Notes */}

Заметки о госте