import { useState, useRef, useEffect } from 'react' import { Plus, Pencil, Trash2, ImagePlus, X as XIcon, Tag, ChevronRight, ChevronDown, ChevronUp, Check, Loader2, DoorOpen } from 'lucide-react' import { cn } from '../lib/utils' import type { RoomCategory, Room } from '../types' import { useAmenities } from '../contexts/AmenitiesContext' import { useBedTypes } from '../contexts/BedTypesContext' import { useAuth } from '../contexts/AuthContext' import { api } from '../lib/api' import type { CategoryApi } from '../lib/api' // ── Mock data ───────────────────────────────────────────────────────────────── const CATEGORY_COLORS = [ '#4F46E5', '#059669', '#2563EB', '#7C3AED', '#DC2626', '#D97706', '#DB2777', '#475569', ] function fromApi(c: CategoryApi): RoomCategory { return { id: c.id, hotelId: c.hotel_id, name: c.name, description: c.description, color: c.color, amenities: c.amenities, photos: c.photos, basePrice: c.base_price, allowHourly: c.allow_hourly, hourlyBasePrice: c.hourly_base_price, } } // ── Category Form Modal ──────────────────────────────────────────────────────── interface CategoryFormProps { category?: RoomCategory onClose: () => void onSave: (cat: RoomCategory) => Promise } function CategoryForm({ category, onClose, onSave }: CategoryFormProps) { const isEdit = !!category const { amenities: allAmenities } = useAmenities() const [tab, setTab] = useState<'main' | 'photos'>('main') const [name, setName] = useState(category?.name ?? '') const [description, setDescription] = useState(category?.description ?? '') const [color, setColor] = useState(category?.color ?? '#4F46E5') const [amenities, setAmenities] = useState(category?.amenities ?? []) const [photos, setPhotos] = useState(category?.photos ?? []) const [basePrice, setBasePrice] = useState(category?.basePrice ?? 0) const [allowHourly, setAllowHourly] = useState(category?.allowHourly ?? false) const [hourlyBasePrice, setHourlyBasePrice] = useState(category?.hourlyBasePrice ?? 0) const [photoIdx, setPhotoIdx] = useState(0) const [saving, setSaving] = useState(false) const [saveError, setSaveError] = useState('') const [dragging, setDragging] = useState(false) const [dragSrcIdx, setDragSrcIdx] = useState(null) const fileRef = useRef(null) const toggleAmenity = (a: string) => setAmenities(prev => prev.includes(a) ? prev.filter(x => x !== a) : [...prev, a]) const [uploading, setUploading] = useState(false) const uploadFiles = async (files: FileList | null) => { const list = Array.from(files ?? []).filter(f => f.type.startsWith('image/')) if (!list.length) return setUploading(true) try { const urls = await Promise.all(list.map(f => api.upload.photo(f, 'categories'))) setPhotos(prev => { const next = [...prev, ...urls] setPhotoIdx(next.length - 1) return next }) } catch { setSaveError('Ошибка загрузки фото') } finally { setUploading(false) } } const handlePhotoUpload = (e: React.ChangeEvent) => { uploadFiles(e.target.files) e.target.value = '' } const handleDrop = (e: React.DragEvent) => { e.preventDefault() setDragging(false) // Only handle external file drops (not internal thumbnail DnD) if (e.dataTransfer.files.length > 0) { uploadFiles(e.dataTransfer.files) } } const handleDragOver = (e: React.DragEvent) => { e.preventDefault() // Only show drop highlight for external files, not internal thumbnail reorder if (e.dataTransfer.types.includes('Files')) { setDragging(true) } } const removePhoto = (i: number) => { const url = photos[i] setPhotos(prev => prev.filter((_, idx) => idx !== i)) setPhotoIdx(p => Math.max(0, p - 1)) // Delete from server (best-effort, don't block UI) if (url.startsWith('https://cdn.hotelsync.ru/')) { api.upload.deletePhoto(url).catch(console.error) } } const handleSave = async () => { if (!name.trim() || saving) return setSaving(true) setSaveError('') try { await onSave({ id: category?.id ?? `cat-${Date.now()}`, hotelId: category?.hotelId ?? '', name: name.trim(), description, color, amenities, photos, basePrice, allowHourly, hourlyBasePrice, }) } catch (err) { setSaveError(err instanceof Error ? err.message : 'Ошибка сохранения') setSaving(false) } } return (
{/* Header */}

{isEdit ? `Редактировать: ${category.name}` : 'Новая категория'}

{/* Tab bar */}
{(['main', 'photos'] as const).map(t => ( ))}
{/* Body */}
{tab === 'main' && ( <>
setName(e.target.value)} />
{CATEGORY_COLORS.map(c => (
{/* Pricing */}
setBasePrice(parseInt(e.target.value) || 0)} />

0 = цена из номеров категории

{allowHourly && (
setHourlyBasePrice(parseInt(e.target.value) || 0)} />
)}

Отображается в виджете бронирования как описание категории