FluxInator / static /components /StatsVisualization.js
Scalino84
Initial commit
1e7308f
import React, { useState, useEffect } from 'react';
import { LineChart, Line, BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'];
const StatsVisualization = () => {
const [stats, setStats] = useState(null);
const [storageStats, setStorageStats] = useState(null);
useEffect(() => {
fetchStats();
const interval = setInterval(fetchStats, 30000);
return () => clearInterval(interval);
}, []);
const fetchStats = async () => {
try {
const [statsResponse, storageResponse] = await Promise.all([
fetch('/backend/image-stats'),
fetch('/backend/storage-stats')
]);
const statsData = await statsResponse.json();
const storageData = await storageResponse.json();
setStats(statsData);
setStorageStats(storageData);
} catch (error) {
console.error('Fehler beim Laden der Statistiken:', error);
}
};
if (!stats || !storageStats) return <div>Lade Statistiken...</div>;
return (
<div className="container-fluid p-4">
<div className="row g-4">
{/* Monatliche Bilderzahl */}
<div className="col-12">
<div className="card">
<div className="card-body">
<h5 className="card-title">Bilder pro Monat</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={stats.monthly}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="count"
stroke="#8884d8"
name="Anzahl Bilder"
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Album & Kategorie Verteilung */}
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">Alben Verteilung</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={[
{ name: 'In Album', value: stats.albums.with },
{ name: 'Ohne Album', value: stats.albums.without }
]}
cx="50%"
cy="50%"
labelLine={false}
label={({name, percent}) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{stats.albums.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">Kategorie Verteilung</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={[
{ name: 'Mit Kategorie', value: stats.categories.with },
{ name: 'Ohne Kategorie', value: stats.categories.without }
]}
cx="50%"
cy="50%"
labelLine={false}
label={({name, percent}) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{stats.categories.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Speichernutzung nach Format */}
<div className="col-12">
<div className="card">
<div className="card-body">
<h5 className="card-title">Speichernutzung nach Format</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={storageStats}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="format" />
<YAxis yAxisId="left" orientation="left" stroke="#8884d8" />
<YAxis yAxisId="right" orientation="right" stroke="#82ca9d" />
<Tooltip />
<Legend />
<Bar yAxisId="left" dataKey="count" name="Anzahl" fill="#8884d8" />
<Bar yAxisId="right" dataKey="sizeMB" name="Größe (MB)" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default StatsVisualization;
### END: stats-visualization.txt
### START: stats-visualization-updated.txt
import React, { useState, useEffect, useCallback } from 'react';
import { API, APIHandler } from '@/api';
import {
LineChart, Line, BarChart, Bar, PieChart, Pie,
XAxis, YAxis, CartesianGrid, Tooltip, Legend,
ResponsiveContainer, Cell
} from 'recharts';
// Farbpalette für Charts
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8', '#82ca9d'];
const StatsVisualization = () => {
const [stats, setStats] = useState(null);
const [storageStats, setStorageStats] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [refreshInterval, setRefreshInterval] = useState(30000); // 30 Sekunden
// Daten laden
const fetchStats = useCallback(async () => {
try {
setLoading(true);
const [statsData, storageData] = await Promise.all([
APIHandler.get(API.statistics.images),
APIHandler.get(API.statistics.storage)
]);
setStats(statsData);
setStorageStats(storageData);
setError(null);
} catch (err) {
setError('Fehler beim Laden der Statistiken: ' + err.message);
console.error('Fetch error:', err);
} finally {
setLoading(false);
}
}, []);
// Auto-Refresh
useEffect(() => {
fetchStats();
if (refreshInterval > 0) {
const interval = setInterval(fetchStats, refreshInterval);
return () => clearInterval(interval);
}
}, [fetchStats, refreshInterval]);
// Format für Byte-Größen
const formatBytes = (bytes) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
// Custom Tooltip für Charts
const CustomTooltip = ({ active, payload, label, valueFormatter }) => {
if (!active || !payload || !payload.length) return null;
return (
<div className="custom-tooltip bg-white p-3 rounded shadow">
<p className="label mb-2">{`${label}`}</p>
{payload.map((entry, index) => (
<p key={index} style={{ color: entry.color }} className="mb-1">
{`${entry.name}: ${valueFormatter ? valueFormatter(entry.value) : entry.value}`}
</p>
))}
</div>
);
};
if (loading) {
return (
<div className="d-flex justify-content-center p-5">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Laden...</span>
</div>
</div>
);
}
if (error) {
return (
<div className="alert alert-danger m-3" role="alert">
<h4 className="alert-heading">Fehler</h4>
<p>{error}</p>
<button
className="btn btn-outline-danger"
onClick={fetchStats}
>
Erneut versuchen
</button>
</div>
);
}
return (
<div className="container-fluid p-4">
{/* Toolbar */}
<div className="d-flex justify-content-between align-items-center mb-4">
<div className="btn-group">
<button
className="btn btn-primary"
onClick={fetchStats}
>
<i className="bi bi-arrow-clockwise"></i> Aktualisieren
</button>
<select
className="form-select"
value={refreshInterval}
onChange={(e) => setRefreshInterval(Number(e.target.value))}
>
<option value="0">Kein Auto-Refresh</option>
<option value="15000">Alle 15 Sekunden</option>
<option value="30000">Alle 30 Sekunden</option>
<option value="60000">Jede Minute</option>
</select>
</div>
</div>
<div className="row g-4">
{/* Monatliche Bilder */}
<div className="col-12">
<div className="card">
<div className="card-body">
<h5 className="card-title">Bilder pro Monat</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={stats?.monthly}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Line
type="monotone"
dataKey="count"
name="Anzahl Bilder"
stroke={COLORS[0]}
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Album & Kategorie Verteilung */}
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">Album Verteilung</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={[
{ name: 'In Alben', value: stats?.albums.with },
{ name: 'Ohne Album', value: stats?.albums.without }
]}
cx="50%"
cy="50%"
labelLine={false}
label={({name, percent}) =>
`${name}: ${(percent * 100).toFixed(1)}%`
}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{stats?.albums.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">Kategorie Verteilung</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={[
{ name: 'Mit Kategorie', value: stats?.categories.with },
{ name: 'Ohne Kategorie', value: stats?.categories.without }
]}
cx="50%"
cy="50%"
labelLine={false}
label={({name, percent}) =>
`${name}: ${(percent * 100).toFixed(1)}%`
}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{stats?.categories.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Speichernutzung */}
<div className="col-12">
<div className="card">
<div className="card-body">
<h5 className="card-title">Speichernutzung nach Format</h5>
<div style={{ height: '300px' }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={storageStats}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="format" />
<YAxis
yAxisId="left"
orientation="left"
stroke={COLORS[0]}
/>
<YAxis
yAxisId="right"
orientation="right"
stroke={COLORS[1]}
/>
<Tooltip content={<CustomTooltip valueFormatter={formatBytes} />} />
<Legend />
<Bar
yAxisId="left"
dataKey="count"
name="Anzahl"
fill={COLORS[0]}
/>
<Bar
yAxisId="right"
dataKey="size"
name="Größe"
fill={COLORS[1]}
/>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default StatsVisualization;
window.StatsVisualization = StatsVisualization; // <-- Diese Zeile ans Ende setzen