/** * 진단 요약 → 이렇게 보완하겠습니다 → 빌드. * * 버튼만 두면 무엇에 동의하는지 모른 채 누르게 된다. 진단을 요약하고, 무엇을 보완하는지 말한 뒤 누르게 한다. * * 이 화면에서 틀리면 안 되는 것: 빌드는 병원 홈페이지를 한 줄도 고치지 않는다. * 그래서 진단의 실행 항목은 단 하나도 이 빌드로 해결되지 않는다. 전부 병원 몫이다. * 빌드가 하는 일은 AI가 인용할 새 표면을 하나 더 만들어 약한 카테고리를 우회 보완하는 것이다. * "실행 항목 N건 해결"로 쓰면 지키지 못할 약속이 된다. */ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; import { motion } from 'motion/react'; import { supabase } from '../../lib/supabase'; import { STATE_LABEL } from '../../lib/buildPhases'; import type { BuildState } from '../../lib/buildPhases'; import { weakSpots } from '../../lib/buildCoverage'; import type { DiscoveryResult, OverallScore } from '../../types/discovery'; import { CheckFilled, WarningFilled } from '../icons/FilledIcons'; import { ArrowRight } from 'lucide-react'; type Props = { result: DiscoveryResult; overall: OverallScore; summary: { rubricVersion: string; grade: string; score: number; aeo?: number; geo?: number; verified: number; total: number }; }; export function BuildPlanSection({ result, overall, summary }: Props) { const navigate = useNavigate(); const [existing, setExisting] = useState(null); const [checking, setChecking] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const weak = weakSpots(overall.categories); const critical = result.keyFindings.filter((f) => f.severity === 'critical'); const p0 = result.actions.filter((a) => a.priority === 'P0'); useEffect(() => { let alive = true; void (async () => { const { data } = await supabase.from('supporter_builds').select('status') .eq('clinic_id', result.id).order('created_at', { ascending: false }).limit(1); if (!alive) return; setExisting((data?.[0]?.status as BuildState) ?? null); setChecking(false); })(); return () => { alive = false; }; }, [result.id]); async function approve() { setBusy(true); setError(null); await supabase.from('supporter_inputs').insert({ clinic_id: result.id, key: 'report_approval', value: { ...summary, weakSpots: weak.map((w) => w.id), approvedAt: new Date().toISOString() }, }); const { error: e } = await supabase.from('supporter_builds').insert({ clinic_id: result.id, clinic_name: result.clinicName, url: result.url, status: 'queued', }); setBusy(false); if (e) { setError(e.message); return; } navigate(`/build/${result.id}`); } const inFlight = existing === 'queued' || existing === 'running'; return (

What We Will Fix

{/* 1. 진단 요약 */}

진단 요약 · {result.auditedAt} 실측

{summary.grade} 종합 {summary.score}점 {typeof summary.aeo === 'number' && ( {summary.aeo}AEO 답변 준비도 )} {typeof summary.geo === 'number' && ( {summary.geo}GEO 출처 준비도 )} 실측 {summary.verified} / {summary.total}항목
{critical.length > 0 && (
    {critical.slice(0, 3).map((f) => (
  • {f.title}
  • ))}
)}
{/* 2. 이렇게 보완하겠습니다 */}

홈페이지를 고치지 않고, AI가 인용할 수 있는 사이트를 하나 더 만들어 아래를 보완하겠습니다.

{weak.map((w, i) => (
{w.name} {w.pct}%

지금

{w.now}

보완

{w.fix}

))}
{/* 3. 빌드가 대신할 수 없는 것 */}

이 빌드가 대신할 수 없는 것

이 빌드는 별도 사이트를 만들 뿐, 병원 홈페이지를 고치지 않습니다.{' '} 진단의 실행 항목 {result.actions.length}건은 그대로 병원 홈페이지에서 하셔야 합니다.

    {p0.map((a) => (
  • P0{a.title}
  • ))}
{/* 4. 실행 버튼 배경은 밝은 보라로 둔다. 기본 CTA 그라디언트(#4F1DA1→#021341)는 흰 배경용이라 다크 섹션(#0A1128) 위에 올리면 배경에 묻혀 버튼으로 읽히지 않는다. 문구에 "결과"를 쓰지 않는다. 빌드가 끝나도 사진 확인과 확인 항목이 남아 최종이 아니다. */}
{checking ? ( 빌드 상태를 확인하는 중입니다. ) : existing ? ( <> {!inFlight && ( )} {STATE_LABEL[existing]} ) : ( )}
{error &&

시작하지 못했습니다. {error}

}

만들어진 사이트는 검색에 노출되지 않는 상태로 올라갑니다. 공개 여부는 병원이 확인한 뒤에 정합니다.

); }