- {(locationPickCount ?? 0) > 1 ? (
- {locationName} ({locationPickCount})
- ) : (
- {locationName} )}
+
+ {locationName}
+ {(locationPickCount ?? 0) > 1 && ({locationPickCount}) }
+
-
+
- {locationLoginList.map((entry: [string, UserLunchChoice], index) => {
+ {locationLoginList.map((entry: [string, UserLunchChoice]) => {
const login = entry[0];
const userPayload = entry[1];
const userChoices = userPayload?.selectedFoods;
@@ -508,13 +506,12 @@ function App() {
const isBuyer = userPayload?.isBuyer || false;
return
- {/* TODO zrefaktorovat, oddělit řádek do samostatné komponenty (a akce možná zvlášť) */}
{trusted &&
}
- {login}
- {userPayload.departureTime && ({userPayload.departureTime}) }
- {userPayload.note && ({userPayload.note}) }
+ {login}
+ {userPayload.departureTime && ({userPayload.departureTime}) }
+ {userPayload.note && ({userPayload.note}) }
{login === auth.login && canChangeChoice && locationKey === LunchChoice.OBJEDNAVAM &&
{
markAsBuyer();
@@ -569,97 +566,94 @@ function App() {
)}
- : Zatím nikdo nehlasoval...
+ : Zatím nikdo nehlasoval...
}
{dayIndex === data.todayDayIndex &&
-
+
{!data.pizzaDay &&
-
+ <>
+
Pizza Day
Pro dnešní den není aktuálně založen Pizza day.
{loadingPizzaDay ?
-
- Zjišťujeme dostupné pizzy
+
+ Zjišťujeme dostupné pizzy
:
- <>
+
{
setLoadingPizzaDay(true);
await createPizzaDay().then(() => setLoadingPizzaDay(false));
}}>Založit Pizza day
- Jdeme na oběd !
- >
+ Jdeme na oběd!
+
}
-
+ >
}
{data.pizzaDay &&
-
-
-
Pizza day
- {
- data.pizzaDay.state === PizzaDayState.CREATED &&
-
-
- Pizza Day je založen a spravován uživatelem {data.pizzaDay.creator}.
- Můžete upravovat své objednávky.
-
- {
- data.pizzaDay.creator === auth.login &&
- <>
-
{
- await deletePizzaDay();
- }}>Smazat Pizza day
-
{
- await lockPizzaDay();
- }}>Uzamknout
- >
- }
-
- }
- {
- data.pizzaDay.state === PizzaDayState.LOCKED &&
-
-
Objednávky jsou uzamčeny uživatelem {data.pizzaDay.creator}
- {data.pizzaDay.creator === auth.login &&
- <>
-
{
- await unlockPizzaDay();
- }}>Odemknout
-
{
- await finishOrder();
- }}>Objednáno
- >
- }
-
- }
- {
- data.pizzaDay.state === PizzaDayState.ORDERED &&
-
-
Pizzy byly objednány uživatelem {data.pizzaDay.creator}
- {data.pizzaDay.creator === auth.login &&
-
- {
- await lockPizzaDay();
- }}>Vrátit do "uzamčeno"
- {
- await finishDelivery({ body: { bankAccount: settings?.bankAccount, bankAccountHolder: settings?.holderName } });
- }}>Doručeno
-
- }
-
- }
- {
- data.pizzaDay.state === PizzaDayState.DELIVERED &&
-
-
- Pizzy byly doručeny.
- {myOrder?.hasQr ? ` Objednávku můžete uživateli ${data.pizzaDay.creator} uhradit pomocí QR kódu níže.` : ''}
-
-
- }
-
+ <>
+
Pizza Day
+ {
+ data.pizzaDay.state === PizzaDayState.CREATED &&
+ <>
+
+ Pizza Day je založen a spravován uživatelem {data.pizzaDay.creator} .
+ Můžete upravovat své objednávky.
+
+ {
+ data.pizzaDay.creator === auth.login &&
+
+ {
+ await deletePizzaDay();
+ }}>Smazat Pizza day
+ {
+ await lockPizzaDay();
+ }}>Uzamknout
+
+ }
+ >
+ }
+ {
+ data.pizzaDay.state === PizzaDayState.LOCKED &&
+ <>
+
Objednávky jsou uzamčeny uživatelem {data.pizzaDay.creator}
+ {data.pizzaDay.creator === auth.login &&
+
+ {
+ await unlockPizzaDay();
+ }}>Odemknout
+ {
+ await finishOrder();
+ }}>Objednáno
+
+ }
+ >
+ }
+ {
+ data.pizzaDay.state === PizzaDayState.ORDERED &&
+ <>
+
Pizzy byly objednány uživatelem {data.pizzaDay.creator}
+ {data.pizzaDay.creator === auth.login &&
+
+ {
+ await lockPizzaDay();
+ }}>Vrátit do "uzamčeno"
+ {
+ await finishDelivery({ body: { bankAccount: settings?.bankAccount, bankAccountHolder: settings?.holderName } });
+ }}>Doručeno
+
+ }
+ >
+ }
+ {
+ data.pizzaDay.state === PizzaDayState.DELIVERED &&
+
+ Pizzy byly doručeny.
+ {myOrder?.hasQr ? ` Objednávku můžete uživateli ${data.pizzaDay.creator} uhradit pomocí QR kódu níže.` : ''}
+
+ }
{data.pizzaDay.state === PizzaDayState.CREATED &&
-
+
}
{
- data.pizzaDay.state === PizzaDayState.DELIVERED && myOrder?.hasQr ?
-
-
QR platba
-
-
: null
+ data.pizzaDay.state === PizzaDayState.DELIVERED && myOrder?.hasQr &&
+
+
QR platba
+
+
}
-
+ >
}
}
diff --git a/client/src/Login.css b/client/src/Login.css
index 1b0e950..0965166 100644
--- a/client/src/Login.css
+++ b/client/src/Login.css
@@ -1,13 +1,89 @@
-.login {
- height: 100%;
+.login-page {
+ min-height: 100vh;
display: flex;
- flex-direction: column;
- text-align: center;
+ align-items: center;
justify-content: center;
+ background: var(--luncher-bg);
+ padding: 24px;
}
-.login-inner {
+.login-card {
+ background: var(--luncher-bg-card);
+ border-radius: var(--luncher-radius-xl);
+ box-shadow: var(--luncher-shadow-lg);
+ padding: 48px;
+ max-width: 420px;
+ width: 100%;
+ text-align: center;
+ border: 1px solid var(--luncher-border-light);
+}
+
+.login-logo {
+ font-size: 2.5rem;
+ font-weight: 700;
+ color: var(--luncher-text);
+ margin-bottom: 8px;
+ letter-spacing: -0.5px;
+}
+
+.login-subtitle {
+ color: var(--luncher-text-secondary);
+ font-size: 1rem;
+ margin-bottom: 40px;
+ line-height: 1.5;
+}
+
+.login-form {
display: flex;
flex-direction: column;
- align-items: center;
-}
\ No newline at end of file
+ gap: 20px;
+}
+
+.login-form label {
+ display: block;
+ text-align: left;
+ font-weight: 500;
+ color: var(--luncher-text);
+ margin-bottom: 8px;
+}
+
+.login-form .hint {
+ font-size: 0.85rem;
+ color: var(--luncher-text-muted);
+ margin-top: 8px;
+ text-align: left;
+ line-height: 1.5;
+}
+
+.login-form input[type="text"] {
+ width: 100%;
+ padding: 14px 18px;
+ font-size: 1rem;
+ border: 2px solid var(--luncher-border);
+ border-radius: var(--luncher-radius-sm);
+ background: var(--luncher-bg);
+ color: var(--luncher-text);
+ transition: var(--luncher-transition);
+}
+
+.login-form input[type="text"]:hover {
+ border-color: var(--luncher-text-muted);
+}
+
+.login-form input[type="text"]:focus {
+ border-color: var(--luncher-primary);
+ box-shadow: 0 0 0 3px var(--luncher-primary-light);
+ outline: none;
+}
+
+.login-form input[type="text"]::placeholder {
+ color: var(--luncher-text-muted);
+}
+
+.login-form .btn {
+ width: 100%;
+ padding: 14px 24px;
+ font-size: 1rem;
+ font-weight: 600;
+ margin-top: 8px;
+}
diff --git a/client/src/Login.tsx b/client/src/Login.tsx
index 650d764..b5cc446 100644
--- a/client/src/Login.tsx
+++ b/client/src/Login.tsx
@@ -36,21 +36,35 @@ export default function Login() {
}, [auth]);
if (!auth?.login) {
- return
-
Luncher
-
Aplikace pro profesionální management obědů
-
-
- Zobrazované jméno by mělo být vaše jméno nebo přezdívka, pod kterou vás kolegové dokáží snadno identifikovat. Jméno je možné kdykoli změnit.
-
- Zobrazované jméno:
{
- if (event.key === 'Enter') {
- doLogin()
- }
- }} />
-
Uložit
+ return (
+
+
+
Luncher
+
Aplikace pro profesionální management obědů
+
+
+
Zobrazované jméno
+
{
+ if (event.key === 'Enter') {
+ doLogin()
+ }
+ }}
+ />
+
+ Zadejte jméno nebo přezdívku, pod kterou vás kolegové snadno identifikují.
+ Jméno je možné kdykoli změnit.
+
+
+
Pokračovat
+
+
-
+ );
}
return
Neplatný stav
}
diff --git a/client/src/components/Footer.tsx b/client/src/components/Footer.tsx
index 42b56c0..1e23b07 100644
--- a/client/src/components/Footer.tsx
+++ b/client/src/components/Footer.tsx
@@ -1,12 +1,12 @@
-import { Navbar } from "react-bootstrap";
-
export default function Footer() {
- return
- 🄯 Žádná práva nevyhrazena. TODO a zdrojové kódy dostupné zde .
-
-}
\ No newline at end of file
+ return (
+
+
+ Zdroj. kódy dostupné na{' '}
+
+ Gitea
+
+
+
+ );
+}
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx
index 7803d9b..9fdd257 100644
--- a/client/src/components/Header.tsx
+++ b/client/src/components/Header.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { Navbar, Nav, NavDropdown } from "react-bootstrap";
+import { Navbar, Nav, NavDropdown, Modal, Button } from "react-bootstrap";
import { useAuth } from "../context/auth";
import SettingsModal from "./modals/SettingsModal";
import { useSettings, ThemePreference } from "../context/settings";
@@ -9,6 +9,14 @@ import RefreshMenuModal from "./modals/RefreshMenuModal";
import { useNavigate } from "react-router";
import { STATS_URL } from "../AppRoutes";
import { FeatureRequest, getVotes, updateVote } from "../../../types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faSun, faMoon } from "@fortawesome/free-solid-svg-icons";
+
+const CHANGELOG = [
+ "Nový moderní design aplikace",
+ "Oprava parsování Sladovnické a TechTower",
+ "Možnost označit se jako objednávající u volby \"budu objednávat\"",
+];
export default function Header() {
const auth = useAuth();
@@ -18,8 +26,29 @@ export default function Header() {
const [votingModalOpen, setVotingModalOpen] = useState
(false);
const [pizzaModalOpen, setPizzaModalOpen] = useState(false);
const [refreshMenuModalOpen, setRefreshMenuModalOpen] = useState(false);
+ const [changelogModalOpen, setChangelogModalOpen] = useState(false);
const [featureVotes, setFeatureVotes] = useState([]);
+ // Zjistíme aktuální efektivní téma (pro zobrazení správné ikony)
+ const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light');
+
+ useEffect(() => {
+ const updateEffectiveTheme = () => {
+ if (settings?.themePreference === 'system') {
+ const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ setEffectiveTheme(isDark ? 'dark' : 'light');
+ } else {
+ setEffectiveTheme(settings?.themePreference || 'light');
+ }
+ };
+
+ updateEffectiveTheme();
+
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ mediaQuery.addEventListener('change', updateEffectiveTheme);
+ return () => mediaQuery.removeEventListener('change', updateEffectiveTheme);
+ }, [settings?.themePreference]);
+
useEffect(() => {
if (auth?.login) {
getVotes().then(response => {
@@ -44,6 +73,12 @@ export default function Header() {
setRefreshMenuModalOpen(false);
}
+ const toggleTheme = () => {
+ // Přepínáme mezi light a dark (ignorujeme system pro jednoduchost)
+ const newTheme: ThemePreference = effectiveTheme === 'dark' ? 'light' : 'dark';
+ settings?.setThemePreference(newTheme);
+ }
+
const isValidInteger = (str: string) => {
str = str.trim();
if (!str) {
@@ -121,12 +156,21 @@ export default function Header() {
+
+
+
setSettingsModalOpen(true)}>Nastavení
setRefreshMenuModalOpen(true)}>Přenačtení menu
setVotingModalOpen(true)}>Hlasovat o nových funkcích
setPizzaModalOpen(true)}>Pizza kalkulačka
navigate(STATS_URL)}>Statistiky
+ setChangelogModalOpen(true)}>Novinky
Odhlásit se
@@ -136,5 +180,22 @@ export default function Header() {
+ setChangelogModalOpen(false)}>
+
+ Novinky
+
+
+
+ {CHANGELOG.map((item, index) => (
+ {item}
+ ))}
+
+
+
+ setChangelogModalOpen(false)}>
+ Zavřít
+
+
+
-}
\ No newline at end of file
+}
diff --git a/client/src/components/Loader.tsx b/client/src/components/Loader.tsx
index f898705..96ba7cc 100644
--- a/client/src/components/Loader.tsx
+++ b/client/src/components/Loader.tsx
@@ -9,11 +9,13 @@ type Props = {
}
function Loader(props: Readonly) {
- return
-
{props.title ?? 'Prosím čekejte...'}
-
-
{props.description}
-
+ return (
+
+
+
{props.title ?? 'Prosím čekejte...'}
+
{props.description}
+
+ );
}
export default Loader;
diff --git a/client/src/components/PizzaOrderList.tsx b/client/src/components/PizzaOrderList.tsx
index cbc9cc7..9e276a6 100644
--- a/client/src/components/PizzaOrderList.tsx
+++ b/client/src/components/PizzaOrderList.tsx
@@ -15,29 +15,43 @@ export default function PizzaOrderList({ state, orders, onDelete, creator }: Rea
}
if (!orders?.length) {
- return Zatím žádné objednávky...
+ return Zatím žádné objednávky...
}
const total = orders.reduce((total, order) => total + order.totalPrice, 0);
- return
-
-
- Jméno
- Objednávka
- Poznámka
- Příplatek
- Cena
-
-
-
- {orders.map(order =>
-
- )}
-
- Celkem
- {`${total} Kč`}
-
-
-
-}
\ No newline at end of file
+ return (
+
+
+
+
+ Jméno
+ Objednávka
+ Poznámka
+ Příplatek
+ Cena
+
+
+
+ {orders.map(order =>
+
+ )}
+
+ Celkem
+ {`${total} Kč`}
+
+
+
+
+ );
+}
diff --git a/client/src/components/modals/RefreshMenuModal.tsx b/client/src/components/modals/RefreshMenuModal.tsx
index 70f2785..6b6ee6d 100644
--- a/client/src/components/modals/RefreshMenuModal.tsx
+++ b/client/src/components/modals/RefreshMenuModal.tsx
@@ -1,5 +1,5 @@
import { useRef, useState } from "react";
-import { Modal, Button, Alert } from "react-bootstrap";
+import { Modal, Button, Alert, Form } from "react-bootstrap";
type Props = {
isOpen: boolean;
@@ -30,7 +30,6 @@ export default function RefreshMenuModal({ isOpen, onClose }: Readonly) {
if (res.ok) {
setRefreshMessage({ type: 'success', text: 'Uspesny fetch' });
if (refreshPassRef.current) {
- // Clean hesla xd
refreshPassRef.current.value = '';
}
} else {
@@ -50,7 +49,7 @@ export default function RefreshMenuModal({ isOpen, onClose }: Readonly) {
};
return (
-
+
Přenačtení menu
@@ -63,36 +62,29 @@ export default function RefreshMenuModal({ isOpen, onClose }: Readonly) {
)}
-
- Heslo:
+
Heslo
+ e.stopPropagation()}
/>
-
+
-
- Typ refreshe:
+
+ Typ refreshe
+
Týden
Den
-
-
+
+
- {refreshLoading ? 'Refreshing...' : 'Refresh'}
+ {refreshLoading ? 'Načítám...' : 'Obnovit menu'}
@@ -102,4 +94,4 @@ export default function RefreshMenuModal({ isOpen, onClose }: Readonly) {
);
-}
\ No newline at end of file
+}
diff --git a/client/src/components/modals/SettingsModal.tsx b/client/src/components/modals/SettingsModal.tsx
index 16e42ca..a1055f1 100644
--- a/client/src/components/modals/SettingsModal.tsx
+++ b/client/src/components/modals/SettingsModal.tsx
@@ -16,38 +16,81 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly(null);
const themeRef = useRef(null);
- return
-
- Nastavení
-
-
- Vzhled
-
- Barevný motiv
-
- Podle systému
- Světlý
- Tmavý
-
-
-
- Obecné
-
- Skrýt polévky
-
-
- Bankovní účet
- 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. Pokud vaše číslo účtu neobsahuje předčíslí, je možné ho zcela vynechat. Číslo účtu není ukládáno na serveru, posílá se na něj pouze za účelem vygenerování QR kódů.
- Číslo účtu: e.stopPropagation()} />
- Název příjemce (jméno majitele účtu): e.stopPropagation()} />
-
-
-
- Storno
-
- onSave(bankAccountRef.current?.value, nameRef.current?.value, hideSoupsRef.current?.checked, themeRef.current?.value as ThemePreference)}>
- Uložit
-
-
-
-}
\ No newline at end of file
+ return (
+
+
+ Nastavení
+
+
+ Vzhled
+
+ Barevný motiv
+
+ Podle systému
+ Světlý
+ Tmavý
+
+
+
+
+
+ Obecné
+
+
+
+ Experimentální funkce - zejména u TechTower bývá problém polévky spolehlivě rozeznat.
+
+
+
+
+
+ Bankovní účet
+
+ 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.
+
+
+
+ Číslo účtu
+ e.stopPropagation()}
+ />
+
+ Pokud vaše číslo účtu neobsahuje předčíslí, je možné ho zcela vynechat.
+
+
+
+
+ Název příjemce
+ e.stopPropagation()}
+ />
+
+ Jméno majitele účtu pro QR platbu.
+
+
+
+
+
+ Storno
+
+ onSave(bankAccountRef.current?.value, nameRef.current?.value, hideSoupsRef.current?.checked, themeRef.current?.value as ThemePreference)}>
+ Uložit
+
+
+
+ );
+}
diff --git a/client/src/context/settings.tsx b/client/src/context/settings.tsx
index 5e67855..1775c79 100644
--- a/client/src/context/settings.tsx
+++ b/client/src/context/settings.tsx
@@ -33,11 +33,23 @@ export const useSettings = () => {
return useContext(settingsContext);
}
+function getInitialTheme(): ThemePreference {
+ try {
+ const saved = localStorage.getItem(THEME_KEY) as ThemePreference | null;
+ if (saved && ['system', 'light', 'dark'].includes(saved)) {
+ return saved;
+ }
+ } catch (e) {
+ // localStorage nedostupný
+ }
+ return 'system';
+}
+
function useProvideSettings(): SettingsContextProps {
const [bankAccount, setBankAccount] = useState();
const [holderName, setHolderName] = useState();
const [hideSoups, setHideSoups] = useState();
- const [themePreference, setTheme] = useState('system');
+ const [themePreference, setTheme] = useState(getInitialTheme);
useEffect(() => {
const accountNumber = localStorage.getItem(BANK_ACCOUNT_NUMBER_KEY);
@@ -52,10 +64,6 @@ function useProvideSettings(): SettingsContextProps {
if (hideSoups !== null) {
setHideSoups(hideSoups === 'true');
}
- const savedTheme = localStorage.getItem(THEME_KEY) as ThemePreference | null;
- if (savedTheme && ['system', 'light', 'dark'].includes(savedTheme)) {
- setTheme(savedTheme);
- }
}, [])
useEffect(() => {
diff --git a/client/src/index.css b/client/src/index.css
index 57dcd7a..b0d4941 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -7,14 +7,32 @@ body,
body {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ line-height: 1.5;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
-}
\ No newline at end of file
+}
+
+/* Smooth scrolling */
+html {
+ scroll-behavior: smooth;
+}
+
+/* Better focus styles */
+:focus-visible {
+ outline: 2px solid var(--luncher-primary);
+ outline-offset: 2px;
+}
+
+/* Selection color */
+::selection {
+ background: var(--luncher-primary-light);
+ color: var(--luncher-primary);
+}
diff --git a/client/src/pages/StatsPage.scss b/client/src/pages/StatsPage.scss
index 6b7c304..7b6c82b 100644
--- a/client/src/pages/StatsPage.scss
+++ b/client/src/pages/StatsPage.scss
@@ -2,15 +2,91 @@
display: flex;
flex-direction: column;
align-items: center;
- padding: 20px;
+ padding: 32px 24px;
+ min-height: calc(100vh - 140px);
+ background: var(--luncher-bg);
+
+ h1 {
+ font-size: 2rem;
+ font-weight: 700;
+ color: var(--luncher-text);
+ margin-bottom: 24px;
+ }
.week-navigator {
display: flex;
align-items: center;
- font-size: xx-large;
+ gap: 24px;
+ margin-bottom: 32px;
+
+ svg {
+ font-size: 1.5rem;
+ color: var(--luncher-text-secondary);
+ cursor: pointer;
+ padding: 12px;
+ border-radius: 50%;
+ background: var(--luncher-bg-card);
+ box-shadow: var(--luncher-shadow-sm);
+ transition: var(--luncher-transition);
+
+ &:hover {
+ color: var(--luncher-primary);
+ background: var(--luncher-primary-light);
+ transform: scale(1.05);
+ }
+ }
.date-range {
- margin: 5px 20px;
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--luncher-text);
+ min-width: 280px;
+ text-align: center;
}
}
-}
\ No newline at end of file
+
+ // Chart container
+ .recharts-wrapper {
+ background: var(--luncher-bg-card);
+ border-radius: var(--luncher-radius-lg);
+ box-shadow: var(--luncher-shadow);
+ padding: 24px;
+ border: 1px solid var(--luncher-border-light);
+ }
+
+ // Chart text styling
+ .recharts-cartesian-axis-tick-value {
+ fill: var(--luncher-text-secondary);
+ font-size: 0.85rem;
+ }
+
+ .recharts-legend-item-text {
+ color: var(--luncher-text) !important;
+ font-weight: 500;
+ }
+
+ .recharts-tooltip-wrapper {
+ .recharts-default-tooltip {
+ background: var(--luncher-bg-card) !important;
+ border: 1px solid var(--luncher-border) !important;
+ border-radius: var(--luncher-radius-sm) !important;
+ box-shadow: var(--luncher-shadow-lg) !important;
+
+ .recharts-tooltip-label {
+ color: var(--luncher-text) !important;
+ font-weight: 600;
+ margin-bottom: 8px;
+ }
+
+ .recharts-tooltip-item {
+ color: var(--luncher-text-secondary) !important;
+ }
+ }
+ }
+
+ .recharts-cartesian-grid-horizontal line,
+ .recharts-cartesian-grid-vertical line {
+ stroke: var(--luncher-border);
+ }
+}