CI / Generate TypeScript types (push) Successful in 12s
CI / Server unit tests (push) Successful in 22s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 41s
CI / Playwright E2E tests (push) Successful in 1m48s
CI / Build and push Docker image (push) Successful in 53s
CI / Notify (push) Successful in 2s
Nové zaškrtávátko v Nastavení → Notifikace pod výběrem času připomínky. Když skupinová objednávka sledovaná přes Bolt Food přejde do stavu delivered/finished, dostanou push členové skupiny, kteří si to zapnuli. Zakladatel skupiny notifikaci nedostává — ten je informován aplikací Bolt. Push subscription byla dosud svázaná s časem připomínky (subscribe endpoint bez reminderTime vracel 400), takže by nový přepínač samostatně nefungoval. Čas připomínky je nově v registru volitelný a scheduler připomínek záznamy bez času přeskakuje. Service worker nemá natvrdo tag a akci "Mám vlastní/neobědvám" — obojí se řídí payloadem, aby je doručovací notifikace nedědila.
262 lines
12 KiB
TypeScript
262 lines
12 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import { Modal, Button, Form } from "react-bootstrap"
|
|
import { useSettings, ThemePreference } from "../../context/settings";
|
|
import { NotificationSettings, UdalostEnum, getNotificationSettings, updateNotificationSettings } from "../../../../types";
|
|
import { useAuth } from "../../context/auth";
|
|
import { subscribeToPush, unsubscribeFromPush } from "../../hooks/usePushReminder";
|
|
|
|
type Props = {
|
|
isOpen: boolean,
|
|
onClose: () => void,
|
|
onSave: (bankAccountNumber?: string, bankAccountHolderName?: string, hideSoupsOption?: boolean, themePreference?: ThemePreference) => void,
|
|
}
|
|
|
|
/** Modální dialog pro uživatelská nastavení. */
|
|
export default function SettingsModal({ isOpen, onClose, onSave }: Readonly<Props>) {
|
|
const auth = useAuth();
|
|
const settings = useSettings();
|
|
const bankAccountRef = useRef<HTMLInputElement>(null);
|
|
const nameRef = useRef<HTMLInputElement>(null);
|
|
const hideSoupsRef = useRef<HTMLInputElement>(null);
|
|
const themeRef = useRef<HTMLSelectElement>(null);
|
|
|
|
const reminderTimeRef = useRef<HTMLInputElement>(null);
|
|
const boltDeliveredRef = useRef<HTMLInputElement>(null);
|
|
const ntfyTopicRef = useRef<HTMLInputElement>(null);
|
|
const discordWebhookRef = useRef<HTMLInputElement>(null);
|
|
const teamsWebhookRef = useRef<HTMLInputElement>(null);
|
|
const [notifSettings, setNotifSettings] = useState<NotificationSettings>({});
|
|
const [enabledEvents, setEnabledEvents] = useState<UdalostEnum[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && auth?.login) {
|
|
getNotificationSettings().then(response => {
|
|
if (response.data) {
|
|
setNotifSettings(response.data);
|
|
setEnabledEvents(response.data.enabledEvents ?? []);
|
|
}
|
|
}).catch(() => {});
|
|
}
|
|
}, [isOpen, auth?.login]);
|
|
|
|
const toggleEvent = (event: UdalostEnum) => {
|
|
setEnabledEvents(prev =>
|
|
prev.includes(event) ? prev.filter(e => e !== event) : [...prev, event]
|
|
);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
const newReminderTime = reminderTimeRef.current?.value || undefined;
|
|
const oldReminderTime = notifSettings.reminderTime;
|
|
const newBoltDelivered = boltDeliveredRef.current?.checked ?? false;
|
|
const oldBoltDelivered = notifSettings.boltDeliveredPush ?? false;
|
|
|
|
// Uložení notifikačních nastavení na server
|
|
const newSettings: NotificationSettings = {
|
|
ntfyTopic: ntfyTopicRef.current?.value || undefined,
|
|
discordWebhookUrl: discordWebhookRef.current?.value || undefined,
|
|
teamsWebhookUrl: teamsWebhookRef.current?.value || undefined,
|
|
enabledEvents,
|
|
reminderTime: newReminderTime,
|
|
boltDeliveredPush: newBoltDelivered,
|
|
};
|
|
await updateNotificationSettings({ body: newSettings }).catch(() => {});
|
|
setNotifSettings(newSettings);
|
|
|
|
// Správa push subscription — drží ji naživu kterákoli z push funkcí.
|
|
// Záměrně bez await: subscribeToPush si vyžádá oprávnění prohlížeče a modal
|
|
// by na tu dobu zůstal otevřený.
|
|
const wantsPush = !!newReminderTime || newBoltDelivered;
|
|
const hadPush = !!oldReminderTime || oldBoltDelivered;
|
|
if (wantsPush && (newReminderTime !== oldReminderTime || newBoltDelivered !== oldBoltDelivered)) {
|
|
subscribeToPush(newReminderTime);
|
|
} else if (!wantsPush && hadPush) {
|
|
unsubscribeFromPush();
|
|
}
|
|
|
|
// Uložení ostatních nastavení (localStorage)
|
|
onSave(
|
|
bankAccountRef.current?.value,
|
|
nameRef.current?.value,
|
|
hideSoupsRef.current?.checked,
|
|
themeRef.current?.value as ThemePreference,
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Modal show={isOpen} onHide={onClose} size="lg">
|
|
<Modal.Header closeButton>
|
|
<Modal.Title><h2>Nastavení</h2></Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
<h4>Vzhled</h4>
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>Barevný motiv</Form.Label>
|
|
<Form.Select ref={themeRef} defaultValue={settings?.themePreference}>
|
|
<option value="system">Podle systému</option>
|
|
<option value="light">Světlý</option>
|
|
<option value="dark">Tmavý</option>
|
|
</Form.Select>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<h4>Obecné</h4>
|
|
<Form.Group className="mb-3">
|
|
<Form.Check
|
|
id="hideSoupsCheckbox"
|
|
ref={hideSoupsRef}
|
|
type="checkbox"
|
|
label="Skrýt polévky"
|
|
defaultChecked={settings?.hideSoups}
|
|
title="V nabídkách nebudou zobrazovány polévky. Tato funkce je experimentální."
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Experimentální funkce - zejména u TechTower bývá problém polévky spolehlivě rozeznat.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<h4>Notifikace</h4>
|
|
<p>
|
|
Nastavením notifikací budete dostávat upozornění o událostech (např. "Jdeme na oběd") přímo do vámi zvoleného komunikačního kanálu.
|
|
</p>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>Připomínka výběru oběda</Form.Label>
|
|
<Form.Control
|
|
ref={reminderTimeRef}
|
|
type="time"
|
|
defaultValue={notifSettings.reminderTime ?? ''}
|
|
key={notifSettings.reminderTime ?? 'reminder-empty'}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
V zadaný čas vám přijde push notifikace, pokud nemáte zvolenou možnost stravování. Nechte prázdné pro vypnutí.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Check
|
|
id="boltDeliveredCheckbox"
|
|
ref={boltDeliveredRef}
|
|
type="checkbox"
|
|
label="Upozornění na doručení objednávky (Bolt Food)"
|
|
defaultChecked={notifSettings.boltDeliveredPush ?? false}
|
|
key={`bolt-delivered-${notifSettings.boltDeliveredPush ?? false}`}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Až bude skupinová objednávka sledovaná přes Bolt Food doručena, přijde vám push notifikace.
|
|
Zakladateli skupiny se neposílá — ten dostane upozornění přímo z aplikace Bolt.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>ntfy téma (topic)</Form.Label>
|
|
<Form.Control
|
|
ref={ntfyTopicRef}
|
|
type="text"
|
|
placeholder="moje-tema"
|
|
defaultValue={notifSettings.ntfyTopic}
|
|
key={notifSettings.ntfyTopic ?? 'ntfy-empty'}
|
|
onKeyDown={e => e.stopPropagation()}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Téma pro ntfy push notifikace. Nechte prázdné pro vypnutí.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>Discord webhook URL</Form.Label>
|
|
<Form.Control
|
|
ref={discordWebhookRef}
|
|
type="text"
|
|
placeholder="https://discord.com/api/webhooks/..."
|
|
defaultValue={notifSettings.discordWebhookUrl}
|
|
key={notifSettings.discordWebhookUrl ?? 'discord-empty'}
|
|
onKeyDown={e => e.stopPropagation()}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
URL webhooku Discord kanálu. Nechte prázdné pro vypnutí.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>MS Teams webhook URL</Form.Label>
|
|
<Form.Control
|
|
ref={teamsWebhookRef}
|
|
type="text"
|
|
placeholder="https://outlook.office.com/webhook/..."
|
|
defaultValue={notifSettings.teamsWebhookUrl}
|
|
key={notifSettings.teamsWebhookUrl ?? 'teams-empty'}
|
|
onKeyDown={e => e.stopPropagation()}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
URL webhooku MS Teams kanálu. Nechte prázdné pro vypnutí.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>Události k odběru</Form.Label>
|
|
{Object.values(UdalostEnum).map(event => (
|
|
<Form.Check
|
|
key={event}
|
|
id={`notif-event-${event}`}
|
|
type="checkbox"
|
|
label={event}
|
|
checked={enabledEvents.includes(event)}
|
|
onChange={() => toggleEvent(event)}
|
|
/>
|
|
))}
|
|
<Form.Text className="text-muted">
|
|
Zvolte události, o kterých chcete být notifikováni. Notifikace jsou odesílány pouze uživatelům se stejnou zvolenou lokalitou.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<h4>Bankovní účet</h4>
|
|
<p>
|
|
Nastavením čísla účtu umožníte automatické generování QR kódů pro úhradu za vámi provedené objednávky v rámci Pizza day.
|
|
</p>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>Číslo účtu</Form.Label>
|
|
<Form.Control
|
|
ref={bankAccountRef}
|
|
type="text"
|
|
placeholder="123456-1234567890/1234"
|
|
defaultValue={settings?.bankAccount}
|
|
onKeyDown={e => e.stopPropagation()}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Pokud vaše číslo účtu neobsahuje předčíslí, je možné ho zcela vynechat.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3">
|
|
<Form.Label>Název příjemce</Form.Label>
|
|
<Form.Control
|
|
ref={nameRef}
|
|
type="text"
|
|
placeholder="Jan Novák"
|
|
defaultValue={settings?.holderName}
|
|
onKeyDown={e => e.stopPropagation()}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Jméno majitele účtu pro QR platbu.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
</Modal.Body>
|
|
<Modal.Footer>
|
|
<Button variant="secondary" onClick={onClose}>
|
|
Storno
|
|
</Button>
|
|
<Button onClick={handleSave}>
|
|
Uložit
|
|
</Button>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
);
|
|
}
|