This commit is contained in:
		
							parent
							
								
									0af78e72d9
								
							
						
					
					
						commit
						ca400638d1
					
				| @ -21,9 +21,12 @@ | |||||||
|     "react-dom": "^19.0.0", |     "react-dom": "^19.0.0", | ||||||
|     "react-jwt": "^1.2.0", |     "react-jwt": "^1.2.0", | ||||||
|     "react-modal": "^3.16.1", |     "react-modal": "^3.16.1", | ||||||
|  |     "react-router": "^7.2.0", | ||||||
|  |     "react-router-dom": "^7.2.0", | ||||||
|     "react-select-search": "^4.1.6", |     "react-select-search": "^4.1.6", | ||||||
|     "react-snowfall": "^2.2.0", |     "react-snowfall": "^2.2.0", | ||||||
|     "react-toastify": "^10.0.4", |     "react-toastify": "^10.0.4", | ||||||
|  |     "recharts": "^2.15.1", | ||||||
|     "sass": "^1.80.6", |     "sass": "^1.80.6", | ||||||
|     "socket.io-client": "^4.6.1", |     "socket.io-client": "^4.6.1", | ||||||
|     "typescript": "^5.3.3", |     "typescript": "^5.3.3", | ||||||
|  | |||||||
| @ -23,6 +23,8 @@ import { getHumanDateTime, isInTheFuture } from './Utils'; | |||||||
| import NoteModal from './components/modals/NoteModal'; | import NoteModal from './components/modals/NoteModal'; | ||||||
| import { useEasterEgg } from './context/eggs'; | import { useEasterEgg } from './context/eggs'; | ||||||
| import { getImage } from './api/EasterEggApi'; | import { getImage } from './api/EasterEggApi'; | ||||||
|  | import { Link } from 'react-router'; | ||||||
|  | import { STATS_URL } from './AppRoutes'; | ||||||
| 
 | 
 | ||||||
| const EVENT_CONNECT = "connect" | const EVENT_CONNECT = "connect" | ||||||
| 
 | 
 | ||||||
| @ -409,8 +411,8 @@ function App() { | |||||||
|             <img src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} /> |             <img src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} /> | ||||||
|             Poslední změny: |             Poslední změny: | ||||||
|             <ul> |             <ul> | ||||||
|               <li>Přidání restaurací Zastávka u Michala a Pivovarský šenk Šeříková</li> |  | ||||||
|               <li>Možnost výběru restaurace a jídel kliknutím v tabulce</li> |               <li>Možnost výběru restaurace a jídel kliknutím v tabulce</li> | ||||||
|  |               <li><Link to={STATS_URL}>Statistiky</Link></li> | ||||||
|             </ul> |             </ul> | ||||||
|           </Alert> |           </Alert> | ||||||
|           {dayIndex != null && |           {dayIndex != null && | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								client/src/AppRoutes.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								client/src/AppRoutes.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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 ( | ||||||
|  |         <Routes> | ||||||
|  |             <Route path={STATS_URL} element={<StatsPage />} /> | ||||||
|  |             <Route path="/" element={ | ||||||
|  |                 <ProvideSettings> | ||||||
|  |                     <SocketContext.Provider value={socket}> | ||||||
|  |                         <> | ||||||
|  |                             <Snowfall style={{ | ||||||
|  |                                 zIndex: 2, | ||||||
|  |                                 position: 'fixed', | ||||||
|  |                                 width: '100vw', | ||||||
|  |                                 height: '100vh' | ||||||
|  |                             }} /> | ||||||
|  |                             <App /> | ||||||
|  |                         </> | ||||||
|  |                         <ToastContainer /> | ||||||
|  |                     </SocketContext.Provider> | ||||||
|  |                 </ProvideSettings> | ||||||
|  |             } /> | ||||||
|  |         </Routes> | ||||||
|  |     ); | ||||||
|  | } | ||||||
| @ -62,3 +62,45 @@ export function isInTheFuture(time: DepartureTime) { | |||||||
|     return true; |     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}`; | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								client/src/api/StatsApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/src/api/StatsApi.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<WeeklyStats>(`${STATS_API_PREFIX}?startDate=${startDate}&endDate=${endDate}`); | ||||||
|  | } | ||||||
| @ -8,11 +8,14 @@ import { FeatureRequest } from "../../../types"; | |||||||
| import { errorHandler } from "../api/Api"; | import { errorHandler } from "../api/Api"; | ||||||
| import { getFeatureVotes, updateFeatureVote } from "../api/VotingApi"; | import { getFeatureVotes, updateFeatureVote } from "../api/VotingApi"; | ||||||
| import PizzaCalculatorModal from "./modals/PizzaCalculatorModal"; | import PizzaCalculatorModal from "./modals/PizzaCalculatorModal"; | ||||||
|  | import { useNavigate } from "react-router"; | ||||||
|  | import { STATS_URL } from "../AppRoutes"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default function Header() { | export default function Header() { | ||||||
|     const auth = useAuth(); |     const auth = useAuth(); | ||||||
|     const settings = useSettings(); |     const settings = useSettings(); | ||||||
|  |     const navigate = useNavigate(); | ||||||
|     const [settingsModalOpen, setSettingsModalOpen] = useState<boolean>(false); |     const [settingsModalOpen, setSettingsModalOpen] = useState<boolean>(false); | ||||||
|     const [votingModalOpen, setVotingModalOpen] = useState<boolean>(false); |     const [votingModalOpen, setVotingModalOpen] = useState<boolean>(false); | ||||||
|     const [pizzaModalOpen, setPizzaModalOpen] = useState<boolean>(false); |     const [pizzaModalOpen, setPizzaModalOpen] = useState<boolean>(false); | ||||||
| @ -108,7 +111,7 @@ export default function Header() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return <Navbar variant='dark' expand="lg"> |     return <Navbar variant='dark' expand="lg"> | ||||||
|         <Navbar.Brand>Luncher</Navbar.Brand> |         <Navbar.Brand href="/">Luncher</Navbar.Brand> | ||||||
|         <Navbar.Toggle aria-controls="basic-navbar-nav" /> |         <Navbar.Toggle aria-controls="basic-navbar-nav" /> | ||||||
|         <Navbar.Collapse id="basic-navbar-nav"> |         <Navbar.Collapse id="basic-navbar-nav"> | ||||||
|             <Nav className="nav"> |             <Nav className="nav"> | ||||||
| @ -116,6 +119,7 @@ export default function Header() { | |||||||
|                     <NavDropdown.Item onClick={() => setSettingsModalOpen(true)}>Nastavení</NavDropdown.Item> |                     <NavDropdown.Item onClick={() => setSettingsModalOpen(true)}>Nastavení</NavDropdown.Item> | ||||||
|                     <NavDropdown.Item onClick={() => setVotingModalOpen(true)}>Hlasovat o nových funkcích</NavDropdown.Item> |                     <NavDropdown.Item onClick={() => setVotingModalOpen(true)}>Hlasovat o nových funkcích</NavDropdown.Item> | ||||||
|                     <NavDropdown.Item onClick={() => setPizzaModalOpen(true)}>Pizza kalkulačka</NavDropdown.Item> |                     <NavDropdown.Item onClick={() => setPizzaModalOpen(true)}>Pizza kalkulačka</NavDropdown.Item> | ||||||
|  |                     <NavDropdown.Item onClick={() => navigate(STATS_URL)}>Statistiky</NavDropdown.Item> | ||||||
|                     <NavDropdown.Divider /> |                     <NavDropdown.Divider /> | ||||||
|                     <NavDropdown.Item onClick={auth?.logout}>Odhlásit se</NavDropdown.Item> |                     <NavDropdown.Item onClick={auth?.logout}>Odhlásit se</NavDropdown.Item> | ||||||
|                 </NavDropdown> |                 </NavDropdown> | ||||||
|  | |||||||
| @ -1,33 +1,20 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ReactDOM from 'react-dom/client'; | import ReactDOM from 'react-dom/client'; | ||||||
| import App from './App'; |  | ||||||
| import { SocketContext, socket } from './context/socket'; |  | ||||||
| import { ProvideAuth } from './context/auth'; | import { ProvideAuth } from './context/auth'; | ||||||
| import { ToastContainer } from 'react-toastify'; |  | ||||||
| import { ProvideSettings } from './context/settings'; |  | ||||||
| import 'react-toastify/dist/ReactToastify.css'; | import 'react-toastify/dist/ReactToastify.css'; | ||||||
| import './index.css'; | import './index.css'; | ||||||
| import Snowfall from 'react-snowfall'; | import AppRoutes from './AppRoutes'; | ||||||
|  | import { BrowserRouter } from 'react-router'; | ||||||
| 
 | 
 | ||||||
| const root = ReactDOM.createRoot( | const root = ReactDOM.createRoot( | ||||||
|   document.getElementById('root') as HTMLElement |   document.getElementById('root') as HTMLElement | ||||||
| ); | ); | ||||||
| root.render( | root.render( | ||||||
|   <React.StrictMode> |   <React.StrictMode> | ||||||
|     <ProvideAuth> |     <BrowserRouter> | ||||||
|       <ProvideSettings> |       <ProvideAuth> | ||||||
|         <SocketContext.Provider value={socket}> |         <AppRoutes /> | ||||||
|           <> |       </ProvideAuth> | ||||||
|             <Snowfall style={{ |     </BrowserRouter> | ||||||
|               zIndex: 2, |  | ||||||
|               position: 'fixed', |  | ||||||
|               width: '100vw', |  | ||||||
|               height: '100vh'}} /> |  | ||||||
|             <App /> |  | ||||||
|           </> |  | ||||||
|           <ToastContainer /> |  | ||||||
|         </SocketContext.Provider> |  | ||||||
|       </ProvideSettings> |  | ||||||
|     </ProvideAuth> |  | ||||||
|   </React.StrictMode> |   </React.StrictMode> | ||||||
| ); | ); | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								client/src/pages/StatsPage.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								client/src/pages/StatsPage.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | .stats-page { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 20px; | ||||||
|  | 
 | ||||||
|  |   .week-navigator { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     font-size: xx-large; | ||||||
|  | 
 | ||||||
|  |     .date-range { | ||||||
|  |       margin: 5px 20px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										124
									
								
								client/src/pages/StatsPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								client/src/pages/StatsPage.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | |||||||
|  | import { useCallback, useEffect, 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 { getStats } from "../api/StatsApi"; | ||||||
|  | import { WeeklyStats, LocationKey, Locations } 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 './StatsPage.scss'; | ||||||
|  | 
 | ||||||
|  | const CHART_WIDTH = 1400; | ||||||
|  | const CHART_HEIGHT = 700; | ||||||
|  | const STROKE_WIDTH = 2.5; | ||||||
|  | 
 | ||||||
|  | const COLORS = [ | ||||||
|  |   // Komentáře jsou kvůli vizualizaci barev ve VS Code
 | ||||||
|  |   '#ff1493', // #ff1493
 | ||||||
|  |   '#1e90ff', // #1e90ff
 | ||||||
|  |   '#c5a700', // #c5a700
 | ||||||
|  |   '#006400', // #006400
 | ||||||
|  |   '#b300ff', // #b300ff
 | ||||||
|  |   '#ff4500', // #ff4500
 | ||||||
|  |   '#bc8f8f', // #bc8f8f
 | ||||||
|  |   '#00ff00', // #00ff00
 | ||||||
|  |   '#7c7c7c', // #7c7c7c
 | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | export default function StatsPage() { | ||||||
|  |   const auth = useAuth(); | ||||||
|  |   const [dateRange, setDateRange] = useState<Date[]>(); | ||||||
|  |   const [data, setData] = useState<WeeklyStats>(); | ||||||
|  | 
 | ||||||
|  |   // 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(formatDate(dateRange[0]), formatDate(dateRange[1])).then(setData); | ||||||
|  |     } | ||||||
|  |   }, [dateRange]); | ||||||
|  | 
 | ||||||
|  |   const renderLine = (location: Locations) => { | ||||||
|  |     const index = Object.values(Locations).indexOf(location); | ||||||
|  |     const key = Object.keys(Locations)[index]; | ||||||
|  |     return <Line key={location} name={location} type="monotone" dataKey={data => data.locations[key] ?? 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 handleKeyDown = useCallback((e: any) => { | ||||||
|  |     if (e.keyCode === 37) { | ||||||
|  |       handlePreviousWeek(); | ||||||
|  |     } else if (e.keyCode === 39) { | ||||||
|  |       handleNextWeek() | ||||||
|  |     } | ||||||
|  |   }, [dateRange]); | ||||||
|  | 
 | ||||||
|  |   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"> | ||||||
|  |           <FontAwesomeIcon title="Předchozí týden" icon={faChevronLeft} style={{ cursor: "pointer" }} onClick={handlePreviousWeek} /> | ||||||
|  |           <h2 className="date-range">{getHumanDate(dateRange[0])} - {getHumanDate(dateRange[1])}</h2> | ||||||
|  |           <FontAwesomeIcon title="Následující týden" icon={faChevronRight} style={{ cursor: "pointer" }} onClick={handleNextWeek} /> | ||||||
|  |         </div> | ||||||
|  |         <LineChart width={CHART_WIDTH} height={CHART_HEIGHT} data={data}> | ||||||
|  |           {Object.values(Locations).map(location => renderLine(location))} | ||||||
|  |           <XAxis dataKey="date" /> | ||||||
|  |           <YAxis /> | ||||||
|  |           <Tooltip /> | ||||||
|  |           <Legend /> | ||||||
|  |         </LineChart> | ||||||
|  |       </div> | ||||||
|  |       <Footer /> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										243
									
								
								client/yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										243
									
								
								client/yarn.lock
									
									
									
									
									
								
							| @ -725,6 +725,62 @@ | |||||||
|   dependencies: |   dependencies: | ||||||
|     "@babel/types" "^7.20.7" |     "@babel/types" "^7.20.7" | ||||||
| 
 | 
 | ||||||
|  | "@types/cookie@^0.6.0": | ||||||
|  |   version "0.6.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" | ||||||
|  |   integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== | ||||||
|  | 
 | ||||||
|  | "@types/d3-array@^3.0.3": | ||||||
|  |   version "3.2.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" | ||||||
|  |   integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== | ||||||
|  | 
 | ||||||
|  | "@types/d3-color@*": | ||||||
|  |   version "3.1.3" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" | ||||||
|  |   integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== | ||||||
|  | 
 | ||||||
|  | "@types/d3-ease@^3.0.0": | ||||||
|  |   version "3.0.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" | ||||||
|  |   integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== | ||||||
|  | 
 | ||||||
|  | "@types/d3-interpolate@^3.0.1": | ||||||
|  |   version "3.0.4" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" | ||||||
|  |   integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== | ||||||
|  |   dependencies: | ||||||
|  |     "@types/d3-color" "*" | ||||||
|  | 
 | ||||||
|  | "@types/d3-path@*": | ||||||
|  |   version "3.1.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" | ||||||
|  |   integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== | ||||||
|  | 
 | ||||||
|  | "@types/d3-scale@^4.0.2": | ||||||
|  |   version "4.0.9" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" | ||||||
|  |   integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== | ||||||
|  |   dependencies: | ||||||
|  |     "@types/d3-time" "*" | ||||||
|  | 
 | ||||||
|  | "@types/d3-shape@^3.1.0": | ||||||
|  |   version "3.1.7" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555" | ||||||
|  |   integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg== | ||||||
|  |   dependencies: | ||||||
|  |     "@types/d3-path" "*" | ||||||
|  | 
 | ||||||
|  | "@types/d3-time@*", "@types/d3-time@^3.0.0": | ||||||
|  |   version "3.0.4" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" | ||||||
|  |   integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== | ||||||
|  | 
 | ||||||
|  | "@types/d3-timer@^3.0.0": | ||||||
|  |   version "3.0.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" | ||||||
|  |   integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== | ||||||
|  | 
 | ||||||
| "@types/estree@1.0.6": | "@types/estree@1.0.6": | ||||||
|   version "1.0.6" |   version "1.0.6" | ||||||
|   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" |   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" | ||||||
| @ -885,7 +941,7 @@ classnames@^2.3.2: | |||||||
|   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" |   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" | ||||||
|   integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== |   integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== | ||||||
| 
 | 
 | ||||||
| clsx@^2.1.0: | clsx@^2.0.0, clsx@^2.1.0: | ||||||
|   version "2.1.1" |   version "2.1.1" | ||||||
|   resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" |   resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" | ||||||
|   integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== |   integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== | ||||||
| @ -907,11 +963,87 @@ convert-source-map@^2.0.0: | |||||||
|   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" |   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" | ||||||
|   integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== |   integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== | ||||||
| 
 | 
 | ||||||
|  | cookie@^1.0.1: | ||||||
|  |   version "1.0.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" | ||||||
|  |   integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== | ||||||
|  | 
 | ||||||
| csstype@^3.0.2: | csstype@^3.0.2: | ||||||
|   version "3.1.3" |   version "3.1.3" | ||||||
|   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" |   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" | ||||||
|   integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== |   integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== | ||||||
| 
 | 
 | ||||||
|  | "d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: | ||||||
|  |   version "3.2.4" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" | ||||||
|  |   integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== | ||||||
|  |   dependencies: | ||||||
|  |     internmap "1 - 2" | ||||||
|  | 
 | ||||||
|  | "d3-color@1 - 3": | ||||||
|  |   version "3.1.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" | ||||||
|  |   integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== | ||||||
|  | 
 | ||||||
|  | d3-ease@^3.0.1: | ||||||
|  |   version "3.0.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" | ||||||
|  |   integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== | ||||||
|  | 
 | ||||||
|  | "d3-format@1 - 3": | ||||||
|  |   version "3.1.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" | ||||||
|  |   integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== | ||||||
|  | 
 | ||||||
|  | "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: | ||||||
|  |   version "3.0.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" | ||||||
|  |   integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== | ||||||
|  |   dependencies: | ||||||
|  |     d3-color "1 - 3" | ||||||
|  | 
 | ||||||
|  | d3-path@^3.1.0: | ||||||
|  |   version "3.1.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" | ||||||
|  |   integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== | ||||||
|  | 
 | ||||||
|  | d3-scale@^4.0.2: | ||||||
|  |   version "4.0.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" | ||||||
|  |   integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== | ||||||
|  |   dependencies: | ||||||
|  |     d3-array "2.10.0 - 3" | ||||||
|  |     d3-format "1 - 3" | ||||||
|  |     d3-interpolate "1.2.0 - 3" | ||||||
|  |     d3-time "2.1.1 - 3" | ||||||
|  |     d3-time-format "2 - 4" | ||||||
|  | 
 | ||||||
|  | d3-shape@^3.1.0: | ||||||
|  |   version "3.2.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" | ||||||
|  |   integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== | ||||||
|  |   dependencies: | ||||||
|  |     d3-path "^3.1.0" | ||||||
|  | 
 | ||||||
|  | "d3-time-format@2 - 4": | ||||||
|  |   version "4.1.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" | ||||||
|  |   integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== | ||||||
|  |   dependencies: | ||||||
|  |     d3-time "1 - 3" | ||||||
|  | 
 | ||||||
|  | "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: | ||||||
|  |   version "3.1.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" | ||||||
|  |   integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== | ||||||
|  |   dependencies: | ||||||
|  |     d3-array "2 - 3" | ||||||
|  | 
 | ||||||
|  | d3-timer@^3.0.1: | ||||||
|  |   version "3.0.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" | ||||||
|  |   integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== | ||||||
|  | 
 | ||||||
| debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: | debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: | ||||||
|   version "4.4.0" |   version "4.4.0" | ||||||
|   resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" |   resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" | ||||||
| @ -926,6 +1058,11 @@ debug@~4.3.1, debug@~4.3.2: | |||||||
|   dependencies: |   dependencies: | ||||||
|     ms "^2.1.3" |     ms "^2.1.3" | ||||||
| 
 | 
 | ||||||
|  | decimal.js-light@^2.4.1: | ||||||
|  |   version "2.5.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" | ||||||
|  |   integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== | ||||||
|  | 
 | ||||||
| dequal@^2.0.3: | dequal@^2.0.3: | ||||||
|   version "2.0.3" |   version "2.0.3" | ||||||
|   resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" |   resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" | ||||||
| @ -1010,6 +1147,11 @@ escape-string-regexp@^2.0.0: | |||||||
|   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" |   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" | ||||||
|   integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== |   integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== | ||||||
| 
 | 
 | ||||||
|  | eventemitter3@^4.0.1: | ||||||
|  |   version "4.0.7" | ||||||
|  |   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" | ||||||
|  |   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== | ||||||
|  | 
 | ||||||
| exenv@^1.2.0: | exenv@^1.2.0: | ||||||
|   version "1.2.2" |   version "1.2.2" | ||||||
|   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" |   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" | ||||||
| @ -1026,6 +1168,11 @@ expect@^29.0.0: | |||||||
|     jest-message-util "^29.7.0" |     jest-message-util "^29.7.0" | ||||||
|     jest-util "^29.7.0" |     jest-util "^29.7.0" | ||||||
| 
 | 
 | ||||||
|  | fast-equals@^5.0.1: | ||||||
|  |   version "5.2.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.2.2.tgz#885d7bfb079fac0ce0e8450374bce29e9b742484" | ||||||
|  |   integrity sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw== | ||||||
|  | 
 | ||||||
| fill-range@^7.1.1: | fill-range@^7.1.1: | ||||||
|   version "7.1.1" |   version "7.1.1" | ||||||
|   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" |   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" | ||||||
| @ -1068,6 +1215,11 @@ immutable@^5.0.2: | |||||||
|   resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" |   resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" | ||||||
|   integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== |   integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== | ||||||
| 
 | 
 | ||||||
|  | "internmap@1 - 2": | ||||||
|  |   version "2.0.3" | ||||||
|  |   resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" | ||||||
|  |   integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== | ||||||
|  | 
 | ||||||
| invariant@^2.2.4: | invariant@^2.2.4: | ||||||
|   version "2.2.4" |   version "2.2.4" | ||||||
|   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" |   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" | ||||||
| @ -1159,6 +1311,11 @@ json5@^2.2.3: | |||||||
|   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" |   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" | ||||||
|   integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== |   integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== | ||||||
| 
 | 
 | ||||||
|  | lodash@^4.17.21: | ||||||
|  |   version "4.17.21" | ||||||
|  |   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" | ||||||
|  |   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== | ||||||
|  | 
 | ||||||
| loose-envify@^1.0.0, loose-envify@^1.4.0: | loose-envify@^1.0.0, loose-envify@^1.4.0: | ||||||
|   version "1.4.0" |   version "1.4.0" | ||||||
|   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" |   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" | ||||||
| @ -1291,7 +1448,7 @@ react-is@^16.13.1, react-is@^16.3.2: | |||||||
|   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" |   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" | ||||||
|   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== |   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== | ||||||
| 
 | 
 | ||||||
| react-is@^18.0.0: | react-is@^18.0.0, react-is@^18.3.1: | ||||||
|   version "18.3.1" |   version "18.3.1" | ||||||
|   resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" |   resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" | ||||||
|   integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== |   integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== | ||||||
| @ -1323,11 +1480,37 @@ react-refresh@^0.14.2: | |||||||
|   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" |   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" | ||||||
|   integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== |   integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== | ||||||
| 
 | 
 | ||||||
|  | react-router-dom@^7.2.0: | ||||||
|  |   version "7.2.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.2.0.tgz#b8a7eae7827cd5207cf91e89807d01217737797d" | ||||||
|  |   integrity sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q== | ||||||
|  |   dependencies: | ||||||
|  |     react-router "7.2.0" | ||||||
|  | 
 | ||||||
|  | react-router@7.2.0, react-router@^7.2.0: | ||||||
|  |   version "7.2.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.2.0.tgz#a8f2729d39f634a7a870d14dd906a1b406f39d6f" | ||||||
|  |   integrity sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@types/cookie" "^0.6.0" | ||||||
|  |     cookie "^1.0.1" | ||||||
|  |     set-cookie-parser "^2.6.0" | ||||||
|  |     turbo-stream "2.4.0" | ||||||
|  | 
 | ||||||
| react-select-search@^4.1.6: | react-select-search@^4.1.6: | ||||||
|   version "4.1.8" |   version "4.1.8" | ||||||
|   resolved "https://registry.yarnpkg.com/react-select-search/-/react-select-search-4.1.8.tgz#435bdd7893685d45332813ad65b000e0dafcfbac" |   resolved "https://registry.yarnpkg.com/react-select-search/-/react-select-search-4.1.8.tgz#435bdd7893685d45332813ad65b000e0dafcfbac" | ||||||
|   integrity sha512-mzHhYzpaAJ3iYDjayydfb+grvvP59VWlLUWLqP26Trvm4xj2rzLnksopWZdkwWORB3dhAneqmXos3RWOMjFOxQ== |   integrity sha512-mzHhYzpaAJ3iYDjayydfb+grvvP59VWlLUWLqP26Trvm4xj2rzLnksopWZdkwWORB3dhAneqmXos3RWOMjFOxQ== | ||||||
| 
 | 
 | ||||||
|  | react-smooth@^4.0.4: | ||||||
|  |   version "4.0.4" | ||||||
|  |   resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.4.tgz#a5875f8bb61963ca61b819cedc569dc2453894b4" | ||||||
|  |   integrity sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q== | ||||||
|  |   dependencies: | ||||||
|  |     fast-equals "^5.0.1" | ||||||
|  |     prop-types "^15.8.1" | ||||||
|  |     react-transition-group "^4.4.5" | ||||||
|  | 
 | ||||||
| react-snowfall@^2.2.0: | react-snowfall@^2.2.0: | ||||||
|   version "2.2.0" |   version "2.2.0" | ||||||
|   resolved "https://registry.yarnpkg.com/react-snowfall/-/react-snowfall-2.2.0.tgz#0856a72c4d29d27a6a14301c0f5deb51296fb4df" |   resolved "https://registry.yarnpkg.com/react-snowfall/-/react-snowfall-2.2.0.tgz#0856a72c4d29d27a6a14301c0f5deb51296fb4df" | ||||||
| @ -1362,6 +1545,27 @@ readdirp@^4.0.1: | |||||||
|   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" |   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" | ||||||
|   integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA== |   integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA== | ||||||
| 
 | 
 | ||||||
|  | recharts-scale@^0.4.4: | ||||||
|  |   version "0.4.5" | ||||||
|  |   resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" | ||||||
|  |   integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== | ||||||
|  |   dependencies: | ||||||
|  |     decimal.js-light "^2.4.1" | ||||||
|  | 
 | ||||||
|  | recharts@^2.15.1: | ||||||
|  |   version "2.15.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.15.1.tgz#0941adf0402528d54f6d81997eb15840c893aa3c" | ||||||
|  |   integrity sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q== | ||||||
|  |   dependencies: | ||||||
|  |     clsx "^2.0.0" | ||||||
|  |     eventemitter3 "^4.0.1" | ||||||
|  |     lodash "^4.17.21" | ||||||
|  |     react-is "^18.3.1" | ||||||
|  |     react-smooth "^4.0.4" | ||||||
|  |     recharts-scale "^0.4.4" | ||||||
|  |     tiny-invariant "^1.3.1" | ||||||
|  |     victory-vendor "^36.6.8" | ||||||
|  | 
 | ||||||
| regenerator-runtime@^0.14.0: | regenerator-runtime@^0.14.0: | ||||||
|   version "0.14.1" |   version "0.14.1" | ||||||
|   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" |   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" | ||||||
| @ -1416,6 +1620,11 @@ semver@^6.3.1: | |||||||
|   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" |   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" | ||||||
|   integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== |   integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== | ||||||
| 
 | 
 | ||||||
|  | set-cookie-parser@^2.6.0: | ||||||
|  |   version "2.7.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" | ||||||
|  |   integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== | ||||||
|  | 
 | ||||||
| slash@^3.0.0: | slash@^3.0.0: | ||||||
|   version "3.0.0" |   version "3.0.0" | ||||||
|   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" |   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" | ||||||
| @ -1458,6 +1667,11 @@ supports-color@^7.1.0: | |||||||
|   dependencies: |   dependencies: | ||||||
|     has-flag "^4.0.0" |     has-flag "^4.0.0" | ||||||
| 
 | 
 | ||||||
|  | tiny-invariant@^1.3.1: | ||||||
|  |   version "1.3.3" | ||||||
|  |   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" | ||||||
|  |   integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== | ||||||
|  | 
 | ||||||
| to-regex-range@^5.0.1: | to-regex-range@^5.0.1: | ||||||
|   version "5.0.1" |   version "5.0.1" | ||||||
|   resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" |   resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" | ||||||
| @ -1475,6 +1689,11 @@ tslib@^2.8.0: | |||||||
|   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" |   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" | ||||||
|   integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== |   integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== | ||||||
| 
 | 
 | ||||||
|  | turbo-stream@2.4.0: | ||||||
|  |   version "2.4.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" | ||||||
|  |   integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g== | ||||||
|  | 
 | ||||||
| typescript@^5.3.3: | typescript@^5.3.3: | ||||||
|   version "5.7.2" |   version "5.7.2" | ||||||
|   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" |   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" | ||||||
| @ -1513,6 +1732,26 @@ update-browserslist-db@^1.1.1: | |||||||
|     escalade "^3.2.0" |     escalade "^3.2.0" | ||||||
|     picocolors "^1.1.0" |     picocolors "^1.1.0" | ||||||
| 
 | 
 | ||||||
|  | victory-vendor@^36.6.8: | ||||||
|  |   version "36.9.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" | ||||||
|  |   integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@types/d3-array" "^3.0.3" | ||||||
|  |     "@types/d3-ease" "^3.0.0" | ||||||
|  |     "@types/d3-interpolate" "^3.0.1" | ||||||
|  |     "@types/d3-scale" "^4.0.2" | ||||||
|  |     "@types/d3-shape" "^3.1.0" | ||||||
|  |     "@types/d3-time" "^3.0.0" | ||||||
|  |     "@types/d3-timer" "^3.0.0" | ||||||
|  |     d3-array "^3.1.6" | ||||||
|  |     d3-ease "^3.0.1" | ||||||
|  |     d3-interpolate "^3.0.1" | ||||||
|  |     d3-scale "^4.0.2" | ||||||
|  |     d3-shape "^3.1.0" | ||||||
|  |     d3-time "^3.0.0" | ||||||
|  |     d3-timer "^3.0.1" | ||||||
|  | 
 | ||||||
| vite-tsconfig-paths@^5.1.4: | vite-tsconfig-paths@^5.1.4: | ||||||
|   version "5.1.4" |   version "5.1.4" | ||||||
|   resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz#d9a71106a7ff2c1c840c6f1708042f76a9212ed4" |   resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz#d9a71106a7ff2c1c840c6f1708042f76a9212ed4" | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import pizzaDayRoutes from "./routes/pizzaDayRoutes"; | |||||||
| import foodRoutes from "./routes/foodRoutes"; | import foodRoutes from "./routes/foodRoutes"; | ||||||
| import votingRoutes from "./routes/votingRoutes"; | import votingRoutes from "./routes/votingRoutes"; | ||||||
| import easterEggRoutes from "./routes/easterEggRoutes"; | import easterEggRoutes from "./routes/easterEggRoutes"; | ||||||
|  | import statsRoutes from "./routes/statsRoutes"; | ||||||
| 
 | 
 | ||||||
| const ENVIRONMENT = process.env.NODE_ENV || 'production'; | const ENVIRONMENT = process.env.NODE_ENV || 'production'; | ||||||
| dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) }); | dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) }); | ||||||
| @ -132,6 +133,7 @@ app.use("/api/pizzaDay", pizzaDayRoutes); | |||||||
| app.use("/api/food", foodRoutes); | app.use("/api/food", foodRoutes); | ||||||
| app.use("/api/voting", votingRoutes); | app.use("/api/voting", votingRoutes); | ||||||
| app.use("/api/easterEggs", easterEggRoutes); | app.use("/api/easterEggs", easterEggRoutes); | ||||||
|  | app.use("/api/stats", statsRoutes); | ||||||
| app.use(express.static('public')) | app.use(express.static('public')) | ||||||
| 
 | 
 | ||||||
| // Middleware pro zpracování chyb
 | // Middleware pro zpracování chyb
 | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { getDayOfWeekIndex } from "./utils"; | import { WeeklyStats, Locations } from "../../types"; | ||||||
| 
 | 
 | ||||||
| // Mockovací data pro podporované podniky, na jeden týden
 | // Mockovací data pro podporované podniky, na jeden týden
 | ||||||
| const MOCK_DATA = { | const MOCK_DATA = { | ||||||
| @ -1383,4 +1383,80 @@ export const getMenuSenkSerikovaMock = () => { | |||||||
| 
 | 
 | ||||||
| export const getPizzaListMock = () => { | export const getPizzaListMock = () => { | ||||||
|     return MOCK_PIZZA_LIST; |     return MOCK_PIZZA_LIST; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const getStatsMock = (): WeeklyStats => { | ||||||
|  |     // TODO stačilo by iterovat ten enum, jako to už děláme jinde
 | ||||||
|  |     return [ | ||||||
|  |         { | ||||||
|  |             date: '24.02.', | ||||||
|  |             locations: { | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SLADOVNICKA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.TECHTOWER)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ZASTAVKAUMICHALA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SENKSERIKOVA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SPSE)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.PIZZA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.OBJEDNAVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.NEOBEDVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ROZHODUJI)]]: Math.floor(Math.random() * 10), | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             date: '25.02.', | ||||||
|  |             locations: { | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SLADOVNICKA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.TECHTOWER)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ZASTAVKAUMICHALA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SENKSERIKOVA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SPSE)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.PIZZA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.OBJEDNAVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.NEOBEDVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ROZHODUJI)]]: Math.floor(Math.random() * 10), | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             date: '26.02.', | ||||||
|  |             locations: { | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SLADOVNICKA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.TECHTOWER)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ZASTAVKAUMICHALA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SENKSERIKOVA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SPSE)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.PIZZA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.OBJEDNAVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.NEOBEDVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ROZHODUJI)]]: Math.floor(Math.random() * 10), | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             date: '27.02.', | ||||||
|  |             locations: { | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SLADOVNICKA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.TECHTOWER)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ZASTAVKAUMICHALA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SENKSERIKOVA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SPSE)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.PIZZA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.OBJEDNAVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.NEOBEDVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ROZHODUJI)]]: Math.floor(Math.random() * 10), | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             date: '28.02.', | ||||||
|  |             locations: { | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SLADOVNICKA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.TECHTOWER)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ZASTAVKAUMICHALA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SENKSERIKOVA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.SPSE)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.PIZZA)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.OBJEDNAVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.NEOBEDVAM)]]: Math.floor(Math.random() * 10), | ||||||
|  |                 [Object.keys(Locations)[Object.values(Locations).indexOf(Locations.ROZHODUJI)]]: Math.floor(Math.random() * 10), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ]; | ||||||
| } | } | ||||||
							
								
								
									
										22
									
								
								server/src/routes/statsRoutes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								server/src/routes/statsRoutes.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | import express, { Request, Response } from "express"; | ||||||
|  | import { getLogin } from "../auth"; | ||||||
|  | import { parseToken } from "../utils"; | ||||||
|  | import { WeeklyStats } from "../../../types"; | ||||||
|  | import { getStats } from "../stats"; | ||||||
|  | 
 | ||||||
|  | const router = express.Router(); | ||||||
|  | 
 | ||||||
|  | router.get("/", async (req: Request<{}, any, undefined>, res: Response<WeeklyStats>) => { | ||||||
|  |     getLogin(parseToken(req)); | ||||||
|  |     if (typeof req.query.startDate === 'string' && typeof req.query.endDate === 'string') { | ||||||
|  |         try { | ||||||
|  |             const data = await getStats(req.query.startDate, req.query.endDate); | ||||||
|  |             return res.status(200).json(data); | ||||||
|  |         } catch (e) { | ||||||
|  |             // necháme to zatím spadnout na 400
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     res.sendStatus(400); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default router; | ||||||
							
								
								
									
										44
									
								
								server/src/stats.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								server/src/stats.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | import { WeeklyStats, DayData, Locations, DailyStats, LocationKey } from "../../types"; | ||||||
|  | import { getStatsMock } from "./mock"; | ||||||
|  | import getStorage from "./storage"; | ||||||
|  | import { formatDate } from "./utils"; | ||||||
|  | 
 | ||||||
|  | const storage = getStorage(); | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Vypočte a vrátí statistiky jednotlivých možností pro předaný rozsah dat. | ||||||
|  |  *  | ||||||
|  |  * @param startDate počáteční datum | ||||||
|  |  * @param endDate koncové datum | ||||||
|  |  * @returns statistiky pro zadaný rozsah dat | ||||||
|  |  */ | ||||||
|  | export async function getStats(startDate: string, endDate: string): Promise<WeeklyStats> { | ||||||
|  |     if (process.env.MOCK_DATA === 'true') { | ||||||
|  |         return getStatsMock(); | ||||||
|  |     } | ||||||
|  |     const start = new Date(startDate); | ||||||
|  |     const end = new Date(endDate); | ||||||
|  | 
 | ||||||
|  |     // Dočasná validace, aby to někdo ručně neshodil
 | ||||||
|  |     const daysDiff = ((end as any) - (start as any)) / (1000 * 60 * 60 * 24); | ||||||
|  |     if (daysDiff > 4) { | ||||||
|  |         throw Error('Neplatný rozsah'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const result = []; | ||||||
|  |     for (const date = start; date <= end; date.setDate(date.getDate() + 1)) { | ||||||
|  |         const locationsStats: DailyStats = { | ||||||
|  |             // TODO vytáhnout do utils funkce
 | ||||||
|  |             date: `${String(date.getDate()).padStart(2, "0")}.${String(date.getMonth() + 1).padStart(2, "0")}.`, | ||||||
|  |             locations: {} | ||||||
|  |         } | ||||||
|  |         const data: DayData = await storage.getData(formatDate(date)); | ||||||
|  |         if (data?.choices) { | ||||||
|  |             Object.keys(data.choices).forEach(locationKey => { | ||||||
|  |                 locationsStats.locations[locationKey as LocationKey] = Object.keys(data.choices[locationKey as LocationKey]!).length; | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         result.push(locationsStats); | ||||||
|  |     } | ||||||
|  |     return result as WeeklyStats; | ||||||
|  | } | ||||||
| @ -220,4 +220,13 @@ export type AnimationPosition = { | |||||||
|     endBottom?: string, |     endBottom?: string, | ||||||
|     "--end-bottom"?: string, |     "--end-bottom"?: string, | ||||||
|     rotate?: string, |     rotate?: string, | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** Statistiky pro jeden konkrétní den. */ | ||||||
|  | export type DailyStats = { | ||||||
|  |     date: string, // zkrácené datum v human-readable formátu (např. 24.02.)
 | ||||||
|  |     locations: { [location in LocationKey]?: number } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** Statistiky pro jeden konkrétní týden (pondělí-pátek) */ | ||||||
|  | export type WeeklyStats = [DailyStats, DailyStats, DailyStats, DailyStats, DailyStats]; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user