feat: 자재 관리 페이지 대규모 개선
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 파이프 수량 계산 로직 수정 (단관 개수가 아닌 실제 길이 기반 계산) - UI 전면 개편 (DevonThink 스타일의 간결하고 세련된 디자인) - 자재별 그룹핑 로직 개선: * 플랜지: 동일 사양별 그룹핑, WN 스케줄 표시, ORIFICE 풀네임 표시 * 피팅: 상세 타입 표시 (니플 길이, 엘보 각도/연결, 티 타입, 리듀서 타입 등) * 밸브: 동일 사양별 그룹핑, 타입/연결방식/압력 표시 * 볼트: 크기/재질/길이별 그룹핑 (8SET → 개별 집계) * 가스켓: 동일 사양별 그룹핑, 재질/상세내역/두께 분리 표시 * UNKNOWN: 원본 설명 전체 표시, 동일 항목 그룹핑 - 전체 카테고리 버튼 제거 (표시 복잡도 감소) - 카테고리별 동적 컬럼 헤더 및 레이아웃 적용
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import SimpleLogin from './SimpleLogin';
|
||||
import NavigationMenu from './components/NavigationMenu';
|
||||
import DashboardPage from './pages/DashboardPage';
|
||||
import ProjectsPage from './pages/ProjectsPage';
|
||||
import BOMStatusPage from './pages/BOMStatusPage';
|
||||
import SimpleMaterialsPage from './pages/SimpleMaterialsPage';
|
||||
import MaterialComparisonPage from './pages/MaterialComparisonPage';
|
||||
import RevisionPurchasePage from './pages/RevisionPurchasePage';
|
||||
import JobSelectionPage from './pages/JobSelectionPage';
|
||||
import BOMWorkspacePage from './pages/BOMWorkspacePage';
|
||||
import NewMaterialsPage from './pages/NewMaterialsPage';
|
||||
import SystemSettingsPage from './pages/SystemSettingsPage';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
@@ -16,6 +11,7 @@ function App() {
|
||||
const [user, setUser] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState('dashboard');
|
||||
const [pageParams, setPageParams] = useState({});
|
||||
const [selectedProject, setSelectedProject] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// 저장된 토큰 확인
|
||||
@@ -28,6 +24,24 @@ function App() {
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
// 자재 목록 페이지로 이동 이벤트 리스너
|
||||
const handleNavigateToMaterials = (event) => {
|
||||
const { jobNo, revision, bomName, message, file_id } = event.detail;
|
||||
navigateToPage('materials', {
|
||||
jobNo: jobNo,
|
||||
revision: revision,
|
||||
bomName: bomName,
|
||||
message: message,
|
||||
file_id: file_id // file_id 추가
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('navigateToMaterials', handleNavigateToMaterials);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('navigateToMaterials', handleNavigateToMaterials);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 로그인 성공 시 호출될 함수
|
||||
@@ -54,152 +68,393 @@ function App() {
|
||||
setPageParams(params);
|
||||
};
|
||||
|
||||
// 핵심 기능만 제공
|
||||
const getCoreFeatures = () => {
|
||||
return [
|
||||
{
|
||||
id: 'bom',
|
||||
title: '📋 BOM 업로드 & 분류',
|
||||
description: '엑셀 파일 업로드 → 자동 분류 → 검토 → 자재 확인 → 엑셀 내보내기',
|
||||
color: '#4299e1'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
// 관리자 전용 기능
|
||||
const getAdminFeatures = () => {
|
||||
if (user?.role !== 'admin') return [];
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'system-settings',
|
||||
title: '⚙️ 시스템 설정',
|
||||
description: '사용자 계정 관리',
|
||||
color: '#dc2626'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
// 페이지 렌더링 함수
|
||||
const renderCurrentPage = () => {
|
||||
console.log('현재 페이지:', currentPage, '페이지 파라미터:', pageParams);
|
||||
switch (currentPage) {
|
||||
case 'dashboard':
|
||||
return <DashboardPage user={user} />;
|
||||
case 'projects':
|
||||
return <ProjectsPage user={user} />;
|
||||
const coreFeatures = getCoreFeatures();
|
||||
const adminFeatures = getAdminFeatures();
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: '#f7fafc' }}>
|
||||
{/* 상단 헤더 */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderBottom: '1px solid #e2e8f0',
|
||||
padding: '16px 32px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<div>
|
||||
<h1 style={{ fontSize: '24px', fontWeight: '700', color: '#2d3748', margin: 0 }}>
|
||||
🏭 TK-MP BOM 관리 시스템
|
||||
</h1>
|
||||
<p style={{ color: '#718096', fontSize: '14px', margin: '4px 0 0 0' }}>
|
||||
{user?.full_name || user?.username}님 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{
|
||||
background: '#e2e8f0',
|
||||
color: '#4a5568',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '10px 16px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 메인 콘텐츠 */}
|
||||
<div style={{ padding: '32px', maxWidth: '800px', margin: '0 auto' }}>
|
||||
|
||||
{/* 프로젝트 선택 */}
|
||||
<div style={{ marginBottom: '32px' }}>
|
||||
<h2 style={{ fontSize: '18px', fontWeight: '600', color: '#2d3748', marginBottom: '12px' }}>
|
||||
📁 프로젝트 선택
|
||||
</h2>
|
||||
<select
|
||||
value={selectedProject?.official_project_code || ''}
|
||||
onChange={(e) => {
|
||||
const projectCode = e.target.value;
|
||||
if (projectCode) {
|
||||
setSelectedProject({
|
||||
official_project_code: projectCode,
|
||||
project_name: e.target.options[e.target.selectedIndex].text.split(' - ')[1]
|
||||
});
|
||||
} else {
|
||||
setSelectedProject(null);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '12px',
|
||||
border: '1px solid #e2e8f0',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px',
|
||||
background: 'white'
|
||||
}}
|
||||
>
|
||||
<option value="">프로젝트를 선택하세요</option>
|
||||
<option value="J24-001">J24-001 - 테스트 프로젝트 A</option>
|
||||
<option value="J24-002">J24-002 - 테스트 프로젝트 B</option>
|
||||
<option value="J24-003">J24-003 - 테스트 프로젝트 C</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* 핵심 기능 */}
|
||||
{selectedProject && (
|
||||
<>
|
||||
<div style={{ marginBottom: '32px' }}>
|
||||
<h2 style={{ fontSize: '20px', fontWeight: '600', color: '#2d3748', marginBottom: '16px' }}>
|
||||
📋 BOM 관리 워크플로우
|
||||
</h2>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '16px'
|
||||
}}>
|
||||
{coreFeatures.map((feature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '24px',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.07)',
|
||||
border: '1px solid #e2e8f0',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s, box-shadow 0.2s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
e.currentTarget.style.boxShadow = '0 8px 12px rgba(0, 0, 0, 0.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.07)';
|
||||
}}
|
||||
>
|
||||
<h3 style={{ fontSize: '18px', fontWeight: '600', color: '#2d3748', marginBottom: '12px' }}>
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p style={{ color: '#718096', marginBottom: '16px', fontSize: '14px' }}>
|
||||
{feature.description}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => navigateToPage(feature.id, { selectedProject })}
|
||||
style={{
|
||||
background: feature.color,
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '12px 20px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
시작하기
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 관리자 기능 (있는 경우만) */}
|
||||
{adminFeatures.length > 0 && (
|
||||
<div style={{ marginBottom: '32px' }}>
|
||||
<h2 style={{ fontSize: '20px', fontWeight: '600', color: '#2d3748', marginBottom: '16px' }}>
|
||||
⚙️ 시스템 관리
|
||||
</h2>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '16px'
|
||||
}}>
|
||||
{adminFeatures.map((feature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '24px',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.07)',
|
||||
border: '1px solid #e2e8f0',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s, box-shadow 0.2s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
e.currentTarget.style.boxShadow = '0 8px 12px rgba(0, 0, 0, 0.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.07)';
|
||||
}}
|
||||
>
|
||||
<h3 style={{ fontSize: '18px', fontWeight: '600', color: '#2d3748', marginBottom: '12px' }}>
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p style={{ color: '#718096', marginBottom: '16px', fontSize: '14px' }}>
|
||||
{feature.description}
|
||||
</p>
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<span style={{
|
||||
background: '#fef7e0',
|
||||
color: '#92400e',
|
||||
padding: '2px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>
|
||||
관리자 전용
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigateToPage(feature.id)}
|
||||
style={{
|
||||
background: feature.color,
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '12px 20px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
관리하기
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 간단한 사용법 안내 */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '24px',
|
||||
border: '1px solid #e2e8f0'
|
||||
}}>
|
||||
<h3 style={{ fontSize: '16px', fontWeight: '600', color: '#2d3748', marginBottom: '16px' }}>
|
||||
📖 간단한 사용법
|
||||
</h3>
|
||||
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<span style={{
|
||||
background: '#4299e1',
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>1</span>
|
||||
<span style={{ fontSize: '14px', color: '#4a5568' }}>BOM 업로드</span>
|
||||
</div>
|
||||
<span style={{ color: '#a0aec0' }}>→</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<span style={{
|
||||
background: '#ed8936',
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>2</span>
|
||||
<span style={{ fontSize: '14px', color: '#4a5568' }}>자동 분류</span>
|
||||
</div>
|
||||
<span style={{ color: '#a0aec0' }}>→</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<span style={{
|
||||
background: '#48bb78',
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>3</span>
|
||||
<span style={{ fontSize: '14px', color: '#4a5568' }}>엑셀 내보내기</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)} {/* selectedProject 조건문 닫기 */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'bom':
|
||||
return <JobSelectionPage onJobSelect={(jobNo, jobName) =>
|
||||
navigateToPage('bom-status', { job_no: jobNo, job_name: jobName })
|
||||
} />;
|
||||
case 'bom-status':
|
||||
return <BOMStatusPage
|
||||
jobNo={pageParams.job_no}
|
||||
jobName={pageParams.job_name}
|
||||
onNavigate={navigateToPage}
|
||||
/>;
|
||||
return (
|
||||
<BOMWorkspacePage
|
||||
project={pageParams.selectedProject}
|
||||
onNavigate={navigateToPage}
|
||||
onBack={() => navigateToPage('dashboard')}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'materials':
|
||||
return <SimpleMaterialsPage
|
||||
fileId={pageParams.file_id}
|
||||
jobNo={pageParams.jobNo}
|
||||
bomName={pageParams.bomName}
|
||||
revision={pageParams.revision}
|
||||
filename={pageParams.filename}
|
||||
onNavigate={navigateToPage}
|
||||
/>;
|
||||
case 'material-comparison':
|
||||
return <MaterialComparisonPage
|
||||
jobNo={pageParams.job_no}
|
||||
currentRevision={pageParams.current_revision}
|
||||
previousRevision={pageParams.previous_revision}
|
||||
filename={pageParams.filename}
|
||||
onNavigate={navigateToPage}
|
||||
/>;
|
||||
case 'revision-purchase':
|
||||
return <RevisionPurchasePage
|
||||
jobNo={pageParams.job_no}
|
||||
revision={pageParams.revision}
|
||||
onNavigate={navigateToPage}
|
||||
/>;
|
||||
case 'quotes':
|
||||
return <div style={{ padding: '32px' }}>💰 견적 관리 페이지 (곧 구현 예정)</div>;
|
||||
case 'procurement':
|
||||
return <div style={{ padding: '32px' }}>🛒 구매 관리 페이지 (곧 구현 예정)</div>;
|
||||
case 'production':
|
||||
return <div style={{ padding: '32px' }}>🏭 생산 관리 페이지 (곧 구현 예정)</div>;
|
||||
case 'shipment':
|
||||
return <div style={{ padding: '32px' }}>🚚 출하 관리 페이지 (곧 구현 예정)</div>;
|
||||
case 'users':
|
||||
return <div style={{ padding: '32px' }}>👥 사용자 관리 페이지 (곧 구현 예정)</div>;
|
||||
case 'system':
|
||||
return <div style={{ padding: '32px' }}>⚙️ 시스템 설정 페이지 (곧 구현 예정)</div>;
|
||||
return (
|
||||
<NewMaterialsPage
|
||||
onNavigate={navigateToPage}
|
||||
selectedProject={pageParams.selectedProject}
|
||||
fileId={pageParams.file_id}
|
||||
jobNo={pageParams.jobNo}
|
||||
bomName={pageParams.bomName}
|
||||
revision={pageParams.revision}
|
||||
filename={pageParams.filename}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'system-settings':
|
||||
return (
|
||||
<SystemSettingsPage
|
||||
onNavigate={navigateToPage}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return <DashboardPage user={user} />;
|
||||
return (
|
||||
<div style={{ padding: '32px', textAlign: 'center' }}>
|
||||
<h2>페이지를 찾을 수 없습니다</h2>
|
||||
<button
|
||||
onClick={() => navigateToPage('dashboard')}
|
||||
style={{
|
||||
background: '#4299e1',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '12px 24px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '16px'
|
||||
}}
|
||||
>
|
||||
대시보드로 돌아가기
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 로딩 중
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
background: '#f7fafc'
|
||||
}}>
|
||||
<div>로딩 중...</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '24px', marginBottom: '16px' }}>🔄</div>
|
||||
<div style={{ fontSize: '16px', color: '#718096' }}>로딩 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 로그인하지 않은 경우
|
||||
if (!isAuthenticated) {
|
||||
return <SimpleLogin onLoginSuccess={handleLoginSuccess} />;
|
||||
}
|
||||
|
||||
// 메인 애플리케이션
|
||||
return (
|
||||
<div style={{ display: 'flex', minHeight: '100vh' }}>
|
||||
<NavigationMenu
|
||||
user={user}
|
||||
currentPage={currentPage}
|
||||
onPageChange={(page) => navigateToPage(page, {})}
|
||||
/>
|
||||
|
||||
{/* 메인 콘텐츠 영역 */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
marginLeft: '280px', // 사이드바 너비만큼 여백
|
||||
background: '#f7fafc'
|
||||
}}>
|
||||
{/* 상단 헤더 */}
|
||||
<header style={{
|
||||
background: 'white',
|
||||
borderBottom: '1px solid #e2e8f0',
|
||||
padding: '16px 32px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<div>
|
||||
<h2 style={{
|
||||
margin: '0',
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
color: '#2d3748'
|
||||
}}>
|
||||
{currentPage === 'dashboard' && '대시보드'}
|
||||
{currentPage === 'projects' && '프로젝트 관리'}
|
||||
{currentPage === 'bom' && 'BOM 관리'}
|
||||
{currentPage === 'materials' && '자재 관리'}
|
||||
{currentPage === 'quotes' && '견적 관리'}
|
||||
{currentPage === 'procurement' && '구매 관리'}
|
||||
{currentPage === 'production' && '생산 관리'}
|
||||
{currentPage === 'shipment' && '출하 관리'}
|
||||
{currentPage === 'users' && '사용자 관리'}
|
||||
{currentPage === 'system' && '시스템 설정'}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
background: '#e53e3e',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px'
|
||||
}}
|
||||
>
|
||||
<span>🚪</span>
|
||||
로그아웃
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* 페이지 콘텐츠 */}
|
||||
<main>
|
||||
{renderCurrentPage()}
|
||||
</main>
|
||||
</div>
|
||||
<div style={{ minHeight: '100vh', background: '#f7fafc' }}>
|
||||
{renderCurrentPage()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
Reference in New Issue
Block a user