feat(portfolio): 포트폴리오 페이지 전체 구현
- 3탭 구조: 프로필&경력, 프로젝트, 자기소개 - 비밀번호 인증 → 편집 모드 - 클립보드 복사, PDF 내보내기 (window.print) - 사이버펑크 테마 CSS, 모바일 반응형 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
82
src/pages/portfolio/ResumeView.jsx
Normal file
82
src/pages/portfolio/ResumeView.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
export default function ResumeView({ data, onClose }) {
|
||||
const { profile, careers, projects, skills, main_introduction } = data;
|
||||
|
||||
const handlePrint = () => {
|
||||
window.print();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pf-resume-overlay">
|
||||
<div className="pf-resume-actions no-print">
|
||||
<button className="button primary" onClick={handlePrint}>PDF 저장 / 인쇄</button>
|
||||
<button className="button ghost" onClick={onClose}>닫기</button>
|
||||
</div>
|
||||
<div className="pf-resume">
|
||||
{/* 헤더 */}
|
||||
<header className="pf-resume__header">
|
||||
<div>
|
||||
<h1 className="pf-resume__name">{profile.name}</h1>
|
||||
<p className="pf-resume__role">{profile.role}</p>
|
||||
</div>
|
||||
<div className="pf-resume__contact">
|
||||
{profile.email && <span>{profile.email}</span>}
|
||||
{profile.phone && <span>{profile.phone}</span>}
|
||||
{profile.github_url && <span>{profile.github_url}</span>}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* About */}
|
||||
{(main_introduction?.content || profile.bio) && (
|
||||
<section className="pf-resume__section">
|
||||
<h2>About</h2>
|
||||
<p>{main_introduction?.content || profile.bio}</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Experience */}
|
||||
{careers.length > 0 && (
|
||||
<section className="pf-resume__section">
|
||||
<h2>Experience</h2>
|
||||
{careers.map(c => (
|
||||
<div key={c.id} className="pf-resume__item">
|
||||
<div className="pf-resume__item-header">
|
||||
<strong>{c.role}</strong>
|
||||
<span>{c.organization}</span>
|
||||
<span className="pf-resume__period">{c.start_date} — {c.end_date || '현재'}</span>
|
||||
</div>
|
||||
{c.description && <p>{c.description}</p>}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Projects */}
|
||||
{projects.length > 0 && (
|
||||
<section className="pf-resume__section">
|
||||
<h2>Projects</h2>
|
||||
{projects.map(p => (
|
||||
<div key={p.id} className="pf-resume__item">
|
||||
<div className="pf-resume__item-header">
|
||||
<strong>{p.title}</strong>
|
||||
<span className="pf-resume__period">{p.start_date} — {p.end_date || '현재'}</span>
|
||||
</div>
|
||||
{p.description && <p>{p.description}</p>}
|
||||
{p.tech_stack?.length > 0 && (
|
||||
<p className="pf-resume__tech">{p.tech_stack.join(' · ')}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Skills */}
|
||||
{skills.length > 0 && (
|
||||
<section className="pf-resume__section">
|
||||
<h2>Skills</h2>
|
||||
<p className="pf-resume__skills">{skills.map(s => s.name).join(' · ')}</p>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user