4 Commits

Author SHA1 Message Date
Stánek Pavel
f13cd4ffa9 fix: opravy zobrazeni sekce vybranych jidel
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
2026-02-05 10:18:58 +01:00
086646fd1c fix: přidání nových typů do OpenAPI spec pro přežití regenerace
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
Typy PendingQr, NotificationSettings a nové endpointy
(dismissQr, notifications/settings) byly přidány přímo
do YAML specifikace místo ručních úprav generovaných souborů.
2026-02-04 17:34:05 +01:00
b8629afef2 feat: trvalé zobrazení QR kódu do ručního zavření (#31)
QR kódy pro platbu za pizza day jsou nyní zobrazeny persistentně
i po následující dny, dokud uživatel nepotvrdí platbu tlačítkem
"Zaplatil jsem". Nevyřízené QR kódy jsou uloženy per-user v storage
a zobrazeny v sekci "Nevyřízené platby".
2026-02-04 17:34:05 +01:00
d366ac39d4 feat: podpora per-user notifikací s Discord, ntfy a Teams (#39)
Uživatelé mohou v nastavení konfigurovat vlastní webhook URL/topic
pro Discord, MS Teams a ntfy, a zvolit události k odběru.
Notifikace jsou odesílány pouze uživatelům se stejnou zvolenou lokalitou.
2026-02-04 17:33:53 +01:00
8 changed files with 188 additions and 1626 deletions

View File

@@ -628,56 +628,77 @@ input[type="text"], input[type="email"], input[type="password"] {
tbody tr { tbody tr {
transition: var(--luncher-transition); transition: var(--luncher-transition);
border-bottom: 1px solid var(--luncher-border-light);
&:hover { &:hover {
background: var(--luncher-bg-hover); background: var(--luncher-bg-hover);
} }
&:last-child td { &:last-child {
border-bottom: none; border-bottom: none;
td {
border-bottom: none;
}
} }
} }
td { td {
padding: 12px 16px; padding: 12px 16px;
border-color: var(--luncher-border-light); border: none;
color: var(--luncher-text); color: var(--luncher-text);
white-space: nowrap;
vertical-align: middle; vertical-align: middle;
} }
}
ul { .user-row {
padding: 0; display: flex;
margin: 8px 0 0 20px; justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
li { .user-info {
color: var(--luncher-text-secondary); display: flex;
font-size: 0.9rem; align-items: center;
margin-bottom: 4px; gap: 8px;
flex-wrap: wrap;
}
.user-actions {
display: flex;
gap: 8px;
align-items: center;
white-space: nowrap;
} }
} }
.food-choices { .food-choices {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 4px;
margin-top: 8px;
} }
.food-choice-item { .food-choice-item {
background: var(--luncher-primary-light); display: inline-flex;
padding: 8px 12px;
border-radius: var(--luncher-radius-sm);
display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 6px;
font-size: 0.9rem; font-size: 0.85rem;
border-left: 3px solid var(--luncher-primary); color: var(--luncher-text-secondary);
.action-icon {
opacity: 0;
transition: var(--luncher-transition);
}
&:hover .action-icon {
opacity: 1;
}
} }
.food-choice-name { .food-choice-name {
color: var(--luncher-text); color: var(--luncher-text-secondary);
font-weight: 500; font-weight: 400;
} }
} }

View File

@@ -544,55 +544,61 @@ function App() {
const isBuyer = userPayload?.isBuyer || false; const isBuyer = userPayload?.isBuyer || false;
return <tr key={entry[0]}> return <tr key={entry[0]}>
<td> <td>
{trusted && <span className='trusted-icon' title='Uživatel ověřený doménovým přihlášením'> <div className="user-row">
<FontAwesomeIcon icon={faCircleCheck} style={{ cursor: "help" }} /> <div className="user-info">
</span>} {trusted && <span className='trusted-icon' title='Uživatel ověřený doménovým přihlášením'>
<strong>{login}</strong> <FontAwesomeIcon icon={faCircleCheck} style={{ cursor: "help" }} />
{userPayload.departureTime && <small className="ms-2" style={{ color: 'var(--luncher-text-muted)' }}>({userPayload.departureTime})</small>} </span>}
{userPayload.note && <span className="ms-2" style={{ fontSize: 'small', color: 'var(--luncher-text-secondary)' }}>({userPayload.note})</span>} <strong>{login}</strong>
{login === auth.login && canChangeChoice && locationKey === LunchChoice.OBJEDNAVAM && <span title='Označit/odznačit se jako objednávající'> {userPayload.departureTime && <small className="ms-2" style={{ color: 'var(--luncher-text-muted)' }}>({userPayload.departureTime})</small>}
<FontAwesomeIcon onClick={() => { {userPayload.note && <span className="ms-2" style={{ fontSize: 'small', color: 'var(--luncher-text-secondary)' }}>({userPayload.note})</span>}
markAsBuyer(); </div>
}} icon={faBasketShopping} className={isBuyer ? 'buyer-icon' : 'action-icon'} style={{cursor: 'pointer'}} /> <div className="user-actions">
</span>} {login === auth.login && canChangeChoice && locationKey === LunchChoice.OBJEDNAVAM && <span title='Označit/odznačit se jako objednávající'>
{login !== auth.login && locationKey === LunchChoice.OBJEDNAVAM && isBuyer && <span title='Objednávající'> <FontAwesomeIcon onClick={() => {
<FontAwesomeIcon onClick={() => { markAsBuyer();
copyNote(userPayload.note!); }} icon={faBasketShopping} className={isBuyer ? 'buyer-icon' : 'action-icon'} style={{cursor: 'pointer'}} />
}} icon={faBasketShopping} className='buyer-icon' /> </span>}
</span>} {login !== auth.login && locationKey === LunchChoice.OBJEDNAVAM && isBuyer && <span title='Objednávající'>
{login !== auth.login && canChangeChoice && userPayload?.note?.length && <span title='Převzít poznámku'> <FontAwesomeIcon onClick={() => {
<FontAwesomeIcon onClick={() => { copyNote(userPayload.note!);
copyNote(userPayload.note!); }} icon={faBasketShopping} className='buyer-icon' />
}} className='action-icon' icon={faComment} /> </span>}
</span>} {login !== auth.login && canChangeChoice && userPayload?.note?.length && <span title='Převzít poznámku'>
{login === auth.login && canChangeChoice && <span title='Upravit poznámku'> <FontAwesomeIcon onClick={() => {
<FontAwesomeIcon onClick={() => { copyNote(userPayload.note!);
setNoteModalOpen(true); }} className='action-icon' icon={faComment} />
}} className='action-icon' icon={faNoteSticky} /> </span>}
</span>} {login === auth.login && canChangeChoice && <span title='Upravit poznámku'>
{login === auth.login && canChangeChoice && <span title={`Odstranit volbu ${locationName}, včetně případných zvolených jídel`}> <FontAwesomeIcon onClick={() => {
<FontAwesomeIcon onClick={() => { setNoteModalOpen(true);
doRemoveChoices(key as LunchChoice); }} className='action-icon' icon={faNoteSticky} />
}} className='action-icon' icon={faTrashCan} /> </span>}
</span>} {login === auth.login && canChangeChoice && <span title={`Odstranit volbu ${locationName}, včetně případných zvolených jídel`}>
</td> <FontAwesomeIcon onClick={() => {
{userChoices?.length && food ? <td> doRemoveChoices(key as LunchChoice);
<div className="food-choices"> }} className='action-icon' icon={faTrashCan} />
{userChoices?.map(foodIndex => { </span>}
const restaurantKey = key as Restaurant; </div>
const foodName = food[restaurantKey]?.food?.[foodIndex].name;
return <div key={foodIndex} className="food-choice-item">
<span className="food-choice-name">{foodName}</span>
{login === auth.login && canChangeChoice &&
<span title={`Odstranit ${foodName}`}>
<FontAwesomeIcon onClick={() => {
doRemoveFoodChoice(restaurantKey, foodIndex);
}} className='action-icon' icon={faTrashCan} />
</span>}
</div>
})}
</div> </div>
</td> : null} {userChoices && userChoices.length > 0 && food && (
<div className="food-choices">
{userChoices.map(foodIndex => {
const restaurantKey = key as Restaurant;
const foodName = food[restaurantKey]?.food?.[foodIndex].name;
return <div key={foodIndex} className="food-choice-item">
<span className="food-choice-name">{foodName}</span>
{login === auth.login && canChangeChoice &&
<span title={`Odstranit ${foodName}`}>
<FontAwesomeIcon onClick={() => {
doRemoveFoodChoice(restaurantKey, foodIndex);
}} className='action-icon' icon={faTrashCan} />
</span>}
</div>
})}
</div>
)}
</td>
</tr> </tr>
} }
)} )}

View File

@@ -50,6 +50,12 @@ paths:
$ref: "./paths/pizzaDay/updatePizzaDayNote.yml" $ref: "./paths/pizzaDay/updatePizzaDayNote.yml"
/pizzaDay/updatePizzaFee: /pizzaDay/updatePizzaFee:
$ref: "./paths/pizzaDay/updatePizzaFee.yml" $ref: "./paths/pizzaDay/updatePizzaFee.yml"
/pizzaDay/dismissQr:
$ref: "./paths/pizzaDay/dismissQr.yml"
# Notifikace (/api/notifications)
/notifications/settings:
$ref: "./paths/notifications/settings.yml"
# Easter eggy (/api/easterEggs) # Easter eggy (/api/easterEggs)
/easterEggs: /easterEggs:

View File

@@ -1,527 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
import type { LoginData, LoginResponse, GetPizzaQrData, GetPizzaQrResponse, GetDataData, GetDataResponse, AddChoiceData, AddChoiceResponse, RemoveChoiceData, RemoveChoiceResponse, UpdateNoteData, UpdateNoteResponse, RemoveChoicesData, RemoveChoicesResponse, ChangeDepartureTimeData, ChangeDepartureTimeResponse, JdemeObedData, SetBuyerData, CreatePizzaDayData, DeletePizzaDayData, LockPizzaDayData, UnlockPizzaDayData, FinishOrderData, FinishDeliveryData, AddPizzaData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData, GetEasterEggData, GetEasterEggResponse, GetEasterEggImageData, GetEasterEggImageResponse, GetStatsData, GetStatsResponse, GetVotesData, GetVotesResponse, UpdateVoteData, GetVotingStatsData, GetVotingStatsResponse, GetNotificationSettingsData, GetNotificationSettingsResponse, UpdateNotificationSettingsData, DismissQrData } from './types.gen';
import { client as _heyApiClient } from './client.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
/**
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
* custom client.
*/
client?: Client;
/**
* You can pass arbitrary values through the `meta` object. This can be
* used to access values that aren't defined as part of the SDK function.
*/
meta?: Record<string, unknown>;
};
/**
* Přihlášení uživatele
*/
export const login = <ThrowOnError extends boolean = false>(options?: Options<LoginData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<LoginResponse, unknown, ThrowOnError>({
url: '/login',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Získání QR kódu pro platbu za Pizza day
*/
export const getPizzaQr = <ThrowOnError extends boolean = false>(options: Options<GetPizzaQrData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<GetPizzaQrResponse, unknown, ThrowOnError>({
url: '/qr',
...options
});
};
/**
* Načtení klientských dat pro aktuální nebo předaný den
*/
export const getData = <ThrowOnError extends boolean = false>(options?: Options<GetDataData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetDataResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/data',
...options
});
};
/**
* Přidání či nahrazení volby uživatele pro zvolený den/podnik
*/
export const addChoice = <ThrowOnError extends boolean = false>(options: Options<AddChoiceData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<AddChoiceResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/addChoice',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Odstranění jednoho zvoleného jídla uživatele pro zvolený den/podnik
*/
export const removeChoice = <ThrowOnError extends boolean = false>(options: Options<RemoveChoiceData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<RemoveChoiceResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/removeChoice',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Nastavení poznámky k volbě uživatele
*/
export const updateNote = <ThrowOnError extends boolean = false>(options: Options<UpdateNoteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<UpdateNoteResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/updateNote',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Odstranění volby uživatele pro zvolený den/podnik, včetně případných jídel
*/
export const removeChoices = <ThrowOnError extends boolean = false>(options: Options<RemoveChoicesData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<RemoveChoicesResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/removeChoices',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Úprava preferovaného času odchodu do aktuálně zvoleného podniku.
*/
export const changeDepartureTime = <ThrowOnError extends boolean = false>(options: Options<ChangeDepartureTimeData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<ChangeDepartureTimeResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/changeDepartureTime',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Odeslání notifikací "jdeme na oběd" dle konfigurace.
*/
export const jdemeObed = <ThrowOnError extends boolean = false>(options?: Options<JdemeObedData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/jdemeObed',
...options
});
};
/**
* Nastavení/odnastavení aktuálně přihlášeného uživatele jako objednatele pro stav "Budu objednávat" pro aktuální den.
*/
export const setBuyer = <ThrowOnError extends boolean = false>(options?: Options<SetBuyerData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/food/updateBuyer',
...options
});
};
/**
* Založení pizza day.
*/
export const createPizzaDay = <ThrowOnError extends boolean = false>(options?: Options<CreatePizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/create',
...options
});
};
/**
* Smazání pizza day.
*/
export const deletePizzaDay = <ThrowOnError extends boolean = false>(options?: Options<DeletePizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/delete',
...options
});
};
/**
* Uzamkne pizza day. Nebude možné přidávat či odebírat pizzy.
*/
export const lockPizzaDay = <ThrowOnError extends boolean = false>(options?: Options<LockPizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/lock',
...options
});
};
/**
* Odemkne pizza day. Bude opět možné přidávat či odebírat pizzy.
*/
export const unlockPizzaDay = <ThrowOnError extends boolean = false>(options?: Options<UnlockPizzaDayData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/unlock',
...options
});
};
/**
* Přepnutí pizza day do stavu "Pizzy objednány". Není možné měnit objednávky, příslušným uživatelům je odeslána notifikace o provedené objednávce.
*/
export const finishOrder = <ThrowOnError extends boolean = false>(options?: Options<FinishOrderData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/finishOrder',
...options
});
};
/**
* Převod pizza day do stavu "Pizzy byly doručeny". Pokud má objednávající nastaveno číslo účtu, je ostatním uživatelům vygenerován a následně zobrazen QR kód pro úhradu jejich objednávky.
*/
export const finishDelivery = <ThrowOnError extends boolean = false>(options: Options<FinishDeliveryData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/finishDelivery',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Přidání pizzy do objednávky.
*/
export const addPizza = <ThrowOnError extends boolean = false>(options: Options<AddPizzaData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/add',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Odstranění pizzy z objednávky.
*/
export const removePizza = <ThrowOnError extends boolean = false>(options: Options<RemovePizzaData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/remove',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Nastavení poznámky k objednávkám pizz přihlášeného uživatele.
*/
export const updatePizzaDayNote = <ThrowOnError extends boolean = false>(options: Options<UpdatePizzaDayNoteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/updatePizzaDayNote',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Nastavení přirážky/slevy k objednávce pizz uživatele.
*/
export const updatePizzaFee = <ThrowOnError extends boolean = false>(options: Options<UpdatePizzaFeeData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/updatePizzaFee',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Vrátí náhodně metadata jednoho z definovaných easter egg obrázků pro přihlášeného uživatele, nebo nic, pokud žádné definované nemá.
*/
export const getEasterEgg = <ThrowOnError extends boolean = false>(options?: Options<GetEasterEggData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetEasterEggResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/easterEggs',
...options
});
};
/**
* Vrátí obrázek konkrétního easter eggu
*/
export const getEasterEggImage = <ThrowOnError extends boolean = false>(options: Options<GetEasterEggImageData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<GetEasterEggImageResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/easterEggs/{url}',
...options
});
};
/**
* Vrátí statistiky způsobu stravování pro předaný rozsah dat.
*/
export const getStats = <ThrowOnError extends boolean = false>(options: Options<GetStatsData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<GetStatsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/stats',
...options
});
};
/**
* Vrátí statistiky hlasování o nových funkcích.
*/
export const getVotes = <ThrowOnError extends boolean = false>(options?: Options<GetVotesData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetVotesResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/voting/getVotes',
...options
});
};
/**
* Aktualizuje hlasování uživatele o dané funkcionalitě.
*/
export const updateVote = <ThrowOnError extends boolean = false>(options: Options<UpdateVoteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/voting/updateVote',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Vrátí agregované statistiky hlasování o nových funkcích.
*/
export const getVotingStats = <ThrowOnError extends boolean = false>(options?: Options<GetVotingStatsData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetVotingStatsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/voting/stats',
...options
});
};
/**
* Vrátí nastavení notifikací přihlášeného uživatele.
*/
export const getNotificationSettings = <ThrowOnError extends boolean = false>(options?: Options<GetNotificationSettingsData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<GetNotificationSettingsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/notifications/settings',
...options
});
};
/**
* Uloží nastavení notifikací přihlášeného uživatele.
*/
export const updateNotificationSettings = <ThrowOnError extends boolean = false>(options: Options<UpdateNotificationSettingsData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<GetNotificationSettingsResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/notifications/settings',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};
/**
* Označí QR kód jako uhrazený (zaplacený).
*/
export const dismissQr = <ThrowOnError extends boolean = false>(options: Options<DismissQrData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).post<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/pizzaDay/dismissQr',
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
get:
operationId: getNotificationSettings
summary: Vrátí nastavení notifikací pro přihlášeného uživatele.
responses:
"200":
description: Nastavení notifikací
content:
application/json:
schema:
$ref: "../../schemas/_index.yml#/NotificationSettings"
post:
operationId: updateNotificationSettings
summary: Uloží nastavení notifikací pro přihlášeného uživatele.
requestBody:
required: true
content:
application/json:
schema:
$ref: "../../schemas/_index.yml#/NotificationSettings"
responses:
"200":
description: Nastavení notifikací bylo uloženo.
content:
application/json:
schema:
$ref: "../../schemas/_index.yml#/NotificationSettings"

View File

@@ -0,0 +1,17 @@
post:
operationId: dismissQr
summary: Označí QR kód pro daný den jako uhrazený (odstraní ho ze seznamu nevyřízených).
requestBody:
required: true
content:
application/json:
schema:
properties:
date:
description: Datum Pizza day, ke kterému se QR kód vztahuje
type: string
required:
- date
responses:
"200":
description: QR kód byl označen jako uhrazený.

View File

@@ -53,6 +53,11 @@ ClientData:
description: Datum a čas poslední aktualizace pizz description: Datum a čas poslední aktualizace pizz
type: string type: string
format: date-time format: date-time
pendingQrs:
description: Nevyřízené QR kódy pro platbu z předchozích pizza day
type: array
items:
$ref: "#/PendingQr"
# --- OBĚDY --- # --- OBĚDY ---
UserLunchChoice: UserLunchChoice:
@@ -527,6 +532,24 @@ NotifikaceData:
type: boolean type: boolean
ntfy: ntfy:
type: boolean type: boolean
NotificationSettings:
description: Nastavení notifikací pro konkrétního uživatele
type: object
properties:
ntfyTopic:
description: Téma pro ntfy push notifikace
type: string
discordWebhookUrl:
description: URL webhooku Discord kanálu
type: string
teamsWebhookUrl:
description: URL webhooku MS Teams kanálu
type: string
enabledEvents:
description: Seznam událostí, o kterých chce být uživatel notifikován
type: array
items:
$ref: "#/UdalostEnum"
GotifyServer: GotifyServer:
type: object type: object
required: required:
@@ -539,3 +562,23 @@ GotifyServer:
type: array type: array
items: items:
type: string type: string
# --- NEVYŘÍZENÉ QR KÓDY ---
PendingQr:
description: Nevyřízený QR kód pro platbu z předchozího Pizza day
type: object
additionalProperties: false
required:
- date
- creator
- totalPrice
properties:
date:
description: Datum Pizza day, ke kterému se QR kód vztahuje
type: string
creator:
description: Jméno zakladatele Pizza day (objednávajícího)
type: string
totalPrice:
description: Celková cena objednávky v Kč
type: number