// cert.jsx — C-01 인증 패키지 빌더 const { Icon, Pill, Tag, Avatar, Button, Progress } = window.RAlly; const CertPackage = ({ go }) => { const [tab, setTab] = React.useState('required'); const [openGroups, setOpenGroups] = React.useState({ tech: true, risk: true, sop: false, capa: false }); const [data, setData] = React.useState(null); const [trackId, setTrackId] = React.useState(null); const [sub, setSub] = React.useState(null); React.useEffect(() => { window.RAlly.resolveTrackId().then((tid) => { setTrackId(tid); if (tid) { fetch('/api/track/' + tid + '/cert').then((r) => r.json()).then(setData).catch(() => {}); fetch('/api/track/' + tid + '/submission').then((r) => r.json()).then(setSub).catch(() => {}); } }); }, []); const toggle = (k) => setOpenGroups((p) => ({ ...p, [k]: !p[k] })); const cd = data || { groups: [], total: 0, done: 0, pct: 0, missing: 0 }; const trackName = (cd.track && cd.track.name) || '트랙'; return (
{/* Header */}
STAGE 04 · 결승선 2등급 의료기기 + SaMD MFDS 품목허가
결승선까지 같이 달려요
{trackName} · MFDS 품목허가 패키지
{/* Coral banner with stat + checklist preview */}
제출 준비 진행률
필수 자료 {cd.done}개 완료, {cd.missing}개 미비
완주하지 않은 문서를 채우면 제출 패키지가 자동으로 생성됩니다. 아직 {cd.missing}개가 남아 있어요.
NECESSARY DOCS
{cd.done}/{cd.total}
{cd.pct}% — {cd.pct >= 90 ? '결승선 직전' : '주행 중'}
{/* 제출 준비 패키지 — 심사구분×pillar 체크리스트 + 게이트 + 경로 (실데이터) */} {/* Track-style milestone strip (데모 전용) */} {window.RAlly.isDemo() && (
제출 마일스톤
~ 2026.09.30
)} {/* Tabs */}
{/* Groups (백엔드 집계) */} {tab === 'required' && (
{cd.groups.map((g) => ( toggle(g.id)} rows={g.rows} /> ))}
)} {tab === 'optional' && (
선택 자료 6/8 — 이 탭의 모킹은 생략되었어요.
)} {tab === 'form' && (
MFDS 신청서 양식 — 이 탭의 모킹은 생략되었어요.
)} {tab === 'history' && (
제출 이력 — 첫 제출 전이라 비어 있어요.
)} {/* Bottom action banner — 실제 게이트 기준 */}
{sub && sub.gate && sub.gate.submittable ? '문서 세트가 준비됐어요 — 패키지를 내려받아 제출하세요' : ((sub && sub.gate && sub.gate.summary) || '미완 문서를 채우면 제출 패키지가 자동 생성돼요')}
ZIP · 카테고리별 문서 + 요건 매핑 표. 실측·서명 등 [입력 필요] 값은 실무자가 채웁니다.
); }; // 제출 준비 패키지 — 실데이터(/api/track/{id}/submission) const SubmissionPanel = ({ sub, go }) => { if (!sub || !sub.ok) return null; const gate = sub.gate || {}; const ok = gate.submittable; return (
{/* 게이트 요약 */}
{ok ? '제출 준비 완료 — 최종 RA 검토 후 제출' : '제출 전 남은 항목이 있습니다'}
{gate.summary || '문서·입력 상태를 점검하세요.'}
{(sub.pillars || []).map((p) => {p})} 심사구분 · {sub.review_type}
{/* 제출 경로 안내 */}
제출 경로 안내
{(sub.routes || []).map((r, i) => (
{r.pillar}
{r.title}
{r.detail}
))}
{/* pillar별 제출문서 체크리스트 */} {(sub.groups || []).map((g) => (
{g.pillar} 기둥 {g.ready}/{g.total} 준비완료
{g.docs.map((d) => (
{d.done ? : }
{d.label}
{d.inputs > 0 && 입력 {d.inputs}} {d.state}
))}
))}
{sub.note}
); }; const Milestones = () => { const items = [ { label: '내부 검토 완료', date: '2026.06.15', state: 'done' }, { label: '자문 RA 승인', date: '2026.07.10', state: 'done' }, { label: 'GMP 적합성 평가', date: '2026.08.01', state: 'current' }, { label: 'MFDS 사전 상담', date: '2026.08.20', state: 'todo' }, { label: '품목허가 신청', date: '2026.09.30', state: 'todo', finish: true }, ]; return (
{items.map((m, i) => (
{m.state === 'done' && } {m.finish && m.state === 'todo' && }
{m.label}
{m.date}
))}
); }; const CertGroup = ({ title, count, tone, open, onToggle, rows }) => (
{title}
{count} {rows.length}개 항목
{open && (
{rows.map((r, i) => ( ))}
)}
); const CertRow = ({ state, title, meta, cta }) => { const isMissing = state === 'missing'; return (
{isMissing ? : }
{title}
{meta}
{cta ? ( ) : ( PDF )}
); }; window.RAlly.CertPackage = CertPackage;