feat: enhance DataFetcher with better UI components and add reactive data fetching intervals (#1901) (#1902)

This commit is contained in:
Bram Suurd 2025-01-31 14:09:28 +01:00 committed by GitHub
parent 3adc22d837
commit c8829beddd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 509 additions and 295 deletions

View file

@ -1,132 +1,193 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Chart as ChartJS, ArcElement, Tooltip as ChartTooltip, Legend } from "chart.js";
import ChartDataLabels from "chartjs-plugin-datalabels";
import { BarChart3, PieChart } from "lucide-react";
import React, { useState } from "react";
import { Pie } from "react-chartjs-2";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
import ChartDataLabels from "chartjs-plugin-datalabels";
import Modal from "@/components/Modal";
ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels);
ChartJS.register(ArcElement, ChartTooltip, Legend, ChartDataLabels);
interface ApplicationChartProps {
data: { nsapp: string }[];
}
const ApplicationChart: React.FC<ApplicationChartProps> = ({ data }) => {
const ITEMS_PER_PAGE = 20;
const CHART_COLORS = [
"#ff6384",
"#36a2eb",
"#ffce56",
"#4bc0c0",
"#9966ff",
"#ff9f40",
"#4dc9f6",
"#f67019",
"#537bc4",
"#acc236",
"#166a8f",
"#00a950",
"#58595b",
"#8549ba",
];
export default function ApplicationChart({ data }: ApplicationChartProps) {
const [isChartOpen, setIsChartOpen] = useState(false);
const [isTableOpen, setIsTableOpen] = useState(false);
const [chartStartIndex, setChartStartIndex] = useState(0);
const [tableLimit, setTableLimit] = useState(20);
const [tableLimit, setTableLimit] = useState(ITEMS_PER_PAGE);
const appCounts: Record<string, number> = {};
data.forEach((item) => {
appCounts[item.nsapp] = (appCounts[item.nsapp] || 0) + 1;
});
// Calculate application counts
const appCounts = data.reduce((acc, item) => {
acc[item.nsapp] = (acc[item.nsapp] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const sortedApps = Object.entries(appCounts).sort(([, a], [, b]) => b - a);
const chartApps = sortedApps.slice(chartStartIndex, chartStartIndex + 20);
const sortedApps = Object.entries(appCounts)
.sort(([, a], [, b]) => b - a);
const chartApps = sortedApps.slice(
chartStartIndex,
chartStartIndex + ITEMS_PER_PAGE
);
const chartData = {
labels: chartApps.map(([name]) => name),
datasets: [
{
label: "Applications",
data: chartApps.map(([, count]) => count),
backgroundColor: [
"#ff6384",
"#36a2eb",
"#ffce56",
"#4bc0c0",
"#9966ff",
"#ff9f40",
],
backgroundColor: CHART_COLORS,
},
],
};
const chartOptions = {
plugins: {
legend: { display: false },
datalabels: {
color: "white",
font: { weight: "bold" as const },
formatter: (value: number, context: any) => {
const label = context.chart.data.labels?.[context.dataIndex];
return `${label}\n(${value})`;
},
},
},
responsive: true,
maintainAspectRatio: false,
};
return (
<div className="mt-6 text-center">
<button
onClick={() => setIsChartOpen(true)}
className="m-2 p-2 bg-blue-500 text-white rounded"
>
📊 Open Chart
</button>
<button
onClick={() => setIsTableOpen(true)}
className="m-2 p-2 bg-green-500 text-white rounded"
>
📋 Open Table
</button>
<div className="mt-6 flex justify-center gap-4">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setIsChartOpen(true)}
>
<PieChart className="h-5 w-5" />
</Button>
</TooltipTrigger>
<TooltipContent>Open Chart View</TooltipContent>
</Tooltip>
<Modal isOpen={isChartOpen} onClose={() => setIsChartOpen(false)}>
<h2 className="text-xl font-bold text-black dark:text-white mb-4">Top Applications (Chart)</h2>
<div className="w-3/4 mx-auto">
<Pie
data={chartData}
options={{
plugins: {
legend: { display: false },
datalabels: {
color: "white",
font: { weight: "bold" },
formatter: (value, context) =>
context.chart.data.labels?.[context.dataIndex] || "",
},
},
}}
/>
</div>
<div className="flex justify-center space-x-4 mt-4">
<button
onClick={() => setChartStartIndex(Math.max(0, chartStartIndex - 20))}
disabled={chartStartIndex === 0}
className="p-2 border rounded bg-blue-500 text-white"
>
Last 20
</button>
<button
onClick={() => setChartStartIndex(chartStartIndex + 20)}
disabled={chartStartIndex + 20 >= sortedApps.length}
className="p-2 border rounded bg-blue-500 text-white"
>
Next 20
</button>
</div>
</Modal>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setIsTableOpen(true)}
>
<BarChart3 className="h-5 w-5" />
</Button>
</TooltipTrigger>
<TooltipContent>Open Table View</TooltipContent>
</Tooltip>
</TooltipProvider>
<Modal isOpen={isTableOpen} onClose={() => setIsTableOpen(false)}>
<h2 className="text-xl font-bold text-black dark:text-white mb-4">Application Count Table</h2>
<table className="w-full border-collapse border border-gray-600 dark:border-gray-500">
<thead>
<tr className="bg-gray-800 text-white">
<th className="p-2 border">Application</th>
<th className="p-2 border">Count</th>
</tr>
</thead>
<tbody>
{sortedApps.slice(0, tableLimit).map(([name, count]) => (
<tr key={name} className="hover:bg-gray-200 dark:hover:bg-gray-700 text-black dark:text-white">
<td className="p-2 border">{name}</td>
<td className="p-2 border">{count}</td>
</tr>
))}
</tbody>
</table>
<Dialog open={isChartOpen} onOpenChange={setIsChartOpen}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>Applications Distribution</DialogTitle>
</DialogHeader>
<div className="h-[60vh] w-full">
<Pie data={chartData} options={chartOptions} />
</div>
<div className="flex justify-center gap-4">
<Button
variant="outline"
onClick={() => setChartStartIndex(Math.max(0, chartStartIndex - ITEMS_PER_PAGE))}
disabled={chartStartIndex === 0}
>
Previous {ITEMS_PER_PAGE}
</Button>
<Button
variant="outline"
onClick={() => setChartStartIndex(chartStartIndex + ITEMS_PER_PAGE)}
disabled={chartStartIndex + ITEMS_PER_PAGE >= sortedApps.length}
>
Next {ITEMS_PER_PAGE}
</Button>
</div>
</DialogContent>
</Dialog>
{tableLimit < sortedApps.length && (
<div className="text-center mt-4">
<button
onClick={() => setTableLimit(tableLimit + 20)}
className="p-2 bg-green-500 text-white rounded"
<Dialog open={isTableOpen} onOpenChange={setIsTableOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Applications Count</DialogTitle>
</DialogHeader>
<div className="max-h-[60vh] overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Application</TableHead>
<TableHead className="text-right">Count</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedApps.slice(0, tableLimit).map(([name, count]) => (
<TableRow key={name}>
<TableCell>{name}</TableCell>
<TableCell className="text-right">{count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{tableLimit < sortedApps.length && (
<Button
variant="outline"
className="w-full"
onClick={() => setTableLimit(prev => prev + ITEMS_PER_PAGE)}
>
Load More
</button>
</div>
)}
</Modal>
</Button>
)}
</DialogContent>
</Dialog>
</div>
);
};
export default ApplicationChart;
}