기타 보관함/개발자정보
React 17 챠트 에제 클래스형 컴퍼넌트
오아름 샘
2025. 5. 14. 23:41
반응형
import React, { Component } from 'react';
import axios from 'axios';
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
Cell, LabelList
} from 'recharts';
import { RefreshCw } from 'lucide-react';
// 타입 정의
interface DashboardState {
testPlan: {
scenario: { mapped: number, total: number },
testCase: { mapped: number, total: number },
activity: { mapped: number, total: number }
},
testExecution: {
scenario: { success: number, total: number },
testCase: { success: number, total: number },
activity: { success: number, total: number }
},
answerSheet: {
plan: number,
execution: number
},
notification: {
error: number,
checkTarget: number
},
personalResults: {
person1: { scenario: number, testCase: number, activity: number, total: number },
person2: { scenario: number, testCase: number, activity: number, total: number },
person3: { scenario: number, testCase: number, activity: number, total: number }
},
workTrend: {
process: Array<{
date: string,
success: number,
failure: number,
error: number,
notExecuted: number
}>,
answerSheet: Array<{
date: string,
success: number,
failure: number,
error: number,
notExecuted: number
}>,
notification: Array<{
date: string,
success: number,
failure: number,
error: number,
notExecuted: number
}>
},
currentTime: string,
loading: boolean
}
class TestDashboard extends Component<{}, DashboardState> {
constructor(props: {}) {
super(props);
this.state = {
testPlan: {
scenario: { mapped: 0, total: 0 },
testCase: { mapped: 0, total: 0 },
activity: { mapped: 0, total: 0 }
},
testExecution: {
scenario: { success: 0, total: 0 },
testCase: { success: 0, total: 0 },
activity: { success: 0, total: 0 }
},
answerSheet: {
plan: 0,
execution: 0
},
notification: {
error: 0,
checkTarget: 0
},
personalResults: {
person1: { scenario: 0, testCase: 0, activity: 0, total: 0 },
person2: { scenario: 0, testCase: 0, activity: 0, total: 0 },
person3: { scenario: 0, testCase: 0, activity: 0, total: 0 }
},
workTrend: {
process: [],
answerSheet: [],
notification: []
},
currentTime: new Date().toLocaleString(),
loading: false
};
}
componentDidMount() {
this.fetchDashboardData();
// 1분마다 시간 업데이트
this.timer = setInterval(() => {
this.setState({
currentTime: new Date().toLocaleString()
});
}, 60000);
}
componentWillUnmount() {
if (this.timer) {
clearInterval(this.timer);
}
}
timer: NodeJS.Timeout | null = null;
fetchDashboardData = async () => {
this.setState({ loading: true });
try {
const response = await axios.get('http://127.0.0.1:8080/do/initDashboard');
const data = response.data;
// 실제 API 응답에 맞게 데이터를 파싱하여 상태를 업데이트합니다.
// 여기서는 예시 데이터로 가정합니다.
this.setState({
testPlan: {
scenario: { mapped: data.testPlan?.scenario?.mapped || 45, total: data.testPlan?.scenario?.total || 50 },
testCase: { mapped: data.testPlan?.testCase?.mapped || 120, total: data.testPlan?.testCase?.total || 150 },
activity: { mapped: data.testPlan?.activity?.mapped || 280, total: data.testPlan?.activity?.total || 300 }
},
testExecution: {
scenario: { success: data.testExecution?.scenario?.success || 40, total: data.testExecution?.scenario?.total || 50 },
testCase: { success: data.testExecution?.testCase?.success || 130, total: data.testExecution?.testCase?.total || 150 },
activity: { success: data.testExecution?.activity?.success || 270, total: data.testExecution?.activity?.total || 300 }
},
answerSheet: {
plan: data.answerSheet?.plan || 80,
execution: data.answerSheet?.execution || 70
},
notification: {
error: data.notification?.error || 5,
checkTarget: data.notification?.checkTarget || 20
},
personalResults: {
person1: {
scenario: data.personalResults?.person1?.scenario || 15,
testCase: data.personalResults?.person1?.testCase || 50,
activity: data.personalResults?.person1?.activity || 100,
total: data.personalResults?.person1?.total || 165
},
person2: {
scenario: data.personalResults?.person2?.scenario || 20,
testCase: data.personalResults?.person2?.testCase || 60,
activity: data.personalResults?.person2?.activity || 120,
total: data.personalResults?.person2?.total || 200
},
person3: {
scenario: data.personalResults?.person3?.scenario || 15,
testCase: data.personalResults?.person3?.testCase || 40,
activity: data.personalResults?.person3?.activity || 80,
total: data.personalResults?.person3?.total || 135
}
},
workTrend: {
process: data.workTrend?.process || [
{ date: '05-10', success: 20, failure: 5, error: 2, notExecuted: 3 },
{ date: '05-11', success: 25, failure: 3, error: 1, notExecuted: 1 },
{ date: '05-12', success: 18, failure: 4, error: 2, notExecuted: 6 },
{ date: '05-13', success: 22, failure: 2, error: 1, notExecuted: 5 },
{ date: '05-14', success: 30, failure: 3, error: 0, notExecuted: 2 }
],
answerSheet: data.workTrend?.answerSheet || [
{ date: '05-10', success: 15, failure: 3, error: 1, notExecuted: 1 },
{ date: '05-11', success: 18, failure: 2, error: 0, notExecuted: 0 },
{ date: '05-12', success: 12, failure: 4, error: 2, notExecuted: 2 },
{ date: '05-13', success: 16, failure: 1, error: 1, notExecuted: 2 },
{ date: '05-14', success: 20, failure: 2, error: 0, notExecuted: 3 }
],
notification: data.workTrend?.notification || [
{ date: '05-10', success: 4, failure: 1, error: 0, notExecuted: 0 },
{ date: '05-11', success: 3, failure: 2, error: 0, notExecuted: 0 },
{ date: '05-12', success: 5, failure: 0, error: 1, notExecuted: 0 },
{ date: '05-13', success: 4, failure: 1, error: 0, notExecuted: 0 },
{ date: '05-14', success: 6, failure: 0, error: 0, notExecuted: 0 }
]
},
currentTime: new Date().toLocaleString()
});
} catch (error) {
console.error('데이터 불러오기 오류:', error);
// 오류 처리를 위한 추가 로직
} finally {
this.setState({ loading: false });
}
};
handleRefresh = () => {
this.fetchDashboardData();
};
// 상단 통계 카드 컴포넌트
renderStatCard = (title: string, data: any, type: string) => {
let content;
if (type === 'testPlan' || type === 'testExecution') {
const firstKey = type === 'testPlan' ? 'mapped' : 'success';
content = (
<div className="flex flex-col">
<div className="flex justify-between mb-1">
<span>시나리오:</span>
<span className="font-bold">{data.scenario[firstKey]}/{data.scenario.total}</span>
</div>
<div className="flex justify-between mb-1">
<span>테스트 케이스:</span>
<span className="font-bold">{data.testCase[firstKey]}/{data.testCase.total}</span>
</div>
<div className="flex justify-between">
<span>액티비티:</span>
<span className="font-bold">{data.activity[firstKey]}/{data.activity.total}</span>
</div>
</div>
);
} else if (type === 'answerSheet') {
content = (
<div className="flex flex-col">
<div className="flex justify-between mb-1">
<span>계획:</span>
<span className="font-bold">{data.plan}</span>
</div>
<div className="flex justify-between">
<span>수행:</span>
<span className="font-bold">{data.execution}</span>
</div>
</div>
);
} else if (type === 'notification') {
content = (
<div className="flex flex-col">
<div className="flex justify-between mb-1">
<span>오류:</span>
<span className="font-bold">{data.error}</span>
</div>
<div className="flex justify-between">
<span>체크대상:</span>
<span className="font-bold">{data.checkTarget}</span>
</div>
</div>
);
}
return (
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-lg font-bold mb-2 border-b pb-2">{title}</h3>
{content}
</div>
);
};
// Personal Results 차트 데이터 준비
preparePersonalResultsData = () => {
const { personalResults } = this.state;
return [
{ name: '담당자1', 시나리오: personalResults.person1.scenario, 테스트케이스: personalResults.person1.testCase, 액티비티: personalResults.person1.activity, 전체: personalResults.person1.total },
{ name: '담당자2', 시나리오: personalResults.person2.scenario, 테스트케이스: personalResults.person2.testCase, 액티비티: personalResults.person2.activity, 전체: personalResults.person2.total },
{ name: '담당자3', 시나리오: personalResults.person3.scenario, 테스트케이스: personalResults.person3.testCase, 액티비티: personalResults.person3.activity, 전체: personalResults.person3.total }
];
};
// Work Trend 스택 바 차트 렌더링
renderStackedBarChart = (data: any[], title: string) => {
return (
<div className="bg-white p-4 rounded-lg shadow h-full">
<h3 className="text-lg font-bold mb-4">{title}</h3>
<ResponsiveContainer width="100%" height={220}>
<BarChart data={data} barSize={30}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="success" stackId="a" fill="#4caf50" name="성공" />
<Bar dataKey="failure" stackId="a" fill="#ff9800" name="실패" />
<Bar dataKey="error" stackId="a" fill="#f44336" name="오류" />
<Bar dataKey="notExecuted" stackId="a" fill="#9e9e9e" name="비수행" />
</BarChart>
</ResponsiveContainer>
</div>
);
};
render() {
const { testPlan, testExecution, answerSheet, notification, workTrend, currentTime, loading } = this.state;
const personalResultsData = this.preparePersonalResultsData();
return (
<div className="p-6 bg-gray-100 min-h-screen">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-1">테스트 관리 대시보드</h1>
<p className="text-gray-600">테스트 계획 수립 및 실행 현황을 한눈에 확인하세요.</p>
</div>
{/* 상단 통계 카드 영역 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
{this.renderStatCard('테스트 계획', testPlan, 'testPlan')}
{this.renderStatCard('테스트 수행', testExecution, 'testExecution')}
{this.renderStatCard('정답지', answerSheet, 'answerSheet')}
{this.renderStatCard('알람', notification, 'notification')}
</div>
{/* 차트 영역 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Personal Results 차트 */}
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-lg font-bold mb-4">Personal Results</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={personalResultsData} barGap={8}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="시나리오" fill="#8884d8">
<LabelList dataKey="시나리오" position="top" />
</Bar>
<Bar dataKey="테스트케이스" fill="#82ca9d">
<LabelList dataKey="테스트케이스" position="top" />
</Bar>
<Bar dataKey="액티비티" fill="#ffc658">
<LabelList dataKey="액티비티" position="top" />
</Bar>
<Bar dataKey="전체" fill="#ff8042">
<LabelList dataKey="전체" position="top" />
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
{/* Work Trend 차트 */}
<div className="grid grid-cols-1 gap-4">
<h3 className="text-lg font-bold ml-4">Work Trend</h3>
{this.renderStackedBarChart(workTrend.process, "프로세스")}
{this.renderStackedBarChart(workTrend.answerSheet, "정답지")}
{this.renderStackedBarChart(workTrend.notification, "알람")}
</div>
</div>
{/* 푸터 영역 */}
<div className="flex justify-between items-center bg-white p-4 rounded-lg shadow">
<div className="text-gray-600">
마지막 업데이트: {currentTime}
</div>
<button
className="flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg"
onClick={this.handleRefresh}
disabled={loading}
>
<RefreshCw size={16} />
{loading ? '로딩 중...' : '새로 고침'}
</button>
</div>
</div>
);
}
}
export default TestDashboard;
반응형