Files
tk-factory-services/tkeg/web/src/pages/ProjectWorkspacePage.jsx
2026-03-16 15:41:58 +09:00

359 lines
15 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { api } from '../api';
const ProjectWorkspacePage = ({ project, user, onNavigate, onBackToDashboard }) => {
const [projectStats, setProjectStats] = useState(null);
const [recentFiles, setRecentFiles] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (project) {
loadProjectData();
}
}, [project]);
const loadProjectData = async () => {
try {
// 실제 파일 데이터만 로드
const filesResponse = await api.get(`/files?job_no=${project.job_no}&limit=5`);
if (filesResponse.data && Array.isArray(filesResponse.data)) {
setRecentFiles(filesResponse.data);
// 파일 데이터를 기반으로 통계 계산
const stats = {
totalFiles: filesResponse.data.length,
totalMaterials: filesResponse.data.reduce((sum, file) => sum + (file.parsed_count || 0), 0),
classifiedMaterials: 0, // API에서 분류 정보를 가져와야 함
pendingVerification: 0, // API에서 검증 정보를 가져와야 함
};
setProjectStats(stats);
} else {
setRecentFiles([]);
setProjectStats({
totalFiles: 0,
totalMaterials: 0,
classifiedMaterials: 0,
pendingVerification: 0
});
}
} catch (error) {
console.error('프로젝트 데이터 로딩 실패:', error);
setRecentFiles([]);
setProjectStats({
totalFiles: 0,
totalMaterials: 0,
classifiedMaterials: 0,
pendingVerification: 0
});
} finally {
setLoading(false);
}
};
const getAvailableActions = () => {
const userRole = user?.role || 'user';
const allActions = {
// BOM 관리 (통합)
'bom-management': {
title: 'BOM 관리',
description: 'BOM 파일 업로드, 관리 및 리비전 추적을 수행합니다',
icon: '📋',
color: '#667eea',
roles: ['designer', 'manager', 'admin'],
path: 'bom-status'
},
// 자재 관리
'material-management': {
title: '자재 관리',
description: '자재 분류, 검증 및 구매 관리를 수행합니다',
icon: '🔧',
color: '#48bb78',
roles: ['designer', 'purchaser', 'manager', 'admin'],
path: 'materials'
}
};
// 사용자 권한에 따라 필터링
return Object.entries(allActions).filter(([key, action]) =>
action.roles.includes(userRole)
);
};
const handleActionClick = (actionPath) => {
switch (actionPath) {
case 'bom-management':
onNavigate('bom-status', {
job_no: project.job_no,
job_name: project.project_name
});
break;
case 'material-management':
onNavigate('materials', {
job_no: project.job_no,
job_name: project.project_name
});
break;
default:
alert(`${actionPath} 기능은 곧 구현될 예정입니다.`);
}
};
if (loading) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '200px'
}}>
<div>프로젝트 데이터를 불러오는 ...</div>
</div>
);
}
const availableActions = getAvailableActions();
return (
<div style={{
padding: '32px',
background: '#f7fafc',
minHeight: '100vh'
}}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
{/* 헤더 */}
<div style={{
display: 'flex',
alignItems: 'center',
marginBottom: '32px'
}}>
<button
onClick={onBackToDashboard}
style={{
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
marginRight: '16px',
padding: '8px'
}}
>
</button>
<div>
<h1 style={{
margin: '0 0 8px 0',
fontSize: '28px',
fontWeight: 'bold',
color: '#2d3748'
}}>
{project.project_name}
</h1>
<div style={{
fontSize: '16px',
color: '#718096'
}}>
{project.job_no} 진행률: {project.progress || 0}%
</div>
</div>
</div>
{/* 프로젝트 통계 */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '20px',
marginBottom: '32px'
}}>
{[
{ label: 'BOM 파일', value: projectStats.totalFiles, icon: '📄', color: '#667eea' },
{ label: '전체 자재', value: projectStats.totalMaterials, icon: '📦', color: '#48bb78' },
{ label: '분류 완료', value: projectStats.classifiedMaterials, icon: '✅', color: '#38b2ac' },
{ label: '검증 대기', value: projectStats.pendingVerification, icon: '⏳', color: '#ed8936' }
].map((stat, index) => (
<div key={index} style={{
background: 'white',
padding: '20px',
borderRadius: '12px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '1px solid #e2e8f0'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<div>
<div style={{
fontSize: '14px',
color: '#718096',
marginBottom: '4px'
}}>
{stat.label}
</div>
<div style={{
fontSize: '24px',
fontWeight: 'bold',
color: stat.color
}}>
{stat.value}
</div>
</div>
<div style={{ fontSize: '24px' }}>
{stat.icon}
</div>
</div>
</div>
))}
</div>
{/* 업무 메뉴 */}
<div style={{
background: 'white',
borderRadius: '16px',
padding: '32px',
boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
marginBottom: '32px'
}}>
<h2 style={{
margin: '0 0 24px 0',
fontSize: '20px',
fontWeight: 'bold',
color: '#2d3748'
}}>
🚀 사용 가능한 업무
</h2>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '20px'
}}>
{availableActions.map(([key, action]) => (
<div
key={key}
onClick={() => handleActionClick(key)}
style={{
padding: '20px',
border: '1px solid #e2e8f0',
borderRadius: '12px',
cursor: 'pointer',
transition: 'all 0.2s ease',
background: 'white'
}}
onMouseEnter={(e) => {
e.target.style.borderColor = action.color;
e.target.style.boxShadow = `0 4px 12px ${action.color}20`;
e.target.style.transform = 'translateY(-2px)';
}}
onMouseLeave={(e) => {
e.target.style.borderColor = '#e2e8f0';
e.target.style.boxShadow = 'none';
e.target.style.transform = 'translateY(0)';
}}
>
<div style={{
display: 'flex',
alignItems: 'flex-start',
gap: '16px'
}}>
<div style={{
fontSize: '32px',
lineHeight: 1
}}>
{action.icon}
</div>
<div style={{ flex: 1 }}>
<h3 style={{
margin: '0 0 8px 0',
fontSize: '16px',
fontWeight: 'bold',
color: action.color
}}>
{action.title}
</h3>
<p style={{
margin: 0,
fontSize: '14px',
color: '#718096',
lineHeight: 1.5
}}>
{action.description}
</p>
</div>
</div>
</div>
))}
</div>
</div>
{/* 최근 활동 (옵션) */}
{recentFiles.length > 0 && (
<div style={{
background: 'white',
borderRadius: '16px',
padding: '32px',
boxShadow: '0 4px 12px rgba(0,0,0,0.05)'
}}>
<h2 style={{
margin: '0 0 24px 0',
fontSize: '20px',
fontWeight: 'bold',
color: '#2d3748'
}}>
📁 최근 BOM 파일
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{recentFiles.map((file, index) => (
<div key={index} style={{
padding: '16px',
border: '1px solid #e2e8f0',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<div>
<div style={{
fontSize: '16px',
fontWeight: '500',
color: '#2d3748',
marginBottom: '4px'
}}>
{file.original_filename || file.filename}
</div>
<div style={{
fontSize: '14px',
color: '#718096'
}}>
{file.revision} {file.uploaded_by || '시스템'} {file.parsed_count || 0} 자재
</div>
</div>
<button
onClick={() => handleActionClick('materials')}
style={{
padding: '8px 16px',
backgroundColor: '#667eea',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px'
}}
>
자재 보기
</button>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default ProjectWorkspacePage;