138 lines
6.2 KiB
TypeScript
138 lines
6.2 KiB
TypeScript
import { Button, Form } from "react-bootstrap";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
|
import { RecipeIngredient } from "../../../types";
|
|
import { formatCalories, formatPriceInput, getIngredientCalories, parseIntInput, parsePriceInput } from "../Utils";
|
|
|
|
/** Rozpracovaná surovina — hodnoty jsou texty, protože se editují v polích. */
|
|
export type DraftIngredient = {
|
|
name: string,
|
|
weight: string,
|
|
caloriesPer100g: string,
|
|
calories: string,
|
|
price: string,
|
|
};
|
|
|
|
export const emptyIngredient = (): DraftIngredient => ({
|
|
name: '',
|
|
weight: '',
|
|
caloriesPer100g: '',
|
|
calories: '',
|
|
price: '',
|
|
});
|
|
|
|
/** Převede uloženou surovinu na rozpracovanou pro editaci. */
|
|
export const toDraftIngredient = (ingredient: RecipeIngredient): DraftIngredient => ({
|
|
name: ingredient.name,
|
|
weight: ingredient.weight != null ? String(ingredient.weight) : '',
|
|
caloriesPer100g: ingredient.caloriesPer100g != null ? String(ingredient.caloriesPer100g) : '',
|
|
calories: ingredient.calories != null ? String(ingredient.calories) : '',
|
|
price: formatPriceInput(ingredient.price),
|
|
});
|
|
|
|
/** Převede rozpracovanou surovinu na hodnoty pro výpočet a uložení. */
|
|
export const toIngredient = (draft: DraftIngredient): RecipeIngredient => ({
|
|
name: draft.name.trim(),
|
|
weight: parseIntInput(draft.weight),
|
|
caloriesPer100g: parseIntInput(draft.caloriesPer100g),
|
|
calories: parseIntInput(draft.calories),
|
|
price: parsePriceInput(draft.price),
|
|
});
|
|
|
|
type Props = {
|
|
ingredients: DraftIngredient[],
|
|
onChange: (ingredients: DraftIngredient[]) => void,
|
|
};
|
|
|
|
/**
|
|
* Editor surovin receptu.
|
|
*
|
|
* Energie suroviny se dopočte z gramáže a hodnoty na 100 g; ručně jde zadat jen
|
|
* tam, kde dopočítat nejde (třeba „lžíce oleje" bez vážení).
|
|
*/
|
|
const RecipeIngredientsEditor = ({ ingredients, onChange }: Readonly<Props>) => {
|
|
const updateAt = (index: number, changes: Partial<DraftIngredient>) =>
|
|
onChange(ingredients.map((item, i) => (i === index ? { ...item, ...changes } : item)));
|
|
|
|
return (
|
|
<>
|
|
{ingredients.map((ingredient, index) => {
|
|
const derived = getIngredientCalories({
|
|
weight: parseIntInput(ingredient.weight),
|
|
caloriesPer100g: parseIntInput(ingredient.caloriesPer100g),
|
|
});
|
|
return (
|
|
// Suroviny nemají vlastní id, v rámci rozpracovaného receptu je index jediný klíč
|
|
<div className="workout-item-row recipe-ingredient-row" key={index}>
|
|
<Form.Control
|
|
value={ingredient.name}
|
|
placeholder="Surovina"
|
|
aria-label="Název suroviny"
|
|
onChange={event => updateAt(index, { name: event.target.value })}
|
|
/>
|
|
<div className="input-group recipe-ingredient-weight">
|
|
<Form.Control
|
|
inputMode="numeric"
|
|
value={ingredient.weight}
|
|
placeholder="200"
|
|
aria-label="Množství v gramech"
|
|
onChange={event => updateAt(index, { weight: event.target.value.replace(/\D/g, '') })}
|
|
/>
|
|
<span className="input-group-text">g</span>
|
|
</div>
|
|
<Form.Control
|
|
className="recipe-ingredient-rate"
|
|
inputMode="numeric"
|
|
value={ingredient.caloriesPer100g}
|
|
placeholder="kcal/100 g"
|
|
aria-label="Energie na 100 g"
|
|
onChange={event => updateAt(index, { caloriesPer100g: event.target.value.replace(/\D/g, '') })}
|
|
/>
|
|
{derived != null ? (
|
|
<div className="recipe-ingredient-calories">{formatCalories(derived)}</div>
|
|
) : (
|
|
<Form.Control
|
|
className="recipe-ingredient-calories"
|
|
inputMode="numeric"
|
|
value={ingredient.calories}
|
|
placeholder="kcal"
|
|
title="Energie použitého množství, když ji nejde dopočítat z gramáže"
|
|
onChange={event => updateAt(index, { calories: event.target.value.replace(/\D/g, '') })}
|
|
/>
|
|
)}
|
|
<div className="input-group recipe-ingredient-price">
|
|
<Form.Control
|
|
inputMode="decimal"
|
|
value={ingredient.price}
|
|
placeholder="cena"
|
|
aria-label="Cena použitého množství"
|
|
onChange={event => updateAt(index, { price: event.target.value })}
|
|
/>
|
|
<span className="input-group-text">Kč</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="btn-icon danger"
|
|
onClick={() => onChange(ingredients.filter((_, i) => i !== index))}
|
|
disabled={ingredients.length === 1}
|
|
title="Odebrat surovinu"
|
|
aria-label="Odebrat surovinu"
|
|
>
|
|
<FontAwesomeIcon icon={faTrash} />
|
|
</button>
|
|
</div>
|
|
);
|
|
})}
|
|
<Button
|
|
variant="outline-secondary"
|
|
size="sm"
|
|
onClick={() => onChange([...ingredients, emptyIngredient()])}
|
|
>
|
|
<FontAwesomeIcon icon={faPlus} /> Surovina
|
|
</Button>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default RecipeIngredientsEditor;
|