import React, { useState, useEffect } from 'react'; import { Typography, Box, Card, CardContent, Grid, CircularProgress, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, FormControl, InputLabel, Select, MenuItem } from '@mui/material'; import { fetchMaterials } from '../api'; import { Bar, Pie, Line } from 'react-chartjs-2'; import 'chart.js/auto'; import Toast from './Toast'; function Dashboard({ selectedProject, projects }) { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [materials, setMaterials] = useState([]); const [barData, setBarData] = useState(null); const [pieData, setPieData] = useState(null); const [materialGradeData, setMaterialGradeData] = useState(null); const [sizeData, setSizeData] = useState(null); const [topMaterials, setTopMaterials] = useState([]); const [toast, setToast] = useState({ open: false, message: '', type: 'info' }); useEffect(() => { if (selectedProject) { fetchMaterialStats(); fetchMaterialList(); } }, [selectedProject]); const fetchMaterialStats = async () => { setLoading(true); try { const response = await fetch(`/files/materials/summary?project_id=${selectedProject.id}`); if (response.ok) { const data = await response.json(); setStats(data.summary); } } catch (error) { setToast({ open: true, message: '자재 통계 로드 실패', type: 'error' }); } finally { setLoading(false); } }; const fetchMaterialList = async () => { try { // 최대 1000개까지 조회(실무에서는 서버 페이징/집계 API 권장) const params = { project_id: selectedProject.id, skip: 0, limit: 1000 }; const response = await fetchMaterials(params); setMaterials(response.data.materials || []); } catch (error) { setToast({ open: true, message: '자재 목록 로드 실패', type: 'error' }); } }; useEffect(() => { if (materials.length > 0) { // 분류별 집계 const typeCounts = {}; const typeQuantities = {}; const materialGrades = {}; const sizes = {}; const materialQuantities = {}; materials.forEach(mat => { const type = mat.item_type || 'OTHER'; const grade = mat.material_grade || '미분류'; const size = mat.size_spec || '미분류'; const desc = mat.original_description; typeCounts[type] = (typeCounts[type] || 0) + 1; typeQuantities[type] = (typeQuantities[type] || 0) + (mat.quantity || 0); materialGrades[grade] = (materialGrades[grade] || 0) + 1; sizes[size] = (sizes[size] || 0) + 1; materialQuantities[desc] = (materialQuantities[desc] || 0) + (mat.quantity || 0); }); // Bar 차트 데이터 setBarData({ labels: Object.keys(typeCounts), datasets: [ { label: '자재 수', data: Object.values(typeCounts), backgroundColor: 'rgba(25, 118, 210, 0.6)', borderColor: 'rgba(25, 118, 210, 1)', borderWidth: 1 }, { label: '총 수량', data: Object.values(typeQuantities), backgroundColor: 'rgba(220, 0, 78, 0.4)', borderColor: 'rgba(220, 0, 78, 1)', borderWidth: 1 } ] }); // 재질별 Pie 차트 setMaterialGradeData({ labels: Object.keys(materialGrades), datasets: [{ data: Object.values(materialGrades), backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#FF6384', '#C9CBCF' ], borderWidth: 2 }] }); // 사이즈별 Pie 차트 setSizeData({ labels: Object.keys(sizes), datasets: [{ data: Object.values(sizes), backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#FF6384', '#C9CBCF' ], borderWidth: 2 }] }); // 상위 자재 (수량 기준) const sortedMaterials = Object.entries(materialQuantities) .sort(([,a], [,b]) => b - a) .slice(0, 10) .map(([desc, qty]) => ({ description: desc, quantity: qty })); setTopMaterials(sortedMaterials); } else { setBarData(null); setMaterialGradeData(null); setSizeData(null); setTopMaterials([]); } }, [materials]); return ( 📊 대시보드 {/* 선택된 프로젝트 정보 */} {selectedProject && ( {selectedProject.project_name} ({selectedProject.official_project_code}) 상태: {selectedProject.status} | 생성일: {new Date(selectedProject.created_at).toLocaleDateString()} )} {/* 전역 Toast */} setToast({ open: false, message: '', type: 'info' })} /> {/* 프로젝트 현황 */} 프로젝트 현황 {projects.length} 총 프로젝트 수 선택된 프로젝트: {selectedProject.project_name} {/* 자재 현황 */} 자재 현황 {loading ? ( ) : stats ? ( {stats.total_items.toLocaleString()} 총 자재 수 최초 업로드: {stats.earliest_upload ? new Date(stats.earliest_upload).toLocaleString() : '-'} 최신 업로드: {stats.latest_upload ? new Date(stats.latest_upload).toLocaleString() : '-'} ) : ( 프로젝트를 선택하면 자재 현황을 확인할 수 있습니다. )} {/* 분류별 자재 통계 */} 분류별 자재 통계 {barData ? ( ) : ( 자재 데이터가 없습니다. )} {/* 재질별 분포 */} 재질별 분포 {materialGradeData ? ( ) : ( 재질 데이터가 없습니다. )} {/* 사이즈별 분포 */} 사이즈별 분포 {sizeData ? ( ) : ( 사이즈 데이터가 없습니다. )} {/* 상위 자재 */} 상위 자재 (수량 기준) {topMaterials.length > 0 ? ( 순위 자재명 총 수량 {topMaterials.map((material, index) => ( {index + 1} {material.description} ))}
) : ( 자재 데이터가 없습니다. )}
{/* 프로젝트 상세 정보 */} {selectedProject && ( 📋 프로젝트 상세 정보 프로젝트 코드 {selectedProject.official_project_code} 프로젝트명 {selectedProject.project_name} 상태 생성일 {new Date(selectedProject.created_at).toLocaleDateString()} )}
); } export default Dashboard;