diff --git a/client/package.json b/client/package.json
index 859efd8..c541fa5 100644
--- a/client/package.json
+++ b/client/package.json
@@ -21,9 +21,12 @@
"react-dom": "^19.0.0",
"react-jwt": "^1.2.0",
"react-modal": "^3.16.1",
+ "react-router": "^7.2.0",
+ "react-router-dom": "^7.2.0",
"react-select-search": "^4.1.6",
"react-snowfall": "^2.2.0",
"react-toastify": "^10.0.4",
+ "recharts": "^2.15.1",
"sass": "^1.80.6",
"socket.io-client": "^4.6.1",
"typescript": "^5.3.3",
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 26cb66f..f80daea 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -23,6 +23,8 @@ import { getHumanDateTime, isInTheFuture } from './Utils';
import NoteModal from './components/modals/NoteModal';
import { useEasterEgg } from './context/eggs';
import { getImage } from './api/EasterEggApi';
+import { Link } from 'react-router';
+import { STATS_URL } from './AppRoutes';
const EVENT_CONNECT = "connect"
@@ -409,8 +411,8 @@ function App() {
Poslední změny:
- - Přidání restaurací Zastávka u Michala a Pivovarský šenk Šeříková
- Možnost výběru restaurace a jídel kliknutím v tabulce
+ - Statistiky
{dayIndex != null &&
diff --git a/client/src/AppRoutes.tsx b/client/src/AppRoutes.tsx
new file mode 100644
index 0000000..db8b4bf
--- /dev/null
+++ b/client/src/AppRoutes.tsx
@@ -0,0 +1,33 @@
+import { Routes, Route } from "react-router-dom";
+import { ProvideSettings } from "./context/settings";
+import Snowfall from "react-snowfall";
+import { ToastContainer } from "react-toastify";
+import { SocketContext, socket } from "./context/socket";
+import StatsPage from "./pages/StatsPage";
+import App from "./App";
+
+export const STATS_URL = '/stats';
+
+export default function AppRoutes() {
+ return (
+
+ } />
+
+
+ <>
+
+
+ >
+
+
+
+ } />
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/Utils.tsx b/client/src/Utils.tsx
index d43bb67..e79e77e 100644
--- a/client/src/Utils.tsx
+++ b/client/src/Utils.tsx
@@ -62,3 +62,45 @@ export function isInTheFuture(time: DepartureTime) {
return true;
}
+/**
+ * Vrátí index dne v týdnu, kde pondělí=0, neděle=6
+ *
+ * @param date datum
+ * @returns index dne v týdnu
+ */
+export const getDayOfWeekIndex = (date: Date) => {
+ // https://stackoverflow.com/a/4467559
+ return (((date.getDay() - 1) % 7) + 7) % 7;
+}
+
+/** Vrátí první pracovní den v týdnu předaného data. */
+export function getFirstWorkDayOfWeek(date: Date) {
+ const firstDay = new Date(date.getTime());
+ firstDay.setDate(date.getDate() - getDayOfWeekIndex(date));
+ return firstDay;
+}
+
+/** Vrátí poslední pracovní den v týdnu předaného data. */
+export function getLastWorkDayOfWeek(date: Date) {
+ const lastDay = new Date(date.getTime());
+ lastDay.setDate(date.getDate() + (4 - getDayOfWeekIndex(date)));
+ return lastDay;
+}
+
+/** Vrátí datum v ISO formátu. */
+export function formatDate(date: Date, format?: string) {
+ let day = String(date.getDate()).padStart(2, '0');
+ let month = String(date.getMonth() + 1).padStart(2, "0");
+ let year = String(date.getFullYear());
+
+ const f = (format === undefined) ? 'YYYY-MM-DD' : format;
+ return f.replace('DD', day).replace('MM', month).replace('YYYY', year);
+}
+
+/** Vrátí human-readable reprezentaci předaného data pro zobrazení. */
+export function getHumanDate(date: Date) {
+ let currentDay = String(date.getDate()).padStart(2, '0');
+ let currentMonth = String(date.getMonth() + 1).padStart(2, "0");
+ let currentYear = date.getFullYear();
+ return `${currentDay}.${currentMonth}.${currentYear}`;
+}
\ No newline at end of file
diff --git a/client/src/api/StatsApi.ts b/client/src/api/StatsApi.ts
new file mode 100644
index 0000000..03a3a71
--- /dev/null
+++ b/client/src/api/StatsApi.ts
@@ -0,0 +1,8 @@
+import { WeeklyStats } from "../../../types";
+import { api } from "./Api";
+
+const STATS_API_PREFIX = '/api/stats';
+
+export const getStats = async (startDate: string, endDate: string) => {
+ return await api.get(`${STATS_API_PREFIX}?startDate=${startDate}&endDate=${endDate}`);
+}
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx
index 840e43e..c45377e 100644
--- a/client/src/components/Header.tsx
+++ b/client/src/components/Header.tsx
@@ -8,11 +8,14 @@ import { FeatureRequest } from "../../../types";
import { errorHandler } from "../api/Api";
import { getFeatureVotes, updateFeatureVote } from "../api/VotingApi";
import PizzaCalculatorModal from "./modals/PizzaCalculatorModal";
+import { useNavigate } from "react-router";
+import { STATS_URL } from "../AppRoutes";
export default function Header() {
const auth = useAuth();
const settings = useSettings();
+ const navigate = useNavigate();
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
const [votingModalOpen, setVotingModalOpen] = useState(false);
const [pizzaModalOpen, setPizzaModalOpen] = useState(false);
@@ -108,7 +111,7 @@ export default function Header() {
}
return
- Luncher
+ Luncher