Files
TK-BOM-Project/frontend/src/components/MaterialList.jsx

246 lines
8.1 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import {
Typography,
Box,
Card,
CardContent,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TablePagination,
CircularProgress,
Alert,
Chip
} from '@mui/material';
import { Inventory } from '@mui/icons-material';
function MaterialList({ selectedProject }) {
const [materials, setMaterials] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(25);
const [totalCount, setTotalCount] = useState(0);
useEffect(() => {
if (selectedProject) {
fetchMaterials();
} else {
setMaterials([]);
setTotalCount(0);
}
}, [selectedProject, page, rowsPerPage]);
const fetchMaterials = async () => {
setLoading(true);
setError('');
try {
const skip = page * rowsPerPage;
const response = await fetch(
`http://localhost:8000/api/files/materials?project_id=${selectedProject.id}&skip=${skip}&limit=${rowsPerPage}`
);
if (response.ok) {
const data = await response.json();
setMaterials(data.materials || []);
setTotalCount(data.total_count || 0);
} else {
setError('자재 데이터를 불러오는데 실패했습니다.');
}
} catch (error) {
console.error('자재 조회 실패:', error);
setError('네트워크 오류가 발생했습니다.');
} finally {
setLoading(false);
}
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const getItemTypeColor = (itemType) => {
const colors = {
'PIPE': 'primary',
'FITTING': 'secondary',
'VALVE': 'success',
'FLANGE': 'warning',
'BOLT': 'info',
'OTHER': 'default'
};
return colors[itemType] || 'default';
};
if (!selectedProject) {
return (
<Box>
<Typography variant="h4" gutterBottom>
📋 자재 목록
</Typography>
<Card>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<Inventory sx={{ fontSize: 64, color: 'secondary.main', mb: 2 }} />
<Typography variant="h6" gutterBottom>
프로젝트를 선택해주세요
</Typography>
<Typography variant="body2" color="textSecondary">
프로젝트 관리 탭에서 프로젝트를 선택하면 자재 목록을 확인할 있습니다.
</Typography>
</CardContent>
</Card>
</Box>
);
}
return (
<Box>
<Typography variant="h4" gutterBottom>
📋 자재 목록 (그룹핑)
</Typography>
<Typography variant="h6" color="primary" sx={{ mb: 2 }}>
{selectedProject.project_name} ({selectedProject.official_project_code})
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{loading ? (
<Card>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<CircularProgress size={60} />
<Typography variant="h6" sx={{ mt: 2 }}>
자재 데이터 로딩 ...
</Typography>
</CardContent>
</Card>
) : materials.length === 0 ? (
<Card>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<Inventory sx={{ fontSize: 64, color: 'secondary.main', mb: 2 }} />
<Typography variant="h6" gutterBottom>
자재 데이터가 없습니다
</Typography>
<Typography variant="body2" color="textSecondary">
파일 업로드 탭에서 BOM 파일을 업로드해주세요.
</Typography>
</CardContent>
</Card>
) : (
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h6">
{totalCount.toLocaleString()} 자재 그룹
</Typography>
<Chip
label={`${materials.length}개 표시 중`}
color="primary"
variant="outlined"
/>
</Box>
<TableContainer component={Paper} variant="outlined">
<Table>
<TableHead>
<TableRow sx={{ bgcolor: 'grey.50' }}>
<TableCell><strong>번호</strong></TableCell>
<TableCell><strong>유형</strong></TableCell>
<TableCell><strong>자재명</strong></TableCell>
<TableCell align="center"><strong> 수량</strong></TableCell>
<TableCell align="center"><strong>단위</strong></TableCell>
<TableCell align="center"><strong>사이즈</strong></TableCell>
<TableCell><strong>재질</strong></TableCell>
<TableCell align="center"><strong>라인 </strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{materials.map((material, index) => (
<TableRow
key={material.id}
sx={{ '&:hover': { bgcolor: 'grey.50' } }}
>
<TableCell>
{page * rowsPerPage + index + 1}
</TableCell>
<TableCell>
<Chip
label={material.item_type || 'OTHER'}
size="small"
color={getItemTypeColor(material.item_type)}
/>
</TableCell>
<TableCell>
<Typography variant="body2" sx={{ maxWidth: 300 }}>
{material.original_description}
</Typography>
</TableCell>
<TableCell align="center">
<Typography variant="h6" color="primary">
{material.quantity.toLocaleString()}
</Typography>
</TableCell>
<TableCell align="center">
<Chip label={material.unit} size="small" />
</TableCell>
<TableCell align="center">
<Chip
label={material.size_spec || '-'}
size="small"
color="secondary"
/>
</TableCell>
<TableCell>
<Typography variant="body2" color="primary">
{material.material_grade || '-'}
</Typography>
</TableCell>
<TableCell align="center">
<Chip
label={`${material.line_count || 1}개 라인`}
size="small"
variant="outlined"
title={material.line_numbers_str}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div"
count={totalCount}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleChangeRowsPerPage}
rowsPerPageOptions={[10, 25, 50, 100]}
labelRowsPerPage="페이지당 행 수:"
labelDisplayedRows={({ from, to, count }) =>
`${from}-${to} / 총 ${count !== -1 ? count : to}`
}
/>
</CardContent>
</Card>
)}
</Box>
);
}
export default MaterialList;