/**
* 반나절 산책 — 승차권 캐러셀.
*
* 표 한 장이 정거장 하나다. 절취선·펀치 구멍·완주 도장은 스탬프투어라는 실제 형식을 그대로 옮긴 것.
* ★ 링크는 만들지 않는다. searchQuery 만 보여준다 — 지어낸 주소를 링크하지 않는 이 레포의 규약.
*/
import {SectionBody, SectionFrame} from '../../primitives';
import type {SectionRenderProps} from '../../types';
import {parseSectionData, type CourseItem, type CourseStop} from '@o2o/shared';
import {
CarouselNav,
ITEM_ACCENT,
ITEM_BODY,
ITEM_BORDER,
ITEM_CARD,
ITEM_HEADING,
ITEM_INK,
ITEM_SURFACE,
ParseError,
PasteHint,
SourceLine,
useCarousel,
} from '../items/common';
import '../items/items.css';
function Ticket({
stop,
courseName,
index,
isLast,
}: {
stop: CourseStop;
courseName: string;
index: number;
isLast: boolean;
}) {
const no = String(stop.order ?? index + 1).padStart(2, '0');
return (
{courseName}
NO.{no}
{no}
{stop.name}
{stop.note && (
{stop.note}
)}
{stop.minutes ? `도보 ${stop.minutes}분` : '이동 시간 미정'}
{stop.searchQuery && 지도 검색 · {stop.searchQuery}}
{/* 펀치 구멍 — 표를 표로 만드는 자리 */}
{isLast && (
완주
도장
)}
);
}
function CourseRow({course}: {course: CourseItem}) {
const {ref, scrollBy} = useCarousel();
const stops = course.stops ?? [];
return (
{course.name}
{[course.duration, course.startsFrom ? `${course.startsFrom} 출발` : undefined, `${stops.length}곳`]
.filter(Boolean)
.join(' · ')}
{stops.length > 2 && (
scrollBy(-1)} onNext={() => scrollBy(1)} label={course.name} />
)}
{stops.length === 0 ? (
정거장이 아직 없습니다. JSON 의 stops 배열을 채워 주세요.
) : (
{stops.map((stop, index) => (
))}
)}
);
}
export function CourseTickets(props: SectionRenderProps) {
const {section, isSelected, onSelect} = props;
const parsed = parseSectionData(section.type, section.data);
return (
{parsed.title || section.name}
{(parsed.subtitle || section.description) && (
{parsed.subtitle || section.description}
)}
{parsed.error ? (
) : parsed.items.length === 0 ? (
) : (
{parsed.items.map((course, index) => (
))}
)}
);
}