Files
TK-BOM-Project/frontend/src/App.jsx
Hyungi Ahn 0c99697a6f
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
fix: 로그인 후 사용자 역할 인식 문제 해결
- AuthContext를 통한 통합 인증 상태 관리로 변경
- App.jsx와 AuthContext 간 중복 사용자 상태 관리 제거
- SimpleLogin에서 AuthContext의 login 함수 직접 사용
- 로그인 후 사용자 역할이 즉시 올바르게 인식되도록 개선
- 새로고침 없이도 시스템 관리자 권한 정상 표시

변경사항:
- App.jsx: AuthProvider 래퍼 추가, 중복 인증 로직 제거
- SimpleLogin.jsx: AuthContext 직접 사용으로 변경
- 사용자 상태 동기화 문제 완전 해결
2025-10-20 07:22:27 +09:00

412 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 { AuthProvider, useAuth } from './contexts/AuthContext';
import errorLogger from './utils/errorLogger';
import api from './api';
import './App.css';
function AppContent() {
// TK-MP BOM Management System v2.0 - Project Selection Dashboard
const { user, isAuthenticated, isLoading, logout } = useAuth();
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(() => {
if (isAuthenticated && user) {
loadPendingSignups();
loadProjects();
}
}, [isAuthenticated, user]);
// 페이지 이동 함수
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 = async () => {
await logout();
setCurrentPage('dashboard');
};
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 />
) : (
<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>
);
}
// AuthProvider로 감싸는 래퍼 컴포넌트
function App() {
return (
<AuthProvider>
<AppContent />
</AuthProvider>
);
}
export default App;