171 lines
5.5 KiB
TypeScript
171 lines
5.5 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
import Footer from "../components/Footer";
|
|
import Header from "../components/Header";
|
|
import { useAuth } from "../context/auth";
|
|
import Login from "../Login";
|
|
import { formatDate, getFirstWorkDayOfWeek, getHumanDate, getLastWorkDayOfWeek } from "../Utils";
|
|
import { WeeklyStats, LunchChoice, VotingStats, FeatureRequest, getStats, getVotingStats } from "../../../types";
|
|
import Loader from "../components/Loader";
|
|
import { faChevronLeft, faChevronRight, faGear } from "@fortawesome/free-solid-svg-icons";
|
|
import { Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { getLunchChoiceName } from "../enums";
|
|
import './StatsPage.scss';
|
|
|
|
const CHART_WIDTH = 1400;
|
|
const CHART_HEIGHT = 700;
|
|
const STROKE_WIDTH = 2.5;
|
|
|
|
const COLORS = [
|
|
'#ff1493',
|
|
'#1e90ff',
|
|
'#c5a700',
|
|
'#006400',
|
|
'#b300ff',
|
|
'#ff4500',
|
|
'#bc8f8f',
|
|
'#00ff00',
|
|
'#7c7c7c',
|
|
]
|
|
|
|
export default function StatsPage() {
|
|
const auth = useAuth();
|
|
const [dateRange, setDateRange] = useState<Date[]>();
|
|
const [data, setData] = useState<WeeklyStats>();
|
|
const [votingStats, setVotingStats] = useState<VotingStats>();
|
|
|
|
// Prvotní nastavení aktuálního týdne
|
|
useEffect(() => {
|
|
const today = new Date();
|
|
setDateRange([getFirstWorkDayOfWeek(today), getLastWorkDayOfWeek(today)]);
|
|
}, []);
|
|
|
|
// Přenačtení pro zvolený týden
|
|
useEffect(() => {
|
|
if (dateRange) {
|
|
getStats({ query: { startDate: formatDate(dateRange[0]), endDate: formatDate(dateRange[1]) } }).then(response => {
|
|
setData(response.data);
|
|
});
|
|
}
|
|
}, [dateRange]);
|
|
|
|
// Načtení statistik hlasování
|
|
useEffect(() => {
|
|
getVotingStats().then(response => {
|
|
setVotingStats(response.data);
|
|
});
|
|
}, []);
|
|
|
|
const sortedVotingStats = useMemo(() => {
|
|
if (!votingStats) return [];
|
|
return Object.entries(votingStats)
|
|
.sort((a, b) => (b[1] as number) - (a[1] as number));
|
|
}, [votingStats]);
|
|
|
|
const renderLine = (location: LunchChoice) => {
|
|
const index = Object.values(LunchChoice).indexOf(location);
|
|
return <Line key={location} name={getLunchChoiceName(location)} type="monotone" dataKey={data => data.locations[location] ?? 0} stroke={COLORS[index]} strokeWidth={STROKE_WIDTH} />
|
|
}
|
|
|
|
const handlePreviousWeek = () => {
|
|
if (dateRange) {
|
|
const previousStartDate = new Date(dateRange[0]);
|
|
previousStartDate.setDate(previousStartDate.getDate() - 7);
|
|
const previousEndDate = new Date(previousStartDate);
|
|
previousEndDate.setDate(previousEndDate.getDate() + 4);
|
|
setDateRange([previousStartDate, previousEndDate]);
|
|
}
|
|
}
|
|
|
|
const handleNextWeek = () => {
|
|
if (dateRange) {
|
|
const nextStartDate = new Date(dateRange[0]);
|
|
nextStartDate.setDate(nextStartDate.getDate() + 7);
|
|
const nextEndDate = new Date(nextStartDate);
|
|
nextEndDate.setDate(nextEndDate.getDate() + 4);
|
|
setDateRange([nextStartDate, nextEndDate]);
|
|
}
|
|
}
|
|
|
|
const isCurrentOrFutureWeek = useMemo(() => {
|
|
if (!dateRange) return true;
|
|
const currentWeekEnd = getLastWorkDayOfWeek(new Date());
|
|
currentWeekEnd.setHours(23, 59, 59, 999);
|
|
return dateRange[1] >= currentWeekEnd;
|
|
}, [dateRange]);
|
|
|
|
const handleKeyDown = useCallback((e: any) => {
|
|
if (e.keyCode === 37) {
|
|
handlePreviousWeek();
|
|
} else if (e.keyCode === 39 && !isCurrentOrFutureWeek) {
|
|
handleNextWeek()
|
|
}
|
|
}, [dateRange, isCurrentOrFutureWeek]);
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown);
|
|
}
|
|
}, [handleKeyDown]);
|
|
|
|
if (!auth?.login) {
|
|
return <Login />;
|
|
}
|
|
|
|
if (!dateRange) {
|
|
return <Loader
|
|
icon={faGear}
|
|
description={'Načítám data...'}
|
|
animation={'fa-bounce'}
|
|
/>
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Header />
|
|
<div className="stats-page">
|
|
<h1>Statistiky</h1>
|
|
<div className="week-navigator">
|
|
<span title="Předchozí týden">
|
|
<FontAwesomeIcon icon={faChevronLeft} style={{ cursor: "pointer" }} onClick={handlePreviousWeek} />
|
|
</span>
|
|
<h2 className="date-range">{getHumanDate(dateRange[0])} - {getHumanDate(dateRange[1])}</h2>
|
|
<span title="Následující týden">
|
|
<FontAwesomeIcon icon={faChevronRight} style={{ cursor: "pointer", visibility: isCurrentOrFutureWeek ? "hidden" : "visible" }} onClick={handleNextWeek} />
|
|
</span>
|
|
</div>
|
|
<LineChart width={CHART_WIDTH} height={CHART_HEIGHT} data={data}>
|
|
{Object.values(LunchChoice).map(location => renderLine(location))}
|
|
<XAxis dataKey="date" />
|
|
<YAxis />
|
|
<Tooltip />
|
|
<Legend />
|
|
</LineChart>
|
|
{sortedVotingStats.length > 0 && (
|
|
<div className="voting-stats-section">
|
|
<h2>Hlasování o funkcích</h2>
|
|
<table className="voting-stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Funkce</th>
|
|
<th>Počet hlasů</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{sortedVotingStats.map(([feature, count]) => (
|
|
<tr key={feature}>
|
|
<td>{FeatureRequest[feature as keyof typeof FeatureRequest] ?? feature}</td>
|
|
<td>{count as number}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<Footer />
|
|
</>
|
|
);
|
|
}
|