Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 파이프 수량 계산 로직 수정 (단관 개수가 아닌 실제 길이 기반 계산) - UI 전면 개편 (DevonThink 스타일의 간결하고 세련된 디자인) - 자재별 그룹핑 로직 개선: * 플랜지: 동일 사양별 그룹핑, WN 스케줄 표시, ORIFICE 풀네임 표시 * 피팅: 상세 타입 표시 (니플 길이, 엘보 각도/연결, 티 타입, 리듀서 타입 등) * 밸브: 동일 사양별 그룹핑, 타입/연결방식/압력 표시 * 볼트: 크기/재질/길이별 그룹핑 (8SET → 개별 집계) * 가스켓: 동일 사양별 그룹핑, 재질/상세내역/두께 분리 표시 * UNKNOWN: 원본 설명 전체 표시, 동일 항목 그룹핑 - 전체 카테고리 버튼 제거 (표시 복잡도 감소) - 카테고리별 동적 컬럼 헤더 및 레이아웃 적용
405 lines
17 KiB
JavaScript
405 lines
17 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
|
||
const ProjectsPage = ({ user }) => {
|
||
const [projects, setProjects] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [showCreateForm, setShowCreateForm] = useState(false);
|
||
|
||
useEffect(() => {
|
||
// 실제로는 API에서 프로젝트 데이터를 가져올 예정
|
||
// 현재는 더미 데이터 사용
|
||
setTimeout(() => {
|
||
setProjects([
|
||
{
|
||
id: 1,
|
||
name: '냉동기 시스템 개발',
|
||
type: '냉동기',
|
||
status: '진행중',
|
||
startDate: '2024-01-15',
|
||
endDate: '2024-06-30',
|
||
deliveryMethod: 'FOB',
|
||
progress: 65,
|
||
manager: '김철수'
|
||
},
|
||
{
|
||
id: 2,
|
||
name: 'BOG 처리 시스템',
|
||
type: 'BOG',
|
||
status: '계획',
|
||
startDate: '2024-02-01',
|
||
endDate: '2024-08-15',
|
||
deliveryMethod: 'CIF',
|
||
progress: 15,
|
||
manager: '이영희'
|
||
},
|
||
{
|
||
id: 3,
|
||
name: '다이아프람 펌프 제작',
|
||
type: '다이아프람',
|
||
status: '완료',
|
||
startDate: '2023-10-01',
|
||
endDate: '2024-01-31',
|
||
deliveryMethod: 'FOB',
|
||
progress: 100,
|
||
manager: '박민수'
|
||
}
|
||
]);
|
||
setLoading(false);
|
||
}, 1000);
|
||
}, []);
|
||
|
||
const getStatusColor = (status) => {
|
||
const colors = {
|
||
'계획': '#ed8936',
|
||
'진행중': '#48bb78',
|
||
'완료': '#38b2ac',
|
||
'보류': '#e53e3e'
|
||
};
|
||
return colors[status] || '#718096';
|
||
};
|
||
|
||
const getProgressColor = (progress) => {
|
||
if (progress >= 80) return '#48bb78';
|
||
if (progress >= 50) return '#ed8936';
|
||
return '#e53e3e';
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
minHeight: '400px',
|
||
fontSize: '16px',
|
||
color: '#718096'
|
||
}}>
|
||
프로젝트 목록을 불러오는 중...
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{
|
||
padding: '32px',
|
||
background: '#f7fafc',
|
||
minHeight: '100vh'
|
||
}}>
|
||
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||
{/* 헤더 */}
|
||
<div style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
marginBottom: '32px'
|
||
}}>
|
||
<div>
|
||
<h1 style={{
|
||
fontSize: '28px',
|
||
fontWeight: '700',
|
||
color: '#2d3748',
|
||
margin: '0 0 8px 0'
|
||
}}>
|
||
📋 프로젝트 관리
|
||
</h1>
|
||
<p style={{
|
||
color: '#718096',
|
||
fontSize: '16px',
|
||
margin: '0'
|
||
}}>
|
||
전체 프로젝트를 관리하고 진행 상황을 확인하세요.
|
||
</p>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => setShowCreateForm(true)}
|
||
style={{
|
||
padding: '12px 24px',
|
||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||
color: 'white',
|
||
border: 'none',
|
||
borderRadius: '8px',
|
||
cursor: 'pointer',
|
||
fontSize: '14px',
|
||
fontWeight: '600',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '8px',
|
||
transition: 'transform 0.2s ease'
|
||
}}
|
||
onMouseEnter={(e) => e.target.style.transform = 'translateY(-1px)'}
|
||
onMouseLeave={(e) => e.target.style.transform = 'translateY(0)'}
|
||
>
|
||
<span>➕</span>
|
||
새 프로젝트
|
||
</button>
|
||
</div>
|
||
|
||
{/* 프로젝트 통계 */}
|
||
<div style={{
|
||
display: 'grid',
|
||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||
gap: '16px',
|
||
marginBottom: '32px'
|
||
}}>
|
||
{[
|
||
{ label: '전체', count: projects.length, color: '#667eea' },
|
||
{ label: '진행중', count: projects.filter(p => p.status === '진행중').length, color: '#48bb78' },
|
||
{ label: '완료', count: projects.filter(p => p.status === '완료').length, color: '#38b2ac' },
|
||
{ label: '계획', count: projects.filter(p => p.status === '계획').length, color: '#ed8936' }
|
||
].map((stat, index) => (
|
||
<div key={index} style={{
|
||
background: 'white',
|
||
padding: '20px',
|
||
borderRadius: '12px',
|
||
border: '1px solid #e2e8f0',
|
||
textAlign: 'center'
|
||
}}>
|
||
<div style={{
|
||
fontSize: '24px',
|
||
fontWeight: '700',
|
||
color: stat.color,
|
||
marginBottom: '4px'
|
||
}}>
|
||
{stat.count}
|
||
</div>
|
||
<div style={{
|
||
fontSize: '14px',
|
||
color: '#718096'
|
||
}}>
|
||
{stat.label}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 프로젝트 목록 */}
|
||
<div style={{
|
||
background: 'white',
|
||
borderRadius: '12px',
|
||
border: '1px solid #e2e8f0',
|
||
overflow: 'hidden'
|
||
}}>
|
||
<div style={{
|
||
padding: '20px 24px',
|
||
borderBottom: '1px solid #e2e8f0',
|
||
background: '#f7fafc'
|
||
}}>
|
||
<h3 style={{
|
||
margin: '0',
|
||
fontSize: '16px',
|
||
fontWeight: '600',
|
||
color: '#2d3748'
|
||
}}>
|
||
프로젝트 목록 ({projects.length}개)
|
||
</h3>
|
||
</div>
|
||
|
||
<div style={{ overflowX: 'auto' }}>
|
||
<table style={{
|
||
width: '100%',
|
||
borderCollapse: 'collapse'
|
||
}}>
|
||
<thead>
|
||
<tr style={{ background: '#f7fafc' }}>
|
||
{['프로젝트명', '유형', '상태', '수주일', '납기일', '납품방법', '진행률', '담당자'].map(header => (
|
||
<th key={header} style={{
|
||
padding: '12px 16px',
|
||
textAlign: 'left',
|
||
fontSize: '12px',
|
||
fontWeight: '600',
|
||
color: '#4a5568',
|
||
borderBottom: '1px solid #e2e8f0'
|
||
}}>
|
||
{header}
|
||
</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{projects.map(project => (
|
||
<tr key={project.id} style={{
|
||
borderBottom: '1px solid #e2e8f0',
|
||
transition: 'background 0.2s ease'
|
||
}}
|
||
onMouseEnter={(e) => e.currentTarget.style.background = '#f7fafc'}
|
||
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
|
||
<td style={{ padding: '16px', fontWeight: '600', color: '#2d3748' }}>
|
||
{project.name}
|
||
</td>
|
||
<td style={{ padding: '16px' }}>
|
||
<span style={{
|
||
padding: '4px 8px',
|
||
background: '#edf2f7',
|
||
borderRadius: '4px',
|
||
fontSize: '12px',
|
||
fontWeight: '500',
|
||
color: '#4a5568'
|
||
}}>
|
||
{project.type}
|
||
</span>
|
||
</td>
|
||
<td style={{ padding: '16px' }}>
|
||
<span style={{
|
||
padding: '4px 8px',
|
||
background: getStatusColor(project.status) + '20',
|
||
color: getStatusColor(project.status),
|
||
borderRadius: '4px',
|
||
fontSize: '12px',
|
||
fontWeight: '600'
|
||
}}>
|
||
{project.status}
|
||
</span>
|
||
</td>
|
||
<td style={{ padding: '16px', color: '#4a5568' }}>
|
||
{project.startDate}
|
||
</td>
|
||
<td style={{ padding: '16px', color: '#4a5568' }}>
|
||
{project.endDate}
|
||
</td>
|
||
<td style={{ padding: '16px', color: '#4a5568' }}>
|
||
{project.deliveryMethod}
|
||
</td>
|
||
<td style={{ padding: '16px' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||
<div style={{
|
||
flex: 1,
|
||
height: '6px',
|
||
background: '#e2e8f0',
|
||
borderRadius: '3px',
|
||
overflow: 'hidden'
|
||
}}>
|
||
<div style={{
|
||
width: `${project.progress}%`,
|
||
height: '100%',
|
||
background: getProgressColor(project.progress),
|
||
transition: 'width 0.3s ease'
|
||
}} />
|
||
</div>
|
||
<span style={{
|
||
fontSize: '12px',
|
||
fontWeight: '600',
|
||
color: getProgressColor(project.progress),
|
||
minWidth: '35px'
|
||
}}>
|
||
{project.progress}%
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td style={{ padding: '16px', color: '#4a5568' }}>
|
||
{project.manager}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 프로젝트가 없을 때 */}
|
||
{projects.length === 0 && (
|
||
<div style={{
|
||
background: 'white',
|
||
borderRadius: '12px',
|
||
border: '1px solid #e2e8f0',
|
||
padding: '60px 40px',
|
||
textAlign: 'center'
|
||
}}>
|
||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>📋</div>
|
||
<h3 style={{
|
||
fontSize: '18px',
|
||
fontWeight: '600',
|
||
color: '#2d3748',
|
||
margin: '0 0 8px 0'
|
||
}}>
|
||
등록된 프로젝트가 없습니다
|
||
</h3>
|
||
<p style={{
|
||
color: '#718096',
|
||
margin: '0 0 24px 0'
|
||
}}>
|
||
첫 번째 프로젝트를 등록해보세요.
|
||
</p>
|
||
<button
|
||
onClick={() => setShowCreateForm(true)}
|
||
style={{
|
||
padding: '12px 24px',
|
||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||
color: 'white',
|
||
border: 'none',
|
||
borderRadius: '8px',
|
||
cursor: 'pointer',
|
||
fontSize: '14px',
|
||
fontWeight: '600'
|
||
}}
|
||
>
|
||
새 프로젝트 등록
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 프로젝트 생성 폼 모달 (향후 구현) */}
|
||
{showCreateForm && (
|
||
<div style={{
|
||
position: 'fixed',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
background: 'rgba(0, 0, 0, 0.5)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
zIndex: 1000
|
||
}}>
|
||
<div style={{
|
||
background: 'white',
|
||
borderRadius: '12px',
|
||
padding: '32px',
|
||
maxWidth: '500px',
|
||
width: '90%',
|
||
maxHeight: '90vh',
|
||
overflow: 'auto'
|
||
}}>
|
||
<h3 style={{ margin: '0 0 16px 0' }}>새 프로젝트 등록</h3>
|
||
<p style={{ color: '#718096', margin: '0 0 24px 0' }}>
|
||
이 기능은 곧 구현될 예정입니다.
|
||
</p>
|
||
<button
|
||
onClick={() => setShowCreateForm(false)}
|
||
style={{
|
||
padding: '8px 16px',
|
||
background: '#e2e8f0',
|
||
border: 'none',
|
||
borderRadius: '6px',
|
||
cursor: 'pointer'
|
||
}}
|
||
>
|
||
닫기
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ProjectsPage;
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|