o2o-infinith-demo/templates/supporters-astro/src/pages/clinic.astro
Haewon Kam 622441e6aa feat(supporters): 템플릿화 — 병원 고유값을 데이터로 분리하고 templates/supporters-astro 내보내기
- src/data/site.json 신설(매체 이름·로고·홈 큐레이션), factSheet에 kind·areaLabel·transitShort·transitNote·reservationUrl·doctorsUrl·youtube.title 추가
- lib.ts: PENDING('확인 대기')·v()·has()·surface()·editor() 도입, 스키마는 빈 값을 빼고 생성
- 페이지·컴포넌트 15개에서 뷰성형외과 하드코딩 제거, 빈 데이터 empty-state (글 0편·영상 0건·뉴스 0건·이미지 없음)
- 검증: 28페이지 표시 텍스트·구조 지문 이전 빌드와 동일(홈 title 마침표 1건만 정정), JSON-LD 차이는 빈 sameAs 제거뿐
- scripts/export_template.mjs + scripts/template/data(빈 데이터 세트) → templates/supporters-astro (빈 데이터로 10페이지 0오류 빌드, 게이트 통과)
- 게이트 픽스처를 자체 authors로 분리해 템플릿에서도 테스트 통과, 구조 기준선의 글 상세 페이지는 사전순 첫 글로 동적 선택

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 14:07:14 +09:00

46 lines
4.8 KiB
Plaintext

---
import Base from '../layouts/Base.astro';
import FactBlock from '../components/FactBlock.astro';
import { fact, site as S, clinicSchema, physicianSchema, physician, surface, v, has } from '../lib';
import Gallery from '../components/Gallery.astro';
const f = fact as Record<string, any>;
const clinic = v(f.shortName, '병원');
const docIds = (S.clinicDoctors ?? []).filter((id) => physician(id));
const docs = docIds.map(physician);
const site = Astro.site!.toString().replace(/\/$/, '');
const ld = [clinicSchema(site), ...docIds.slice(0, 1).map(physicianSchema)];
const yt = surface('youtube'); const gu = surface('gangnamunni'); const blog = surface('blog');
---
<Base title={`${clinic} 기본 정보`} description={`${v(f.name, clinic)}의 주소·전화·진료시간·층별 구성·진료과·공개 이력을 한 곳에 정리했습니다. 방문 전에 확인하세요.`} jsonLd={ld}>
<article class="wrap" style="padding-bottom:4rem">
<header class="article-head"><div class="eyebrow">Fact Sheet</div><h1 class="serif">Fact Sheet</h1><p style="color:var(--slate-600);margin-top:0.6rem"><strong style="color:var(--primary-900)">{clinic} 기본 정보.</strong> 주소, 전화, 진료시간, 층별 안내, 의료진을 한 곳에 모았습니다. 항목마다 어디서 확인한 정보인지 출처를 적어 두었습니다.</p></header>
<FactBlock />
<h2>진료시간</h2>
<div class="table-wrap"><table><thead><tr><th>요일</th><th>진료</th></tr></thead><tbody>{(f.hours?.rows ?? []).map((r: any) => <tr><td>{r.days}</td><td>{r.open ? `${r.open} ~ ${r.close}` : r.note}</td></tr>)}{!(f.hours?.rows ?? []).length && <tr><td colspan="2">{v(null)}</td></tr>}</tbody></table></div>
<p style="font-size:0.88rem;color:var(--slate-500)">출처: {has(f.hours?.source) ? <a href={f.hours.source} rel="noopener">{f.hours.sourceLabel}</a> : v(null)}. 진료시간은 바뀔 수 있으니 방문 전 전화로 한 번 더 확인하세요.</p>
<h2>건물과 층별 구성</h2>
{has(S.buildingImage?.src) && <figure class="figure"><img src={S.buildingImage.src} alt={S.buildingImage.alt} /><figcaption>{S.buildingImage.caption}</figcaption></figure>}
<p>{has(f.building?.summary) ? `${f.building.summary}입니다.` : `건물 규모 ${v(null)}.`}</p>
<div class="table-wrap"><table><thead><tr><th>층</th><th>용도</th></tr></thead><tbody>{(f.building?.floors ?? []).map((fl: any) => <tr><td>{fl.floor}</td><td>{fl.use}</td></tr>)}{!(f.building?.floors ?? []).length && <tr><td colspan="2">{v(null)}</td></tr>}</tbody></table></div>
{S.clinicGallery?.length > 0 && <Gallery cols={3} items={S.clinicGallery} />}
<h2>진료과와 진료 분야</h2>
<p>진료과: {(f.departments ?? []).join(', ') || v(null)}</p>
<p>진료 분야: {(f.specialties ?? []).join(', ') || v(null)}</p>
<h2>글과 영상에서 설명하는 의료진</h2>
{!docs.length && <p style="color:var(--slate-500)">의료진 정보는 병원이 확정한 담당 원장부터 싣습니다. {v(null)}.</p>}
{docs.map((p) => (<div class="doctor-strip">{p.image && <img src={p.image} alt={`${p.name} ${p.title}`} />}<div><div class="name">{p.name} <span style="font-weight:500;color:var(--slate-600)">{p.title}</span></div><p class="creds">{p.credentials.slice(0,3).join(' · ')} <a href={p.url} rel="noopener">프로필</a></p></div></div>))}
<p style="font-size:0.88rem;color:var(--slate-500)">전체 의료진 {v(gu.doctors)}명(강남언니 등록 기준)은 {has(f.doctorsUrl) ? <a href={f.doctorsUrl} rel="noopener">홈페이지 의료진 소개</a> : '홈페이지 의료진 소개'}에서 확인할 수 있습니다.</p>
<h2>병원이 공개한 이력</h2>
<ul>{(f.recognitions ?? []).map((r: any) => <li>{r.text} <a href={r.source} rel="noopener" style="font-size:0.85rem">(출처)</a></li>)}{!(f.recognitions ?? []).length && <li>{v(null)}</li>}</ul>
<h2>공식 채널</h2>
<ul>
<li>홈페이지: {has(f.url) ? <a href={f.url} rel="noopener">{f.url}</a> : v(null)}{has(f.urlEn) && <> (영문 <a href={f.urlEn} rel="noopener">{f.urlEn}</a>)</>}</li>
<li>유튜브: {has(yt.url) ? <a href={yt.url} rel="noopener">{yt.handle}</a> : v(null)} · 구독자 {v(yt.subscribers)} · 영상 {v(yt.videos)}개 ({v(yt.checked)} 기준)</li>
<li>강남언니: {has(gu.url) ? <a href={gu.url} rel="noopener">병원 페이지</a> : v(null)} · 평점 {v(gu.rating)} · 후기 {v(gu.reviews)}건 · 의료진 {v(gu.doctors)}명</li>
<li>카카오톡 채널: {has(f.kakao) ? <a href={f.kakao} rel="noopener">상담 채널</a> : v(null)}</li>
<li>블로그: {has(blog.url) ? <a href={blog.url} rel="noopener">{blog.url.replace(/^https?:\/\//, '').replace(/\/$/, '')}</a> : v(null)}{has(blog.note) && <> ({blog.note})</>}</li>
</ul>
<p class="disclosure">{fact.sideEffectNotice}</p>
</article>
</Base>