import React, { useEffect, useRef, useState } from 'react' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' import remarkBreaks from 'remark-breaks' import { X, Pencil, Copy, Trash2, MapPin, Link2, Plus, ExternalLink, Check, Tag } from 'lucide-react' import type { CollectionPlace, CollectionStatus, CollectionLink } from '@trek/shared' import type { Category, TranslationFn } from '../../types' import MarkdownToolbar from '../Journey/MarkdownToolbar' import { entityGradient } from '../../utils/gradients' import { getCategoryIcon } from '../shared/categoryIcons' import { STATUS_META, STATUS_ORDER, normalizeLinkUrl } from '../../pages/collections/collectionsModel' import { useToast } from '../shared/Toast' import { getApiErrorMessage } from '../../types' function linkHost(url: string): string { try { return new URL(url).hostname.replace(/^www\./, '') } catch { return url } } interface CollectionPlaceDetailProps { place: CollectionPlace canEdit: boolean categories: Category[] /** When set, dock the sheet over that column (desktop split) instead of centred. */ anchorRect?: { left: number; width: number } | null onClose: () => void onSetStatus: (status: CollectionStatus) => void onSave: (patch: { name?: string; description?: string | null; links?: CollectionLink[]; category_id?: number | null }) => Promise onCopyToTrip: () => void onRemove: () => void t: TranslationFn } function StatusSegment({ status, onSet, t }: { status: CollectionStatus; onSet: (s: CollectionStatus) => void; t: TranslationFn }): React.ReactElement { return (
{STATUS_ORDER.map(s => { const Icon = STATUS_META[s].icon const on = status === s return ( ) })}
) } /** * Bottom detail sheet for a saved place — an opaque, clearly-sectioned card * (cover → meta → status → description → links) docked over the list column. * Read mode renders the description as markdown + link chips; edit mode swaps in * name / category / markdown description / links, saving via updatePlace. Status * is an always-live segmented control (auto-saves). */ export default function CollectionPlaceDetail({ place, canEdit, categories, anchorRect, onClose, onSetStatus, onSave, onCopyToTrip, onRemove, t, }: CollectionPlaceDetailProps): React.ReactElement { const toast = useToast() const [editing, setEditing] = useState(false) const [name, setName] = useState(place.name) const [categoryId, setCategoryId] = useState(place.category_id ?? null) const [description, setDescription] = useState(place.description ?? '') const [links, setLinks] = useState(place.links ?? []) const [saving, setSaving] = useState(false) const descRef = useRef(null) // Reset only when a DIFFERENT place is opened (keyed on id, not on every field). useEffect(() => { setEditing(false) setName(place.name) setCategoryId(place.category_id ?? null) setDescription(place.description ?? '') setLinks(place.links ?? []) // eslint-disable-next-line react-hooks/exhaustive-deps }, [place.id]) const banner = place.image_url const setLink = (i: number, patch: Partial) => setLinks(links.map((l, idx) => (idx === i ? { ...l, ...patch } : l))) const resetForm = () => { setEditing(false); setName(place.name); setCategoryId(place.category_id ?? null); setDescription(place.description ?? ''); setLinks(place.links ?? []) } const save = async () => { const cleanLinks = links.map(l => ({ label: l.label?.trim() || undefined, url: normalizeLinkUrl(l.url) })).filter(l => l.url) setSaving(true) try { await onSave({ name: name.trim() || place.name, description: description.trim() || null, links: cleanLinks, category_id: categoryId }) setEditing(false) } catch (err) { toast.error(getApiErrorMessage(err, t('common.error'))) } finally { setSaving(false) } } const dockStyle = anchorRect ? { left: anchorRect.left, width: anchorRect.width, transform: 'none' as const } : undefined const CatIcon = getCategoryIcon(place.category?.icon) return (
e.stopPropagation()}>
{banner && }
{editing ? setName(e.target.value)} className="col-detail-name-input" autoFocus aria-label={t('collections.listName')} /> :

{place.name}

}
{/* Meta (view only) */} {!editing && (place.category?.name || place.address) && (
{place.category?.name && ( {place.category.name} )} {place.address && {place.address}}
)} {/* Status — always live */} {editing ? (
{/* Category */}
{t('collections.category')}
{categories.map(cat => { const Icon = getCategoryIcon(cat.icon ?? undefined) const on = categoryId === cat.id return ( ) })}
{/* Description */}
{t('collections.description')}