✨ Phase 1: 도면 자재 분석 업로드 페이지 구현
This commit is contained in:
245
frontend/src/components/MaterialList.jsx
Normal file
245
frontend/src/components/MaterialList.jsx
Normal file
@@ -0,0 +1,245 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user