feat: 구매신청 기능 완성 및 SUPPORT/SPECIAL 카테고리 개선

- 모든 카테고리 구매신청 기능 완성 (PIPE, FITTING, VALVE, FLANGE, GASKET, BOLT, SUPPORT, SPECIAL, UNKNOWN)
- 구매신청 완료 항목: 회색 배경, 체크박스 비활성화, '구매신청완료' 배지 표시
- 전체 선택/구매신청 시 이미 구매신청된 항목 자동 제외
- 구매신청 quantity 타입 에러 수정 (문자열 -> 정수 변환)

SUPPORT 카테고리 (구 U-BOLT):
- U-BOLT -> SUPPORT로 카테고리명 변경
- 클램프, 유볼트, 우레탄블럭슈 분류 개선
- 테이블 헤더: 선택-종류-타입-크기-디스크립션-추가요구-사용자요구-수량
- 크기 정보 main_nom 필드에서 가져오기 (배관 인치)
- 엑셀 내보내기 형식 조정

SPECIAL 카테고리:
- SPECIAL 키워드 자재 자동 분류 (SPECIFICATION 제외)
- 파일 업로드 시 SPECIAL 카테고리 처리 로직 추가
- 도면번호 필드 추가 (drawing_name, line_no)
- 타입 필드: 크기/스케줄/재질 제외한 핵심 정보 표시
- 엑셀 DWG_NAME, LINE_NUM 컬럼 파싱 및 저장

FITTING 카테고리:
- 테이블 컬럼 너비 조정 (선택 2%, 종류 8.5%, 수량 12%)

구매신청 관리:
- 엑셀 재다운로드 형식 개선 (BOM 페이지와 동일한 형식)
- 그룹화된 자재 정보 포함하여 저장 및 다운로드
This commit is contained in:
Hyungi Ahn
2025-10-14 12:39:25 +09:00
parent e468663386
commit e27020ae9b
44 changed files with 13102 additions and 176 deletions

View File

@@ -5,6 +5,8 @@ import NewMaterialsPage from './pages/NewMaterialsPage';
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 ErrorBoundary from './components/ErrorBoundary';
@@ -27,6 +29,20 @@ function App() {
const [newProjectCode, setNewProjectCode] = useState('');
const [newProjectName, setNewProjectName] = useState('');
const [newClientName, setNewClientName] = useState('');
const [pendingSignupCount, setPendingSignupCount] = useState(0);
// 승인 대기 중인 회원가입 수 조회
const loadPendingSignups = async () => {
try {
const response = await api.get('/auth/signup-requests');
// API 응답이 { requests: [...], count: ... } 형태
const pendingCount = response.data.count || 0;
setPendingSignupCount(pendingCount);
} catch (error) {
console.error('승인 대기 조회 실패:', error);
setPendingSignupCount(0);
}
};
// 프로젝트 목록 로드
const loadProjects = async () => {
@@ -143,11 +159,32 @@ function App() {
};
}, [showUserMenu]);
// 관리자인 경우 주기적으로 승인 대기 수 확인
useEffect(() => {
if ((user?.role === 'admin' || user?.role === 'system') && isAuthenticated) {
// 초기 로드
loadPendingSignups();
// 30초마다 확인
const interval = setInterval(() => {
loadPendingSignups();
}, 30000);
return () => clearInterval(interval);
}
}, [user?.role, isAuthenticated]);
// 로그인 성공 시 호출될 함수
const handleLoginSuccess = () => {
const userData = localStorage.getItem('user_data');
if (userData) {
setUser(JSON.parse(userData));
const parsedUser = JSON.parse(userData);
setUser(parsedUser);
// 관리자인 경우 승인 대기 수 확인
if (parsedUser?.role === 'admin' || parsedUser?.role === 'system') {
loadPendingSignups();
}
}
setIsAuthenticated(true);
};
@@ -173,8 +210,14 @@ function App() {
{
id: 'bom',
title: '📋 BOM 업로드 & 분류',
description: '엑셀 파일 업로드 → 자동 분류 → 검토 → 자재 확인 → 엑셀 내보내기',
description: '엑셀 파일 업로드 → 자동 분류 → 검토 → 자재 확인 → 구매신청 (엑셀 내보내기)',
color: '#4299e1'
},
{
id: 'purchase-request',
title: '📦 구매신청 관리',
description: '구매신청한 자재들을 그룹별로 조회하고 엑셀 재다운로드',
color: '#10b981'
}
];
};
@@ -183,15 +226,20 @@ function App() {
const getAdminFeatures = () => {
const features = [];
// 시스템 관리자 전용 기능
if (user?.role === 'system') {
console.log('getAdminFeatures - Current user:', user);
console.log('getAdminFeatures - User role:', user?.role);
console.log('getAdminFeatures - Pending count:', pendingSignupCount);
// 시스템 관리자 기능 (admin role이 시스템 관리자)
if (user?.role === 'admin') {
features.push(
{
id: 'user-management',
title: '👥 사용자 관리',
description: '계정 생성, 역할 변경, 사용자 삭제',
description: '계정 생성, 역할 변경, 회원가입 승인',
color: '#dc2626',
badge: '시스템 관리자'
badge: '시스템 관리자',
pendingCount: pendingSignupCount
},
{
id: 'system-logs',
@@ -203,8 +251,24 @@ function App() {
);
}
// 일반 관리자 기능
if (user?.role === 'manager') {
// 일반 관리자는 회원가입 승인만 가능
features.push(
{
id: 'user-management',
title: '👥 회원가입 승인',
description: '신규 회원가입 승인 및 거부',
color: '#dc2626',
badge: '관리자',
pendingCount: pendingSignupCount
}
);
}
// 관리자 이상 공통 기능
if (user?.role === 'admin' || user?.role === 'system') {
if (user?.role === 'admin' || user?.role === 'manager') {
features.push(
{
id: 'log-monitoring',
@@ -661,13 +725,12 @@ function App() {
)}
</div>
{/* 핵심 기능 */}
{/* 핵심 기능 - 프로젝트 선택 시만 표시 */}
{selectedProject && (
<>
<div style={{ marginBottom: '32px' }}>
<h2 style={{ fontSize: '20px', fontWeight: '600', color: '#2d3748', marginBottom: '16px' }}>
📋 BOM 관리 워크플로우
</h2>
<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))',
@@ -701,7 +764,10 @@ function App() {
{feature.description}
</p>
<button
onClick={() => navigateToPage(feature.id, { selectedProject })}
onClick={() => navigateToPage(feature.id, {
selectedProject,
jobNo: selectedProject?.official_project_code
})}
style={{
background: feature.color,
color: 'white',
@@ -720,8 +786,9 @@ function App() {
))}
</div>
</div>
)} {/* selectedProject 조건문 닫기 */}
{/* 관리자 기능 (있는 경우만) */}
{/* 관리자 기능 (프로젝트 선택과 무관하게 항상 표시) */}
{adminFeatures.length > 0 && (
<div style={{ marginBottom: '32px' }}>
<h2 style={{ fontSize: '20px', fontWeight: '600', color: '#2d3748', marginBottom: '16px' }}>
@@ -753,8 +820,29 @@ function App() {
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 style={{
fontSize: '18px',
fontWeight: '600',
color: '#2d3748',
marginBottom: '12px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<span>{feature.title}</span>
{feature.id === 'user-management' && feature.pendingCount > 0 && (
<span style={{
background: '#ef4444',
color: 'white',
borderRadius: '12px',
padding: '2px 8px',
fontSize: '12px',
fontWeight: '700',
animation: 'pulse 2s ease-in-out infinite'
}}>
{feature.pendingCount} 대기
</span>
)}
</h3>
<p style={{ color: '#718096', marginBottom: '16px', fontSize: '14px' }}>
{feature.description}
@@ -853,8 +941,7 @@ function App() {
</div>
</div>
</div>
</>
)} {/* selectedProject 조건문 닫기 */}
)} {/* adminFeatures 조건문 닫기 */}
</div>
</div>
);
@@ -880,6 +967,25 @@ function App() {
filename={pageParams.filename}
/>
);
case 'purchase-batch':
return (
<PurchaseBatchPage
onNavigate={navigateToPage}
fileId={pageParams.file_id}
jobNo={pageParams.jobNo}
/>
);
case 'purchase-request':
return (
<PurchaseRequestPage
onNavigate={navigateToPage}
fileId={pageParams.file_id}
jobNo={pageParams.jobNo}
selectedProject={pageParams.selectedProject}
/>
);
case 'system-settings':
return (