import { useState, useEffect, useRef } from 'react' import { ArrowLeftRight, Upload, Download, CheckCircle2, ArrowRight, ArrowLeft, FileSpreadsheet } from 'lucide-react' import { cn } from '../lib/utils' const SOURCES = [ { id: 'bnovo', icon: '🏨', name: 'Bnovo', description: 'Российская PMS Bnovo. Экспорт из раздела "Отчёты"' }, { id: 'hotelbird', icon: '🐦', name: 'Hotelbird', description: 'HotelsPro / Hotelbird. CSV из личного кабинета' }, { id: 'fidelio', icon: '🏢', name: 'Fidelio Opera', description: 'Oracle Hospitality / Fidelio. XML / CSV экспорт' }, { id: '1c', icon: '📊', name: '1С Отель', description: 'Выгрузка из 1С:Отель в формате Excel' }, { id: 'rms', icon: '☁️', name: 'RMS Cloud', description: 'RMS Cloud PMS. Экспорт через API или CSV' }, { id: 'csv', icon: '📄', name: 'Другая (CSV)', description: 'Любая другая система. Загрузите CSV или Excel по шаблону' }, ] const SAMPLE_COLUMNS = [ { key: 'room', label: 'Номер комнаты', example: '101' }, { key: 'guest', label: 'Имя гостя', example: 'Иванов Иван' }, { key: 'checkin', label: 'Дата заезда', example: '2026-03-15' }, { key: 'checkout', label: 'Дата выезда', example: '2026-03-18' }, { key: 'amount', label: 'Сумма', example: '13500' }, { key: 'status', label: 'Статус', example: 'confirmed' }, ] type Step = 1 | 2 | 3 export function MigrationPage() { const [step, setStep] = useState(1) const [selectedSource, setSelectedSource] = useState(null) const [fileName, setFileName] = useState(null) const [isDragging, setIsDragging] = useState(false) const [importing, setImporting] = useState(false) const [progress, setProgress] = useState(0) const [done, setDone] = useState(false) const fileInputRef = useRef(null) const handleDrop = (e: React.DragEvent) => { e.preventDefault() setIsDragging(false) const file = e.dataTransfer.files[0] if (file) setFileName(file.name) } const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) setFileName(file.name) } const startImport = () => { setImporting(true) setProgress(0) setDone(false) } useEffect(() => { if (!importing || done) return const interval = setInterval(() => { setProgress(prev => { if (prev >= 100) { clearInterval(interval) setDone(true) return 100 } return prev + Math.random() * 15 }) }, 150) return () => clearInterval(interval) }, [importing, done]) const reset = () => { setStep(1) setSelectedSource(null) setFileName(null) setImporting(false) setProgress(0) setDone(false) } return (
{/* Header */}

Миграция данных

Перенос данных из другой PMS системы

{/* Step indicator */}
{([1, 2, 3] as Step[]).map((s, i) => ( <>
s ? 'bg-violet-200 dark:bg-violet-900/40 text-violet-700 dark:text-violet-300' : 'bg-slate-100 dark:bg-slate-700 text-slate-400', )} > {step > s ? : s}
{i < 2 && (
s ? 'bg-violet-400' : 'bg-slate-200 dark:bg-slate-700')} /> )} ))}
{/* Step labels */}
Выбор источника Загрузка файла Импорт
{/* ── STEP 1: Source selection ── */} {step === 1 && (

Выберите систему, из которой переносите данные

{SOURCES.map(src => ( ))}
)} {/* ── STEP 2: File upload ── */} {step === 2 && (

Загрузите файл экспорта

Выгрузите данные из {SOURCES.find(s => s.id === selectedSource)?.name} в формате CSV или Excel (.xlsx). Файл должен содержать заголовки колонок. Максимальный размер файла: 50 МБ.

{/* Dropzone */}
{ e.preventDefault(); setIsDragging(true) }} onDragLeave={() => setIsDragging(false)} onDrop={handleDrop} onClick={() => fileInputRef.current?.click()} className={cn( 'border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-colors', isDragging ? 'border-violet-400 bg-violet-50 dark:bg-violet-900/20' : fileName ? 'border-emerald-400 bg-emerald-50 dark:bg-emerald-900/10' : 'border-slate-300 dark:border-slate-600 hover:border-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/10', )} > {fileName ? (

{fileName}

Нажмите для замены файла

) : (

Перетащите файл сюда или нажмите для выбора

CSV, XLSX, XLS — до 50 МБ

)}
{/* Download template */}

Нет подходящего формата?

Скачайте шаблон и заполните вручную

{/* Sample table */}

Ожидаемые колонки файла:

{SAMPLE_COLUMNS.map(col => ( ))} {SAMPLE_COLUMNS.map(col => ( ))} {SAMPLE_COLUMNS.map(col => ( ))}
{col.label}
{col.example}
...
)} {/* ── STEP 3: Import ── */} {step === 3 && (

Импорт данных

{!importing && !done && (
{SOURCES.find(s => s.id === selectedSource)?.icon}

{SOURCES.find(s => s.id === selectedSource)?.name}

{fileName}

Система проверит файл, смаппирует поля и импортирует данные. Это займёт несколько секунд.

)} {importing && !done && (

Импорт данных...

Обработка файла и перенос данных

{Math.round(Math.min(100, progress))}%

)} {done && (

Импорт завершён!

Данные успешно перенесены в HotelSync

{[ { label: 'Номеров импортировано', value: '12', color: 'text-brand-600 dark:text-brand-400' }, { label: 'Броней импортировано', value: '47', color: 'text-violet-600 dark:text-violet-400' }, { label: 'Гостей добавлено', value: '38', color: 'text-emerald-600 dark:text-emerald-400' }, ].map(stat => (

{stat.value}

{stat.label}

))}
)}
)}
) }