feat: Základ generování QR kódů
This commit is contained in:
parent
0179afca75
commit
2e8774900f
@ -452,7 +452,7 @@ function App() {
|
|||||||
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 />
|
<Header dayIndex={dayIndex} />
|
||||||
<div className='wrapper'>
|
<div className='wrapper'>
|
||||||
{isTodayWeekend ? <h4>Užívejte víkend :)</h4> : <>
|
{isTodayWeekend ? <h4>Užívejte víkend :)</h4> : <>
|
||||||
<Alert variant={'primary'}>
|
<Alert variant={'primary'}>
|
||||||
|
|||||||
@ -6,11 +6,16 @@ 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";
|
||||||
|
|
||||||
export default function Header() {
|
type Props = {
|
||||||
|
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();
|
||||||
@ -18,6 +23,7 @@ export default function Header() {
|
|||||||
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(() => {
|
||||||
@ -44,6 +50,10 @@ export default function Header() {
|
|||||||
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) {
|
||||||
@ -121,6 +131,7 @@ export default function Header() {
|
|||||||
<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>
|
||||||
@ -131,6 +142,7 @@ export default function Header() {
|
|||||||
</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>
|
||||||
|
|||||||
176
client/src/components/modals/GenerateQRModal.tsx
Normal file
176
client/src/components/modals/GenerateQRModal.tsx
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
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 />
|
<Header dayIndex={undefined} />
|
||||||
<div className="stats-page">
|
<div className="stats-page">
|
||||||
<h1>Statistiky</h1>
|
<h1>Statistiky</h1>
|
||||||
<div className="week-navigator">
|
<div className="week-navigator">
|
||||||
|
|||||||
@ -13,6 +13,8 @@ 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}`) });
|
||||||
@ -99,6 +101,8 @@ 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) {
|
||||||
@ -143,6 +147,7 @@ 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'));
|
||||||
|
|||||||
@ -517,6 +517,24 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -531,6 +549,24 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -545,6 +581,24 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -559,6 +613,24 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -601,6 +673,18 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -615,6 +699,18 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -629,6 +725,18 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -643,6 +751,18 @@ 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,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -1402,7 +1522,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-10'); // pátek
|
return new Date('2025-01-08'); // středa
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMenuSladovnickaMock = () => {
|
export const getMenuSladovnickaMock = () => {
|
||||||
|
|||||||
45
server/src/routes/debugRoutes.ts
Normal file
45
server/src/routes/debugRoutes.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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;
|
||||||
15
server/src/routes/qrRoutes.ts
Normal file
15
server/src/routes/qrRoutes.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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;
|
||||||
@ -65,6 +65,10 @@ 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"
|
||||||
|
|||||||
12
types/paths/qr/generateQr.yml
Normal file
12
types/paths/qr/generateQr.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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.
|
||||||
@ -469,6 +469,43 @@ 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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user