diff --git a/src/App.tsx b/src/App.tsx
index 83cc273..a0df455 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,3 +1,4 @@
+import { useEffect } from 'react';
import { Outlet, useLocation } from 'react-router';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
@@ -8,6 +9,15 @@ export default function App() {
const location = useLocation();
const isLoadingPage = location.pathname.startsWith('/report/loading');
+ // 라우트가 바뀌어도 브라우저가 스크롤 위치를 그대로 들고 온다. 진단 리포트처럼 긴 페이지를
+ // 내려 보다가 "다음 단계"를 누르면, 새로 연 페이지가 그 위치(짧으면 하단 빈 공간)에서 시작해
+ // 항상 위부터 보게 되지 않는다. 라우트 전환마다 맨 위로 되돌린다.
+ // 해시(#breakdown 같은 인앱 앵커)가 있으면 그 앵커로 가려는 의도이므로 건드리지 않는다.
+ useEffect(() => {
+ if (location.hash) return;
+ window.scrollTo(0, 0);
+ }, [location.pathname, location.hash]);
+
return (
{!isLoadingPage && }
diff --git a/src/components/ClinicNav.tsx b/src/components/ClinicNav.tsx
index 8cfe02a..4965e34 100644
--- a/src/components/ClinicNav.tsx
+++ b/src/components/ClinicNav.tsx
@@ -15,6 +15,7 @@ type Step = { key: string; label: string; to: (id: string) => string; match: Reg
const STEPS: Step[] = [
{ key: 'report', label: '진단 리포트', to: (id) => `/discovery/${id}`, match: /^\/discovery\/[^/]+/ },
+ { key: 'actions', label: '개선 제안', to: (id) => `/actions/${id}`, match: /^\/actions\/[^/]+/ },
{ key: 'build', label: '사이트 빌드', to: (id) => `/build/${id}`, match: /^\/build\/[^/]+/ },
{ key: 'images', label: '사진 확인', to: (id) => `/images/${id}`, match: /^\/images\/[^/]+/ },
{ key: 'inputs', label: '확인 항목', to: (id) => `/supporters/${id}`, match: /^\/supporters\/[^/]+/ },
@@ -32,6 +33,7 @@ export default function ClinicNav({ clinicId }: { clinicId: string }) {
const { pathname } = useLocation();
const currentIndex = STEPS.findIndex((s) => s.match.test(pathname));
const prev = currentIndex > 0 ? STEPS[currentIndex - 1] : null;
+ const next = currentIndex > -1 && currentIndex < STEPS.length - 1 ? STEPS[currentIndex + 1] : null;
return (
<>
@@ -87,7 +89,11 @@ export default function ClinicNav({ clinicId }: { clinicId: string }) {
{/* 이전 단계로. 상단 절차 메뉴는 좁은 화면에서 가로 스크롤 안으로 밀려 눈에 안 들어온다.
- 되돌아가는 길은 늘 같은 자리에 있어야 한다. 첫 단계에서는 돌아갈 곳이 없어 띄우지 않는다. */}
+ 되돌아가는 길은 늘 같은 자리에 있어야 한다. 첫 단계에서는 돌아갈 곳이 없어 띄우지 않는다.
+
+ 다음 단계로 가는 길도 같은 이유로 여기에 둔다. 화면마다 따로 심으면 빠지는 곳이 생긴다.
+ 실제로 사이트 빌드 화면은 빌드가 끝났을 때만 다음 단계 카드를 그려서, 빌드 중이거나
+ 멈췄을 때는 상단 메뉴 말고는 넘어갈 길이 없었다. 마지막 단계에서는 갈 곳이 없어 띄우지 않는다. */}
{prev && (
)}
+
+ {next && (
+
+
+
+ 다음 단계
+ {next.label}
+
+
+
+
+
+
+ )}
>
);
}
diff --git a/src/components/discovery/ActionPlanSection.tsx b/src/components/discovery/ActionPlanSection.tsx
new file mode 100644
index 0000000..9033afa
--- /dev/null
+++ b/src/components/discovery/ActionPlanSection.tsx
@@ -0,0 +1,67 @@
+/**
+ * 개선 제안(/actions/:id) 1부: 우선순위별 실행 항목.
+ * 원래 진단 리포트 안의 "Action Plan" 섹션이었다. 진단(증거)과 처방(할 일)은 읽는 목적이 달라
+ * 별도 단계로 뗐다. 내용은 그대로다.
+ */
+import { motion } from 'motion/react';
+import { SectionWrapper } from '../report/ui/SectionWrapper';
+import { Tag } from './Tag';
+import type { ActionPriority, DiscoveryResult } from '../../types/discovery';
+
+const PRIORITY_STYLE: Record = {
+ P0: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]',
+ P1: 'bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]',
+ P2: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]',
+};
+
+const PRIORITY_HEADING: Record = {
+ P0: '즉시: 크롤러 접근·엔티티·측정',
+ P1: '4주 내: 콘텐츠 구조',
+ P2: '지속: 신선도·시딩',
+};
+
+export function ActionPlanSection({ result }: { result: DiscoveryResult }) {
+ return (
+
+
+ {(['P0', 'P1', 'P2'] as ActionPriority[]).map((p) => (
+
-
+ {/* 개선 제안(Action Plan)·하지 않을 것·빌드 제안·확인 항목 패널은 /actions/:id 로 옮겼다.
+ 증거(진단)와 처방(할 일)은 읽는 목적이 달라 여기 한 페이지에 두면 스크롤만 길어진다.
+ 확인 항목 패널은 /supporters/:id 라는 전용 단계가 이미 있어 여기서 다시 보여줄 필요가 없다. */}
- {/* ── 4b. 진단 요약 → 개선 계획 → 빌드 (dark) ── */}
- n + c.verifiedCount, 0),
- total: AEO_GEO_RUBRIC.criteria.length,
- }}
- />
-
- {/* ── 5. 하지 않을 것 + 측정 조건 (dark) ── */}
-
-
-
-
하지 않을 것
-
- {result.notDoing.map((n) => (
-
-
-
-
{n.item}
-
{n.reason}
-
-
- ))}
-
-
-
-
측정 조건
-
- {result.conditions.map((c) => (
-
-
-
{c}
-
- ))}
-
-
-
- 점수는 검증된 항목만으로 정규화했습니다. 미검증 항목(강남언니·GBP·GA4·로그)을
- 확인하면 점수가 오를 수도, 내릴 수도 있습니다. 이 리포트는 답변엔진 노출을 보장하지 않으며,
- 크롤러 접근·엔티티·콘텐츠 구조라는 전제조건의 충족 여부를 진단합니다.
-
-
-
-
-
-
- {/* ── 5b. 서포터즈 자동 빌드 · 병원 확인 항목 (light, v2 §7-5) ── */}
-
-
- {/* ── 6. 채점 기준표 (light) ── */}
+ {/* ── 4. 채점 기준표 (light) ── */}
;
}
-function Tag({ children }: { key?: string; children: ReactNode }) {
- return (
-
- {children}
-
- );
-}
-
function RubricRows({ categoryName, criteria }: { key?: string; categoryName: string; criteria: RubricCriterion[] }) {
return (
<>
diff --git a/src/pages/SiteBuildPage.tsx b/src/pages/SiteBuildPage.tsx
index 04091e8..1be46a8 100644
--- a/src/pages/SiteBuildPage.tsx
+++ b/src/pages/SiteBuildPage.tsx
@@ -205,7 +205,7 @@ export default function SiteBuildPage() {
rel="noreferrer"
className="btn-on-dark justify-center"
>
- 사이트 열어 보기
+ {state === 'published' || state === 'approved' ? '공개본 사이트 열기' : '초안 사이트 열기'}
)}