snp-batch-validation/frontend/src/components/GuideModal.tsx
HYOJIN 033daff378 feat(ui): 각 화면별 사용자 가이드 추가 (#41)
- GuideModal 컴포넌트 신규 생성 (아코디언 방식 가이드 모달 + HelpButton)
- 8개 페이지에 (?) 도움말 버튼 및 화면별 사용자 가이드 추가
  - 대시보드, 작업 목록, 실행 이력, 실행 상세
  - 재수집 이력, 재수집 상세, 스케줄 관리, 타임라인

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:02:12 +09:00

93 lines
3.1 KiB
TypeScript

import { useState } from 'react';
interface GuideSection {
title: string;
content: string;
}
interface Props {
open: boolean;
pageTitle: string;
sections: GuideSection[];
onClose: () => void;
}
export default function GuideModal({ open, pageTitle, sections, onClose }: Props) {
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-wing-overlay" onClick={onClose}>
<div
className="bg-wing-surface rounded-xl shadow-2xl p-6 max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-wing-text">{pageTitle} </h3>
<button
onClick={onClose}
className="p-1 text-wing-muted hover:text-wing-text transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="space-y-4">
{sections.map((section, i) => (
<GuideAccordion key={i} title={section.title} content={section.content} defaultOpen={i === 0} />
))}
</div>
<div className="flex justify-end mt-6">
<button
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-wing-text bg-wing-card rounded-lg hover:bg-wing-hover transition-colors"
>
</button>
</div>
</div>
</div>
);
}
function GuideAccordion({ title, content, defaultOpen }: { title: string; content: string; defaultOpen: boolean }) {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<div className="border border-wing-border rounded-lg overflow-hidden">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between px-4 py-3 text-sm font-medium text-wing-text bg-wing-card hover:bg-wing-hover transition-colors text-left"
>
<span>{title}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`w-4 h-4 text-wing-muted transition-transform ${isOpen ? 'rotate-180' : ''}`}
fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="px-4 py-3 text-sm text-wing-muted leading-relaxed whitespace-pre-line">
{content}
</div>
)}
</div>
);
}
export function HelpButton({ onClick }: { onClick: () => void }) {
return (
<button
onClick={onClick}
title="사용 가이드"
className="inline-flex items-center justify-center w-7 h-7 rounded-full border border-wing-border text-wing-muted hover:text-wing-accent hover:border-wing-accent transition-colors text-sm font-semibold"
>
?
</button>
);
}