heymenn's picture
add features stop and filter class
36fa73c
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);
// Filter Toggle State
const [activeFilters, setActiveFilters] = useState<Classification[]>([
Classification.HIGH,
Classification.MEDIUM,
Classification.LOW,
Classification.UNCLASSIFIED
]);
// Fetch from DB for the "True" state
const fetchDbInnovations = async () => {
setLoading(true);
const results = await backendService.fetchClassifiedInnovations();
// Transform ResultResponse -> Innovation
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) => {
// Find the innovation
const inv = dbInnovations.find(i => i.id === Number(id));
if (inv && inv.id) {
// Optimistic update
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]
);
};
// Filter for Display (High, Medium, Low, Unclassified)
const displayInnovations = dbInnovations.filter(i =>
activeFilters.includes(i.classification as Classification)
);
// Stats Logic - Use DB data for charts to be consistent with the list below
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] },
];
// Mock context data (unchanged)
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;