import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { api, type TourSummary } from '../api/client'; import type { Painting } from '../types'; import './ToursPage.css'; interface Props { onBack: () => void; } interface StopDraft { paintingId: number; title: string; artistName: string; year: number | null; thumbnailPath: string | null; body: string; } export default function ToursPage({ onBack }: Props) { const { t } = useTranslation('tours'); const [tours, setTours] = useState([]); const [selectedId, setSelectedId] = useState(null); const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [status, setStatus] = useState<'draft' | 'published'>('draft'); const [stops, setStops] = useState([]); const [searchQ, setSearchQ] = useState(''); const [searchHits, setSearchHits] = useState< Array<{ id: number; title: string; artistName: string; year: number | null; thumbnailPath: string | null }> >([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [newTitle, setNewTitle] = useState(''); const loadList = useCallback(async () => { setLoading(true); setError(null); try { const data = await api.listAdminTours(); setTours(data.tours); } catch (err) { setError(err instanceof Error ? err.message : t('loadFailed')); } finally { setLoading(false); } }, [t]); useEffect(() => { void loadList(); }, [loadList]); const openTour = async (id: number) => { setSelectedId(id); setError(null); try { const data = await api.getTour(id); setTitle(data.tour.title); setDescription(data.tour.description || ''); setStatus(data.tour.status); setStops( data.paintings.map((p: Painting) => ({ paintingId: p.id, title: p.title, artistName: p.artist_name || '', year: p.year ?? null, thumbnailPath: p.thumbnail_path || null, body: data.stopBodies[p.id] || '', })), ); } catch (err) { setError(err instanceof Error ? err.message : t('loadFailed')); } }; useEffect(() => { const handle = setTimeout(() => { void (async () => { if (searchQ.trim().length < 2) { setSearchHits([]); return; } try { const data = await api.search(searchQ.trim(), { types: 'painting', limit: 12 }); setSearchHits( data.results .filter((r) => r.type === 'painting') .map((r) => ({ id: r.id, title: r.title, artistName: r.artist_name, year: r.year, thumbnailPath: r.thumbnail_path, })), ); } catch { setSearchHits([]); } })(); }, 250); return () => clearTimeout(handle); }, [searchQ]); const createTour = async () => { const name = newTitle.trim(); if (!name) return; setSaving(true); setError(null); try { const { tour } = await api.createTour({ title: name, status: 'draft' }); setNewTitle(''); await loadList(); await openTour(tour.id); } catch (err) { setError(err instanceof Error ? err.message : t('loadFailed')); } finally { setSaving(false); } }; const saveMeta = async () => { if (!selectedId) return; setSaving(true); setError(null); try { await api.updateTour(selectedId, { title: title.trim(), description, status, coverPaintingId: stops[0]?.paintingId ?? null, }); await loadList(); } catch (err) { setError(err instanceof Error ? err.message : t('loadFailed')); } finally { setSaving(false); } }; const saveStops = async () => { if (!selectedId) return; setSaving(true); setError(null); try { await api.saveTourStops( selectedId, stops.map((s) => ({ paintingId: s.paintingId, body: s.body })), ); await api.updateTour(selectedId, { coverPaintingId: stops[0]?.paintingId ?? null, }); await loadList(); } catch (err) { setError(err instanceof Error ? err.message : t('loadFailed')); } finally { setSaving(false); } }; const deleteTour = async () => { if (!selectedId) return; if (!window.confirm(t('confirmDelete'))) return; setSaving(true); try { await api.deleteTour(selectedId); setSelectedId(null); setStops([]); await loadList(); } catch (err) { setError(err instanceof Error ? err.message : t('loadFailed')); } finally { setSaving(false); } }; const addStop = (hit: { id: number; title: string; artistName: string; year: number | null; thumbnailPath: string | null; }) => { if (stops.some((s) => s.paintingId === hit.id)) return; setStops((prev) => [ ...prev, { paintingId: hit.id, title: hit.title, artistName: hit.artistName, year: hit.year, thumbnailPath: hit.thumbnailPath, body: '', }, ]); setSearchQ(''); setSearchHits([]); }; const moveStop = (index: number, dir: -1 | 1) => { const next = index + dir; if (next < 0 || next >= stops.length) return; setStops((prev) => { const copy = [...prev]; const tmp = copy[index]; copy[index] = copy[next]; copy[next] = tmp; return copy; }); }; return (

{t('title')}

{error &&
{error}
}
{!selectedId ? (

{t('selectTour')}

) : ( <>