145 lines
6.0 KiB
TypeScript
145 lines
6.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Button, Form, Modal } from "react-bootstrap";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faTrash, faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
import { UserSettings, getSettings, saveBasalCalories, saveSourceRate } from "../../../../types";
|
|
import { formatPriceInput, parseIntInput, parsePriceInput } from "../../Utils";
|
|
|
|
type Props = {
|
|
isOpen: boolean,
|
|
onClose: () => void,
|
|
};
|
|
|
|
/**
|
|
* Nastavení sazeb za 100 g u podniků, které účtují podle váhy.
|
|
*
|
|
* Sazba slouží jako výchozí hodnota při zadávání jídla — u konkrétní položky
|
|
* ji jde přepsat, protože salát bývá účtovaný jinak než hlavní jídlo.
|
|
*/
|
|
export default function SettingsModal({ isOpen, onClose }: Readonly<Props>) {
|
|
const [settings, setSettings] = useState<UserSettings | undefined>();
|
|
const [newSource, setNewSource] = useState('');
|
|
const [newRate, setNewRate] = useState('');
|
|
const [basal, setBasal] = useState('');
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
setNewSource('');
|
|
setNewRate('');
|
|
getSettings().then(response => {
|
|
setSettings(response.data);
|
|
setBasal(response.data?.basalCalories != null ? String(response.data.basalCalories) : '');
|
|
});
|
|
}, [isOpen]);
|
|
|
|
const save = async (source: string, pricePer100g: number | null) => {
|
|
setSaving(true);
|
|
const response = await saveSourceRate({ body: { source, pricePer100g } });
|
|
setSaving(false);
|
|
if (response.data) {
|
|
setSettings(response.data);
|
|
}
|
|
};
|
|
|
|
/** Uloží klidový výdej. Prázdné pole ho odstraní. */
|
|
const saveBasal = async () => {
|
|
setSaving(true);
|
|
const response = await saveBasalCalories({ body: { basalCalories: parseIntInput(basal) ?? null } });
|
|
setSaving(false);
|
|
if (response.data) {
|
|
setSettings(response.data);
|
|
}
|
|
};
|
|
|
|
const addRate = async () => {
|
|
const rate = parsePriceInput(newRate);
|
|
if (!newSource.trim().length || !rate) return;
|
|
await save(newSource.trim(), rate);
|
|
setNewSource('');
|
|
setNewRate('');
|
|
};
|
|
|
|
return (
|
|
<Modal show={isOpen} onHide={onClose} centered>
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>Nastavení</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
<h3 className="settings-section-title">Klidový výdej</h3>
|
|
<p className="text-body-secondary small">
|
|
Kolik kcal spálíte za den bez pohybu (bazální metabolismus). Bez něj
|
|
porovnává bilance dne jen jídlo proti pohybu a nejde o skutečný deficit.
|
|
</p>
|
|
<div className="rate-form mb-4">
|
|
<div className="input-group">
|
|
<Form.Control
|
|
inputMode="numeric"
|
|
value={basal}
|
|
placeholder="např. 1600"
|
|
onChange={event => setBasal(event.target.value.replace(/\D/g, ''))}
|
|
/>
|
|
<span className="input-group-text">kcal / den</span>
|
|
</div>
|
|
<Button onClick={saveBasal} disabled={saving}>Uložit</Button>
|
|
</div>
|
|
|
|
<h3 className="settings-section-title">Cena za 100 g</h3>
|
|
<p className="text-body-secondary small">
|
|
U podniků, které účtují podle váhy, se z ceny jídla a této sazby dopočítá
|
|
gramáž a z ní kalorie. U konkrétního jídla jde sazbu vždy přepsat.
|
|
</p>
|
|
|
|
{settings?.sourceRates.length === 0 && (
|
|
<p className="text-body-secondary small fst-italic">Zatím nemáte žádnou sazbu.</p>
|
|
)}
|
|
|
|
{settings?.sourceRates.map(rate => (
|
|
<div className="rate-row" key={rate.source}>
|
|
<span className="rate-source">{rate.source}</span>
|
|
<span className="rate-value">{formatPriceInput(rate.pricePer100g)} Kč / 100 g</span>
|
|
<button
|
|
className="btn-icon danger"
|
|
onClick={() => save(rate.source, null)}
|
|
disabled={saving}
|
|
title="Odebrat sazbu"
|
|
aria-label={`Odebrat sazbu ${rate.source}`}
|
|
>
|
|
<FontAwesomeIcon icon={faTrash} />
|
|
</button>
|
|
</div>
|
|
))}
|
|
|
|
<Form
|
|
className="rate-form"
|
|
onSubmit={event => { event.preventDefault(); addRate(); }}
|
|
>
|
|
<Form.Control
|
|
value={newSource}
|
|
placeholder="Podnik, např. TechTower"
|
|
onChange={event => setNewSource(event.target.value)}
|
|
/>
|
|
<div className="input-group rate-form-price">
|
|
<Form.Control
|
|
inputMode="decimal"
|
|
value={newRate}
|
|
placeholder="44,00"
|
|
onChange={event => setNewRate(event.target.value)}
|
|
/>
|
|
<span className="input-group-text">Kč</span>
|
|
</div>
|
|
<Button
|
|
onClick={addRate}
|
|
disabled={saving || !newSource.trim().length || !parsePriceInput(newRate)}
|
|
>
|
|
<FontAwesomeIcon icon={faPlus} />
|
|
</Button>
|
|
</Form>
|
|
</Modal.Body>
|
|
<Modal.Footer>
|
|
<Button variant="outline-secondary" onClick={onClose}>Zavřít</Button>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
);
|
|
}
|