Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
20f4ee0427
|
|||
|
be4cee4cdb
|
@@ -24,6 +24,7 @@
|
|||||||
"react-router": "^7.9.5",
|
"react-router": "^7.9.5",
|
||||||
"react-router-dom": "^7.9.5",
|
"react-router-dom": "^7.9.5",
|
||||||
"react-select-search": "^4.1.6",
|
"react-select-search": "^4.1.6",
|
||||||
|
"react-snow-overlay": "^1.0.14",
|
||||||
"react-snowfall": "^2.3.0",
|
"react-snowfall": "^2.3.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
|
|||||||
+20
-26
@@ -15,13 +15,13 @@ import { useSettings } from './context/settings';
|
|||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import { faChainBroken, faChevronLeft, faChevronRight, faGear, faSatelliteDish, faSearch } from '@fortawesome/free-solid-svg-icons';
|
import { faChainBroken, faChevronLeft, faChevronRight, faGear, faSatelliteDish, faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||||
import Loader from './components/Loader';
|
import Loader from './components/Loader';
|
||||||
import { getDayOfWeekIndex, getHumanDate, getHumanDateTime, getIsWeekend, isInTheFuture } from './Utils';
|
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 { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant, getData, getEasterEggImage, addPizza, removePizza, updatePizzaDayNote, createPizzaDay, deletePizzaDay, lockPizzaDay, unlockPizzaDay, finishOrder, finishDelivery, addChoice, jdemeObed, removeChoices, removeChoice, updateNote, changeDepartureTime } from '../../types';
|
import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant, getData, getEasterEggImage, addPizza, removePizza, updatePizzaDayNote, createPizzaDay, deletePizzaDay, lockPizzaDay, unlockPizzaDay, finishOrder, finishDelivery, addChoice, jdemeObed, removeChoices, removeChoice, updateNote, changeDepartureTime } from '../../types';
|
||||||
import { getLunchChoiceName } from './enums';
|
import { getLunchChoiceName } from './enums';
|
||||||
import FallingLeaves, { LEAF_PRESETS, LEAF_COLOR_THEMES } from './FallingLeaves';
|
// import FallingLeaves, { LEAF_PRESETS, LEAF_COLOR_THEMES } from './FallingLeaves';
|
||||||
import './FallingLeaves.scss';
|
// import './FallingLeaves.scss';
|
||||||
|
|
||||||
const EVENT_CONNECT = "connect"
|
const EVENT_CONNECT = "connect"
|
||||||
|
|
||||||
@@ -71,10 +71,7 @@ function App() {
|
|||||||
const departureChoiceRef = useRef<HTMLSelectElement>(null);
|
const departureChoiceRef = useRef<HTMLSelectElement>(null);
|
||||||
const pizzaPoznamkaRef = useRef<HTMLInputElement>(null);
|
const pizzaPoznamkaRef = useRef<HTMLInputElement>(null);
|
||||||
const [failure, setFailure] = useState<boolean>(false);
|
const [failure, setFailure] = useState<boolean>(false);
|
||||||
const [dayIndex, setDayIndex] = useState<number>(); // Index zobrazovaného dne
|
const [dayIndex, setDayIndex] = useState<number>();
|
||||||
// TODO berka zde je nutné dořešit mocking pro testování
|
|
||||||
const [todayDayIndex, setTodayDayIndex] = useState<number>(getDayOfWeekIndex(new Date())); // Index dnešního dne
|
|
||||||
const [isTodayWeekend, setIsTodayWeekend] = useState<boolean>(getIsWeekend(new Date()));
|
|
||||||
const [loadingPizzaDay, setLoadingPizzaDay] = useState<boolean>(false);
|
const [loadingPizzaDay, setLoadingPizzaDay] = useState<boolean>(false);
|
||||||
const [noteModalOpen, setNoteModalOpen] = useState<boolean>(false);
|
const [noteModalOpen, setNoteModalOpen] = useState<boolean>(false);
|
||||||
const [eggImage, setEggImage] = useState<Blob>();
|
const [eggImage, setEggImage] = useState<Blob>();
|
||||||
@@ -92,9 +89,8 @@ function App() {
|
|||||||
const data = response.data
|
const data = response.data
|
||||||
if (data) {
|
if (data) {
|
||||||
setData(data);
|
setData(data);
|
||||||
const dayIndex = getDayOfWeekIndex(new Date(data.date));
|
setDayIndex(data.dayIndex);
|
||||||
setDayIndex(dayIndex);
|
dayIndexRef.current = data.dayIndex;
|
||||||
dayIndexRef.current = dayIndex;
|
|
||||||
setFood(data.menus);
|
setFood(data.menus);
|
||||||
}
|
}
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
@@ -107,8 +103,6 @@ function App() {
|
|||||||
if (!auth?.login) {
|
if (!auth?.login) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setTodayDayIndex(getDayOfWeekIndex(new Date()));
|
|
||||||
setIsTodayWeekend(getIsWeekend(new Date()));
|
|
||||||
getData({ query: { dayIndex: dayIndex } }).then(response => {
|
getData({ query: { dayIndex: dayIndex } }).then(response => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
setData(data);
|
setData(data);
|
||||||
@@ -131,7 +125,7 @@ function App() {
|
|||||||
socket.on(EVENT_MESSAGE, (newData: ClientData) => {
|
socket.on(EVENT_MESSAGE, (newData: ClientData) => {
|
||||||
// console.log("Přijata nová data ze socketu", newData);
|
// console.log("Přijata nová data ze socketu", newData);
|
||||||
// Aktualizujeme pouze, pokud jsme dostali data pro den, který máme aktuálně zobrazený
|
// Aktualizujeme pouze, pokud jsme dostali data pro den, který máme aktuálně zobrazený
|
||||||
if (dayIndexRef.current == null || getDayOfWeekIndex(new Date(newData.date)) === dayIndexRef.current) {
|
if (dayIndexRef.current == null || newData.dayIndex === dayIndexRef.current) {
|
||||||
setData(newData);
|
setData(newData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -445,24 +439,24 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const noOrders = data?.pizzaDay?.orders?.length === 0;
|
const noOrders = data?.pizzaDay?.orders?.length === 0;
|
||||||
const canChangeChoice = dayIndex == null || dayIndex >= todayDayIndex;
|
const canChangeChoice = dayIndex == null || data.todayDayIndex == null || dayIndex >= data.todayDayIndex;
|
||||||
|
|
||||||
const { path, url, startOffset, endOffset, duration, ...style } = easterEgg || {};
|
const { path, url, startOffset, endOffset, duration, ...style } = easterEgg || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-container">
|
<div className="app-container">
|
||||||
{easterEgg && eggImage && <img ref={eggRef} alt='' src={URL.createObjectURL(eggImage)} style={{ position: 'absolute', ...EASTER_EGG_STYLE, ...style, animationDuration: `${duration ?? EASTER_EGG_DEFAULT_DURATION}s` }} />}
|
{easterEgg && eggImage && <img ref={eggRef} alt='' src={URL.createObjectURL(eggImage)} style={{ position: 'absolute', ...EASTER_EGG_STYLE, ...style, animationDuration: `${duration ?? EASTER_EGG_DEFAULT_DURATION}s` }} />}
|
||||||
<Header dayIndex={dayIndex} />
|
<Header />
|
||||||
<div className='wrapper'>
|
<div className='wrapper'>
|
||||||
{isTodayWeekend ? <h4>Užívejte víkend :)</h4> : <>
|
{data.isWeekend ? <h4>Užívejte víkend :)</h4> : <>
|
||||||
<Alert variant={'primary'}>
|
<Alert variant={'primary'}>
|
||||||
{/* <img alt="" src='hat.png' style={{ position: "absolute", width: "70px", rotate: "-45deg", left: -40, top: -58 }} />
|
<img alt="" src='hat.png' style={{ position: "absolute", width: "70px", rotate: "-45deg", left: -40, top: -58 }} />
|
||||||
<img alt="" src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} /> */}
|
<img alt="" src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} />
|
||||||
Poslední změny:
|
Poslední změny:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Zobrazení alergenu při najetí myší a proklik na seznam alergenů</li>
|
<li>Oprava parsování Sladovnické</li>
|
||||||
<li>Přesun přenačtení menu do samostatného dialogu</li>
|
<li>Oprava parsování cen TechTower</li>
|
||||||
<li>Podzimní atmosféra</li>
|
<li>Zimní atmosféra</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
{dayIndex != null &&
|
{dayIndex != null &&
|
||||||
@@ -470,7 +464,7 @@ function App() {
|
|||||||
<span title='Předchozí den'>
|
<span title='Předchozí den'>
|
||||||
<FontAwesomeIcon icon={faChevronLeft} style={{ cursor: "pointer", visibility: dayIndex > 0 ? "initial" : "hidden" }} onClick={() => handleDayChange(dayIndex - 1)} />
|
<FontAwesomeIcon icon={faChevronLeft} style={{ cursor: "pointer", visibility: dayIndex > 0 ? "initial" : "hidden" }} onClick={() => handleDayChange(dayIndex - 1)} />
|
||||||
</span>
|
</span>
|
||||||
<h1 className='title' style={{ color: dayIndex === todayDayIndex ? 'black' : 'gray' }}>{getHumanDate(new Date(data.date))}</h1>
|
<h1 className='title' style={{ color: dayIndex === data.todayDayIndex ? 'black' : 'gray' }}>{data.date}</h1>
|
||||||
<span title="Následující den">
|
<span title="Následující den">
|
||||||
<FontAwesomeIcon icon={faChevronRight} style={{ cursor: "pointer", visibility: dayIndex < 4 ? "initial" : "hidden" }} onClick={() => handleDayChange(dayIndex + 1)} />
|
<FontAwesomeIcon icon={faChevronRight} style={{ cursor: "pointer", visibility: dayIndex < 4 ? "initial" : "hidden" }} onClick={() => handleDayChange(dayIndex + 1)} />
|
||||||
</span>
|
</span>
|
||||||
@@ -486,7 +480,7 @@ function App() {
|
|||||||
<div className='content-wrapper'>
|
<div className='content-wrapper'>
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
{canChangeChoice && <>
|
{canChangeChoice && <>
|
||||||
<p>{`Jak to ${dayIndex == null || dayIndex === todayDayIndex ? 'dnes' : 'tento den'} vidíš s obědem?`}</p>
|
<p>{`Jak to ${dayIndex == null || dayIndex === data.todayDayIndex ? 'dnes' : 'tento den'} vidíš s obědem?`}</p>
|
||||||
<Form.Select ref={choiceRef} onChange={doAddChoice}>
|
<Form.Select ref={choiceRef} onChange={doAddChoice}>
|
||||||
<option></option>
|
<option></option>
|
||||||
{Object.entries(LunchChoice)
|
{Object.entries(LunchChoice)
|
||||||
@@ -595,7 +589,7 @@ function App() {
|
|||||||
: <div className='mt-5'><i>Zatím nikdo nehlasoval...</i></div>
|
: <div className='mt-5'><i>Zatím nikdo nehlasoval...</i></div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{dayIndex === todayDayIndex &&
|
{dayIndex === data.todayDayIndex &&
|
||||||
<div className='mt-5'>
|
<div className='mt-5'>
|
||||||
{!data.pizzaDay &&
|
{!data.pizzaDay &&
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
@@ -717,10 +711,10 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</> || "Jejda, něco se nám nepovedlo :("}
|
</> || "Jejda, něco se nám nepovedlo :("}
|
||||||
</div>
|
</div>
|
||||||
<FallingLeaves
|
{/* <FallingLeaves
|
||||||
numLeaves={LEAF_PRESETS.NORMAL}
|
numLeaves={LEAF_PRESETS.NORMAL}
|
||||||
leafVariants={LEAF_COLOR_THEMES.AUTUMN}
|
leafVariants={LEAF_COLOR_THEMES.AUTUMN}
|
||||||
/>
|
/> */}
|
||||||
<Footer />
|
<Footer />
|
||||||
<NoteModal isOpen={noteModalOpen} onClose={() => setNoteModalOpen(false)} onSave={saveNote} />
|
<NoteModal isOpen={noteModalOpen} onClose={() => setNoteModalOpen(false)} onSave={saveNote} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Routes, Route } from "react-router-dom";
|
import { Routes, Route } from "react-router-dom";
|
||||||
import { ProvideSettings } from "./context/settings";
|
import { ProvideSettings } from "./context/settings";
|
||||||
// import Snowfall from "react-snowfall";
|
// import Snowfall from "react-snowfall";
|
||||||
|
import { SnowOverlay } from 'react-snow-overlay';
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
import { SocketContext, socket } from "./context/socket";
|
import { SocketContext, socket } from "./context/socket";
|
||||||
import StatsPage from "./pages/StatsPage";
|
import StatsPage from "./pages/StatsPage";
|
||||||
@@ -22,6 +23,7 @@ export default function AppRoutes() {
|
|||||||
width: '100vw',
|
width: '100vw',
|
||||||
height: '100vh'
|
height: '100vh'
|
||||||
}} /> */}
|
}} /> */}
|
||||||
|
<SnowOverlay color={'rgba(240, 240, 240, 0.9)'} disabledOnSingleCpuDevices={true} />
|
||||||
<App />
|
<App />
|
||||||
</>
|
</>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
|
|||||||
@@ -73,12 +73,6 @@ export const getDayOfWeekIndex = (date: Date) => {
|
|||||||
return (((date.getDay() - 1) % 7) + 7) % 7;
|
return (((date.getDay() - 1) % 7) + 7) % 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vrátí true, pokud je předané datum o víkendu. */
|
|
||||||
export function getIsWeekend(date: Date) {
|
|
||||||
const index = getDayOfWeekIndex(date);
|
|
||||||
return index == 5 || index == 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Vrátí první pracovní den v týdnu předaného data. */
|
/** Vrátí první pracovní den v týdnu předaného data. */
|
||||||
export function getFirstWorkDayOfWeek(date: Date) {
|
export function getFirstWorkDayOfWeek(date: Date) {
|
||||||
const firstDay = new Date(date.getTime());
|
const firstDay = new Date(date.getTime());
|
||||||
|
|||||||
@@ -6,16 +6,11 @@ import { useSettings } from "../context/settings";
|
|||||||
import FeaturesVotingModal from "./modals/FeaturesVotingModal";
|
import FeaturesVotingModal from "./modals/FeaturesVotingModal";
|
||||||
import PizzaCalculatorModal from "./modals/PizzaCalculatorModal";
|
import PizzaCalculatorModal from "./modals/PizzaCalculatorModal";
|
||||||
import RefreshMenuModal from "./modals/RefreshMenuModal";
|
import RefreshMenuModal from "./modals/RefreshMenuModal";
|
||||||
import GenerateQRModal from "./modals/GenerateQRModal";
|
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { STATS_URL } from "../AppRoutes";
|
import { STATS_URL } from "../AppRoutes";
|
||||||
import { FeatureRequest, getVotes, updateVote } from "../../../types";
|
import { FeatureRequest, getVotes, updateVote } from "../../../types";
|
||||||
|
|
||||||
type Props = {
|
export default function Header() {
|
||||||
dayIndex?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Header({ dayIndex }: Readonly<Props>) {
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -23,7 +18,6 @@ export default function Header({ dayIndex }: Readonly<Props>) {
|
|||||||
const [votingModalOpen, setVotingModalOpen] = useState<boolean>(false);
|
const [votingModalOpen, setVotingModalOpen] = useState<boolean>(false);
|
||||||
const [pizzaModalOpen, setPizzaModalOpen] = useState<boolean>(false);
|
const [pizzaModalOpen, setPizzaModalOpen] = useState<boolean>(false);
|
||||||
const [refreshMenuModalOpen, setRefreshMenuModalOpen] = useState<boolean>(false);
|
const [refreshMenuModalOpen, setRefreshMenuModalOpen] = useState<boolean>(false);
|
||||||
const [generateQRModalOpen, setGenerateQRModalOpen] = useState<boolean>(false);
|
|
||||||
const [featureVotes, setFeatureVotes] = useState<FeatureRequest[] | undefined>([]);
|
const [featureVotes, setFeatureVotes] = useState<FeatureRequest[] | undefined>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,10 +44,6 @@ export default function Header({ dayIndex }: Readonly<Props>) {
|
|||||||
setRefreshMenuModalOpen(false);
|
setRefreshMenuModalOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeGenerateQRModal = () => {
|
|
||||||
setGenerateQRModalOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValidInteger = (str: string) => {
|
const isValidInteger = (str: string) => {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str) {
|
if (!str) {
|
||||||
@@ -131,7 +121,6 @@ export default function Header({ dayIndex }: Readonly<Props>) {
|
|||||||
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
|
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
|
||||||
<NavDropdown.Item onClick={() => setSettingsModalOpen(true)}>Nastavení</NavDropdown.Item>
|
<NavDropdown.Item onClick={() => setSettingsModalOpen(true)}>Nastavení</NavDropdown.Item>
|
||||||
<NavDropdown.Item onClick={() => setRefreshMenuModalOpen(true)}>Přenačtení menu</NavDropdown.Item>
|
<NavDropdown.Item onClick={() => setRefreshMenuModalOpen(true)}>Přenačtení menu</NavDropdown.Item>
|
||||||
<NavDropdown.Item onClick={() => setGenerateQRModalOpen(true)}>Generování QR</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.Item onClick={() => navigate(STATS_URL)}>Statistiky</NavDropdown.Item>
|
||||||
@@ -142,7 +131,6 @@ export default function Header({ dayIndex }: Readonly<Props>) {
|
|||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
<SettingsModal isOpen={settingsModalOpen} onClose={closeSettingsModal} onSave={saveSettings} />
|
<SettingsModal isOpen={settingsModalOpen} onClose={closeSettingsModal} onSave={saveSettings} />
|
||||||
<RefreshMenuModal isOpen={refreshMenuModalOpen} onClose={closeRefreshMenuModal} />
|
<RefreshMenuModal isOpen={refreshMenuModalOpen} onClose={closeRefreshMenuModal} />
|
||||||
<GenerateQRModal isOpen={generateQRModalOpen} onClose={closeGenerateQRModal} dayIndex={dayIndex} bankAccount={settings?.bankAccount} bankAccountHolder={settings?.holderName} />
|
|
||||||
<FeaturesVotingModal isOpen={votingModalOpen} onClose={closeVotingModal} onChange={saveFeatureVote} initialValues={featureVotes} />
|
<FeaturesVotingModal isOpen={votingModalOpen} onClose={closeVotingModal} onChange={saveFeatureVote} initialValues={featureVotes} />
|
||||||
<PizzaCalculatorModal isOpen={pizzaModalOpen} onClose={closePizzaModal} />
|
<PizzaCalculatorModal isOpen={pizzaModalOpen} onClose={closePizzaModal} />
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Modal, Button, Table, Form, Alert } from "react-bootstrap";
|
|
||||||
import { ClientData, generateQr, getData } from "../../../../types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean,
|
|
||||||
onClose: () => void,
|
|
||||||
dayIndex?: number,
|
|
||||||
bankAccount?: string,
|
|
||||||
bankAccountHolder?: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserQRData = {
|
|
||||||
login: string;
|
|
||||||
selected: boolean;
|
|
||||||
note: string;
|
|
||||||
amount: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Modální dialog pro generování QR kódů. */
|
|
||||||
export default function GenerateQRModal({ isOpen, onClose, dayIndex, bankAccount, bankAccountHolder }: Readonly<Props>) {
|
|
||||||
const [users, setUsers] = useState<UserQRData[]>([]);
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const isBankDataValid = bankAccount && bankAccountHolder;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
setLoading(true);
|
|
||||||
getData({ query: { dayIndex } }).then(response => {
|
|
||||||
const data: ClientData = response.data;
|
|
||||||
const userList: UserQRData[] = [];
|
|
||||||
|
|
||||||
// Projdeme všechny volby stravování a získáme uživatele
|
|
||||||
if (data.choices) {
|
|
||||||
Object.entries(data.choices).forEach(([locationKey, locationUsers]) => {
|
|
||||||
Object.keys(locationUsers).forEach(login => {
|
|
||||||
// Přidáme uživatele pouze pokud tam ještě není
|
|
||||||
if (!userList.find(u => u.login === login)) {
|
|
||||||
userList.push({
|
|
||||||
login,
|
|
||||||
selected: false,
|
|
||||||
note: '',
|
|
||||||
amount: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setUsers(userList);
|
|
||||||
setLoading(false);
|
|
||||||
}).catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isOpen, dayIndex]);
|
|
||||||
|
|
||||||
const handleCheckboxChange = (login: string) => {
|
|
||||||
setUsers(users.map(u =>
|
|
||||||
u.login === login ? { ...u, selected: !u.selected } : u
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNoteChange = (login: string, note: string) => {
|
|
||||||
setUsers(users.map(u =>
|
|
||||||
u.login === login ? { ...u, note } : u
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAmountChange = (login: string, amount: string) => {
|
|
||||||
setUsers(users.map(u =>
|
|
||||||
u.login === login ? { ...u, amount } : u
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGenerate = async () => {
|
|
||||||
const selectedUsers = users.filter(u => u.selected);
|
|
||||||
// TODO: Implementovat generování QR kódů
|
|
||||||
console.log('Generování QR pro:', selectedUsers);
|
|
||||||
alert('Funkce generování QR bude implementována');
|
|
||||||
await generateQr({
|
|
||||||
body: {
|
|
||||||
bankAccount: bankAccount!,
|
|
||||||
bankAccountHolder: bankAccountHolder!,
|
|
||||||
qrCodes: selectedUsers.map(u => ({
|
|
||||||
login: u.login,
|
|
||||||
des: u.note,
|
|
||||||
amount: Number.parseFloat(u.amount)
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setUsers([]);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal show={isOpen} onHide={handleClose} size="lg">
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title><h2>Generování QR kódů</h2></Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
{!isBankDataValid && (
|
|
||||||
<Alert variant="warning">
|
|
||||||
<strong>Upozornění:</strong> Pro generování QR kódů je nutné mít v nastavení vyplněné číslo bankovního účtu a jméno majitele účtu.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{loading ? (
|
|
||||||
<p>Načítání uživatelů...</p>
|
|
||||||
) : users.length === 0 ? (
|
|
||||||
<p>Pro aktuální den nemá žádný uživatel vybranou volbu stravování.</p>
|
|
||||||
) : (
|
|
||||||
<Table striped bordered hover>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style={{ width: '50px' }}></th>
|
|
||||||
<th>Uživatel</th>
|
|
||||||
<th>Poznámka</th>
|
|
||||||
<th style={{ width: '120px' }}>Částka (Kč)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{users.map(user => (
|
|
||||||
<tr key={user.login}>
|
|
||||||
<td className="text-center">
|
|
||||||
<Form.Check
|
|
||||||
type="checkbox"
|
|
||||||
checked={user.selected}
|
|
||||||
onChange={() => handleCheckboxChange(user.login)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>{user.login}</td>
|
|
||||||
<td>
|
|
||||||
<Form.Control
|
|
||||||
type="text"
|
|
||||||
value={user.note}
|
|
||||||
onChange={(e) => handleNoteChange(user.login, e.target.value)}
|
|
||||||
placeholder="Poznámka"
|
|
||||||
disabled={!user.selected}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Control
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
value={user.amount}
|
|
||||||
onChange={(e) => handleAmountChange(user.login, e.target.value)}
|
|
||||||
placeholder="0.00"
|
|
||||||
disabled={!user.selected}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<Button variant="secondary" onClick={handleClose}>
|
|
||||||
Zavřít
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={users.filter(u => u.selected).length === 0 || !isBankDataValid}
|
|
||||||
>
|
|
||||||
Generovat
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -103,7 +103,7 @@ export default function StatsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header dayIndex={undefined} />
|
<Header />
|
||||||
<div className="stats-page">
|
<div className="stats-page">
|
||||||
<h1>Statistiky</h1>
|
<h1>Statistiky</h1>
|
||||||
<div className="week-navigator">
|
<div className="week-navigator">
|
||||||
|
|||||||
@@ -1706,6 +1706,11 @@ react-select-search@^4.1.6:
|
|||||||
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-snow-overlay@^1.0.14:
|
||||||
|
version "1.0.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-snow-overlay/-/react-snow-overlay-1.0.14.tgz#1080ab24018bc9f86e7ae0e341b7d1b94ec80cc8"
|
||||||
|
integrity sha512-i178E9PVcMobSYxGiV+L2SMKcVNKytOwTG6Sz3dNw53Eq3Q0NC8BbVL4ewXXd/QCGX5YBVpv8R5+bSBxpUHeRw==
|
||||||
|
|
||||||
react-snowfall@^2.3.0:
|
react-snowfall@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-snowfall/-/react-snowfall-2.3.0.tgz#3c7af4fa8c3786d3d3b7ccdc36ddb73108d23b07"
|
resolved "https://registry.yarnpkg.com/react-snowfall/-/react-snowfall-2.3.0.tgz#3c7af4fa8c3786d3d3b7ccdc36ddb73108d23b07"
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import foodRoutes, { refreshMetoda } 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";
|
import statsRoutes from "./routes/statsRoutes";
|
||||||
import debugRoutes from "./routes/debugRoutes";
|
|
||||||
import qrRoutes from "./routes/qrRoutes";
|
|
||||||
|
|
||||||
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}`) });
|
||||||
@@ -101,8 +99,6 @@ app.get("/api/qr", (req, res) => {
|
|||||||
// Přeskočení auth pro refresh dat xd
|
// Přeskočení auth pro refresh dat xd
|
||||||
app.use("/api/food/refresh", refreshMetoda);
|
app.use("/api/food/refresh", refreshMetoda);
|
||||||
|
|
||||||
app.use("/api/debug", debugRoutes);
|
|
||||||
|
|
||||||
/** Middleware ověřující JWT token */
|
/** Middleware ověřující JWT token */
|
||||||
app.use("/api/", (req, res, next) => {
|
app.use("/api/", (req, res, next) => {
|
||||||
if (HTTP_REMOTE_USER_ENABLED) {
|
if (HTTP_REMOTE_USER_ENABLED) {
|
||||||
@@ -147,7 +143,6 @@ 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("/api/stats", statsRoutes);
|
||||||
app.use("/api/qr", qrRoutes);
|
|
||||||
|
|
||||||
app.use('/stats', express.static('public'));
|
app.use('/stats', express.static('public'));
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|||||||
+1
-121
@@ -517,24 +517,6 @@ const MOCK_DATA = {
|
|||||||
name: "Pečené vepřové koleno, křen, hořčice, chléb",
|
name: "Pečené vepřové koleno, křen, hořčice, chléb",
|
||||||
price: "320\xA0Kč",
|
price: "320\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Slovácké strapačky s uzenou slaninou, zelím, mletým pepřem & sekanou petrželkou",
|
|
||||||
price: "140\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Hovězí guláš s vejcem, zeleninovou garniturkou & žemlovými knedlíky",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Kuřecí roláda s kaštanovou nádivkou, demi-glace & smetanovou bramborovou kaší",
|
|
||||||
price: "150\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -549,24 +531,6 @@ const MOCK_DATA = {
|
|||||||
name: "Poutine (trhané vepřové, hranolky, sýr, čalamáda, pikantní omáčka)",
|
name: "Poutine (trhané vepřové, hranolky, sýr, čalamáda, pikantní omáčka)",
|
||||||
price: "190\xA0Kč",
|
price: "190\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Slovácké strapačky s uzenou slaninou, zelím, mletým pepřem & sekanou petrželkou",
|
|
||||||
price: "140\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Hovězí guláš s vejcem, zeleninovou garniturkou & žemlovými knedlíky",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Kuřecí roláda s kaštanovou nádivkou, demi-glace & smetanovou bramborovou kaší",
|
|
||||||
price: "150\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -581,24 +545,6 @@ const MOCK_DATA = {
|
|||||||
name: "Vepřový řízek z kotlety, domácí bramborový salát",
|
name: "Vepřový řízek z kotlety, domácí bramborový salát",
|
||||||
price: "170\xA0Kč",
|
price: "170\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Slovácké strapačky s uzenou slaninou, zelím, mletým pepřem & sekanou petrželkou",
|
|
||||||
price: "140\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Hovězí guláš s vejcem, zeleninovou garniturkou & žemlovými knedlíky",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Kuřecí roláda s kaštanovou nádivkou, demi-glace & smetanovou bramborovou kaší",
|
|
||||||
price: "150\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -613,24 +559,6 @@ const MOCK_DATA = {
|
|||||||
name: "Burger z Chuck rollu, hranolky, tatarská omáčka",
|
name: "Burger z Chuck rollu, hranolky, tatarská omáčka",
|
||||||
price: "200\xA0Kč",
|
price: "200\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Slovácké strapačky s uzenou slaninou, zelím, mletým pepřem & sekanou petrželkou",
|
|
||||||
price: "140\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Hovězí guláš s vejcem, zeleninovou garniturkou & žemlovými knedlíky",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Kuřecí roláda s kaštanovou nádivkou, demi-glace & smetanovou bramborovou kaší",
|
|
||||||
price: "150\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -673,18 +601,6 @@ const MOCK_DATA = {
|
|||||||
name: "Hovězí po Burgundsku, bramborová kaše",
|
name: "Hovězí po Burgundsku, bramborová kaše",
|
||||||
price: "155\xA0Kč",
|
price: "155\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Špagety s kuřecím masem, špenátem a smetanou",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Medailonky z vepřové panenky s fazolkami se slaninou, šťouchané brambory",
|
|
||||||
price: "185\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -699,18 +615,6 @@ const MOCK_DATA = {
|
|||||||
name: "Kuřecí plátky na sušených rajčatech, bylinkách a česneku, bramborová kaše",
|
name: "Kuřecí plátky na sušených rajčatech, bylinkách a česneku, bramborová kaše",
|
||||||
price: "155\xA0Kč",
|
price: "155\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Špagety s kuřecím masem, špenátem a smetanou",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Medailonky z vepřové panenky s fazolkami se slaninou, šťouchané brambory",
|
|
||||||
price: "185\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -725,18 +629,6 @@ const MOCK_DATA = {
|
|||||||
name: "Rajská s plněnou paprikou, knedlík",
|
name: "Rajská s plněnou paprikou, knedlík",
|
||||||
price: "170\xA0Kč",
|
price: "170\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Špagety s kuřecím masem, špenátem a smetanou",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Medailonky z vepřové panenky s fazolkami se slaninou, šťouchané brambory",
|
|
||||||
price: "185\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -751,18 +643,6 @@ const MOCK_DATA = {
|
|||||||
name: "Ragú z trhané kachny, onsen vejce, soté ze špenátu a ředkvičky, bramborové pyré, lanýžová sůl, zelený olej",
|
name: "Ragú z trhané kachny, onsen vejce, soté ze špenátu a ředkvičky, bramborové pyré, lanýžová sůl, zelený olej",
|
||||||
price: "189\xA0Kč",
|
price: "189\xA0Kč",
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Špagety s kuřecím masem, špenátem a smetanou",
|
|
||||||
price: "145\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: "-",
|
|
||||||
name: "Medailonky z vepřové panenky s fazolkami se slaninou, šťouchané brambory",
|
|
||||||
price: "185\xA0Kč",
|
|
||||||
isSoup: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -1522,7 +1402,7 @@ const MOCK_PIZZA_LIST = [
|
|||||||
* Funkce vrací mock datu ve formátu YYYY-MM-DD
|
* Funkce vrací mock datu ve formátu YYYY-MM-DD
|
||||||
*/
|
*/
|
||||||
export const getTodayMock = (): Date => {
|
export const getTodayMock = (): Date => {
|
||||||
return new Date('2025-01-08'); // středa
|
return new Date('2025-01-10'); // pátek
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMenuSladovnickaMock = () => {
|
export const getMenuSladovnickaMock = () => {
|
||||||
|
|||||||
+19
-41
@@ -100,7 +100,7 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
|
|||||||
const html = await getHtml(SLADOVNICKA_URL);
|
const html = await getHtml(SLADOVNICKA_URL);
|
||||||
const $ = load(html);
|
const $ = load(html);
|
||||||
|
|
||||||
// Nejdříve zjistíme, které dny jsou k dispozici z tab elementů
|
// Zjistíme, které dny jsou k dispozici z tab elementů
|
||||||
const tabElements = $('#daily-menu-tab-list').children('button[id^="daily-menu-tab-"]');
|
const tabElements = $('#daily-menu-tab-list').children('button[id^="daily-menu-tab-"]');
|
||||||
const availableDays: { [dayIndex: number]: number } = {}; // mapování dayIndex -> contentIndex
|
const availableDays: { [dayIndex: number]: number } = {}; // mapování dayIndex -> contentIndex
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuContentElements = $('#daily-menu-content-list').children('[id^="daily-menu-content-"]');
|
const menuContentElements = $('#daily-menu-content-list').children('.daily-menu-content__content').not('.daily-menu-content__content--static');
|
||||||
|
|
||||||
const result: Food[][] = [];
|
const result: Food[][] = [];
|
||||||
|
|
||||||
@@ -130,59 +130,32 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
|
|||||||
continue; // Přeskočíme, pokud content element neexistuje
|
continue; // Přeskočíme, pokud content element neexistuje
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayChildren = $(menuContentElements[contentIndexNum]).children();
|
const contentElement = $(menuContentElements[contentIndexNum]);
|
||||||
|
const itemElement = contentElement.find('.daily-menu-content__item');
|
||||||
// Ověříme, že má element očekávanou strukturu
|
const table = itemElement.find('table.daily-menu-content__table tbody');
|
||||||
if (dayChildren.length < 2) {
|
const rows = table.children('tr');
|
||||||
console.warn(`Neočekávaný počet children v menu Sladovnické pro den ${dayIndexNum}: ${dayChildren.length}, očekávány alespoň 2 (polévka a hlavní jídlo)`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parsování polévky
|
|
||||||
const soupElement = dayChildren.get(0);
|
|
||||||
const soupTable = $(soupElement).find('table tbody tr');
|
|
||||||
const soupCells = soupTable.children('td');
|
|
||||||
if (soupCells.length !== 3) {
|
|
||||||
console.warn(`Neočekávaný počet buněk v tabulce polévky pro den ${dayIndexNum}: ${soupCells.length}, ale očekávány byly 3`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const soupAmount = sanitizeText($(soupCells.get(0)).text());
|
|
||||||
const soupNameRaw = sanitizeText($(soupCells.get(1)).text());
|
|
||||||
const soupPrice = sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0'));
|
|
||||||
const soupParsed = parseAllergens(soupNameRaw);
|
|
||||||
|
|
||||||
// Parsování hlavních jídel
|
|
||||||
const mainCourseElement = dayChildren.get(1);
|
|
||||||
const mainCourseTable = $(mainCourseElement).find('table tbody');
|
|
||||||
const mainCourseRows = mainCourseTable.children('tr');
|
|
||||||
|
|
||||||
const currentDayFood: Food[] = [];
|
const currentDayFood: Food[] = [];
|
||||||
|
|
||||||
// Přidáme polévku do seznamu jídel
|
// Projdeme všechny řádky - první je polévka, zbytek jsou hlavní jídla
|
||||||
currentDayFood.push({
|
rows.each((i, row) => {
|
||||||
amount: soupAmount,
|
|
||||||
name: soupParsed.cleanName,
|
|
||||||
price: soupPrice,
|
|
||||||
isSoup: true,
|
|
||||||
allergens: soupParsed.allergens.length > 0 ? soupParsed.allergens : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Projdeme všechny řádky hlavních jídel
|
|
||||||
mainCourseRows.each((i, row) => {
|
|
||||||
const cells = $(row).children('td');
|
const cells = $(row).children('td');
|
||||||
|
if (cells.length !== 3) {
|
||||||
|
return; // Přeskočíme řádky s nesprávnou strukturou
|
||||||
|
}
|
||||||
|
|
||||||
const amount = sanitizeText($(cells.get(0)).text());
|
const amount = sanitizeText($(cells.get(0)).text());
|
||||||
const nameRaw = sanitizeText($(cells.get(1)).text());
|
const nameRaw = sanitizeText($(cells.get(1)).text());
|
||||||
const price = sanitizeText($(cells.get(2)).text().replace(' ', '\xA0'));
|
const price = sanitizeText($(cells.get(2)).text().replace(' ', '\xA0'));
|
||||||
const parsed = parseAllergens(nameRaw);
|
const parsed = parseAllergens(nameRaw);
|
||||||
|
|
||||||
// Přeskočíme prázdné řádky (první řádek může být prázdný)
|
// Přeskočíme prázdné řádky
|
||||||
if (parsed.cleanName.trim().length > 0) {
|
if (parsed.cleanName.trim().length > 0) {
|
||||||
currentDayFood.push({
|
currentDayFood.push({
|
||||||
amount,
|
amount,
|
||||||
name: parsed.cleanName,
|
name: parsed.cleanName,
|
||||||
price,
|
price,
|
||||||
isSoup: false,
|
isSoup: i === 0, // První řádek je polévka
|
||||||
allergens: parsed.allergens.length > 0 ? parsed.allergens : undefined,
|
allergens: parsed.allergens.length > 0 ? parsed.allergens : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -351,6 +324,11 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal
|
|||||||
const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
|
const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
|
||||||
price = `${split.slice(1)[0]}\xA0Kč`
|
price = `${split.slice(1)[0]}\xA0Kč`
|
||||||
nameRaw = split[0].replace('•', '');
|
nameRaw = split[0].replace('•', '');
|
||||||
|
} else if (text.toLowerCase().endsWith(',-')) {
|
||||||
|
const tmp = text.replace('\xA0', ' ').split(' ');
|
||||||
|
const split = [tmp.slice(0, -1).join(' ')].concat(tmp.slice(-1));
|
||||||
|
price = `${split.slice(1)[0].replace(',-', '')}\xA0Kč`
|
||||||
|
nameRaw = split[0].replace('•', '');
|
||||||
}
|
}
|
||||||
if (nameRaw.endsWith('–')) {
|
if (nameRaw.endsWith('–')) {
|
||||||
nameRaw = nameRaw.slice(0, -1).trim();
|
nameRaw = nameRaw.slice(0, -1).trim();
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import express, { Request } from "express";
|
|
||||||
import { addChoice, getData, removeChoices } from "../service";
|
|
||||||
import { ClientData, LunchChoice } from "../../../types";
|
|
||||||
|
|
||||||
const NAMES = ["alice", "bob", "carol", "dave", "eve", "frank", "grace", "heidi", "ivan", "judy"];
|
|
||||||
const DATES = ["2025-01-06", "2025-01-07", "2025-01-08", "2025-01-09", "2025-01-10"];
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.get("/createUsers", async (req: Request<{}, any, any>, res) => {
|
|
||||||
for (const element of NAMES) {
|
|
||||||
for (const dateStr of DATES) {
|
|
||||||
// Se šancí 50 % přidat pro tohoto uživatele tento den náhodnou volbu
|
|
||||||
if (Math.random() > 0.5) {
|
|
||||||
const foodIndex = Math.floor(Math.random() * 3); // Předpokládáme, že jsou 3 možnosti jídla
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
// Náhodná volba z LunchChoice
|
|
||||||
const lunchChoices = [
|
|
||||||
"SLADOVNICKA",
|
|
||||||
"TECHTOWER",
|
|
||||||
"ZASTAVKAUMICHALA",
|
|
||||||
"SENKSERIKOVA",
|
|
||||||
];
|
|
||||||
const randomLunchChoice = lunchChoices[Math.floor(Math.random() * lunchChoices.length)];
|
|
||||||
await addChoice(element, true, randomLunchChoice as LunchChoice, foodIndex, date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.status(200).json({});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/clearUsers", async (req: Request<{}, any, any>, res) => {
|
|
||||||
for (const dateStr of DATES) {
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
const data: ClientData = await getData(date);
|
|
||||||
for (const user of NAMES) {
|
|
||||||
for (const locationKey in data.choices) {
|
|
||||||
await removeChoices(user, true, locationKey as keyof ClientData["choices"], date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.status(200).json({});
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import express, { Request, Response } from "express";
|
|
||||||
import { getLogin } from "../auth";
|
|
||||||
import { parseToken } from "../utils";
|
|
||||||
import { GenerateQrData } from "../../../types";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.post("/generate", async (req: Request<{}, any, GenerateQrData["body"]>, res: Response<any>) => {
|
|
||||||
getLogin(parseToken(req));
|
|
||||||
console.log("Bank account for QR codes:", req.body.bankAccount);
|
|
||||||
console.log("Bank account holder for QR codes:", req.body.bankAccountHolder);
|
|
||||||
console.log("Requested QR codes for users:", req.body.qrCodes);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
+10
-3
@@ -1,4 +1,4 @@
|
|||||||
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getIsWeekend, getWeekNumber } from "./utils";
|
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getIsWeekend, getWeekNumber } from "./utils";
|
||||||
import getStorage from "./storage";
|
import getStorage from "./storage";
|
||||||
import { getMenuSladovnicka, getMenuTechTower, getMenuZastavkaUmichala, getMenuSenkSerikova } from "./restaurants";
|
import { getMenuSladovnicka, getMenuTechTower, getMenuZastavkaUmichala, getMenuSenkSerikova } from "./restaurants";
|
||||||
import { getTodayMock } from "./mock";
|
import { getTodayMock } from "./mock";
|
||||||
@@ -31,7 +31,10 @@ export const getDateForWeekIndex = (index: number) => {
|
|||||||
function getEmptyData(date?: Date): ClientData {
|
function getEmptyData(date?: Date): ClientData {
|
||||||
const usedDate = date || getToday();
|
const usedDate = date || getToday();
|
||||||
return {
|
return {
|
||||||
date: usedDate.toISOString().split('T')[0],
|
todayDayIndex: getDayOfWeekIndex(getToday()),
|
||||||
|
date: getHumanDate(usedDate),
|
||||||
|
isWeekend: getIsWeekend(usedDate),
|
||||||
|
dayIndex: getDayOfWeekIndex(usedDate),
|
||||||
choices: {},
|
choices: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -483,5 +486,9 @@ export async function updateDepartureTime(login: string, time?: string, date?: D
|
|||||||
export async function getClientData(date?: Date): Promise<ClientData> {
|
export async function getClientData(date?: Date): Promise<ClientData> {
|
||||||
const targetDate = date ?? getToday();
|
const targetDate = date ?? getToday();
|
||||||
const dateString = formatDate(targetDate);
|
const dateString = formatDate(targetDate);
|
||||||
return await storage.getData<ClientData>(dateString) || getEmptyData(date);
|
const clientData = await storage.getData<ClientData>(dateString) || getEmptyData(date);
|
||||||
|
return {
|
||||||
|
...clientData,
|
||||||
|
todayDayIndex: getDayOfWeekIndex(getToday()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,10 +65,6 @@ paths:
|
|||||||
/voting/updateVote:
|
/voting/updateVote:
|
||||||
$ref: "./paths/voting/updateVote.yml"
|
$ref: "./paths/voting/updateVote.yml"
|
||||||
|
|
||||||
# QR kódy (/api/qr)
|
|
||||||
/qr/generate:
|
|
||||||
$ref: "./paths/qr/generateQr.yml"
|
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
$ref: "./schemas/_index.yml"
|
$ref: "./schemas/_index.yml"
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
post:
|
|
||||||
operationId: generateQr
|
|
||||||
summary: Generování QR kódů.
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "../../schemas/_index.yml#/GenerateQrCodesRequest"
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: QR kódy byly úspěšně vygenerovány.
|
|
||||||
+12
-39
@@ -21,13 +21,23 @@ ClientData:
|
|||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
required:
|
required:
|
||||||
|
- todayDayIndex
|
||||||
- date
|
- date
|
||||||
|
- isWeekend
|
||||||
- choices
|
- choices
|
||||||
properties:
|
properties:
|
||||||
|
todayDayIndex:
|
||||||
|
description: Index dnešního dne v týdnu
|
||||||
|
$ref: "#/DayIndex"
|
||||||
date:
|
date:
|
||||||
description: Datum konkrétního dne
|
description: Human-readable datum dne
|
||||||
type: string
|
type: string
|
||||||
format: date
|
isWeekend:
|
||||||
|
description: Příznak, zda je tento den víkend
|
||||||
|
type: boolean
|
||||||
|
dayIndex:
|
||||||
|
description: Index dne v týdnu, ke kterému se vztahují tato data
|
||||||
|
$ref: "#/DayIndex"
|
||||||
choices:
|
choices:
|
||||||
$ref: "#/LunchChoices"
|
$ref: "#/LunchChoices"
|
||||||
menus:
|
menus:
|
||||||
@@ -469,43 +479,6 @@ PizzaDay:
|
|||||||
items:
|
items:
|
||||||
$ref: "#/PizzaOrder"
|
$ref: "#/PizzaOrder"
|
||||||
|
|
||||||
# --- QR KÓDY ---
|
|
||||||
QrCodeRequest:
|
|
||||||
description: Data potřebná pro vygenerování jednoho QR kódu pro platbu
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- login
|
|
||||||
- note
|
|
||||||
- amount
|
|
||||||
properties:
|
|
||||||
login:
|
|
||||||
description: Přihlašovací jméno uživatele, pro kterého bude QR kód vygenerován
|
|
||||||
type: string
|
|
||||||
note:
|
|
||||||
description: Popis platby
|
|
||||||
type: string
|
|
||||||
amount:
|
|
||||||
description: Částka platby v Kč
|
|
||||||
type: number
|
|
||||||
GenerateQrCodesRequest:
|
|
||||||
description: Data potřebná pro vygenerování QR kódů pro platbu
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- bankAccount
|
|
||||||
- bankAccountHolder
|
|
||||||
properties:
|
|
||||||
bankAccount:
|
|
||||||
description: Číslo bankovního účtu objednávajícího
|
|
||||||
type: string
|
|
||||||
bankAccountHolder:
|
|
||||||
description: Jméno majitele bankovního účtu
|
|
||||||
type: string
|
|
||||||
qrCodes:
|
|
||||||
description: Pole požadavků na vygenerování QR kódů
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/QrCodeRequest"
|
|
||||||
|
|
||||||
# --- NOTIFIKACE ---
|
# --- NOTIFIKACE ---
|
||||||
UdalostEnum:
|
UdalostEnum:
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
Reference in New Issue
Block a user