Files
TK-BOM-Project/frontend/src/App.jsx
hyungi edfe1bdf78
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
feat: 완전한 데이터베이스 스키마 정리 및 테스트 서버 안정화
- 43개 테이블로 구성된 완전한 BOM 관리 시스템
- 카테고리별 상세 테이블 (9개): pipe, fitting, flange, valve, gasket, bolt, support, special, instrument
- 사용자 관리 시스템: users, login_logs, user_sessions, user_activity_logs
- 구매 관리 시스템: purchase_requests, purchase_request_items, purchase_items
- 리비전 관리 시스템: material_revisions_comparison, material_comparison_details
- 튜빙 시스템: tubing_categories, tubing_manufacturers, tubing_products, tubing_specifications
- 표준화/분류 시스템: material_standards, material_categories, material_patterns 등
- 권한 관리 시스템: permissions, role_permissions
- 고급 기능: user_requirements, pipe_end_preparations, material_tubing_mapping
- 성능 최적화: materials 테이블 17개 인덱스, GIN/복합/조건부 인덱스
- 데이터 무결성: 외래키 제약으로 일관성 보장
- 확장성: JSONB 활용한 유연한 메타데이터 저장
2025-10-21 12:33:05 +09:00

432 lines
14 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import SimpleLogin from './SimpleLogin';
import DashboardPage from './pages/DashboardPage';
import { UserMenu, ErrorBoundary } from './components/common';
import NewMaterialsPage from './pages/NewMaterialsPage';
import BOMManagementPage from './pages/BOMManagementPage';
import UnifiedBOMPage from './pages/UnifiedBOMPage';
import SystemSettingsPage from './pages/SystemSettingsPage';
import AccountSettingsPage from './pages/AccountSettingsPage';
import UserManagementPage from './pages/UserManagementPage';
import PurchaseBatchPage from './pages/PurchaseBatchPage';
import PurchaseRequestPage from './pages/PurchaseRequestPage';
import SystemLogsPage from './pages/SystemLogsPage';
import LogMonitoringPage from './pages/LogMonitoringPage';
import InactiveProjectsPage from './pages/InactiveProjectsPage';
import errorLogger from './utils/errorLogger';
import api from './api';
import './App.css';
function App() {
// TK-MP BOM Management System v2.0 - Project Selection Dashboard
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState(null);
const [currentPage, setCurrentPage] = useState('dashboard');
const [pageParams, setPageParams] = useState({});
const [selectedProject, setSelectedProject] = useState(null);
const [showUserMenu, setShowUserMenu] = useState(false);
const [projects, setProjects] = useState([]);
const [editingProject, setEditingProject] = useState(null);
const [editedProjectName, setEditedProjectName] = useState('');
const [showCreateProject, setShowCreateProject] = useState(false);
const [newProjectCode, setNewProjectCode] = useState('');
const [newProjectName, setNewProjectName] = useState('');
const [newClientName, setNewClientName] = useState('');
const [pendingSignupCount, setPendingSignupCount] = useState(0);
const [inactiveProjects, setInactiveProjects] = useState(() => {
// localStorage에서 비활성화된 프로젝트 목록 로드
try {
const saved = localStorage.getItem('inactiveProjects');
return saved ? new Set(JSON.parse(saved)) : new Set();
} catch (error) {
console.error('비활성화 프로젝트 목록 로드 실패:', error);
return new Set();
}
});
// 비활성화 프로젝트 목록이 변경될 때마다 localStorage에 저장
useEffect(() => {
try {
localStorage.setItem('inactiveProjects', JSON.stringify(Array.from(inactiveProjects)));
} catch (error) {
console.error('비활성화 프로젝트 목록 저장 실패:', error);
}
}, [inactiveProjects]);
// 승인 대기 중인 회원가입 수 조회
const loadPendingSignups = async () => {
try {
const response = await api.get('/auth/pending-signups/count');
setPendingSignupCount(response.data.count || 0);
} catch (error) {
console.error('승인 대기 회원가입 수 조회 실패:', error);
setPendingSignupCount(0);
}
};
// 프로젝트 목록 로드
const loadProjects = async () => {
try {
const response = await api.get('/dashboard/projects');
if (response.data && response.data.projects) {
setProjects(response.data.projects);
}
} catch (error) {
console.error('프로젝트 목록 로드 실패:', error);
// API 실패 시 에러를 무시하고 더미 데이터 사용
}
};
// 프로젝트 생성
const createProject = async () => {
if (!newProjectCode || !newProjectName) {
alert('프로젝트 코드와 이름을 입력해주세요.');
return;
}
try {
const response = await api.post(`/dashboard/projects?official_project_code=${encodeURIComponent(newProjectCode)}&project_name=${encodeURIComponent(newProjectName)}&client_name=${encodeURIComponent(newClientName)}`);
if (response.data.success) {
// 프로젝트 목록 갱신
await loadProjects();
// 폼 초기화
setShowCreateProject(false);
setNewProjectCode('');
setNewProjectName('');
setNewClientName('');
alert('프로젝트가 생성되었습니다.');
}
} catch (error) {
console.error('프로젝트 생성 실패:', error);
const errorMsg = error.response?.data?.detail || '프로젝트 생성에 실패했습니다.';
alert(errorMsg);
}
};
// 프로젝트 이름 수정
const updateProjectName = async (projectId) => {
try {
const response = await api.patch(`/dashboard/projects/${projectId}?job_name=${encodeURIComponent(editedProjectName)}`);
if (response.data.success) {
// 프로젝트 목록 갱신
await loadProjects();
// 선택된 프로젝트 업데이트
if (selectedProject && selectedProject.id === projectId) {
setSelectedProject({
...selectedProject,
project_name: editedProjectName
});
}
// 편집 모드 종료
setEditingProject(null);
setEditedProjectName('');
alert('프로젝트 이름이 수정되었습니다.');
}
} catch (error) {
console.error('프로젝트 수정 실패:', error);
const errorMsg = error.response?.data?.detail || '프로젝트 수정에 실패했습니다.';
alert(errorMsg);
}
};
// 프로젝트 삭제
const deleteProject = async (projectId) => {
if (window.confirm('정말로 이 프로젝트를 삭제하시겠습니까?')) {
try {
const response = await api.delete(`/dashboard/projects/${projectId}`);
if (response.data.success) {
alert('프로젝트가 삭제되었습니다.');
await loadProjects();
// 비활성 프로젝트 목록에서도 제거
setInactiveProjects(prev => {
const newSet = new Set(prev);
newSet.delete(projectId);
return newSet;
});
if (selectedProject?.id === projectId) {
setSelectedProject(null);
}
} else {
alert(`프로젝트 삭제 실패: ${response.data.detail}`);
}
} catch (error) {
console.error('프로젝트 삭제 실패:', error);
alert(`프로젝트 삭제 실패: ${error.response?.data?.detail || error.message}`);
}
}
};
// 프로젝트 활성화
const handleActivateProject = (project) => {
const projectId = project.job_no || project.official_project_code || project.id;
console.log('🔄 프로젝트 활성화:', { project, projectId });
setInactiveProjects(prev => {
const newSet = new Set(prev);
newSet.delete(projectId);
console.log('📦 활성화 프로젝트 업데이트:', { prev: Array.from(prev), new: Array.from(newSet) });
return newSet;
});
};
// 인증 상태 확인
useEffect(() => {
const checkAuth = async () => {
try {
const token = localStorage.getItem('access_token');
if (token) {
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
const userResponse = await api.get('/auth/me');
setUser(userResponse.data.user);
setIsAuthenticated(true);
await loadPendingSignups();
await loadProjects(); // 프로젝트 로드 추가
}
} catch (error) {
console.error('인증 확인 실패:', error);
localStorage.removeItem('access_token');
localStorage.removeItem('user_data');
setIsAuthenticated(false);
} finally {
setIsLoading(false);
}
};
checkAuth();
}, []);
// 페이지 이동 함수
const navigateToPage = (page, params = {}) => {
setCurrentPage(page);
setPageParams(params);
setShowUserMenu(false);
};
// 페이지 렌더링 함수
const renderCurrentPage = () => {
switch (currentPage) {
case 'dashboard':
return (
<DashboardPage
user={user}
projects={projects}
pendingSignupCount={pendingSignupCount}
navigateToPage={navigateToPage}
loadProjects={loadProjects}
createProject={createProject}
updateProjectName={updateProjectName}
deleteProject={deleteProject}
editingProject={editingProject}
setEditingProject={setEditingProject}
editedProjectName={editedProjectName}
setEditedProjectName={setEditedProjectName}
showCreateProject={showCreateProject}
setShowCreateProject={setShowCreateProject}
newProjectCode={newProjectCode}
setNewProjectCode={setNewProjectCode}
newProjectName={newProjectName}
setNewProjectName={setNewProjectName}
newClientName={newClientName}
setNewClientName={setNewClientName}
inactiveProjects={inactiveProjects}
setInactiveProjects={setInactiveProjects}
/>
);
case 'unified-bom':
return (
<UnifiedBOMPage
onNavigate={navigateToPage}
selectedProject={pageParams.selectedProject}
user={user}
/>
);
case 'materials':
return (
<BOMManagementPage
onNavigate={navigateToPage}
user={user}
selectedProject={pageParams.selectedProject}
fileId={pageParams.file_id}
jobNo={pageParams.jobNo}
bomName={pageParams.bomName}
revision={pageParams.revision}
filename={pageParams.filename}
/>
);
case 'purchase-batch':
return (
<PurchaseBatchPage
onNavigate={navigateToPage}
user={user}
selectedProject={pageParams.selectedProject}
/>
);
case 'purchase-request':
return (
<PurchaseRequestPage
onNavigate={navigateToPage}
user={user}
selectedProject={pageParams.selectedProject}
/>
);
case 'system-settings':
return (
<SystemSettingsPage
onNavigate={navigateToPage}
user={user}
/>
);
case 'account-settings':
return (
<AccountSettingsPage
onNavigate={navigateToPage}
user={user}
onUserUpdate={(updatedUser) => {
setUser(updatedUser);
localStorage.setItem('user_data', JSON.stringify(updatedUser));
}}
/>
);
case 'user-management':
return (
<UserManagementPage
onNavigate={navigateToPage}
user={user}
/>
);
case 'system-logs':
return (
<SystemLogsPage
onNavigate={navigateToPage}
user={user}
/>
);
case 'log-monitoring':
return (
<LogMonitoringPage
onNavigate={navigateToPage}
user={user}
/>
);
case 'inactive-projects':
return (
<InactiveProjectsPage
onNavigate={navigateToPage}
user={user}
projects={projects}
inactiveProjects={inactiveProjects}
onActivateProject={handleActivateProject}
onDeleteProject={deleteProject}
/>
);
default:
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
background: '#f7fafc'
}}>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '24px', marginBottom: '16px' }}></div>
<div style={{ fontSize: '16px', color: '#718096' }}> 없는 페이지입니다.</div>
<button
onClick={() => navigateToPage('dashboard')}
style={{
background: '#4299e1',
color: 'white',
border: 'none',
borderRadius: '6px',
padding: '12px 24px',
cursor: 'pointer',
marginTop: '16px'
}}
>
대시보드로 돌아가기
</button>
</div>
</div>
);
}
};
const handleLogout = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('user_data');
setIsAuthenticated(false);
setUser(null);
setCurrentPage('dashboard');
window.location.reload();
};
const handleLoginSuccess = (userData) => {
setIsAuthenticated(true);
setUser(userData);
setIsLoading(false);
loadPendingSignups();
loadProjects();
};
return (
<ErrorBoundary errorContext={{ user, currentPage, pageParams }}>
{isLoading ? (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
background: '#f7fafc'
}}>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '24px', marginBottom: '16px' }}>🔄</div>
<div style={{ fontSize: '16px', color: '#718096' }}>로딩 ...</div>
</div>
</div>
) : !isAuthenticated ? (
<SimpleLogin onLoginSuccess={handleLoginSuccess} />
) : (
<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 Management System
</h1>
<p style={{ color: '#718096', fontSize: '14px', margin: '4px 0 0 0' }}>
{user?.name || user?.username} 환영합니다
</p>
</div>
{/* 사용자 메뉴 */}
<UserMenu
user={user}
onNavigate={navigateToPage}
onLogout={handleLogout}
/>
</div>
{/* 페이지 컨텐츠 */}
{renderCurrentPage()}
</div>
)}
</ErrorBoundary>
);
}
export default App;