diff --git a/backend/exports/PR-20251014-003.json b/backend/exports/PR-20251014-003.json new file mode 100644 index 0000000..3e4a76d --- /dev/null +++ b/backend/exports/PR-20251014-003.json @@ -0,0 +1,66 @@ +{ + "request_no": "PR-20251014-003", + "job_no": "테스트용", + "created_at": "2025-10-14T21:40:12.581699", + "materials": [ + { + "material_id": 1, + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3, + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 143, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW * NPT", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A106 B", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [ + { + "group_key": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT|1/2\"|undefined|ASTM A312 TP304", + "material_ids": [ + 1, + 3 + ], + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW * NPT|1\"|undefined|ASTM A106 B", + "material_ids": [ + 143 + ], + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW * NPT", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A106 B", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + } + ] +} \ No newline at end of file diff --git a/backend/exports/PR-20251014-003.xlsx b/backend/exports/PR-20251014-003.xlsx new file mode 100644 index 0000000..5eadfc0 Binary files /dev/null and b/backend/exports/PR-20251014-003.xlsx differ diff --git a/backend/exports/PR-20251014-004.json b/backend/exports/PR-20251014-004.json new file mode 100644 index 0000000..1698b57 --- /dev/null +++ b/backend/exports/PR-20251014-004.json @@ -0,0 +1,576 @@ +{ + "request_no": "PR-20251014-004", + "job_no": "테스트용", + "created_at": "2025-10-14T21:44:18.401404", + "materials": [ + { + "material_id": 4, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 11, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 12, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A106 B", + "quantity": 92, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [ + { + "group_key": "PIPE, SMLS, SCH 40S, ASTM A312 TP304|1/2\"|undefined|ASTM A312 TP304", + "material_ids": [ + 4 + ], + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 11, + "unit": "m", + "total_length": 1395.1, + "pipe_lengths": [ + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 155, + "quantity": 1, + "totalLength": 155 + }, + { + "length": 155, + "quantity": 1, + "totalLength": 155 + }, + { + "length": 200, + "quantity": 1, + "totalLength": 200 + }, + { + "length": 245.1, + "quantity": 1, + "totalLength": 245.1 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + } + ], + "user_requirement": "" + }, + { + "group_key": "PIPE, SMLS, SCH 80, ASTM A106 B|3/4\"|undefined|ASTM A106 B", + "material_ids": [ + 12 + ], + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A106 B", + "quantity": 92, + "unit": "m", + "total_length": 7920.2, + "pipe_lengths": [ + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 88.6, + "quantity": 1, + "totalLength": 88.6 + }, + { + "length": 88.6, + "quantity": 1, + "totalLength": 88.6 + }, + { + "length": 98.4, + "quantity": 1, + "totalLength": 98.4 + }, + { + "length": 98.4, + "quantity": 1, + "totalLength": 98.4 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 120, + "quantity": 1, + "totalLength": 120 + }, + { + "length": 120, + "quantity": 1, + "totalLength": 120 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 223.6, + "quantity": 1, + "totalLength": 223.6 + } + ], + "user_requirement": "" + } + ] +} \ No newline at end of file diff --git a/backend/exports/PR-20251014-004.xlsx b/backend/exports/PR-20251014-004.xlsx new file mode 100644 index 0000000..a00593a Binary files /dev/null and b/backend/exports/PR-20251014-004.xlsx differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 361bb6a..3162fe4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,15 +13,14 @@ 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 App() { +function AppContent() { // 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 { user, isAuthenticated, isLoading, logout } = useAuth(); const [currentPage, setCurrentPage] = useState('dashboard'); const [pageParams, setPageParams] = useState({}); const [selectedProject, setSelectedProject] = useState(null); @@ -177,30 +176,13 @@ function App() { }); }; - // 인증 상태 확인 + // 인증된 사용자의 데이터 로드 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(); - }, []); + if (isAuthenticated && user) { + loadPendingSignups(); + loadProjects(); + } + }, [isAuthenticated, user]); // 페이지 이동 함수 const navigateToPage = (page, params = {}) => { @@ -359,22 +341,11 @@ function App() { } }; - const handleLogout = () => { - localStorage.removeItem('access_token'); - localStorage.removeItem('user_data'); - setIsAuthenticated(false); - setUser(null); + const handleLogout = async () => { + await logout(); setCurrentPage('dashboard'); - window.location.reload(); }; - const handleLoginSuccess = (userData) => { - setIsAuthenticated(true); - setUser(userData); - setIsLoading(false); - loadPendingSignups(); - loadProjects(); - }; return ( @@ -392,7 +363,7 @@ function App() { ) : !isAuthenticated ? ( - + ) : (
{/* 상단 헤더 */} @@ -429,4 +400,13 @@ function App() { ); } +// AuthProvider로 감싸는 래퍼 컴포넌트 +function App() { + return ( + + + + ); +} + export default App; \ No newline at end of file diff --git a/frontend/src/SimpleLogin.jsx b/frontend/src/SimpleLogin.jsx index de626cf..c181928 100644 --- a/frontend/src/SimpleLogin.jsx +++ b/frontend/src/SimpleLogin.jsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; +import { useAuth } from './contexts/AuthContext'; import api from './api'; -const SimpleLogin = ({ onLoginSuccess }) => { +const SimpleLogin = () => { + const { login } = useAuth(); const [formData, setFormData] = useState({ username: '', password: '' @@ -110,28 +112,11 @@ const SimpleLogin = ({ onLoginSuccess }) => { setError(''); try { - const response = await api.post('/auth/login', formData); - const data = response.data; - - if (data.success) { - // 토큰과 사용자 정보 저장 - localStorage.setItem('access_token', data.access_token); - localStorage.setItem('user_data', JSON.stringify(data.user)); - - setSuccess('로그인 성공! 대시보드로 이동합니다...'); - - // 잠깐 성공 메시지 보여준 후 대시보드로 이동 - setTimeout(() => { - if (onLoginSuccess) { - onLoginSuccess(); - } - }, 1000); - } else { - setError(data.error?.message || '로그인에 실패했습니다.'); - } + await login(formData.username, formData.password); + setSuccess('로그인 성공! 대시보드로 이동합니다...'); } catch (err) { console.error('Login error:', err); - setError(err.response?.data?.message || '서버 연결에 실패했습니다.'); + setError(err.message || '서버 연결에 실패했습니다.'); } finally { setIsLoading(false); }