import { useState, useEffect } from 'react' import { Plus, Pencil, Trash2, CalendarDays, X, Save } from 'lucide-react' import { format } from 'date-fns' import { ru } from 'date-fns/locale' import { cn, formatCurrency } from '../lib/utils' import { Badge } from '../components/ui/Badge' import type { RentalObject, RentalBooking } from '../data/rentalData' import { RentalBookingModal } from '../components/rental/RentalBookingModal' import { api } from '../lib/api' import type { RentalObjectApi, RentalBookingApi } from '../lib/api' import { useAuth } from '../contexts/AuthContext' // ── Object form ─────────────────────────────────────────────────────────────── const ICON_OPTIONS = ['🎾', '🛁', '🏛️', '⛳', '🏊', '🎱', '🎮', '🏋️', '🎤', '🏕️', '🚲', '🛶'] const COLOR_OPTIONS = [ { value: 'bg-green-500', label: 'Зелёный' }, { value: 'bg-orange-500', label: 'Оранжевый' }, { value: 'bg-violet-500', label: 'Фиолетовый' }, { value: 'bg-blue-500', label: 'Синий' }, { value: 'bg-red-500', label: 'Красный' }, { value: 'bg-teal-500', label: 'Бирюзовый' }, { value: 'bg-amber-500', label: 'Жёлтый' }, { value: 'bg-pink-500', label: 'Розовый' }, ] const TEXT_COLORS: Record = { 'bg-green-500': 'text-green-700 dark:text-green-400', 'bg-orange-500': 'text-orange-700 dark:text-orange-400', 'bg-violet-500': 'text-violet-700 dark:text-violet-400', 'bg-blue-500': 'text-blue-700 dark:text-blue-400', 'bg-red-500': 'text-red-700 dark:text-red-400', 'bg-teal-500': 'text-teal-700 dark:text-teal-400', 'bg-amber-500': 'text-amber-700 dark:text-amber-400', 'bg-pink-500': 'text-pink-700 dark:text-pink-400', } interface ObjectFormData { name: string icon: string color: string pricePerHour: number pricePerDay: number openHour: number closeHour: number maxHoursPerSlot: number | '' bufferMinutes: number description: string } const EMPTY_FORM: ObjectFormData = { name: '', icon: '🎾', color: 'bg-green-500', pricePerHour: 1000, pricePerDay: 5000, openHour: 9, closeHour: 21, maxHoursPerSlot: '', bufferMinutes: 0, description: '', } function ObjectFormModal({ initial, onSave, onClose, }: { initial?: RentalObject onSave: (obj: RentalObject) => void onClose: () => void }) { const [form, setForm] = useState( initial ? { name: initial.name, icon: initial.icon, color: initial.color, pricePerHour: initial.pricePerHour, pricePerDay: initial.pricePerDay, openHour: initial.openHour, closeHour: initial.closeHour, maxHoursPerSlot: initial.maxHoursPerSlot ?? '', bufferMinutes: initial.bufferMinutes ?? 0, description: '', } : EMPTY_FORM, ) const set = (k: K, v: ObjectFormData[K]) => setForm(prev => ({ ...prev, [k]: v })) const handleSave = () => { if (!form.name.trim()) return onSave({ id: initial?.id ?? `obj-${Date.now()}`, name: form.name.trim(), icon: form.icon, color: form.color, textColor: TEXT_COLORS[form.color] ?? 'text-slate-700', pricePerHour: form.pricePerHour, pricePerDay: form.pricePerDay, openHour: form.openHour, closeHour: form.closeHour, maxHoursPerSlot: form.maxHoursPerSlot !== '' ? Number(form.maxHoursPerSlot) : undefined, bufferMinutes: form.bufferMinutes > 0 ? form.bufferMinutes : undefined, }) } return (

{initial ? 'Редактировать объект' : 'Новый объект аренды'}

{/* Name */}
set('name', e.target.value)} />
{/* Icon + Color */}
{ICON_OPTIONS.map(ico => ( ))}
{COLOR_OPTIONS.map(c => (
{/* Prices */}
set('pricePerHour', parseInt(e.target.value) || 0)} />
set('pricePerDay', parseInt(e.target.value) || 0)} />
{/* Buffer time */}

Время для уборки СПА, подготовки корта и т.д.

{/* Hours */}
set('maxHoursPerSlot', e.target.value === '' ? '' : parseInt(e.target.value))} />
{/* Preview */}
{form.icon}

{form.name || 'Название объекта'}

{formatCurrency(form.pricePerHour)}/ч · {formatCurrency(form.pricePerDay)}/день · {String(form.openHour).padStart(2, '0')}:00–{String(form.closeHour).padStart(2, '0')}:00

) } // ── Main page ───────────────────────────────────────────────────────────────── // Convert API response to local type function fromObjApi(o: RentalObjectApi): RentalObject { return { id: o.id, name: o.name, icon: o.icon, color: o.color, textColor: o.textColor, pricePerHour: o.pricePerHour, pricePerDay: o.pricePerDay, openHour: o.openHour, closeHour: o.closeHour, maxHoursPerSlot: o.maxHoursPerSlot ?? undefined, bufferMinutes: o.bufferMinutes ?? undefined, } } function fromBookingApi(b: RentalBookingApi): RentalBooking { return { id: b.id, objectId: b.objectId, date: (b.date as string).slice(0, 10), isFullDay: b.isFullDay, startHour: b.startHour, endHour: b.endHour, guestName: b.guestName, guestPhone: b.guestPhone ?? '', linkedRoomId: b.linkedRoomId ?? undefined, totalAmount: b.totalAmount, paidAmount: b.paidAmount, status: b.status, notes: b.notes ?? undefined, } } export function RentalPage() { const { user } = useAuth() const slug = user?.hotelSlug ?? '' const [objects, setObjects] = useState([]) const [bookings, setBookings] = useState([]) const [editObj, setEditObj] = useState(null) const [createModal, setCreateModal] = useState(false) const [selectedObj, setSelectedObj] = useState(null) const [rentalModal, setRentalModal] = useState<{ obj: RentalObject; date: string; editBooking?: RentalBooking } | null>(null) const today = format(new Date(), 'yyyy-MM-dd') // Load from API on mount useEffect(() => { if (!slug) return Promise.all([ api.rental.listObjects(slug), api.rental.listBookings(slug), ]).then(([objs, bks]) => { setObjects(objs.map(fromObjApi)) setBookings(bks.map(fromBookingApi)) }).catch(console.error) }, [slug]) const handleSaveObject = async (obj: RentalObject) => { if (!slug) return const payload = { name: obj.name, icon: obj.icon, color: obj.color, text_color: obj.textColor, price_per_hour: obj.pricePerHour, price_per_day: obj.pricePerDay, open_hour: obj.openHour, close_hour: obj.closeHour, max_hours_per_slot: obj.maxHoursPerSlot ?? null, buffer_minutes: obj.bufferMinutes ?? 0, } const isNew = !objects.find(o => o.id === obj.id) const saved = isNew ? await api.rental.createObject(slug, payload) : await api.rental.updateObject(slug, obj.id, payload) const converted = fromObjApi(saved) setObjects(prev => isNew ? [...prev, converted] : prev.map(o => o.id === obj.id ? converted : o)) // Update selectedObj if it was the edited one setSelectedObj(prev => prev?.id === obj.id ? converted : prev) setEditObj(null) setCreateModal(false) } const handleDeleteObject = async (id: string) => { if (!slug) return await api.rental.deleteObject(slug, id) setObjects(prev => prev.filter(o => o.id !== id)) if (selectedObj?.id === id) setSelectedObj(null) } const handleBookingSave = async (b: RentalBooking) => { if (!slug) return const payload = { object_id: b.objectId, date: b.date, is_full_day: b.isFullDay, start_hour: b.startHour, end_hour: b.endHour, guest_name: b.guestName, guest_phone: b.guestPhone || undefined, linked_room_id: b.linkedRoomId || undefined, total_amount: b.totalAmount, paid_amount: b.paidAmount ?? 0, status: b.status, notes: b.notes || undefined, } const isNew = !bookings.find(x => x.id === b.id) const saved = isNew ? await api.rental.createBooking(slug, payload) : await api.rental.updateBooking(slug, b.id, payload) const converted = fromBookingApi(saved) setBookings(prev => isNew ? [...prev, converted] : prev.map(x => x.id === b.id ? converted : x)) setRentalModal(null) } const totalRevenue = bookings .filter(b => b.status === 'confirmed') .reduce((s, b) => s + b.totalAmount, 0) return (
{/* Header */}

Аренда объектов

{objects.length} объектов · управление и расписание

{/* Stats */}
{[ { label: 'Объектов', value: objects.length, color: 'text-slate-900 dark:text-slate-100' }, { label: 'Броней сегодня', value: bookings.filter(b => b.date === today).length, color: 'text-brand-600 dark:text-brand-400' }, { label: 'Всего броней', value: bookings.filter(b => b.status === 'confirmed').length, color: 'text-emerald-600 dark:text-emerald-400' }, { label: 'Общая выручка', value: formatCurrency(totalRevenue), color: 'text-emerald-600 dark:text-emerald-400' }, ].map(s => (

{s.value}

{s.label}

))}
{/* Objects list */}

Объекты аренды

{objects.map(obj => { const objBookings = bookings.filter(b => b.objectId === obj.id && b.status === 'confirmed') const todayB = objBookings.filter(b => b.date === today) const isSelected = selectedObj?.id === obj.id return (
setSelectedObj(isSelected ? null : obj)} className={cn( 'card p-4 cursor-pointer transition-all', isSelected ? 'ring-2 ring-brand-500' : 'hover:shadow-md', )} >
{obj.icon}

{obj.name}

{formatCurrency(obj.pricePerHour)}/ч · {String(obj.openHour).padStart(2, '0')}:00–{String(obj.closeHour).padStart(2, '0')}:00

{todayB.length > 0 && ( {todayB.length} сегодня )} {objBookings.length} всего
) })} {objects.length === 0 && (

Нет объектов аренды

)}
{/* Right: bookings for selected object */}
{selectedObj ? (

Бронирования — {selectedObj.name}

{['Дата', 'Время', 'Гость', 'Телефон', 'Сумма', 'Статус', ''].map((h, i) => ( ))} {bookings.filter(b => b.objectId === selectedObj.id) .sort((a, b) => b.date.localeCompare(a.date)) .map(b => ( ))}
{h}
{format(new Date(b.date), 'd MMM', { locale: ru })} {b.date === today && сегодня} {b.isFullDay ? ( Весь день ) : (() => { const fmt = (m: number) => { const h = Math.floor(m/60), mn = m%60; return mn === 0 ? `${h}:00` : `${h}:${String(mn).padStart(2,'0')}` } return `${fmt(b.startHour)} – ${fmt(b.endHour)}` })()} {b.guestName} {b.guestPhone || '—'} {formatCurrency(b.totalAmount)} {b.status === 'confirmed' ? 'Подтверждено' : 'Отменено'}
{bookings.filter(b => b.objectId === selectedObj.id).length === 0 && (
Нет бронирований для этого объекта
)}
) : (

Выберите объект слева для просмотра бронирований

)}
{/* Object form modals */} {(createModal || editObj) && ( { setCreateModal(false); setEditObj(null) }} /> )} {/* Rental booking modal */} {rentalModal && ( b.objectId === rentalModal.obj.id && b.date === rentalModal.date)} editBooking={rentalModal.editBooking} onClose={() => setRentalModal(null)} onSave={handleBookingSave} /> )}
) }