zioinfo-web/frontend/src/pages/Recruit.jsx
DESKTOP-TKLFCPRython 1e98f0d04a refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포
- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스)
- Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포
- 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:09:17 +09:00

318 lines
16 KiB
JavaScript

import React, { useState } from 'react';
import { Routes, Route, NavLink } from 'react-router-dom';
import './Common.css';
import './Recruit.css';
const SUB_NAV = [
{ path: '/recruit/jobs', label: '채용공고' },
{ path: '/recruit/welfare', label: '복리후생' },
{ path: '/recruit/apply', label: '지원하기' },
];
function SubNav({ title }) {
return (
<>
<div className="page-hero">
<div className="container">
<span className="section-label">Recruit</span>
<h1 className="page-hero-title">{title}</h1>
<p>지오정보기술과 함께 AI 인프라 혁신을 이끌어 인재를 모십니다.</p>
</div>
</div>
<nav className="sub-nav">
<div className="container">
{SUB_NAV.map(n => (
<NavLink key={n.path} to={n.path}
className={({ isActive }) => 'sub-nav-item' + (isActive ? ' active' : '')}>
{n.label}
</NavLink>
))}
</div>
</nav>
</>
);
}
/* ── 채용공고 ── */
const JOBS = [
{
id: 1, title: 'AI/LLM 엔지니어', dept: 'AI팀', type: '정규직', exp: '경력 3년 이상',
stack: ['Python', 'Ollama', 'LangChain', 'FastAPI'],
desc: 'GUARDiA ITSM의 온프레미스 AI 엔진 개발. 자연어→명령 파싱, LLM 파인튜닝, RAG 파이프라인 구축.',
deadline: '2026.06.30', hot: true,
},
{
id: 2, title: 'Java 백엔드 개발자 (Spring Boot)', dept: '개발팀', type: '정규직', exp: '경력 3년 이상',
stack: ['Java', 'Spring Boot', 'Oracle', 'MyBatis'],
desc: '공공기관 SI/SM 프로젝트 백엔드 개발. ERP·CRM·행정정보시스템 구축 및 유지보수.',
deadline: '2026.06.30', hot: true,
},
{
id: 3, title: 'React 프론트엔드 개발자', dept: '개발팀', type: '정규직', exp: '경력 2년 이상',
stack: ['React', 'TypeScript', 'Vite', 'Chart.js'],
desc: 'GUARDiA ITSM 및 고객사 포털 프론트엔드 개발. 공공기관 웹접근성(KWCAG 2.1) 준수 필수.',
deadline: '2026.06.30', hot: false,
},
{
id: 4, title: '인프라 운영 엔지니어 (DBA)', dept: '운영팀', type: '정규직', exp: '경력 3년 이상',
stack: ['Oracle', 'Tibero', 'Linux', 'Shell'],
desc: 'Oracle/Tibero DB 설계·튜닝·이관. 삼성전자·국민연금급 대형 DB 운영 경험 우대.',
deadline: '2026.06.15', hot: false,
},
{
id: 5, title: 'PM / PL (공공 SI)', dept: 'PM본부', type: '정규직', exp: '경력 5년 이상',
stack: ['PMP', 'PMBOK', 'MS Project', 'Jira'],
desc: '공공기관 정보화사업 PM/PL. 헌법재판소·국민연금·시립대 수준 프로젝트 관리 경험 보유자.',
deadline: '2026.06.15', hot: false,
},
{
id: 6, title: 'DevOps / CI·CD 엔지니어', dept: '개발팀', type: '정규직', exp: '경력 2년 이상',
stack: ['Docker', 'Kubernetes', 'Jenkins', 'GitHub Actions'],
desc: 'GUARDiA Vibe CD 파이프라인 구축 및 운영. 폐쇄망 환경 GitOps 경험 우대.',
deadline: '상시', hot: false,
},
];
function Jobs() {
const [selected, setSelected] = useState(null);
if (selected) {
const j = JOBS.find(j => j.id === selected);
return (
<main id="main-content" className="inner-page">
<SubNav title="채용공고" />
<section className="section">
<div className="container" style={{ maxWidth: '760px' }}>
<button className="notice-back" onClick={() => setSelected(null)}> 목록으로</button>
<div className="job-detail-card card" style={{ padding: '40px' }}>
<div className="job-detail-header">
{j.hot && <span className="badge badge-new">HOT</span>}
<h2 style={{ fontSize: '26px', fontWeight: '900', margin: '12px 0 8px' }}>{j.title}</h2>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
<span className="badge badge-primary">{j.dept}</span>
<span className="badge badge-accent">{j.type}</span>
<span style={{ fontSize: '13px', color: 'var(--gray-500)' }}>경력: {j.exp}</span>
<span style={{ fontSize: '13px', color: 'var(--gray-500)' }}>마감: {j.deadline}</span>
</div>
</div>
<div className="divider divider-left" style={{ margin: '24px 0' }} />
<h3 style={{ fontSize: '16px', fontWeight: '700', marginBottom: '12px' }}>업무 내용</h3>
<p style={{ color: 'var(--gray-700)', lineHeight: '1.8', marginBottom: '28px' }}>{j.desc}</p>
<h3 style={{ fontSize: '16px', fontWeight: '700', marginBottom: '12px' }}>기술 스택</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '28px' }}>
{j.stack.map((s, i) => (
<span key={i} style={{ padding: '6px 14px', background: 'var(--secondary)', color: 'var(--accent)', borderRadius: '6px', fontSize: '13px', fontWeight: '600' }}>{s}</span>
))}
</div>
<h3 style={{ fontSize: '16px', fontWeight: '700', marginBottom: '12px' }}>지원 방법</h3>
<p style={{ color: 'var(--gray-700)', lineHeight: '1.8' }}>이력서 포트폴리오를 <strong>recruit@zioinfo.co.kr</strong> , .</p>
<a href="/recruit/apply" className="btn btn-primary btn-lg" style={{ marginTop: '24px', display: 'inline-flex' }}>지원하기 </a>
</div>
</div>
</section>
</main>
);
}
return (
<main id="main-content" className="inner-page">
<SubNav title="채용공고" />
<section className="section">
<div className="container">
<div className="section-header">
<span className="section-label">Open Positions</span>
<h2 className="section-title">현재 채용 중인 포지션</h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{JOBS.map(j => (
<div key={j.id} className="card job-card" onClick={() => setSelected(j.id)}>
<div className="job-info">
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
{j.hot && <span className="badge badge-new">HOT</span>}
<span className="badge badge-primary">{j.dept}</span>
<span className="badge badge-accent">{j.type}</span>
</div>
<h3 className="job-title">{j.title}</h3>
<p className="job-desc">{j.desc}</p>
<div className="job-stack">
{j.stack.map((s, i) => <span key={i} className="job-tech">{s}</span>)}
</div>
</div>
<div className="job-meta">
<div><span className="job-meta-label">경력</span><span>{j.exp}</span></div>
<div><span className="job-meta-label">마감</span><span style={{ color: j.deadline === '' ? 'var(--accent)' : 'var(--gray-700)' }}>{j.deadline}</span></div>
<button className="btn btn-primary btn-sm">상세보기</button>
</div>
</div>
))}
</div>
</div>
</section>
</main>
);
}
/* ── 복리후생 ── */
const WELFARE = [
{
cat: '💼 근무환경', items: [
{ icon: '🕘', name: '유연근무제', desc: '코어타임(10시~16시) 외 자유로운 출퇴근 시간 선택' },
{ icon: '🏠', name: '재택근무', desc: '직무에 따라 주 1~2회 재택근무 지원' },
{ icon: '💻', name: '장비 지원', desc: '맥북 또는 고성능 윈도우 노트북 선택 지급' },
{ icon: '🎯', name: '목표 관리(OKR)', desc: '분기별 OKR로 명확한 목표·성과 관리' },
]
},
{
cat: '📚 성장 지원', items: [
{ icon: '📖', name: '교육비 지원', desc: '연 200만원 교육비 지원 (도서, 강의, 세미나)' },
{ icon: '🏆', name: '자격증 지원', desc: '정보처리기사, PMP, AWS, Oracle 자격증 취득 지원' },
{ icon: '🎓', name: '사내 강의', desc: 'AI·클라우드·보안 월 1회 사내 기술 세미나' },
{ icon: '✈️', name: '컨퍼런스', desc: 'AWS re:Invent, Google I/O 등 국내외 컨퍼런스 참가 지원' },
]
},
{
cat: '🎁 복지 혜택', items: [
{ icon: '🏥', name: '건강검진', desc: '연 1회 종합건강검진 (배우자 포함)' },
{ icon: '🎂', name: '경조사 지원', desc: '경조금·경조휴가 제공 (결혼, 출산, 상조)' },
{ icon: '🍽️', name: '식대 지원', desc: '점심 식대 월 15만원 지원 (식권 또는 카드)' },
{ icon: '🎉', name: '명절 선물', desc: '설·추석 명절 선물 및 상여금 지급' },
]
},
];
function Welfare() {
return (
<main id="main-content" className="inner-page">
<SubNav title="복리후생" />
<section className="section">
<div className="container">
<div className="section-header">
<span className="section-label">Welfare</span>
<h2 className="section-title">함께 성장하는 환경을 만듭니다</h2>
<p className="section-desc">구성원이 최고의 역량을 발휘할 있도록 다양한 지원을 제공합니다</p>
</div>
{WELFARE.map((w, wi) => (
<div key={wi} style={{ marginBottom: '56px' }}>
<h3 className="welfare-cat">{w.cat}</h3>
<div className="grid-4">
{w.items.map((item, i) => (
<div key={i} className="card welfare-card">
<div className="welfare-icon">{item.icon}</div>
<h4 className="welfare-name">{item.name}</h4>
<p className="welfare-desc">{item.desc}</p>
</div>
))}
</div>
</div>
))}
{/* 인재상 */}
<div className="talent-wrap">
<div className="section-header">
<span className="section-label">Talent</span>
<h2 className="section-title">우리가 찾는 인재</h2>
</div>
<div className="grid-3">
{[
{ icon: '🔥', title: '도전하는 인재', desc: '새로운 기술과 문제에 두려움 없이 도전하는 분' },
{ icon: '🤝', title: '협력하는 인재', desc: '팀과 함께 성장하며 지식을 나누는 분' },
{ icon: '🎯', title: '책임지는 인재', desc: '맡은 업무에 오너십을 갖고 끝까지 완수하는 분' },
].map((t, i) => (
<div key={i} className="card" style={{ padding: '36px 28px', textAlign: 'center' }}>
<div style={{ fontSize: '48px', marginBottom: '16px' }}>{t.icon}</div>
<h4 style={{ fontSize: '18px', fontWeight: '800', marginBottom: '12px' }}>{t.title}</h4>
<p style={{ fontSize: '14px', color: 'var(--gray-600)', lineHeight: '1.7' }}>{t.desc}</p>
</div>
))}
</div>
</div>
</div>
</section>
</main>
);
}
/* ── 지원하기 ── */
function Apply() {
const [form, setForm] = useState({ name:'', email:'', phone:'', position:'', exp:'', portfolio:'', message:'' });
const [status, setStatus] = useState(null);
const handleChange = e => setForm(f => ({ ...f, [e.target.name]: e.target.value }));
const handleSubmit = e => {
e.preventDefault();
setStatus('success');
};
return (
<main id="main-content" className="inner-page">
<SubNav title="지원하기" />
<section className="section">
<div className="container" style={{ maxWidth: '720px' }}>
<div className="section-header">
<span className="section-label">Apply</span>
<h2 className="section-title">입사 지원서</h2>
<p className="section-desc">아래 양식을 작성하시거나 <strong>recruit@zioinfo.co.kr</strong> </p>
</div>
{status === 'success' ? (
<div className="apply-success">
<div style={{ fontSize: '64px', marginBottom: '20px' }}></div>
<h3>지원이 완료되었습니다!</h3>
<p>검토 영업일 기준 3~5 내에 연락 드리겠습니다.<br />recruit@zioinfo.co.kr 로도 이력서를 추가 제출하시면 더욱 빠르게 처리됩니다.</p>
<button className="btn btn-outline" onClick={() => setStatus(null)} style={{ marginTop: '24px' }}>다시 지원하기</button>
</div>
) : (
<form className="apply-form card" onSubmit={handleSubmit} style={{ padding: '40px' }}>
<div className="form-row">
<div className="form-group">
<label>성명 <span className="required">*</span></label>
<input name="name" required value={form.name} onChange={handleChange} placeholder="홍길동" />
</div>
<div className="form-group">
<label>연락처 <span className="required">*</span></label>
<input name="phone" required value={form.phone} onChange={handleChange} placeholder="010-0000-0000" />
</div>
</div>
<div className="form-group">
<label>이메일 <span className="required">*</span></label>
<input name="email" type="email" required value={form.email} onChange={handleChange} placeholder="your@email.com" />
</div>
<div className="form-row">
<div className="form-group">
<label>지원 포지션 <span className="required">*</span></label>
<select name="position" required value={form.position} onChange={handleChange}>
<option value="">선택하세요</option>
{JOBS.map(j => <option key={j.id} value={j.title}>{j.title}</option>)}
<option value="기타">기타 (자유 지원)</option>
</select>
</div>
<div className="form-group">
<label>경력 사항</label>
<input name="exp" value={form.exp} onChange={handleChange} placeholder="예: 5년 (현대백화점 → 지오정보기술)" />
</div>
</div>
<div className="form-group">
<label>포트폴리오 / GitHub URL</label>
<input name="portfolio" value={form.portfolio} onChange={handleChange} placeholder="https://github.com/yourname" />
</div>
<div className="form-group">
<label>자기소개 지원동기</label>
<textarea name="message" rows={6} value={form.message} onChange={handleChange} placeholder="간단한 자기소개와 지원동기를 작성해 주세요." />
</div>
<button type="submit" className="btn btn-primary btn-lg" style={{ width: '100%' }}>
지원서 제출하기
</button>
</form>
)}
</div>
</section>
</main>
);
}
export default function Recruit() {
return (
<Routes>
<Route path="jobs" element={<Jobs />} />
<Route path="welfare" element={<Welfare />} />
<Route path="apply" element={<Apply />} />
<Route path="*" element={<Jobs />} />
</Routes>
);
}