`backend` 옆에 `front` 가 있을 이유가 없었다. negosium 의 negodata/front 를 그대로 베꼈고 그게 왜 front 인지는 따져보지 않았다 — 근거 없이 들여온 이름이라 바로잡는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uYhHQdssRubirPirrdJJC
164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
/**
|
|
* 지역 가이드 · 전체 — 날씨 카드 + 맛집 + 명소 + 월별 축제를 세로로 전부.
|
|
* "여기 오면 근처에 뭐가 있나"를 한 화면에서 다 보여주고 싶을 때.
|
|
*/
|
|
import {Calendar, MapPin, Sparkles, Utensils} from 'lucide-react';
|
|
import type {TemplateItem} from '@o2o/shared';
|
|
import {
|
|
EmptyStateNotice,
|
|
ListCard,
|
|
PlaceRow,
|
|
Pill,
|
|
SectionBody,
|
|
SectionFrame,
|
|
SectionHeading,
|
|
} from '../../primitives';
|
|
import type {SectionRenderProps} from '../../types';
|
|
import {LiveClock} from './LiveClock';
|
|
import type {FestivalItem, NearbyPlace} from './types';
|
|
|
|
const naverSearch = (q: string) =>
|
|
`https://search.naver.com/search.naver?query=${encodeURIComponent(q)}`;
|
|
|
|
function GroupHeading({
|
|
icon: Icon,
|
|
title,
|
|
note,
|
|
colors,
|
|
}: {
|
|
icon: typeof Utensils;
|
|
title: string;
|
|
note: string;
|
|
colors: TemplateItem['colors'];
|
|
}) {
|
|
return (
|
|
<div className="flex items-center justify-between">
|
|
<h3
|
|
className="serif-title flex items-center gap-2 text-base font-bold text-stone-900 sm:text-lg"
|
|
style={{color: colors.text}}
|
|
>
|
|
<Icon className="size-4 text-stone-600" style={{color: colors.primary}} />
|
|
<span>{title}</span>
|
|
</h3>
|
|
{/* 오른쪽 출처 문구는 부가 정보라 중립 회색 그대로 둔다. */}
|
|
<span className="text-[11px] text-stone-400">{note}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function LocalFull(props: SectionRenderProps) {
|
|
const {section, isSelected, onSelect, industryId, location, template} = props;
|
|
const isStay = industryId === 'stay';
|
|
const locName = location.split(' ')[0] || '주변';
|
|
// ★ 맛집·명소·축제는 전부 제주 애월 시연용 목록이다 — 실사업장은 빈 목록으로 떨어진다.
|
|
// (지역 큐레이션을 읽어올 백엔드 창구가 아직 없어 당분간 계속 빈다.)
|
|
// 지역 정보는 서버(local.local_contents)가 소유하고 캔버스 계약에 없다 — 시연용 목록을 깔지 않는다.
|
|
const foods: NearbyPlace[] = [];
|
|
const spots: NearbyPlace[] = [];
|
|
const festivals: FestivalItem[] = [];
|
|
|
|
return (
|
|
<SectionFrame section={section} isSelected={isSelected} onSelect={onSelect} tone="paper">
|
|
<SectionBody className="space-y-8">
|
|
<SectionHeading
|
|
variant="eyebrow"
|
|
eyebrow="AI Local Guide"
|
|
title={`AI ${locName} 실시간 가이드`}
|
|
subtitle="실시간 날씨와 인근 미식·명소·축제를 AI가 큐레이션합니다"
|
|
trailing={<LiveClock />}
|
|
colors={template.colors}
|
|
/>
|
|
|
|
<div className="space-y-3.5">
|
|
<GroupHeading
|
|
icon={Utensils}
|
|
title="숙소 인근 엄선 맛집 & 카페"
|
|
note="호스트 & 네이버 플레이스 연동"
|
|
colors={template.colors}
|
|
/>
|
|
{foods.length === 0 ? (
|
|
<EmptyStateNotice>주변 맛집·카페 추천은 아직 준비 중입니다.</EmptyStateNotice>
|
|
) : (
|
|
<ListCard>
|
|
{foods.map((place) => (
|
|
<PlaceRow
|
|
key={place.name}
|
|
name={place.name}
|
|
meta={place.distance}
|
|
description={place.description}
|
|
href={naverSearch(place.searchQuery)}
|
|
colors={template.colors}
|
|
/>
|
|
))}
|
|
</ListCard>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-3.5">
|
|
<GroupHeading
|
|
icon={MapPin}
|
|
title="주변 힐링 명소 & 해변"
|
|
note="차량 5~20분 거리"
|
|
colors={template.colors}
|
|
/>
|
|
{spots.length === 0 ? (
|
|
<EmptyStateNotice>주변 명소 추천은 아직 준비 중입니다.</EmptyStateNotice>
|
|
) : (
|
|
<ListCard>
|
|
{spots.map((spot) => (
|
|
<PlaceRow
|
|
key={spot.name}
|
|
name={spot.name}
|
|
meta={spot.duration}
|
|
description={spot.description}
|
|
href={naverSearch(spot.searchQuery)}
|
|
actionLabel="상세정보"
|
|
colors={template.colors}
|
|
/>
|
|
))}
|
|
</ListCard>
|
|
)}
|
|
</div>
|
|
|
|
{isStay && (
|
|
<div className="space-y-3.5">
|
|
<GroupHeading
|
|
icon={Calendar}
|
|
title="월별 축제 & 문화 행사"
|
|
note="사계절 캘린더"
|
|
colors={template.colors}
|
|
/>
|
|
{festivals.length === 0 ? (
|
|
<EmptyStateNotice>월별 축제 안내는 아직 준비 중입니다.</EmptyStateNotice>
|
|
) : (
|
|
<ListCard>
|
|
{festivals.map((fest) => (
|
|
<PlaceRow
|
|
key={fest.name}
|
|
name={fest.name}
|
|
description={fest.description || fest.period}
|
|
href={naverSearch(fest.searchQuery)}
|
|
actionLabel="검색"
|
|
leading={
|
|
<>
|
|
<Pill tone="accent">{fest.month}</Pill>
|
|
{fest.isFeatured && (
|
|
<Pill tone="good">
|
|
<Sparkles className="size-2.5" />
|
|
대표축제
|
|
</Pill>
|
|
)}
|
|
</>
|
|
}
|
|
colors={template.colors}
|
|
/>
|
|
))}
|
|
</ListCard>
|
|
)}
|
|
</div>
|
|
)}
|
|
</SectionBody>
|
|
</SectionFrame>
|
|
);
|
|
}
|