diff --git a/src/components/dashboard/ErrorList.jsx b/src/components/dashboard/ErrorList.jsx index 6dd7000..a9583a9 100644 --- a/src/components/dashboard/ErrorList.jsx +++ b/src/components/dashboard/ErrorList.jsx @@ -1,6 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { fetchProjectDashboard } from '../../api/dashboardAPI'; import { Warning, Person, AccessTime } from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; function ErrorList({ selectedProjectId }) { const { data: dashboardData, isLoading, error } = useQuery({ @@ -9,11 +10,18 @@ function ErrorList({ selectedProjectId }) { enabled: !!selectedProjectId, }); + const navigate = useNavigate(); const priorities = dashboardData?.data?.priorities?.priority || []; - + const errorPriorities = dashboardData?.data?.errorPriorities || {}; + // WARNING 우선순위만 필터링 (에러로 간주) const errorTasks = priorities.find(p => p.priority === 'WARNING')?.priorityDataList || []; + // 이슈 클릭 핸들러 + const handleIssueClick = (issueId) => { + navigate(`/api/issues/${issueId}`); + }; + if (isLoading) { return (
@@ -74,7 +82,7 @@ function ErrorList({ selectedProjectId }) {
- 즉시 처리 필요 + {errorPriorities.endDate || '즉시 처리 필요'}
@@ -82,7 +90,10 @@ function ErrorList({ selectedProjectId }) { {/* 액션 버튼 */}
-
@@ -67,9 +107,6 @@ function PriorityTasks({ selectedProjectId }) {

우선순위 작업

-
{!selectedProjectId ? '프로젝트를 선택해주세요' : '데이터를 불러올 수 없습니다'} @@ -79,38 +116,45 @@ function PriorityTasks({ selectedProjectId }) { } // WARNING 우선순위는 제외 (에러 리스트 컴포넌트에서 표시) - const filteredPriorities = priorities.filter(p => p.priority !== 'WARNING'); + const filteredPriorities = priorities + .filter(p => p.priority !== 'WARNING') + .sort((a, b) => getPriorityOrder(a.priority) - getPriorityOrder(b.priority)); return (

우선순위 작업

-
{filteredPriorities.map((priorityGroup) => { const config = getPriorityConfig(priorityGroup.priority); + const currentVisibleItems = visibleItems[priorityGroup.priority] || 3; + const totalItems = priorityGroup.priorityDataList.length; + const hasMoreItems = totalItems > currentVisibleItems; + const hasLessItems = currentVisibleItems > 3; return (
- {/* 우선순위 헤더 */} + {/* 우선순위 헤더 - 기존처럼 배경색 없이 */}
{config.label} - ({priorityGroup.priorityDataList.length}개) + ({totalItems}개)
- {/* 작업 목록 */} + {/* 작업 목록 - 우선순위별 배경색 적용 */}
- {priorityGroup.priorityDataList.slice(0, 3).map((task) => ( -
+ {priorityGroup.priorityDataList.slice(0, currentVisibleItems).map((task) => ( +
handleIssueClick(task.id)} + >
@@ -127,10 +171,22 @@ function PriorityTasks({ selectedProjectId }) {
))} - {/* 더 많은 항목이 있을 경우 */} - {priorityGroup.priorityDataList.length > 3 && ( -
- +{priorityGroup.priorityDataList.length - 3}개 더 + {/* 더 보기/더 적게 보기 버튼 */} + {hasMoreItems && ( +
handleShowMore(priorityGroup.priority)} + > + +{totalItems - currentVisibleItems}개 더 보기 +
+ )} + + {hasLessItems && ( +
handleShowLess(priorityGroup.priority)} + > + 접기
)}
@@ -151,4 +207,4 @@ function PriorityTasks({ selectedProjectId }) { ); } -export default PriorityTasks; +export default PriorityTasks; \ No newline at end of file diff --git a/src/components/dashboard/ProjectProgressChart.jsx b/src/components/dashboard/ProjectProgressChart.jsx index 452d160..f4dde64 100644 --- a/src/components/dashboard/ProjectProgressChart.jsx +++ b/src/components/dashboard/ProjectProgressChart.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from 'recharts'; +import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, Label } from 'recharts'; import { useQuery } from '@tanstack/react-query'; import { fetchProjectDashboard } from '../../api/dashboardAPI'; @@ -14,45 +14,51 @@ function ProjectProgressChart({ selectedProjectId }) { // 디버깅을 위한 콘솔 로그 React.useEffect(() => { - if (projectGraph) { - console.log('ProjectGraph 데이터:', projectGraph); + if (dashboardData) { + console.log('전체 대시보드 데이터:', dashboardData); + console.log('projectGraph 데이터:', projectGraph); } - }, [projectGraph]); + }, [dashboardData, projectGraph]); const pieData = React.useMemo(() => { if (!projectGraph) return []; const data = []; - if (projectGraph.todo > 0 || projectGraph.TODO > 0) { + // 각 상태별 데이터 처리 (대소문자 구분 없이) + const todo = projectGraph.todo || projectGraph.TODO || 0; + const progress = projectGraph.progress || projectGraph.IN_PROGRESS || projectGraph.inProgress || 0; + const review = projectGraph.review || projectGraph.REVIEW || 0; + const done = projectGraph.done || projectGraph.DONE || 0; + + if (todo > 0) { data.push({ - name: '할 일', - value: Number(projectGraph.todo || projectGraph.TODO || 0), + name: '계획', + value: Number(todo), color: '#6B7280' }); } - if (projectGraph.progress > 0 || projectGraph.IN_PROGRESS > 0) { + if (progress > 0) { data.push({ name: '진행중', - value: Number(projectGraph.progress || projectGraph.IN_PROGRESS || 0), + value: Number(progress), color: '#3B82F6' }); } - if (projectGraph.review > 0 || projectGraph.REVIEW > 0) { + if (review > 0) { data.push({ - name: '검토', - value: Number(projectGraph.review || projectGraph.REVIEW || 0), + name: '리뷰중', + value: Number(review), color: '#F59E0B' }); } - - if (projectGraph.done > 0 || projectGraph.DONE > 0) { + if (done > 0) { data.push({ name: '완료', - value: Number(projectGraph.done || projectGraph.DONE || 0), + value: Number(done), color: '#10B981' }); } @@ -61,6 +67,55 @@ function ProjectProgressChart({ selectedProjectId }) { return data; }, [projectGraph]); + // 완료율 계산 - 더 안전한 방식 + const completionRate = React.useMemo(() => { + if (!projectGraph) return 0; + + // 총 개수 계산 + const total = Number(projectGraph.total || 0); + const completed = Number(projectGraph.done || projectGraph.DONE || 0); + + console.log('완료율 계산:', { total, completed }); + + if (total === 0) return 0; + + const rate = completed; + console.log('계산된 완료율:', rate); + return rate; + }, [projectGraph]); + + // 각 항목별 퍼센트 계산 + const itemPercentages = React.useMemo(() => { + if (!projectGraph) return []; + + const total = Number(projectGraph.total || 0); + if (total === 0) return []; + + return pieData.map(item => { + const percentage = item.value ; + console.log(`${item.name} 퍼센트:`, { value: item.value, total, percentage }); + return { + ...item, + percentage + }; + }); + }, [pieData, projectGraph]); + + // 중앙 라벨 컴포넌트 + const renderCustomLabel = () => { + return ( + + {completionRate}% + + ); + }; + if (isLoading) { return (
@@ -87,7 +142,7 @@ function ProjectProgressChart({ selectedProjectId }) {

프로젝트 진행률

-
+
{pieData && pieData.length > 0 ? ( @@ -103,20 +158,16 @@ function ProjectProgressChart({ selectedProjectId }) { {pieData.map((entry, index) => ( ))} + ) : ( @@ -129,6 +180,26 @@ function ProjectProgressChart({ selectedProjectId }) { )}
+ {/* 각 항목별 퍼센트 표시 */} + {itemPercentages.length > 0 && ( +
+ {itemPercentages.map((item, index) => ( +
+
+
+ {item.name} +
+ + {item.percentage}% + +
+ ))} +
+ )} + {/* 총 이슈 개수 표시 */}
@@ -139,4 +210,4 @@ function ProjectProgressChart({ selectedProjectId }) { ); } -export default ProjectProgressChart +export default ProjectProgressChart; \ No newline at end of file diff --git a/src/components/dashboard/TeamProductivityTrend.jsx b/src/components/dashboard/TeamProductivityTrend.jsx index b61bcf6..75c3447 100644 --- a/src/components/dashboard/TeamProductivityTrend.jsx +++ b/src/components/dashboard/TeamProductivityTrend.jsx @@ -3,7 +3,7 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Responsive import { useQuery } from '@tanstack/react-query'; import { fetchProjectDashboard } from '../../api/dashboardAPI'; -function TeamProductivityTrend({ selectedProjectId }) { +function TeamProductivityTrend({ selectedProjectId, title = "인원별 이슈 진행률" }) { const { data: dashboardData, isLoading, error } = useQuery({ queryKey: ['projectDashboard', selectedProjectId], queryFn: () => fetchProjectDashboard(selectedProjectId), @@ -25,8 +25,8 @@ function TeamProductivityTrend({ selectedProjectId }) { if (isLoading) { return ( -
-

인원별 이슈 진행률

+
+

{title}

@@ -36,8 +36,8 @@ function TeamProductivityTrend({ selectedProjectId }) { if (error || !selectedProjectId) { return ( -
-

인원별 이슈 진행률

+
+

{title}

{!selectedProjectId ? '프로젝트를 선택해주세요' : '데이터를 불러올 수 없습니다'}
@@ -47,7 +47,7 @@ function TeamProductivityTrend({ selectedProjectId }) { return (
-

인원별 이슈 진행률

+

{title}

{barData.length > 0 ? ( diff --git a/src/components/dashboard/WeeklySchedule.jsx b/src/components/dashboard/WeeklySchedule.jsx index d480b67..1bc0042 100644 --- a/src/components/dashboard/WeeklySchedule.jsx +++ b/src/components/dashboard/WeeklySchedule.jsx @@ -1,5 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { fetchProjectDashboard } from '../../api/dashboardAPI'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; function WeeklySchedule({ selectedProjectId }) { const { data: dashboardData, isLoading, error } = useQuery({ @@ -8,17 +10,37 @@ function WeeklySchedule({ selectedProjectId }) { enabled: !!selectedProjectId, }); + const navigate = useNavigate(); const weekendIssues = dashboardData?.data?.weekendIssues?.weekendIssue || {}; const weekDays = ['월', '화', '수', '목', '금', '토', '일']; + + // 현재 날짜 가져오기 const today = new Date(); const currentDay = today.getDay() === 0 ? 7 : today.getDay(); // 일요일을 7로 변환 + + // 이번 주의 시작일(월요일) 계산 + const startOfWeek = new Date(today); + const dayOfWeek = today.getDay(); + const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // 일요일이면 6일 빼기 + startOfWeek.setDate(today.getDate() - daysToMonday); + + // 이번 주의 날짜들 생성 (1일부터 7일까지) + const weekDates = []; + for (let i = 0; i < 7; i++) { + const date = new Date(startOfWeek); + date.setDate(startOfWeek.getDate() + i); + weekDates.push(date.getDate()); + } + + // 선택된 날짜 상태 관리 + const [selectedDay, setSelectedDay] = useState(currentDay); // 상태별 색상 매핑 const getStatusColor = (status) => { switch (status) { case 'TODO': return 'bg-gray-400'; - case 'PROGRESS': return 'bg-blue-400'; + case 'IN_PROGRESS': return 'bg-blue-400'; case 'REVIEW': return 'bg-orange-400'; case 'DONE': return 'bg-green-400'; default: return 'bg-gray-400'; @@ -28,13 +50,23 @@ function WeeklySchedule({ selectedProjectId }) { const getStatusText = (status) => { switch (status) { case 'TODO': return '할 일'; - case 'PROGRESS': return '진행중'; + case 'IN_PROGRESS': return '진행중'; case 'REVIEW': return '검토'; case 'DONE': return '완료'; default: return status; } }; + // 날짜 클릭 핸들러 + const handleDateClick = (dayNumber) => { + setSelectedDay(dayNumber); + }; + + // 이슈 클릭 핸들러 + const handleIssueClick = (issueId) => { + navigate(`/api/issues/${issueId}`); + }; + if (isLoading) { return (
@@ -67,16 +99,23 @@ function WeeklySchedule({ selectedProjectId }) { const dayNumber = index + 1; const dayIssues = weekendIssues[dayNumber] || []; const isToday = dayNumber === currentDay; + const isSelected = dayNumber === selectedDay; + const dateNumber = weekDates[index]; return (
{day}
-
- {dayNumber} +
handleDateClick(dayNumber)} + > + {dateNumber} {dayIssues.length > 0 && (
{dayIssues.length} @@ -88,16 +127,20 @@ function WeeklySchedule({ selectedProjectId }) { })}
- {/* 오늘 일정 */} + {/* 선택된 날짜의 일정 */}

- 오늘 일정 ({weekDays[currentDay - 1]}) + {selectedDay === currentDay ? '오늘 일정' : `${weekDays[selectedDay - 1]}요일 일정`} ({weekDays[selectedDay - 1]})

- {weekendIssues[currentDay] && weekendIssues[currentDay].length > 0 ? ( + {weekendIssues[selectedDay] && weekendIssues[selectedDay].length > 0 ? (
- {weekendIssues[currentDay].map((issue) => ( -
+ {weekendIssues[selectedDay].map((issue) => ( +
handleIssueClick(issue.id)} + >
{issue.title}
@@ -113,7 +156,7 @@ function WeeklySchedule({ selectedProjectId }) {
) : (
- 오늘 예정된 이슈가 없습니다 + {selectedDay === currentDay ? '오늘 예정된 이슈가 없습니다' : '해당 날짜에 예정된 이슈가 없습니다'}
)} @@ -129,4 +172,4 @@ function WeeklySchedule({ selectedProjectId }) { ); } -export default WeeklySchedule +export default WeeklySchedule; \ No newline at end of file diff --git a/src/components/meeting/MeetingCreate.jsx b/src/components/meeting/MeetingCreate.jsx index c646a3d..81795d6 100644 --- a/src/components/meeting/MeetingCreate.jsx +++ b/src/components/meeting/MeetingCreate.jsx @@ -14,7 +14,12 @@ function MeetingCreate({ onBack, onSave, meeting = null, isEditing = false, proj participants: meeting?.participants || [], content: meeting?.description || meeting?.memo || meeting?.content || "", type: meeting?.type || "MEETING", - progressDate: meeting?.progressDate || new Date().toISOString().slice(0, -1) + '+09:00', + progressDate: meeting?.progressDate || (() => { + // 현재 시간에 18시간 추가 + const now = new Date(); + now.setHours(now.getHours() + 18); + return now.toISOString().slice(0, -1) + '+09:00'; // 한국 시간대 + })(), }); console.log('MeetingCreate에서 받은 projectId:', projectId); @@ -84,6 +89,7 @@ function MeetingCreate({ onBack, onSave, meeting = null, isEditing = false, proj fetchTeamMembers(); }, [fetchTeamMembers]); + // 입력값 변경 핸들러 const handleInputChange = (field, value) => { setMeetingData((prev) => ({ diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 5205b7a..b103ad5 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -1,3 +1,4 @@ +import React, { useEffect } from "react"; import KPICard from "../components/dashboard/KPICard"; import ProjectProgressChart from "../components/dashboard/ProjectProgressChart"; import WeeklySchedule from "../components/dashboard/WeeklySchedule"; @@ -8,15 +9,49 @@ import ProjectList from "../components/dashboard/ProjectList"; import useProjectStore from "../store/projectStore"; import { Source, Warning } from "@mui/icons-material"; import { useNavigate } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import { fetchDashboardProjects, fetchProjectDashboard } from "../api/dashboardAPI"; function Dashboard() { const navigate = useNavigate(); const { selectedProjectId } = useProjectStore(); + // fetchDashboardProjects API에서 데이터 가져오기 + const { data: dashboardData } = useQuery({ + queryKey: ['dashboardProjects'], + queryFn: fetchDashboardProjects, + }); + + // fetchProjectDashboard API에서 프로젝트별 데이터 가져오기 + const { data: projectDashboardData } = useQuery({ + queryKey: ['projectDashboard', selectedProjectId], + queryFn: () => fetchProjectDashboard(selectedProjectId), + enabled: !!selectedProjectId, + }); + + // 서버 데이터에서 KPI 값 추출 + const projectCount = dashboardData?.data?.projectCount || 0; + const issueCount = dashboardData?.data?.issueCount || 0; + + // 사용자 역할 확인 + const userRole = projectDashboardData?.data?.role || 'User'; + const isPM = userRole === 'PM'; + const isUser = userRole === 'User'; + + // 디버깅용 로그 + useEffect(() => { + if (projectDashboardData) { + console.log('프로젝트 대시보드 데이터:', projectDashboardData); + console.log('사용자 역할:', userRole); + console.log('PM 여부:', isPM); + console.log('User 여부:', isUser); + } + }, [projectDashboardData, userRole, isPM, isUser]); + const kpiData = [ { title: "진행 중인 프로젝트", - value: "12", + value: projectCount.toString(), icon: , trend: "up", path: "/projects", @@ -24,7 +59,7 @@ function Dashboard() { }, { title: "이번 주 마감 작업", - value: "8", + value: issueCount.toString(), icon: , trend: "down", trendValue: "-3 from last week", @@ -36,11 +71,14 @@ function Dashboard() {
- {/* KPI 카드들 */} -
- + + {/* 에러 발생 창 - PM일 경우에만 표시 */} + {isPM && ( + + )} +
@@ -60,7 +98,13 @@ function Dashboard() { /> ))}
- + + {/* 인원별 이슈 진행률 - 역할에 따라 제목 변경 */} + +
@@ -75,4 +119,4 @@ function Dashboard() { ); } -export default Dashboard; +export default Dashboard; \ No newline at end of file