import {Phone} from 'lucide-react'; import {useSite} from '@site/lib/site-context'; import {bookingActionLabel, bookingLinks, essentialRows, RULE_FACT_KEYS} from '@site/lib/derive'; import {formatKoreanDate} from '@site/lib/format'; import type {InfoRow} from '@site/lib/derive'; import {Section} from '@site/lib/ui'; /** * 이용 정보 표 전체. * * AnswerBlock 이 상위 6개만 보여준다면 여기는 확인된 것 전부를 낸다. * ★ "확인 안 된 항목이 N개 있다"를 숨기지 않는다 — 없는 척하면 * 손님이 다른 데서 틀린 값을 찾아 온다. 있다고 말하고 문의로 보낸다. * * ★ 이용 규정을 여기로 들여왔다 (2026-09-03) * 따로 '이용 규정' 섹션이 있었는데, 그 줄(체크인·체크아웃·취소규정·취사·반려동물·흡연· * 추가요금)이 **이 표의 완전한 부분집합**이었다 — 같은 fact 를 두 섹션이 두 번 그렸다. * 나눈 이유는 "취소·환불은 몰랐다가 클레임이 나니 따로 세운다"였는데, 양쪽에 다 나오니 * 그 목적도 못 이루고 중복만 남았다. 한 섹션 안에서 **규정만 따로 묶어** 눈에 띄게 한다. * * ★ 카드 격자를 버리고 **줄 목록 하나**로 통일했다 (2026-09-07, 사장님 지시: "박스라서 가독성이 떨어진다") * 값이 짧으면 항목마다 테두리 상자를 세워 2~4열로 벌렸는데, 상자가 상자 안에 들어가 * 테두리가 두 겹이 되고 — 정작 읽어야 할 것은 라벨과 값 한 쌍뿐인데 — 눈이 상자를 먼저 셌다. * 값의 길이로 열 수를 바꾸는 규칙도 같이 버린다. 같은 섹션 안에서 규정 덩이는 목록, * 시설 덩이는 격자로 갈리니 한 화면에 두 가지 문법이 섰다. * 이용안내는 훑는 화면이다 — 라벨이 한 열에 세로로 정렬돼야 값끼리 비교가 된다. /** * 규정으로 묶을 항목의 이름. * * ★ `essentialRows` 는 key 가 아니라 사람이 읽는 label 을 준다(업종 스키마가 붙인 이름). * 그래서 key 목록이 아니라 label 로 가른다 — 스키마가 이름을 바꾸면 여기도 같이 고친다. */ const RULE_LABELS = new Set([ '체크인 시간', '체크아웃 시간', '취소·환불 규정', '취사 가능', '반려동물 동반', '흡연 가능', '인원 추가 요금', ]); export function EssentialInfoSection() { const payload = useSite(); const rows = essentialRows(payload); const hiddenCount = payload.facts.filter( (fact) => fact.scope === 'place' && !rows.some((row) => row.label === fact.label), ).length; if (rows.length === 0) return null; /** * 규정과 나머지를 가른다. * * ★ 순서를 뒤집지 않는다 — 규정이 먼저다. 예약 전에 알아야 클레임이 안 난다. */ const ruleRows = rows.filter((row) => RULE_LABELS.has(row.label)); const otherRows = rows.filter((row) => !RULE_LABELS.has(row.label)); return (
{hiddenCount > 0 ? `확인 중인 항목 ${hiddenCount}개는 표시하지 않았습니다. 필요하시면 전화로 문의해 주세요.` : '모든 항목이 사업자 확인을 거쳤습니다.'} {formatKoreanDate(payload.site.updatedAt)} 기준 } >
{ruleRows.length > 0 && } {otherRows.length > 0 && }
); } /** * 줄 목록 한 덩이. * * ★ 값이 길면 카드 격자가 아니라 **표**다. 1열 격자에 카드를 쌓으면 카드 하나가 * 1200px 폭을 가로지르고 오른쪽이 통째로 빈다 — 국내 사이트의 이용안내는 예외 없이 * 한 판 안의 줄 목록이다. * ★ 규정 덩이(emphasis)는 강조색 선을 왼쪽에 세운다. 몰랐다가 클레임이 나는 항목이다. */ function Rows({title, rows, emphasis}: {title: string; rows: InfoRow[]; emphasis?: boolean}) { return (

{emphasis && ( )} {title}

{/* ★ 테두리는 판 하나에만 준다. 줄 사이는 선(divide)이지 상자가 아니다. */}
{rows.map((row) => (
{row.label}
{/* measure: 긴 규정 문장이 화면 끝까지 늘어나면 다음 줄 첫 글자를 못 찾는다. */}
{row.value} {row.note && {row.note}}
))}
); } /** * 예약 창구 — 전화와 확정 채널. * * ★ 따로 서 있던 '예약 안내' 섹션을 이 자리로 들여왔다. 규정을 읽은 **바로 그 자리**에 * 누를 것이 있어야 한다 — 아래로 한 번 더 내려가야 하면 절반은 안 내려간다. * ★ 확정된 채널만 나간다. 확정 전 URL 은 동명 업소의 예약 페이지일 수 있다. */ function BookingRow() { const payload = useSite(); const links = bookingLinks(payload); const phone = payload.place.phone; if (!phone && links.length === 0) return null; return (

{payload.place.name} 예약은 아래로 받습니다.

{phone && ( {phone} )} {links.map((link) => ( {bookingActionLabel(link)} ))}
); }