feat: import jen některých dnů
feat: násobení jídel/cviků feat: refaktor napojování šablon
This commit is contained in:
1 parent
c3990d80f8
commit
8dac67bdd1
37 files changed
+2314
-481
No files matched your search
@@ -1,3 +1,5 @@
|
||||
import { MealTemplate } from "../../types";
|
||||
|
||||
const TOKEN_KEY = "token";
|
||||
|
||||
/** Uloží token do local storage prohlížeče. */
|
||||
@@ -253,3 +255,103 @@ export const formatQuantityWithSets = (quantity: number, unitShort: string, sets
|
||||
/** Vrátí celkové množství aktivity — počet sérií krát množství v jedné. */
|
||||
export const getTotalQuantity = (quantity?: number, sets?: number): number | undefined =>
|
||||
quantity == null ? undefined : quantity * Math.max(sets ?? 1, 1);
|
||||
|
||||
/**
|
||||
* Vrátí kalorie záznamu po započtení násobku porce.
|
||||
* Musí dávat stejné výsledky jako `applyMultiplier` na serveru.
|
||||
*/
|
||||
export const applyMultiplier = (calories?: number, multiplier?: number): number | undefined =>
|
||||
calories == null ? undefined : Math.round(calories * (multiplier ?? 1));
|
||||
|
||||
/** Naformátuje násobek, např. 1.5 → "×1,5". */
|
||||
export const formatMultiplier = (multiplier?: number): string =>
|
||||
`×${(multiplier ?? 1).toLocaleString('cs-CZ', { maximumFractionDigits: 2 })}`;
|
||||
|
||||
/** Převede násobek zadaný uživatelem (i s desetinnou čárkou) na číslo; nekladný vstup odmítne. */
|
||||
export const parseMultiplierInput = (value: string): number | undefined => {
|
||||
const trimmed = value.trim().replace(',', '.');
|
||||
if (!trimmed.length) return undefined;
|
||||
const parsed = Number(trimmed);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed * 100) / 100 : undefined;
|
||||
};
|
||||
|
||||
/** Převede násobek na text pro editační pole; výchozí 1 nechá prázdné. */
|
||||
export const formatMultiplierInput = (multiplier?: number): string =>
|
||||
multiplier == null || multiplier === 1 ? '' : String(multiplier).replace('.', ',');
|
||||
|
||||
/** Naformátuje dodatečnou zátěž z gramů, např. 12500 → "+12,5 kg". */
|
||||
export const formatExtraWeight = (grams?: number): string =>
|
||||
grams ? `+${(grams / 1000).toLocaleString('cs-CZ', { maximumFractionDigits: 2 })} kg` : '';
|
||||
|
||||
/** Převede gramy na kilogramy pro editační pole bez zbytečných desetinných míst. */
|
||||
export const formatKgInput = (grams?: number): string =>
|
||||
grams ? String(grams / 1000).replace('.', ',') : '';
|
||||
|
||||
/** Surovina receptu tak, jak ji zná výpočet — hodnoty jsou už čísla. */
|
||||
type RecipeIngredientValues = {
|
||||
weight?: number,
|
||||
caloriesPer100g?: number,
|
||||
calories?: number,
|
||||
price?: number,
|
||||
};
|
||||
|
||||
/** Energie suroviny — z gramáže a energie na 100 g, jinak ručně zadaná. */
|
||||
export const getIngredientCalories = (ingredient: RecipeIngredientValues): number | undefined =>
|
||||
ingredient.weight != null && ingredient.caloriesPer100g
|
||||
? Math.round(ingredient.weight / 100 * ingredient.caloriesPer100g)
|
||||
: ingredient.calories;
|
||||
|
||||
/** Sečte vyplněné hodnoty; když není vyplněná žádná, vrátí undefined. */
|
||||
const sumDefined = (valueList: (number | undefined)[]): number | undefined => {
|
||||
const definedList = valueList.filter(value => value != null);
|
||||
return definedList.length ? definedList.reduce((sum, value) => sum + value, 0) : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dopočte ze surovin hodnoty hotového jídla a jedné porce.
|
||||
* Musí dávat stejné výsledky jako `deriveRecipe` na serveru.
|
||||
*
|
||||
* @param ingredients suroviny receptu
|
||||
* @param servings počet porcí, nevyplněný znamená jednu
|
||||
* @param cookedWeight hmotnost hotového jídla v gramech, bez ní součet surovin
|
||||
*/
|
||||
export const deriveRecipe = (
|
||||
ingredients: RecipeIngredientValues[],
|
||||
servings?: number,
|
||||
cookedWeight?: number,
|
||||
) => {
|
||||
const portions = Math.max(servings ?? 1, 1);
|
||||
const totalCalories = sumDefined(ingredients.map(getIngredientCalories));
|
||||
const totalWeight = cookedWeight || sumDefined(ingredients.map(ingredient => ingredient.weight)) || undefined;
|
||||
const totalPrice = sumDefined(ingredients.map(ingredient => ingredient.price));
|
||||
return {
|
||||
totalCalories,
|
||||
totalWeight,
|
||||
totalPrice,
|
||||
weight: totalWeight != null ? Math.round(totalWeight / portions) : undefined,
|
||||
calories: totalCalories != null ? Math.round(totalCalories / portions) : undefined,
|
||||
caloriesPer100g: totalCalories != null && totalWeight
|
||||
? Math.round(totalCalories / totalWeight * 100)
|
||||
: undefined,
|
||||
price: totalPrice != null ? Math.round(totalPrice / portions) : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
/** Znormalizuje text pro hledání — bez diakritiky a malými písmeny. */
|
||||
export const normalizeSearch = (value: string): string =>
|
||||
value.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();
|
||||
|
||||
/**
|
||||
* Popíše šablonu jídla jednou řádkou — zdroj, gramáž porce, kalorie a cena,
|
||||
* co je vyplněné. U receptu jsou to hodnoty jedné porce, které dopočítal server.
|
||||
*/
|
||||
export const describeMealTemplate = (template: MealTemplate): string => {
|
||||
const derived = deriveAmounts(template.price, template.pricePer100g, template.weight, template.caloriesPer100g);
|
||||
const calories = derived.calories ?? template.calories;
|
||||
return [
|
||||
template.source,
|
||||
derived.weight != null ? formatWeight(derived.weight) : undefined,
|
||||
calories != null ? formatCalories(calories) : undefined,
|
||||
template.price != null ? formatPrice(template.price) : undefined,
|
||||
].filter(item => !!item).join(' · ');
|
||||
};
|
||||
Reference in new issue
Block a user