| import React, { useEffect, useState } from 'react'; |
| import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from 'recharts'; |
| import { Classification, Innovation, StatsData, ResultResponse } from '../types'; |
| import { COLOR_MAP } from '../constants'; |
| import { backendService } from '../services/backendService'; |
| import InnovationCard from './InnovationCard'; |
| import { CircleDashed } from 'lucide-react'; |
|
|
| interface StatsDashboardProps { |
| innovations: Innovation[]; |
| isVisible: boolean; |
| } |
|
|
| const StatsDashboard: React.FC<StatsDashboardProps> = ({ isVisible }) => { |
| const [dbInnovations, setDbInnovations] = useState<ResultResponse[]>([]); |
| const [loading, setLoading] = useState(false); |
|
|
| |
| const [activeFilters, setActiveFilters] = useState<Classification[]>([ |
| Classification.HIGH, |
| Classification.MEDIUM, |
| Classification.LOW, |
| Classification.UNCLASSIFIED |
| ]); |
|
|
| |
| const fetchDbInnovations = async () => { |
| setLoading(true); |
| const results = await backendService.fetchClassifiedInnovations(); |
|
|
| |
| const transformed: ResultResponse[] = results.map((r: any) => ({ |
| id: r.id, |
| file_name: r.file_name, |
| content: r.content, |
| context: r.context || "N/A", |
| problem: r.problem || "N/A", |
| methodology: r.methodology || "N/A", |
| classification: r.classification, |
| })); |
|
|
| setDbInnovations(transformed); |
| setLoading(false); |
| }; |
|
|
| useEffect(() => { |
| if (isVisible) { |
| fetchDbInnovations(); |
| } |
| }, [isVisible]); |
|
|
|
|
| const handleClassify = async (id: string, classification: Classification) => { |
| |
| const inv = dbInnovations.find(i => i.id === Number(id)); |
| if (inv && inv.id) { |
| |
| setDbInnovations(prev => prev.map(i => i.id === Number(id) ? { ...i, classification } : i)); |
| await backendService.saveClassification(Number(id), classification); |
| } |
| }; |
|
|
| const toggleFilter = (cls: Classification) => { |
| setActiveFilters(prev => |
| prev.includes(cls) ? prev.filter(c => c !== cls) : [...prev, cls] |
| ); |
| }; |
|
|
| |
| const displayInnovations = dbInnovations.filter(i => |
| activeFilters.includes(i.classification as Classification) |
| ); |
|
|
| |
| const statsSource = dbInnovations.length > 0 ? dbInnovations : []; |
|
|
| const data: StatsData[] = [ |
| { name: 'High Priority', value: statsSource.filter(i => i.classification === Classification.HIGH).length, fill: COLOR_MAP[Classification.HIGH] }, |
| { name: 'Medium Priority', value: statsSource.filter(i => i.classification === Classification.MEDIUM).length, fill: COLOR_MAP[Classification.MEDIUM] }, |
| { name: 'Low Priority', value: statsSource.filter(i => i.classification === Classification.LOW).length, fill: COLOR_MAP[Classification.LOW] }, |
| { name: 'Rejected', value: statsSource.filter(i => i.classification === Classification.DELETE).length, fill: COLOR_MAP[Classification.DELETE] }, |
| { name: 'Unclassified', value: statsSource.filter(i => i.classification === Classification.UNCLASSIFIED).length, fill: COLOR_MAP[Classification.UNCLASSIFIED] }, |
| ]; |
|
|
| |
| const contextData = [ |
| { name: 'Network Opt', count: 12 }, |
| { name: 'Security', count: 8 }, |
| { name: 'QoS', count: 15 }, |
| { name: 'New Use Cases', count: 5 }, |
| ]; |
|
|
| return ( |
| <div className="space-y-8"> |
| {/* Charts Section */} |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| <div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200"> |
| <h3 className="text-lg font-semibold text-slate-800 mb-4">Classification Status</h3> |
| <div className="h-64"> |
| <ResponsiveContainer width="100%" height="100%"> |
| <PieChart> |
| <Pie |
| data={data} |
| cx="50%" |
| cy="50%" |
| innerRadius={60} |
| outerRadius={80} |
| paddingAngle={5} |
| dataKey="value" |
| > |
| {data.map((entry, index) => ( |
| <Cell key={`cell-${index}`} fill={entry.fill} /> |
| ))} |
| </Pie> |
| <Tooltip /> |
| <Legend /> |
| </PieChart> |
| </ResponsiveContainer> |
| </div> |
| </div> |
| |
| <div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200"> |
| <h3 className="text-lg font-semibold text-slate-800 mb-4">Innovation Contexts (Trends)</h3> |
| <div className="h-64"> |
| <ResponsiveContainer width="100%" height="100%"> |
| <BarChart data={contextData}> |
| <XAxis dataKey="name" fontSize={12} tickLine={false} axisLine={false} /> |
| <YAxis hide /> |
| <Tooltip cursor={{ fill: 'transparent' }} /> |
| <Bar dataKey="count" fill="#3b82f6" radius={[4, 4, 0, 0]} /> |
| </BarChart> |
| </ResponsiveContainer> |
| </div> |
| </div> |
| </div> |
| |
| {/* Classified List Section */} |
| <div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200"> |
| <div className="flex flex-col md:flex-row items-center justify-between mb-6 gap-4"> |
| <h3 className="text-lg font-semibold text-slate-800">Classified Innovations</h3> |
| |
| <div className="flex items-center space-x-2 flex-wrap gap-y-2 justify-center"> |
| {[Classification.HIGH, Classification.MEDIUM, Classification.LOW, Classification.UNCLASSIFIED].map(cls => ( |
| <button |
| key={cls} |
| onClick={() => toggleFilter(cls)} |
| className={`px-3 py-1.5 rounded text-xs font-medium border transition-colors flex items-center |
| ${activeFilters.includes(cls) |
| ? `bg-slate-800 text-white border-slate-800` |
| : `bg-white text-slate-600 border-slate-300 hover:bg-slate-50` |
| }`} |
| > |
| <span |
| className="w-2 h-2 rounded-full mr-1.5" |
| style={{ backgroundColor: COLOR_MAP[cls] }} |
| ></span> |
| {cls} |
| </button> |
| ))} |
| <button onClick={fetchDbInnovations} className="text-sm text-slate-500 hover:text-blue-600 flex items-center ml-4"> |
| {loading && <CircleDashed className="w-4 h-4 mr-1 animate-spin" />} |
| Refresh |
| </button> |
| </div> |
| </div> |
| |
| {displayInnovations.length === 0 ? ( |
| <p className="text-center text-slate-500 py-8">No items (High, Medium, Low, Unclassified) found.</p> |
| ) : ( |
| <div className="space-y-4"> |
| {displayInnovations.map(inv => ( |
| <InnovationCard |
| key={inv.id} |
| innovation={inv} |
| onClassify={handleClassify} |
| /> |
| ))} |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default StatsDashboard; |