붙일 자리를 잘못 잡았다. /discovery 는 INFINITH 제품의 진단 화면이고 병원마다 쓰는 범용 도구다. 외국인 환자가 보는 페이지는 병원 서포터즈 사이트다. 그쪽으로 옮겼다. 옮긴 것 - src/components/discovery/MedicalTourismPanel.tsx, src/types/medicalTourism.ts, src/data/medicalTourism.json 삭제. DiscoveryReportPage 원복. - supporters/src/pages/en/stay.astro 신설. 독립 URL 과 자체 WebPage JSON-LD 를 가져 AI 인용 단위가 된다. sitemap 에 등록하고 /visit 에서 hreflang 링크로 잇는다. - 수집 스크립트 출력을 supporters/src/data/medicalTourism.json 으로 바꿨다. 영문 페이지 지원 - Base.astro 에 lang prop 추가(기본 ko). html lang · og:locale · 머리말 · 꼬리말이 따라 바뀐다. 레이아웃을 하나로 유지해 한국어 페이지 동작은 그대로다. - 의료광고 고지(지원 관계·부작용)는 규정 대상이라 임의로 번역하지 않는다. 병원이 sponsorNoticeEn / sideEffectNoticeEn 을 확정하기 전에는 한국어 원문을 그대로 싣고 영문 문구가 확인 대기임을 밝힌다. - factSheet 에 nameEn / shortNameEn / shortNameEnSource 추가. 뷰성형외과 값의 근거는 병원 자신의 영문 도메인 viewplasticsurgery.com 이며 한국관광공사 등재명도 같다. 페이지가 만들지 않는 것 - 평점·리뷰·영업시간: TourAPI 미제공이라 싣지 않는다. - 식당의 회복 적합성: 의학 판단이라 병원이 확정한 영문 문구(site.json dietGuideEn)만 싣는다. 확인 전에는 어떤 식당도 적합으로 표시하지 않는다고 페이지에 적는다. - 회복 단계 일수: 병원 확정값(recoveryStagesEn). 단계는 이동 거리로만 정의한다. - 하단에 이 페이지가 답하지 못하는 것 네 가지를 그대로 적었다. 템플릿 동기화 - templates/supporters-astro 에 같은 변경을 넣되 데이터는 빈 스캐폴드로 둔다. 뷰성형외과 factSheet 와 관광 데이터가 새 병원 빌드에 새어 들어가면 안 된다. 빈 데이터로 빌드해 6개 범주가 "No English listings" 로 나오는 것까지 확인했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
118 lines
7.2 KiB
Plaintext
118 lines
7.2 KiB
Plaintext
---
|
||
// 외국인 환자용 체류 안내. 병원 좌표를 중심으로 숙박·식사·회복·관광·행사를 이동 반경 순으로 놓는다.
|
||
// 데이터는 한국관광공사 TourAPI 영문(EngService2)에서 scripts/fetch_medical_tourism.py 가 미리 수집한
|
||
// src/data/medicalTourism.json 이다. 값을 만들지 않는다.
|
||
// - 평점·리뷰·영업시간은 TourAPI 가 주지 않아 싣지 않는다.
|
||
// - 수술 전후 식이 적합성은 의학 판단이라 병원이 확정한 문구만 싣는다(site.json dietGuideEn).
|
||
// - 회복 단계가 며칠째인지도 병원 확정값이다(site.json recoveryStagesEn). 단계는 이동 거리로만 정의한다.
|
||
import Base from '../../layouts/Base.astro';
|
||
import { fact, site as S, SITE_NAME, clinicSchema, reservationUrl, has } from '../../lib';
|
||
import data from '../../data/medicalTourism.json';
|
||
|
||
const f = fact as Record<string, any>;
|
||
const Sx = S as Record<string, any>;
|
||
const clinic = f.shortNameEn || f.nameEn || f.shortName || 'the clinic';
|
||
const site = Astro.site!.toString().replace(/\/$/, '');
|
||
const T = data as any;
|
||
|
||
const GROUPS: Array<{ key: string; label: string; blurb: string }> = [
|
||
{ key: 'stay', label: 'Where to stay', blurb: 'Hotels and guesthouses near the clinic, for the days when visits are frequent.' },
|
||
{ key: 'restaurant', label: 'Where to eat', blurb: 'Restaurants within walking or short-ride distance.' },
|
||
{ key: 'wellness', label: 'Rest and recovery', blurb: 'Spas and jjimjilbang listed by the Korea Tourism Organization.' },
|
||
{ key: 'attraction', label: 'Places to see', blurb: 'Sights within the city, once you are able to travel further.' },
|
||
{ key: 'culture', label: 'Culture', blurb: 'Museums, theatres and halls.' },
|
||
{ key: 'shopping', label: 'Shopping', blurb: 'Department stores, markets and streets.' },
|
||
];
|
||
|
||
const dist = (m: number | null) => (m === null ? '' : m < 1000 ? `${m} m` : `${(m / 1000).toFixed(1)} km`);
|
||
const dt = (d: string | null) => (d && d.length === 8 ? `${d.slice(0, 4)}.${d.slice(4, 6)}.${d.slice(6, 8)}` : '');
|
||
|
||
// 병원이 확정하기 전에는 비운다. 임의로 채우지 않는다.
|
||
const dietGuide: string = Sx.dietGuideEn || '';
|
||
const recoveryStages: string = Sx.recoveryStagesEn || '';
|
||
|
||
const ld = [
|
||
{ '@type': 'WebPage', '@id': `${site}/en/stay#page`, name: `Your stay near ${clinic}`, url: `${site}/en/stay`, inLanguage: 'en', isPartOf: { '@id': `${site}/#website` }, about: { '@id': `${f.url}/#clinic` }, publisher: { '@type': 'Organization', name: SITE_NAME, url: site } },
|
||
clinicSchema(site),
|
||
];
|
||
---
|
||
<Base
|
||
lang="en"
|
||
title={`Your stay near ${clinic} — hotels, food, recovery, and what to see`}
|
||
description={`Practical guide for international patients: where to stay, eat and rest near ${clinic} in Gangnam, Seoul, plus sights and festivals, listed by distance from the clinic. Source: Korea Tourism Organization.`}
|
||
jsonLd={ld}
|
||
>
|
||
<article class="wrap" style="padding-bottom:4rem">
|
||
<header class="article-head">
|
||
<div class="eyebrow">For international patients</div>
|
||
<h1 class="serif">Your Stay in Seoul</h1>
|
||
<p style="color:var(--slate-600);margin-top:0.6rem">
|
||
<strong style="color:var(--primary-900)">Where to stay, eat and rest around {clinic}.</strong>
|
||
Everything below is listed by distance from the clinic at {f.address?.fullEn || f.address?.full}.
|
||
Addresses and names come from the Korea Tourism Organization, not from us.
|
||
For the clinic's own address, hours and phone, see <a href="/clinic">Clinic information</a>.
|
||
</p>
|
||
</header>
|
||
|
||
<h2>Before you use this page</h2>
|
||
<div class="table-wrap"><table><tbody>
|
||
<tr><th style="width:32%">Reference point</th><td>{clinic} — {T.meta.origin.lat}, {T.meta.origin.lng}</td></tr>
|
||
<tr><th>Source</th><td>Korea Tourism Organization, TourAPI EngService2 — collected {T.meta.fetchedAt.slice(0, 10)}</td></tr>
|
||
<tr><th>Search radius</th><td>Stay and food 5 km · sights, culture, shopping 10 km · festivals {T.meta.festivalRadiusKm} km</td></tr>
|
||
</tbody></table></div>
|
||
|
||
<h2>Planning your days</h2>
|
||
<p>
|
||
How many days each stage lasts depends on your procedure, and only your doctor can tell you that.
|
||
The stages below are defined by <strong>distance from the clinic</strong>, nothing else.
|
||
</p>
|
||
{recoveryStages
|
||
? <p>{recoveryStages}</p>
|
||
: <p class="pending-note">The day-by-day schedule is pending confirmation by {clinic}. Ask at your consultation.</p>}
|
||
<div class="table-wrap"><table><thead><tr><th>Stage</th><th>Distance from clinic</th><th>What fits here</th></tr></thead><tbody>
|
||
<tr><td>Frequent visits</td><td>within 1 km</td><td>Hotels and food you can reach on foot</td></tr>
|
||
<tr><td>Short outings</td><td>1 – 3 km</td><td>Restaurants, spas</td></tr>
|
||
<tr><td>Around the city</td><td>3 – 10 km</td><td>Sights, culture, shopping</td></tr>
|
||
</tbody></table></div>
|
||
|
||
{GROUPS.map((g) => {
|
||
const items = (T.places[g.key] ?? []) as any[];
|
||
return (
|
||
<section>
|
||
<h2>{g.label}</h2>
|
||
<p>{g.blurb}</p>
|
||
{g.key === 'restaurant' && (
|
||
dietGuide
|
||
? <p><strong>What {clinic} advises after surgery.</strong> {dietGuide}</p>
|
||
: <p class="pending-note">What you may eat after surgery is a medical question. {clinic} has not yet published English guidance, so we do not mark any restaurant as suitable. Please ask at your consultation.</p>
|
||
)}
|
||
{items.length === 0
|
||
? <p class="pending-note">No English listings within this radius.</p>
|
||
: <div class="table-wrap"><table><thead><tr><th style="width:44%">Name</th><th style="width:14%">Distance</th><th>Address</th></tr></thead><tbody>
|
||
{items.map((p) => <tr><td>{p.title}</td><td>{dist(p.distanceM)}</td><td>{p.address}</td></tr>)}
|
||
</tbody></table></div>}
|
||
</section>
|
||
);
|
||
})}
|
||
|
||
<h2>Festivals and events while you are here</h2>
|
||
<p>Currently running or starting soon, within {T.meta.festivalRadiusKm} km of the clinic.</p>
|
||
{T.festivals.length === 0
|
||
? <p class="pending-note">No listings.</p>
|
||
: <div class="table-wrap"><table><thead><tr><th style="width:40%">Event</th><th style="width:22%">Dates</th><th style="width:12%">Distance</th><th>Where</th></tr></thead><tbody>
|
||
{T.festivals.map((e: any) => <tr><td>{e.title}</td><td>{dt(e.startDate)} – {dt(e.endDate)}</td><td>{e.distanceKm} km</td><td>{e.address}</td></tr>)}
|
||
</tbody></table></div>}
|
||
|
||
<h2>What this page does not tell you</h2>
|
||
<ul>
|
||
<li>No ratings, review counts or opening hours. The tourism data does not carry them, and we do not guess.</li>
|
||
<li>No claim that any restaurant suits your recovery. That is for {clinic} to say.</li>
|
||
<li>English tourism listings around Gangnam are thin. Some categories hold only a handful of entries.</li>
|
||
<li>We are not a travel agency and take no booking or commission. Links and names are for reference.</li>
|
||
</ul>
|
||
|
||
<p style="margin-top:2rem">{has(reservationUrl) && <a class="btn primary" href={reservationUrl} rel="noopener">Book a consultation with {clinic}</a>}</p>
|
||
<p class="disclosure">{f.sideEffectNoticeEn || f.sideEffectNotice}</p>
|
||
</article>
|
||
</Base>
|