import { useEffect, useState } from 'react'; import { ExternalLink, RefreshCw, Send, X } from 'lucide-react'; import { telegramImportApi } from '../services/api'; import { useCity } from '../contexts/CityContext'; const connectionLabels = { disabled: 'Импорт выключен', configuration_required: 'Нужна настройка аккаунта', waiting: 'Ожидание подключения', connecting: 'Подключение к Telegram', ready: 'Канал подключён', error: 'Ошибка подключения' }; const errorLabels = { SESSION_MISSING: 'Не найден файл авторизации. Выполните авторизацию аккаунта на сервере.', AUTH_REQUIRED: 'Требуется повторная авторизация Telegram-аккаунта.', AUTH_KEY_UNREGISTERED: 'Авторизация отозвана. Подключите аккаунт повторно.', SESSION_REVOKED: 'Авторизация отозвана. Подключите аккаунт повторно.', SESSION_FILE_PERMISSIONS: 'Файл авторизации должен быть доступен только владельцу.', SESSION_FILE_IN_CHECKOUT: 'Перенесите файл авторизации за пределы проекта.', USER_ACCOUNT_REQUIRED: 'Нужен отдельный пользовательский аккаунт Telegram.', CHANNEL_PRIVATE: 'Канал недоступен этому аккаунту.', CHANNEL_INVALID: 'Проверьте имя канала в настройках сервера.', USERNAME_NOT_OCCUPIED: 'Канал с таким именем не найден.', FLOOD_WAIT: 'Telegram ограничил запросы. Импорт продолжится после паузы.', HISTORY_LIMIT: 'В канале слишком много сообщений за последние три дня. Нужна проверка импорта.', IMPORT_FAILED: 'Проверьте соединение с Telegram и миграцию базы данных.' }; const formatTime = value => value ? new Date(value).toLocaleString('ru-RU', { timeZone: 'Europe/Minsk' }) : 'ещё не выполнялось'; export default function TelegramImportPanel({ routes, onChanged }) { const { activeCity } = useCity(); const [data, setData] = useState(null); const [error, setError] = useState(''); const [busy, setBusy] = useState(false); const [editing, setEditing] = useState(null); const [showHistory, setShowHistory] = useState(false); const load = async () => { try { setData((await telegramImportApi.getAll()).data); setError(''); } catch (err) { setError(err.response?.data?.error || 'Не удалось загрузить импорт Telegram.'); } }; useEffect(() => { load(); const timer = setInterval(load, 30_000); return () => clearInterval(timer); }, []); const sync = async () => { setBusy(true); try { await telegramImportApi.sync(); await load(); onChanged(); } catch (err) { setError(err.response?.data?.error || 'Не удалось прочитать канал.'); } finally { setBusy(false); } }; const review = async (row, values) => { setBusy(true); try { await telegramImportApi.review(row.id, { ...values, content_hash: row.content_hash }); setEditing(null); await load(); onChanged(); } catch (err) { setError(err.response?.data?.error || 'Не удалось сохранить решение.'); } finally { setBusy(false); } }; const status = data?.status; const configuredCity = status?.citySlug === activeCity?.slug; const pending = data?.messages.filter(m => m.status === 'review') || []; const rows = showHistory ? data?.messages || [] : pending; return (

Отмены из Telegram

{status ? configuredCity ? connectionLabels[status.connection] : 'Для выбранного города источник не настроен' : 'Загрузка источника…'} {status && <> · @{status.channel}}

{status?.connection === 'disabled' &&

Подключите отдельный аккаунт на сервере. Отмены будут поступать автоматически.

} {status?.enabled &&

Последняя проверка: {formatTime(status.lastSyncAt)} · {status.autoPublish ? 'Однозначные отмены публикуются автоматически' : 'Публикация после проверки'}

} {status?.lastError &&

{errorLabels[status.lastError] || 'Не удалось подключиться к Telegram.'}

} {status?.retryAt &&

Следующая попытка не раньше: {formatTime(status.retryAt)}

}

Срок действия — до полуночи дня отмены по Минску. АВ/ВОКЗАЛ распознаётся как Автовокзал.

{error &&

{error}

} {data && (

На проверке: {pending.length}

{rows.map(row => (
{formatTime(row.published_at)} · {{ review: 'На проверке', published: 'Опубликовано', dismissed: 'Отклонено', expired: 'День отмены завершён' }[row.status]} Оригинал

{row.raw_text || 'Вложение без текста — откройте оригинал.'}

{row.status === 'review' && <> {row.reasons.map(reason =>

{reason}

)}
}
))} {!rows.length &&

{showHistory ? 'Сообщений об отменах пока нет.' : 'Нет сообщений, требующих проверки.'}

}
)} {editing && setEditing(null)} onSubmit={values => review(editing, values)} />}
); } function TelegramReviewForm({ row, routes, busy, onSubmit, onCancel }) { const publicationDate = new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Minsk', year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(row.published_at)); const [form, setForm] = useState({ action: 'publish', service_date: row.parsed?.serviceDate || publicationDate, route_id: row.parsed?.plans?.length === 1 ? row.parsed.plans[0].routeId : '', title: 'Отмена отдельных рейсов', message: row.raw_text.slice(0, 5000) }); const change = e => setForm({ ...form, [e.target.name]: e.target.value }); return (
{ e.preventDefault(); onSubmit(form); }}>

Проверка отмены