Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 업로드 당시 분류된 정보를 그대로 표시하도록 수정
- 복잡한 BOM 페이지 스타일 분류 로직 제거
- 간단하고 안정적인 테이블 형태로 자재 목록 표시
- 카테고리별 그룹화 유지하되 에러 방지를 위해 단순화
✅ 해결된 문제:
- 구매신청 페이지에서 몇몇 항목이 깨지던 문제 해결
- 업로드 당시 정보를 그대로 보여주도록 개선
240 lines
13 KiB
JavaScript
240 lines
13 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import api from '../api';
|
|
import { exportMaterialsToExcel } from '../utils/excelExport';
|
|
import './PurchaseRequestPage.css';
|
|
|
|
const PurchaseRequestPage = ({ onNavigate, fileId, jobNo, selectedProject }) => {
|
|
const [requests, setRequests] = useState([]);
|
|
const [selectedRequest, setSelectedRequest] = useState(null);
|
|
const [requestMaterials, setRequestMaterials] = useState([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadRequests();
|
|
}, [fileId, jobNo]);
|
|
|
|
const loadRequests = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const params = {};
|
|
if (fileId) params.file_id = fileId;
|
|
if (jobNo) params.job_no = jobNo;
|
|
|
|
const response = await api.get('/purchase-request/list', { params });
|
|
setRequests(response.data.requests || []);
|
|
} catch (error) {
|
|
console.error('Failed to load requests:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadRequestMaterials = async (requestId) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const response = await api.get(`/purchase-request/${requestId}/materials`);
|
|
// 그룹화된 자재가 있으면 우선 표시, 없으면 개별 자재 표시
|
|
if (response.data.grouped_materials && response.data.grouped_materials.length > 0) {
|
|
setRequestMaterials(response.data.grouped_materials);
|
|
} else {
|
|
setRequestMaterials(response.data.materials || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load materials:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleRequestSelect = (request) => {
|
|
setSelectedRequest(request);
|
|
loadRequestMaterials(request.request_id);
|
|
};
|
|
|
|
const handleDownloadExcel = async (requestId, requestNo) => {
|
|
try {
|
|
console.log('📥 엑셀 다운로드 시작:', requestId, requestNo);
|
|
|
|
// 서버에서 생성된 엑셀 파일 직접 다운로드 (BOM 페이지와 동일한 파일)
|
|
const response = await api.get(`/purchase-request/${requestId}/download-excel`, {
|
|
responseType: 'blob' // 파일 다운로드용
|
|
});
|
|
|
|
// 파일 다운로드 처리
|
|
const blob = new Blob([response.data], {
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
});
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `${requestNo}_재다운로드.xlsx`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
console.log('✅ 엑셀 파일 다운로드 완료');
|
|
} catch (error) {
|
|
console.error('❌ 엑셀 다운로드 실패:', error);
|
|
alert('엑셀 다운로드 실패: ' + error.message);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="purchase-request-page">
|
|
<div className="page-header">
|
|
<button onClick={() => onNavigate('bom', { selectedProject })} className="back-btn">
|
|
← BOM 관리로 돌아가기
|
|
</button>
|
|
<h1>구매신청 관리</h1>
|
|
<p className="subtitle">구매신청한 자재들을 그룹별로 관리합니다</p>
|
|
</div>
|
|
|
|
<div className="main-content">
|
|
{/* 구매신청 목록 */}
|
|
<div className="requests-panel">
|
|
<div className="panel-header">
|
|
<h2>구매신청 목록 ({requests.length})</h2>
|
|
</div>
|
|
|
|
<div className="requests-list">
|
|
{isLoading ? (
|
|
<div className="loading">로딩중...</div>
|
|
) : requests.length === 0 ? (
|
|
<div className="empty-state">구매신청이 없습니다</div>
|
|
) : (
|
|
requests.map(request => (
|
|
<div
|
|
key={request.request_id}
|
|
className={`request-card ${selectedRequest?.request_id === request.request_id ? 'selected' : ''}`}
|
|
onClick={() => handleRequestSelect(request)}
|
|
>
|
|
<div className="request-header">
|
|
<span className="request-no">{request.request_no}</span>
|
|
<span className="request-date">
|
|
{new Date(request.requested_at).toLocaleDateString()}
|
|
</span>
|
|
</div>
|
|
<div className="request-info">
|
|
<div>{request.job_no} - {request.job_name}</div>
|
|
<div className="material-count">
|
|
{request.category || '전체'} | {request.material_count}개 자재
|
|
</div>
|
|
</div>
|
|
<div className="request-footer">
|
|
<span className="requested-by">{request.requested_by}</span>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleDownloadExcel(request.request_id, request.request_no);
|
|
}}
|
|
className="download-btn"
|
|
>
|
|
📥 엑셀
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 선택된 구매신청 상세 */}
|
|
<div className="details-panel">
|
|
{selectedRequest ? (
|
|
<>
|
|
<div className="panel-header">
|
|
<h2>{selectedRequest.request_no}</h2>
|
|
<button
|
|
onClick={() => handleDownloadExcel(selectedRequest.request_id, selectedRequest.request_no)}
|
|
className="excel-btn"
|
|
>
|
|
📥 엑셀 다운로드
|
|
</button>
|
|
</div>
|
|
|
|
<div className="materials-table">
|
|
{/* 업로드 당시 분류된 정보를 그대로 표시 */}
|
|
{requestMaterials.length === 0 ? (
|
|
<div className="empty-state">자재 정보가 없습니다</div>
|
|
) : (
|
|
<div>
|
|
{/* 카테고리별로 그룹화하여 표시 */}
|
|
{(() => {
|
|
// 카테고리별로 자재 그룹화
|
|
const groupedByCategory = requestMaterials.reduce((acc, material) => {
|
|
const category = material.category || material.classified_category || 'UNKNOWN';
|
|
if (!acc[category]) acc[category] = [];
|
|
acc[category].push(material);
|
|
return acc;
|
|
}, {});
|
|
|
|
return Object.entries(groupedByCategory).map(([category, materials]) => (
|
|
<div key={category} style={{ marginBottom: '30px' }}>
|
|
<h3 style={{
|
|
background: '#f0f0f0',
|
|
padding: '10px',
|
|
borderRadius: '4px',
|
|
fontSize: '14px',
|
|
fontWeight: 'bold'
|
|
}}>
|
|
{category} ({materials.length}개)
|
|
</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th>카테고리</th>
|
|
<th>자재 설명</th>
|
|
<th>크기</th>
|
|
<th>스케줄</th>
|
|
<th>재질</th>
|
|
<th>수량</th>
|
|
<th>사용자요구</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{materials.map((material, idx) => (
|
|
<tr key={material.item_id || material.id || `${category}-${idx}`}>
|
|
<td>{idx + 1}</td>
|
|
<td>
|
|
<span className="category-badge">
|
|
{material.category || material.classified_category}
|
|
</span>
|
|
</td>
|
|
<td>{material.description || material.original_description}</td>
|
|
<td>{material.size || material.size_spec || '-'}</td>
|
|
<td>{material.schedule || '-'}</td>
|
|
<td>{material.material_grade || material.full_material_grade || '-'}</td>
|
|
<td>
|
|
<span style={{ fontWeight: 'bold' }}>
|
|
{Math.round(material.quantity || material.requested_quantity || 0)} {material.unit || material.requested_unit || '개'}
|
|
</span>
|
|
</td>
|
|
<td>{material.user_requirement || '-'}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
));
|
|
})()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="empty-state">
|
|
<div className="empty-icon">📦</div>
|
|
<div>구매신청을 선택하세요</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PurchaseRequestPage;
|