Refaktor: Nálezy SonarQube
Some checks are pending
ci/woodpecker/push/workflow Pipeline is running

This commit is contained in:
Martin Berka 2025-03-05 21:48:02 +01:00
parent 55fd368663
commit e55ee7c11e
31 changed files with 103 additions and 141 deletions

View File

@ -24,7 +24,7 @@ import { useEasterEgg } from './context/eggs';
import { getImage } from './api/EasterEggApi'; import { getImage } from './api/EasterEggApi';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { STATS_URL } from './AppRoutes'; import { STATS_URL } from './AppRoutes';
import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, LunchChoices, UserLunchChoice, PizzaVariant } from '../../types'; import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant } from '../../types';
const EVENT_CONNECT = "connect" const EVENT_CONNECT = "connect"
@ -41,7 +41,7 @@ const EASTER_EGG_DEFAULT_DURATION = 0.75;
function App() { function App() {
const auth = useAuth(); const auth = useAuth();
const settings = useSettings(); const settings = useSettings();
const [easterEgg, easterEggLoading] = useEasterEgg(auth); const [easterEgg, _] = useEasterEgg(auth);
const [isConnected, setIsConnected] = useState<boolean>(false); const [isConnected, setIsConnected] = useState<boolean>(false);
const [data, setData] = useState<ClientData>(); const [data, setData] = useState<ClientData>();
const [food, setFood] = useState<RestaurantDayMenuMap>(); const [food, setFood] = useState<RestaurantDayMenuMap>();
@ -65,7 +65,7 @@ function App() {
// Načtení dat po přihlášení // Načtení dat po přihlášení
useEffect(() => { useEffect(() => {
if (!auth || !auth.login) { if (!auth?.login) {
return return
} }
getData().then(({ data }) => { getData().then(({ data }) => {
@ -82,7 +82,7 @@ function App() {
// Přenačtení pro zvolený den // Přenačtení pro zvolený den
useEffect(() => { useEffect(() => {
if (!auth || !auth.login) { if (!auth?.login) {
return return
} }
getData(dayIndex).then((data: ClientData) => { getData(dayIndex).then((data: ClientData) => {
@ -96,11 +96,9 @@ function App() {
// Registrace socket eventů // Registrace socket eventů
useEffect(() => { useEffect(() => {
socket.on(EVENT_CONNECT, () => { socket.on(EVENT_CONNECT, () => {
// console.log("Connected!");
setIsConnected(true); setIsConnected(true);
}); });
socket.on(EVENT_DISCONNECT, () => { socket.on(EVENT_DISCONNECT, () => {
// console.log("Disconnected!");
setIsConnected(false); setIsConnected(false);
}); });
socket.on(EVENT_MESSAGE, (newData: ClientData) => { socket.on(EVENT_MESSAGE, (newData: ClientData) => {
@ -119,7 +117,7 @@ function App() {
}, [socket]); }, [socket]);
useEffect(() => { useEffect(() => {
if (!auth || !auth.login) { if (!auth?.login) {
return return
} }
// TODO tohle občas náhodně nezafunguje, nutno přepsat, viz https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780 // TODO tohle občas náhodně nezafunguje, nutno přepsat, viz https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780
@ -277,7 +275,7 @@ function App() {
const handlePizzaChange = async (value: SelectedOptionValue | SelectedOptionValue[]) => { const handlePizzaChange = async (value: SelectedOptionValue | SelectedOptionValue[]) => {
if (auth?.login && data?.pizzaList) { if (auth?.login && data?.pizzaList) {
if (!(typeof value === 'string')) { if (typeof value !== 'string') {
throw Error('Nepodporovaný typ hodnoty'); throw Error('Nepodporovaný typ hodnoty');
} }
const s = value.split('|'); const s = value.split('|');
@ -365,13 +363,13 @@ function App() {
content = <h3>Chyba načtení dat</h3> content = <h3>Chyba načtení dat</h3>
} }
return <Col md={12} lg={3} className='mt-3'> return <Col md={12} lg={3} className='mt-3'>
<h3 style={{ cursor: 'pointer' }} onClick={() => doAddClickFoodChoice(location, undefined)}>{location}</h3> <h3 style={{ cursor: 'pointer' }} onClick={() => doAddClickFoodChoice(location)}>{location}</h3>
{menu?.lastUpdate && <small>Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}</small>} {menu?.lastUpdate && <small>Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}</small>}
{content} {content}
</Col> </Col>
} }
if (!auth || !auth.login) { if (!auth?.login) {
return <Login />; return <Login />;
} }
@ -411,8 +409,8 @@ function App() {
<div className='wrapper'> <div className='wrapper'>
{data.isWeekend ? <h4>Užívejte víkend :)</h4> : <> {data.isWeekend ? <h4>Užívejte víkend :)</h4> : <>
<Alert variant={'primary'}> <Alert variant={'primary'}>
<img 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 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>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>
@ -429,7 +427,6 @@ function App() {
<Row className='food-tables'> <Row className='food-tables'>
{/* TODO zjednodušit, stačí iterovat klíče typu Restaurant */} {/* TODO zjednodušit, stačí iterovat klíče typu Restaurant */}
{food['SLADOVNICKA'] && renderFoodTable('SLADOVNICKA', food['SLADOVNICKA'])} {food['SLADOVNICKA'] && renderFoodTable('SLADOVNICKA', food['SLADOVNICKA'])}
{/* {food['UMOTLIKU'] && renderFoodTable('UMOTLIKU', food['UMOTLIKU'])} */}
{food['TECHTOWER'] && renderFoodTable('TECHTOWER', food['TECHTOWER'])} {food['TECHTOWER'] && renderFoodTable('TECHTOWER', food['TECHTOWER'])}
{food['ZASTAVKAUMICHALA'] && renderFoodTable('ZASTAVKAUMICHALA', food['ZASTAVKAUMICHALA'])} {food['ZASTAVKAUMICHALA'] && renderFoodTable('ZASTAVKAUMICHALA', food['ZASTAVKAUMICHALA'])}
{food['SENKSERIKOVA'] && renderFoodTable('SENKSERIKOVA', food['SENKSERIKOVA'])} {food['SENKSERIKOVA'] && renderFoodTable('SENKSERIKOVA', food['SENKSERIKOVA'])}
@ -452,7 +449,7 @@ function App() {
<p style={{ marginTop: "10px" }}>Na co dobrého? <small>(nepovinné)</small></p> <p style={{ marginTop: "10px" }}>Na co dobrého? <small>(nepovinné)</small></p>
<Form.Select ref={foodChoiceRef} onChange={doAddFoodChoice}> <Form.Select ref={foodChoiceRef} onChange={doAddFoodChoice}>
<option></option> <option></option>
{foodChoiceList.map((food, index) => <option key={index} value={index}>{food.name}</option>)} {foodChoiceList.map((food, index) => <option key={food.name} value={index}>{food.name}</option>)}
</Form.Select> </Form.Select>
</>} </>}
{foodChoiceList && !closed && <> {foodChoiceList && !closed && <>
@ -491,7 +488,7 @@ function App() {
const userPayload = entry[1]; const userPayload = entry[1];
const userChoices = userPayload?.selectedFoods; const userChoices = userPayload?.selectedFoods;
const trusted = userPayload?.trusted || false; const trusted = userPayload?.trusted || false;
return <tr key={index}> return <tr key={entry[0]}>
<td> <td>
{trusted && <span className='trusted-icon'> {trusted && <span className='trusted-icon'>
<FontAwesomeIcon title='Uživatel ověřený doménovým přihlášením' icon={faCircleCheck} style={{ cursor: "help" }} /> <FontAwesomeIcon title='Uživatel ověřený doménovým přihlášením' icon={faCircleCheck} style={{ cursor: "help" }} />
@ -587,9 +584,6 @@ function App() {
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => { <Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
await unlockPizzaDay(); await unlockPizzaDay();
}}>Odemknout</Button> }}>Odemknout</Button>
{/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => {
await addToCart();
}}>Přidat vše do košíku</Button> */}
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => { <Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => {
await finishOrder(); await finishOrder();
}}>Objednáno</Button> }}>Objednáno</Button>

View File

@ -25,9 +25,8 @@ export default function Login() {
}, [auth]); }, [auth]);
const doLogin = useCallback(async () => { const doLogin = useCallback(async () => {
const length = loginRef?.current?.value && loginRef?.current?.value.length && loginRef.current.value.replace(/\s/g, '').length const length = loginRef?.current?.value.length && loginRef.current.value.replace(/\s/g, '').length
if (length) { if (length) {
// TODO odchytávat cokoliv mimo 200
const token = await login(loginRef.current?.value); const token = await login(loginRef.current?.value);
if (token) { if (token) {
auth?.setToken(token); auth?.setToken(token);
@ -35,7 +34,7 @@ export default function Login() {
} }
}, [auth]); }, [auth]);
if (!auth || !auth.login) { if (!auth?.login) {
return <div className='login'> return <div className='login'>
<h1>Luncher</h1> <h1>Luncher</h1>
<h4 style={{ marginBottom: "50px" }}>Aplikace pro profesionální management obědů</h4> <h4 style={{ marginBottom: "50px" }}>Aplikace pro profesionální management obědů</h4>

View File

@ -93,7 +93,7 @@ export function formatDate(date: Date, format?: string) {
let month = String(date.getMonth() + 1).padStart(2, "0"); let month = String(date.getMonth() + 1).padStart(2, "0");
let year = String(date.getFullYear()); let year = String(date.getFullYear());
const f = (format === undefined) ? 'YYYY-MM-DD' : format; const f = format ?? 'YYYY-MM-DD';
return f.replace('DD', day).replace('MM', month).replace('YYYY', year); return f.replace('DD', day).replace('MM', month).replace('YYYY', year);
} }

View File

@ -1,5 +1,4 @@
import { AddChoiceRequest, ChangeDepartureTimeRequest, RemoveChoiceRequest, RemoveChoicesRequest, UpdateNoteRequest } from "../../../types"; import { AddChoiceRequest, ChangeDepartureTimeRequest, LunchChoice, RemoveChoiceRequest, RemoveChoicesRequest, UpdateNoteRequest } from "../../../types";
import { LunchChoice } from "../../../types";
import { api } from "./Api"; import { api } from "./Api";
const FOOD_API_PREFIX = '/api/food'; const FOOD_API_PREFIX = '/api/food';

View File

@ -1,5 +1,4 @@
import { AddPizzaRequest, FinishDeliveryRequest, RemovePizzaRequest, UpdatePizzaDayNoteRequest, UpdatePizzaFeeRequest } from "../../../types"; import { AddPizzaRequest, FinishDeliveryRequest, PizzaVariant, RemovePizzaRequest, UpdatePizzaDayNoteRequest, UpdatePizzaFeeRequest } from "../../../types";
import { PizzaVariant } from "../../../types";
import { api } from "./Api"; import { api } from "./Api";
const PIZZADAY_API_PREFIX = '/api/pizzaDay'; const PIZZADAY_API_PREFIX = '/api/pizzaDay';

View File

@ -1,5 +1,4 @@
import { UpdateFeatureVoteRequest } from "../../../types"; import { FeatureRequest, UpdateFeatureVoteRequest } from "../../../types";
import { FeatureRequest } from "../../../types";
import { api } from "./Api"; import { api } from "./Api";
const VOTING_API_PREFIX = '/api/voting'; const VOTING_API_PREFIX = '/api/voting';

View File

@ -78,7 +78,7 @@ export default function Header() {
cislo = cislo.padStart(16, '0'); cislo = cislo.padStart(16, '0');
} }
let sum = 0; let sum = 0;
for (var i = 0; i < cislo.length; i++) { for (let i = 0; i < cislo.length; i++) {
const char = cislo.charAt(i); const char = cislo.charAt(i);
const order = (cislo.length - 1) - i; const order = (cislo.length - 1) - i;
const weight = (2 ** order) % 11; const weight = (2 ** order) % 11;

View File

@ -2,15 +2,15 @@ import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
type Props = { type Props = {
title?: String, title?: string,
icon: IconDefinition, icon: IconDefinition,
description: String, description: string,
animation?: String, animation?: string,
} }
function Loader(props: Props) { function Loader(props: Readonly<Props>) {
return <div className='loader'> return <div className='loader'>
<h1>{props.title || 'Prosím čekejte...'}</h1> <h1>{props.title ?? 'Prosím čekejte...'}</h1>
<FontAwesomeIcon icon={props.icon} className={`loader-icon mb-3 ` + (props.animation ?? '')} /> <FontAwesomeIcon icon={props.icon} className={`loader-icon mb-3 ` + (props.animation ?? '')} />
<p>{props.description}</p> <p>{props.description}</p>
</div> </div>

View File

@ -10,7 +10,7 @@ type Props = {
creator: string, creator: string,
} }
export default function PizzaOrderList({ state, orders, onDelete, creator }: Props) { export default function PizzaOrderList({ state, orders, onDelete, creator }: Readonly<Props>) {
const saveFees = async (customer: string, text?: string, price?: number) => { const saveFees = async (customer: string, text?: string, price?: number) => {
await updatePizzaFee(customer, text, price); await updatePizzaFee(customer, text, price);
} }
@ -21,26 +21,24 @@ export default function PizzaOrderList({ state, orders, onDelete, creator }: Pro
const total = orders.reduce((total, order) => total + order.totalPrice, 0); const total = orders.reduce((total, order) => total + order.totalPrice, 0);
return <> return <Table className="mt-3" striped bordered hover>
<Table className="mt-3" striped bordered hover> <thead>
<thead> <tr>
<tr> <th>Jméno</th>
<th>Jméno</th> <th>Objednávka</th>
<th>Objednávka</th> <th>Poznámka</th>
<th>Poznámka</th> <th>Příplatek</th>
<th>Příplatek</th> <th>Cena</th>
<th>Cena</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {orders.map(order => <tr key={order.customer}>
{orders.map(order => <tr key={order.customer}> <PizzaOrderRow creator={creator} state={state} order={order} onDelete={onDelete} onFeeModalSave={saveFees} />
<PizzaOrderRow creator={creator} state={state} order={order} onDelete={onDelete} onFeeModalSave={saveFees} /> </tr>)}
</tr>)} <tr style={{ fontWeight: 'bold' }}>
<tr style={{ fontWeight: 'bold' }}> <td colSpan={4}>Celkem</td>
<td colSpan={4}>Celkem</td> <td>{`${total}`}</td>
<td>{`${total}`}</td> </tr>
</tr> </tbody>
</tbody> </Table>
</Table>
</>
} }

View File

@ -13,19 +13,19 @@ type Props = {
onFeeModalSave: (customer: string, name?: string, price?: number) => void, onFeeModalSave: (customer: string, name?: string, price?: number) => void,
} }
export default function PizzaOrderRow({ creator, order, state, onDelete, onFeeModalSave }: Props) { export default function PizzaOrderRow({ creator, order, state, onDelete, onFeeModalSave }: Readonly<Props>) {
const auth = useAuth(); const auth = useAuth();
const [isFeeModalOpen, setFeeModalOpen] = useState<boolean>(false); const [isFeeModalOpen, setIsFeeModalOpen] = useState<boolean>(false);
const saveFees = (customer: string, text?: string, price?: number) => { const saveFees = (customer: string, text?: string, price?: number) => {
onFeeModalSave(customer, text, price); onFeeModalSave(customer, text, price);
setFeeModalOpen(false); setIsFeeModalOpen(false);
} }
return <> return <>
<td>{order.customer}</td> <td>{order.customer}</td>
<td>{order.pizzaList!.map<React.ReactNode>((pizzaOrder, index) => <td>{order.pizzaList!.map<React.ReactNode>(pizzaOrder =>
<span key={index}> <span key={pizzaOrder.name}>
{`${pizzaOrder.name}, ${pizzaOrder.size} (${pizzaOrder.price} Kč)`} {`${pizzaOrder.name}, ${pizzaOrder.size} (${pizzaOrder.price} Kč)`}
{auth?.login === order.customer && state === PizzaDayState.CREATED && {auth?.login === order.customer && state === PizzaDayState.CREATED &&
<FontAwesomeIcon onClick={() => { <FontAwesomeIcon onClick={() => {
@ -35,11 +35,11 @@ export default function PizzaOrderRow({ creator, order, state, onDelete, onFeeMo
</span>) </span>)
.reduce((prev, curr, index) => [prev, <br key={`br-${index}`} />, curr])} .reduce((prev, curr, index) => [prev, <br key={`br-${index}`} />, curr])}
</td> </td>
<td style={{ maxWidth: "200px" }}>{order.note || '-'}</td> <td style={{ maxWidth: "200px" }}>{order.note ?? '-'}</td>
<td style={{ maxWidth: "200px" }}>{order.fee?.price ? `${order.fee.price}${order.fee.text ? ` (${order.fee.text})` : ''}` : '-'}</td> <td style={{ maxWidth: "200px" }}>{order.fee?.price ? `${order.fee.price}${order.fee.text ? ` (${order.fee.text})` : ''}` : '-'}</td>
<td> <td>
{order.totalPrice} {auth?.login === creator && state === PizzaDayState.CREATED && <FontAwesomeIcon onClick={() => { setFeeModalOpen(true) }} title='Nastavit příplatek' className='action-icon' icon={faMoneyBill1} />} {order.totalPrice} {auth?.login === creator && state === PizzaDayState.CREATED && <FontAwesomeIcon onClick={() => { setIsFeeModalOpen(true) }} title='Nastavit příplatek' className='action-icon' icon={faMoneyBill1} />}
</td> </td>
<PizzaAdditionalFeeModal customerName={order.customer} isOpen={isFeeModalOpen} onClose={() => setFeeModalOpen(false)} onSave={saveFees} initialValues={{ text: order.fee?.text, price: order.fee?.price?.toString() }} /> <PizzaAdditionalFeeModal customerName={order.customer} isOpen={isFeeModalOpen} onClose={() => setIsFeeModalOpen(false)} onSave={saveFees} initialValues={{ text: order.fee?.text, price: order.fee?.price?.toString() }} />
</> </>
} }

View File

@ -9,7 +9,7 @@ type Props = {
} }
/** Modální dialog pro hlasování o nových funkcích. */ /** Modální dialog pro hlasování o nových funkcích. */
export default function FeaturesVotingModal({ isOpen, onClose, onChange, initialValues }: Props) { export default function FeaturesVotingModal({ isOpen, onClose, onChange, initialValues }: Readonly<Props>) {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.currentTarget.value as FeatureRequest, e.currentTarget.checked); onChange(e.currentTarget.value as FeatureRequest, e.currentTarget.checked);
@ -31,7 +31,7 @@ export default function FeaturesVotingModal({ isOpen, onClose, onChange, initial
label={FeatureRequest[key]} label={FeatureRequest[key]}
onChange={handleChange} onChange={handleChange}
value={key} value={key}
defaultChecked={initialValues && initialValues.includes(key as FeatureRequest)} defaultChecked={initialValues?.includes(key as FeatureRequest)}
/> />
})} })}
<p className="mt-3" style={{ fontSize: '12px' }}>Něco jiného? Dejte vědět.</p> <p className="mt-3" style={{ fontSize: '12px' }}>Něco jiného? Dejte vědět.</p>

View File

@ -8,7 +8,7 @@ type Props = {
} }
/** Modální dialog pro úpravu obecné poznámky. */ /** Modální dialog pro úpravu obecné poznámky. */
export default function NoteModal({ isOpen, onClose, onSave }: Props) { export default function NoteModal({ isOpen, onClose, onSave }: Readonly<Props>) {
const note = useRef<HTMLInputElement>(null); const note = useRef<HTMLInputElement>(null);
const save = () => { const save = () => {

View File

@ -10,17 +10,17 @@ type Props = {
} }
/** Modální dialog pro nastavení příplatků za pizzu. */ /** Modální dialog pro nastavení příplatků za pizzu. */
export default function PizzaAdditionalFeeModal({ customerName, isOpen, onClose, onSave, initialValues }: Props) { export default function PizzaAdditionalFeeModal({ customerName, isOpen, onClose, onSave, initialValues }: Readonly<Props>) {
const textRef = useRef<HTMLInputElement>(null); const textRef = useRef<HTMLInputElement>(null);
const priceRef = useRef<HTMLInputElement>(null); const priceRef = useRef<HTMLInputElement>(null);
const doSubmit = () => { const doSubmit = () => {
onSave(customerName, textRef.current?.value, parseInt(priceRef.current?.value || "0")); onSave(customerName, textRef.current?.value, parseInt(priceRef.current?.value ?? "0"));
} }
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
onSave(customerName, textRef.current?.value, parseInt(priceRef.current?.value || "0")); onSave(customerName, textRef.current?.value, parseInt(priceRef.current?.value ?? "0"));
} }
} }

View File

@ -23,7 +23,7 @@ type Result = {
} }
/** Modální dialog pro výpočet výhodnosti pizzy. */ /** Modální dialog pro výpočet výhodnosti pizzy. */
export default function PizzaCalculatorModal({ isOpen, onClose }: Props) { export default function PizzaCalculatorModal({ isOpen, onClose }: Readonly<Props>) {
const diameter1Ref = useRef<HTMLInputElement>(null); const diameter1Ref = useRef<HTMLInputElement>(null);
const price1Ref = useRef<HTMLInputElement>(null); const price1Ref = useRef<HTMLInputElement>(null);
const diameter2Ref = useRef<HTMLInputElement>(null); const diameter2Ref = useRef<HTMLInputElement>(null);

View File

@ -9,7 +9,7 @@ type Props = {
} }
/** Modální dialog pro uživatelská nastavení. */ /** Modální dialog pro uživatelská nastavení. */
export default function SettingsModal({ isOpen, onClose, onSave }: Props) { export default function SettingsModal({ isOpen, onClose, onSave }: Readonly<Props>) {
const settings = useSettings(); const settings = useSettings();
const bankAccountRef = useRef<HTMLInputElement>(null); const bankAccountRef = useRef<HTMLInputElement>(null);
const nameRef = useRef<HTMLInputElement>(null); const nameRef = useRef<HTMLInputElement>(null);

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useContext, useState } from "react" import React, { ReactNode, useContext, useEffect, useState } from "react"
import { useEffect } from "react"
import { useJwt } from "react-jwt"; import { useJwt } from "react-jwt";
import { deleteToken, getToken, storeToken } from "../Utils"; import { deleteToken, getToken, storeToken } from "../Utils";
@ -16,7 +15,7 @@ type ContextProps = {
const authContext = React.createContext<AuthContextProps | null>(null); const authContext = React.createContext<AuthContextProps | null>(null);
export function ProvideAuth(props: ContextProps) { export function ProvideAuth(props: Readonly<ContextProps>) {
const auth = useProvideAuth(); const auth = useProvideAuth();
return <authContext.Provider value={auth}>{props.children}</authContext.Provider> return <authContext.Provider value={auth}>{props.children}</authContext.Provider>
} }
@ -29,7 +28,7 @@ function useProvideAuth(): AuthContextProps {
const [loginName, setLoginName] = useState<string | undefined>(); const [loginName, setLoginName] = useState<string | undefined>();
const [trusted, setTrusted] = useState<boolean | undefined>(); const [trusted, setTrusted] = useState<boolean | undefined>();
const [token, setToken] = useState<string | undefined>(getToken()); const [token, setToken] = useState<string | undefined>(getToken());
const { decodedToken } = useJwt(token || ''); const { decodedToken } = useJwt(token ?? '');
useEffect(() => { useEffect(() => {
if (token && token.length > 0) { if (token && token.length > 0) {

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useContext, useState } from "react" import React, { ReactNode, useContext, useEffect, useState } from "react"
import { useEffect } from "react"
const BANK_ACCOUNT_NUMBER_KEY = 'bank_account_number'; const BANK_ACCOUNT_NUMBER_KEY = 'bank_account_number';
const BANK_ACCOUNT_HOLDER_KEY = 'bank_account_holder_name'; const BANK_ACCOUNT_HOLDER_KEY = 'bank_account_holder_name';
@ -20,7 +19,7 @@ type ContextProps = {
const settingsContext = React.createContext<SettingsContextProps | null>(null); const settingsContext = React.createContext<SettingsContextProps | null>(null);
export function ProvideSettings(props: ContextProps) { export function ProvideSettings(props: Readonly<ContextProps>) {
const settings = useProvideSettings(); const settings = useProvideSettings();
return <settingsContext.Provider value={settings}>{props.children}</settingsContext.Provider> return <settingsContext.Provider value={settings}>{props.children}</settingsContext.Provider>
} }
@ -45,7 +44,7 @@ function useProvideSettings(): SettingsContextProps {
} }
const hideSoups = localStorage.getItem(HIDE_SOUPS_KEY); const hideSoups = localStorage.getItem(HIDE_SOUPS_KEY);
if (hideSoups !== null) { if (hideSoups !== null) {
setHideSoups(hideSoups === 'true' ? true : false); setHideSoups(hideSoups === 'true');
} }
}, []) }, [])

View File

@ -18,8 +18,3 @@ export const SocketContext = React.createContext();
export const EVENT_CONNECT = 'connect'; export const EVENT_CONNECT = 'connect';
export const EVENT_DISCONNECT = 'disconnect'; export const EVENT_DISCONNECT = 'disconnect';
export const EVENT_MESSAGE = 'message'; export const EVENT_MESSAGE = 'message';
// export const EVENT_CONFIG = 'config';
// export const EVENT_TOASTER = 'toaster';
// export const EVENT_VOTING = 'voting';
// export const EVENT_VOTE_CONFIG = 'voteSettings';
// export const EVENT_ADMIN = 'admin';

View File

@ -49,16 +49,16 @@ export async function downloadPizzy(mock: boolean): Promise<Pizza[]> {
const $ = load(html); const $ = load(html);
const links = $('.vypisproduktu > div > h4 > a'); const links = $('.vypisproduktu > div > h4 > a');
const urls = []; const urls = [];
for (let i = 0; i < links.length; i++) { for (const element of links) {
if (links[i].name === 'a' && links[i].attribs?.href) { if (element.name === 'a' && element.attribs?.href) {
const pizzaUrl = links[i].attribs?.href; const pizzaUrl = element.attribs?.href;
urls.push(buildPizzaUrl(pizzaUrl)); urls.push(buildPizzaUrl(pizzaUrl));
} }
} }
// Scrapneme jednotlivé pizzy // Scrapneme jednotlivé pizzy
const result: Pizza[] = []; const result: Pizza[] = [];
for (let i = 0; i < urls.length; i++) { for (const element of urls) {
const pizzaUrl = urls[i]; const pizzaUrl = element;
const pizzaHtml = await axios.get(pizzaUrl).then(res => res.data); const pizzaHtml = await axios.get(pizzaUrl).then(res => res.data);
// Název // Název
const name = $('.produkt > h2', pizzaHtml).first().text() const name = $('.produkt > h2', pizzaHtml).first().text()

View File

@ -14,7 +14,7 @@ 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";
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}`) });
// Validace nastavení JWT tokenu - nemá bez něj smysl vůbec povolit server spustit // Validace nastavení JWT tokenu - nemá bez něj smysl vůbec povolit server spustit
@ -35,7 +35,7 @@ app.use(cors({
// Zapínatelný login přes hlavičky - pokud je zapnutý nepovolí "basicauth" // Zapínatelný login přes hlavičky - pokud je zapnutý nepovolí "basicauth"
const HTTP_REMOTE_USER_ENABLED = process.env.HTTP_REMOTE_USER_ENABLED === 'true' || false; const HTTP_REMOTE_USER_ENABLED = process.env.HTTP_REMOTE_USER_ENABLED === 'true' || false;
const HTTP_REMOTE_USER_HEADER_NAME = process.env.HTTP_REMOTE_USER_HEADER_NAME || 'remote-user'; const HTTP_REMOTE_USER_HEADER_NAME = process.env.HTTP_REMOTE_USER_HEADER_NAME ?? 'remote-user';
if (HTTP_REMOTE_USER_ENABLED) { if (HTTP_REMOTE_USER_ENABLED) {
if (!process.env.HTTP_REMOTE_TRUSTED_IPS) { if (!process.env.HTTP_REMOTE_TRUSTED_IPS) {
throw new Error('Je zapnutý login z hlaviček, ale není nastaven rozsah adres ze kterých hlavička může přijít.'); throw new Error('Je zapnutý login z hlaviček, ale není nastaven rozsah adres ze kterých hlavička může přijít.');
@ -79,7 +79,6 @@ app.post("/api/login", (req, res) => {
// TODO dočasné řešení - QR se zobrazuje přes <img>, nemáme sem jak dostat token // TODO dočasné řešení - QR se zobrazuje přes <img>, nemáme sem jak dostat token
app.get("/api/qr", (req, res) => { app.get("/api/qr", (req, res) => {
// const login = getLogin(parseToken(req));
if (!req.query?.login) { if (!req.query?.login) {
throw Error("Nebyl předán login"); throw Error("Nebyl předán login");
} }
@ -148,8 +147,8 @@ app.use((err: any, req: any, res: any, next: any) => {
next(); next();
}); });
const PORT = process.env.PORT || 3001; const PORT = process.env.PORT ?? 3001;
const HOST = process.env.HOST || '0.0.0.0'; const HOST = process.env.HOST ?? '0.0.0.0';
server.listen(PORT, () => { server.listen(PORT, () => {
console.log(`Server listening on ${HOST}, port ${PORT}`); console.log(`Server listening on ${HOST}, port ${PORT}`);

View File

@ -1,14 +1,11 @@
/** Notifikace */
import axios from 'axios'; import axios from 'axios';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import path from 'path'; import path from 'path';
import { getClientData, getToday } from "./service"; import { getClientData, getToday } from "./service";
import { getUsersByLocation, getHumanTime } from "./utils"; import { getUsersByLocation, getHumanTime } from "./utils";
import getStorage from "./storage";
import { NotifikaceData, NotifikaceInput } from '../../types'; import { NotifikaceData, NotifikaceInput } from '../../types';
const storage = getStorage(); 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}`) });
// const gotifyDataRaw = process.env.GOTIFY_SERVERS_AND_KEYS || "{}"; // const gotifyDataRaw = process.env.GOTIFY_SERVERS_AND_KEYS || "{}";

View File

@ -128,11 +128,11 @@ export async function removePizzaOrder(login: string, pizzaOrder: PizzaVariant)
if (!clientData.pizzaDay) { if (!clientData.pizzaDay) {
throw Error("Pizza day pro dnešní den neexistuje"); throw Error("Pizza day pro dnešní den neexistuje");
} }
const orderIndex = clientData.pizzaDay!.orders!.findIndex(o => o.customer === login); const orderIndex = clientData.pizzaDay.orders!.findIndex(o => o.customer === login);
if (orderIndex < 0) { if (orderIndex < 0) {
throw Error("Nebyly nalezeny žádné objednávky pro uživatele " + login); throw Error("Nebyly nalezeny žádné objednávky pro uživatele " + login);
} }
const order = clientData.pizzaDay!.orders![orderIndex]; const order = clientData.pizzaDay.orders![orderIndex];
const index = order.pizzaList!.findIndex(o => o.name === pizzaOrder.name && o.size === pizzaOrder.size); const index = order.pizzaList!.findIndex(o => o.name === pizzaOrder.name && o.size === pizzaOrder.size);
if (index < 0) { if (index < 0) {
throw Error("Objednávka s danými parametry nebyla nalezena"); throw Error("Objednávka s danými parametry nebyla nalezena");
@ -308,7 +308,7 @@ export async function updatePizzaFee(login: string, targetLogin: string, text?:
targetOrder.fee = { text, price }; targetOrder.fee = { text, price };
} }
// Přepočet ceny // Přepočet ceny
targetOrder.totalPrice = targetOrder.pizzaList.reduce((price, pizzaOrder) => price + pizzaOrder.price, 0) + (targetOrder.fee?.price || 0); targetOrder.totalPrice = targetOrder.pizzaList.reduce((price, pizzaOrder) => price + pizzaOrder.price, 0) + (targetOrder.fee?.price ?? 0);
await storage.setData(today, clientData); await storage.setData(today, clientData);
return clientData; return clientData;
} }

View File

@ -92,7 +92,6 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
const rowText = $(dayRow).first().text().trim(); const rowText = $(dayRow).first().text().trim();
if (rowText === searchedDayText) { if (rowText === searchedDayText) {
index = i; index = i;
return;
} }
}) })
if (index === undefined) { if (index === undefined) {
@ -360,7 +359,6 @@ export const getMenuZastavkaUmichala = async (firstDayOfWeek: Date, mock: boolea
price: "", price: "",
isSoup: false, isSoup: false,
}]; }];
continue;
} else { } else {
const url = (currentDate.getDate() === nowDate) ? const url = (currentDate.getDate() === nowDate) ?
ZASTAVKAUMICHALA_URL : ZASTAVKAUMICHALA_URL + '/?do=dailyMenu-changeDate&dailyMenu-dateString=' + formatDate(currentDate, 'DD.MM.YYYY'); ZASTAVKAUMICHALA_URL : ZASTAVKAUMICHALA_URL + '/?do=dailyMenu-changeDate&dailyMenu-dateString=' + formatDate(currentDate, 'DD.MM.YYYY');

View File

@ -1,5 +1,5 @@
import express, { NextFunction } from "express"; import express, { NextFunction } from "express";
import { getLogin, getTrusted } from "../auth"; import { getLogin } from "../auth";
import { parseToken } from "../utils"; import { parseToken } from "../utils";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
@ -34,16 +34,11 @@ function generateUrl() {
*/ */
function getEasterEggImage(req: any, res: any, next: NextFunction) { function getEasterEggImage(req: any, res: any, next: NextFunction) {
const login = getLogin(parseToken(req)); const login = getLogin(parseToken(req));
const trusted = getTrusted(parseToken(req));
try { try {
// TODO vrátit! if (login in easterEggs) {
// if (trusted) { const imagePath = easterEggs[login][Math.floor(Math.random() * easterEggs[login].length)].path;
if (true) { res.sendFile(path.join(__dirname, IMAGES_PATH, imagePath));
if (login in easterEggs) { return;
const imagePath = easterEggs[login][Math.floor(Math.random() * easterEggs[login].length)].path;
res.sendFile(path.join(__dirname, IMAGES_PATH, imagePath));
return;
}
} }
res.sendStatus(404); res.sendStatus(404);
} catch (e: any) { next(e) } } catch (e: any) { next(e) }
@ -124,7 +119,7 @@ let easterEggs: EasterEggsJson;
if (fs.existsSync(EASTER_EGGS_JSON_PATH)) { if (fs.existsSync(EASTER_EGGS_JSON_PATH)) {
const content = fs.readFileSync(EASTER_EGGS_JSON_PATH, 'utf-8'); const content = fs.readFileSync(EASTER_EGGS_JSON_PATH, 'utf-8');
easterEggs = JSON.parse(content); easterEggs = JSON.parse(content);
for (const [key, eggs] of Object.entries(easterEggs)) { for (const [_, eggs] of Object.entries(easterEggs)) {
for (const easterEgg of eggs) { for (const easterEgg of eggs) {
const url = generateUrl(); const url = generateUrl();
easterEgg.url = url; easterEgg.url = url;
@ -138,16 +133,11 @@ if (fs.existsSync(EASTER_EGGS_JSON_PATH)) {
// Získání náhodného easter eggu pro přihlášeného uživatele // Získání náhodného easter eggu pro přihlášeného uživatele
router.get("/", async (req, res, next) => { router.get("/", async (req, res, next) => {
const login = getLogin(parseToken(req)); const login = getLogin(parseToken(req));
const trusted = getTrusted(parseToken(req));
try { try {
// TODO vrátit! if (easterEggs && login in easterEggs) {
// if (trusted) { const randomEasterEgg = easterEggs[login][Math.floor(Math.random() * easterEggs[login].length)];
if (true) { const { path, startOffset, endOffset, ...strippedEasterEgg } = randomEasterEgg; // Path klient k ničemu nepotřebuje a nemá ho znát
if (easterEggs && login in easterEggs) { return res.status(200).json({ ...strippedEasterEgg, ...getRandomPosition(startOffset, endOffset) });
const randomEasterEgg = easterEggs[login][Math.floor(Math.random() * easterEggs[login].length)];
const { path, startOffset, endOffset, ...strippedEasterEgg } = randomEasterEgg; // Path klient k ničemu nepotřebuje a nemá ho znát
return res.status(200).json({ ...strippedEasterEgg, ...getRandomPosition(startOffset, endOffset) });
}
} }
return res.status(200).send(); return res.status(200).send();
} catch (e: any) { next(e) } } catch (e: any) { next(e) }

View File

@ -4,8 +4,7 @@ import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices,
import { getDayOfWeekIndex, parseToken } from "../utils"; import { getDayOfWeekIndex, parseToken } from "../utils";
import { getWebsocket } from "../websocket"; import { getWebsocket } from "../websocket";
import { callNotifikace } from "../notifikace"; import { callNotifikace } from "../notifikace";
import { AddChoiceRequest, ChangeDepartureTimeRequest, IDayIndex, RemoveChoiceRequest, RemoveChoicesRequest, UpdateNoteRequest } from "../../../types"; import { AddChoiceRequest, ChangeDepartureTimeRequest, IDayIndex, RemoveChoiceRequest, RemoveChoicesRequest, UdalostEnum, UpdateNoteRequest } from "../../../types";
import { UdalostEnum } from "../../../types";
/** /**
* Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň * Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň

View File

@ -2,8 +2,7 @@ import express, { Request, Response } from "express";
import { getLogin } from "../auth"; import { getLogin } from "../auth";
import { parseToken } from "../utils"; import { parseToken } from "../utils";
import { getUserVotes, updateFeatureVote } from "../voting"; import { getUserVotes, updateFeatureVote } from "../voting";
import { UpdateFeatureVoteRequest } from "../../../types"; import { FeatureRequest, UpdateFeatureVoteRequest } from "../../../types";
import { FeatureRequest } from "../../../types";
const router = express.Router(); const router = express.Router();

View File

@ -1,6 +1,6 @@
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, 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, getMenuUMotliku, getMenuZastavkaUmichala, getMenuSenkSerikova } from "./restaurants"; import { getMenuSladovnicka, getMenuTechTower, getMenuZastavkaUmichala, getMenuSenkSerikova } from "./restaurants";
import { getTodayMock } from "./mock"; import { getTodayMock } from "./mock";
import { ClientData, DepartureTime, LunchChoice, Restaurant, RestaurantDayMenu, WeekMenu } from "../../types"; import { ClientData, DepartureTime, LunchChoice, Restaurant, RestaurantDayMenu, WeekMenu } from "../../types";
@ -152,10 +152,10 @@ export async function getRestaurantMenu(restaurant: keyof typeof Restaurant, dat
weekMenu[i][restaurant]!.closed = true; weekMenu[i][restaurant]!.closed = true;
} }
} }
break;
} catch (e: any) { } catch (e: any) {
console.error("Selhalo načtení jídel pro podnik TechTower", e); console.error("Selhalo načtení jídel pro podnik TechTower", e);
} }
break;
case 'ZASTAVKAUMICHALA': case 'ZASTAVKAUMICHALA':
try { try {
const zastavkaUmichalaFood = await getMenuZastavkaUmichala(firstDay, mock); const zastavkaUmichalaFood = await getMenuZastavkaUmichala(firstDay, mock);
@ -165,10 +165,10 @@ export async function getRestaurantMenu(restaurant: keyof typeof Restaurant, dat
weekMenu[i][restaurant]!.closed = true; weekMenu[i][restaurant]!.closed = true;
} }
} }
break;
} catch (e: any) { } catch (e: any) {
console.error("Selhalo načtení jídel pro podnik Zastávka u Michala", e); console.error("Selhalo načtení jídel pro podnik Zastávka u Michala", e);
} }
break;
case 'SENKSERIKOVA': case 'SENKSERIKOVA':
try { try {
const senkSerikovaFood = await getMenuSenkSerikova(firstDay, mock); const senkSerikovaFood = await getMenuSenkSerikova(firstDay, mock);
@ -178,10 +178,10 @@ export async function getRestaurantMenu(restaurant: keyof typeof Restaurant, dat
weekMenu[i][restaurant]!.closed = true; weekMenu[i][restaurant]!.closed = true;
} }
} }
break;
} catch (e: any) { } catch (e: any) {
console.error("Selhalo načtení jídel pro podnik Pivovarský šenk Šeříková", e); console.error("Selhalo načtení jídel pro podnik Pivovarský šenk Šeříková", e);
} }
break;
} }
await storage.setData(getMenuKey(usedDate), weekMenu); await storage.setData(getMenuKey(usedDate), weekMenu);
} }

View File

@ -2,7 +2,6 @@ import { DailyStats, LunchChoice, WeeklyStats } from "../../types";
import { getStatsMock } from "./mock"; import { getStatsMock } from "./mock";
import { getClientData } from "./service"; import { getClientData } from "./service";
import getStorage from "./storage"; import getStorage from "./storage";
import { formatDate } from "./utils";
const storage = getStorage(); const storage = getStorage();

View File

@ -4,7 +4,7 @@ import { StorageInterface } from "./StorageInterface";
import JsonStorage from "./json"; import JsonStorage from "./json";
import RedisStorage from "./redis"; import RedisStorage from "./redis";
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}`) });
const JSON_KEY = 'json'; const JSON_KEY = 'json';

View File

@ -8,15 +8,15 @@ let client: RedisClientType;
*/ */
export default class RedisStorage implements StorageInterface { export default class RedisStorage implements StorageInterface {
constructor() { constructor() {
const HOST = process.env.REDIS_HOST || 'localhost'; const HOST = process.env.REDIS_HOST ?? 'localhost';
const PORT = process.env.REDIS_PORT || 6379; const PORT = process.env.REDIS_PORT ?? 6379;
client = createClient({ url: `redis://${HOST}:${PORT}` }); client = createClient({ url: `redis://${HOST}:${PORT}` });
client.connect(); client.connect();
} }
async hasData(key: string) { async hasData(key: string) {
const data = await client.json.get(key); const data = await client.json.get(key);
return (data ? true : false); return (!!data);
} }
async getData<Type>(key: string) { async getData<Type>(key: string) {

View File

@ -8,7 +8,7 @@ export function formatDate(date: Date, format?: string) {
let month = String(date.getMonth() + 1).padStart(2, "0"); let month = String(date.getMonth() + 1).padStart(2, "0");
let year = String(date.getFullYear()); let year = String(date.getFullYear());
const f = (format === undefined) ? 'YYYY-MM-DD' : format; const f = format ?? 'YYYY-MM-DD';
return f.replace('DD', day).replace('MM', month).replace('YYYY', year); return f.replace('DD', day).replace('MM', month).replace('YYYY', year);
} }
@ -61,10 +61,10 @@ export function getLastWorkDayOfWeek(date: Date) {
/** Vrátí pořadové číslo týdne předaného data v roce dle ISO 8601. */ /** Vrátí pořadové číslo týdne předaného data v roce dle ISO 8601. */
export function getWeekNumber(inputDate: Date) { export function getWeekNumber(inputDate: Date) {
var date = new Date(inputDate.getTime()); const date = new Date(inputDate.getTime());
date.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0);
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
var week1 = new Date(date.getFullYear(), 0, 4); const week1 = new Date(date.getFullYear(), 0, 4);
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7); return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
} }