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
+53
-17
@@ -1,7 +1,9 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { DayRecord, MealEntry, MealType } from "../../types/gen/types.gen";
|
||||
import getStorage from "./storage";
|
||||
import { BadRequestError, NotFoundError, isValidIsoDate, listDatesInRange } from "./utils";
|
||||
import {
|
||||
BadRequestError, NotFoundError, applyMultiplier, isValidIsoDate, listDatesInRange, normalizeMultiplier,
|
||||
} from "./utils";
|
||||
import { rememberCalories } from "./calories";
|
||||
|
||||
const storage = getStorage();
|
||||
@@ -47,7 +49,7 @@ export function buildDayRecord(date: string, entries: MealEntry[]): DayRecord {
|
||||
date,
|
||||
entries: sorted,
|
||||
totalPrice: sorted.reduce((sum, entry) => sum + (entry.price ?? 0), 0),
|
||||
totalCalories: sorted.reduce((sum, entry) => sum + (entry.calories ?? 0), 0),
|
||||
totalCalories: sorted.reduce((sum, entry) => sum + (applyMultiplier(entry.calories, entry.multiplier) ?? 0), 0),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,6 +124,7 @@ export type NormalizedMealInput = {
|
||||
weight?: number;
|
||||
pricePer100g?: number;
|
||||
caloriesPer100g?: number;
|
||||
multiplier?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -168,6 +171,7 @@ export function normalizeInput(input: unknown): NormalizedMealInput {
|
||||
weight: normalizeNumber(value.weight, 'weight'),
|
||||
pricePer100g: normalizeNumber(value.pricePer100g, 'pricePer100g'),
|
||||
caloriesPer100g: normalizeNumber(value.caloriesPer100g, 'caloriesPer100g'),
|
||||
multiplier: normalizeMultiplier(value.multiplier),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,8 +204,10 @@ export async function addMeal(login: string, input: unknown): Promise<DayRecord>
|
||||
*
|
||||
* @param login přihlašovací jméno uživatele
|
||||
* @param entries záznamy k uložení
|
||||
* @param replaceImportSource pokud je vyplněný, odeberou se z dotčených dnů nejdřív
|
||||
* dřívější záznamy z tohoto importu; ručně zadaná jídla zůstanou
|
||||
*/
|
||||
export async function addEntries(login: string, entries: MealEntry[]): Promise<string[]> {
|
||||
export async function addEntries(login: string, entries: MealEntry[], replaceImportSource?: string): Promise<string[]> {
|
||||
const byDate = new Map<string, MealEntry[]>();
|
||||
for (const entry of entries) {
|
||||
const forDate = byDate.get(entry.date) ?? [];
|
||||
@@ -211,7 +217,10 @@ export async function addEntries(login: string, entries: MealEntry[]): Promise<s
|
||||
for (const [date, forDate] of byDate) {
|
||||
await storage.updateData<MealEntry[]>(
|
||||
getDayKey(login, date),
|
||||
current => [...(current ?? []), ...forDate]
|
||||
current => [
|
||||
...(current ?? []).filter(entry => !replaceImportSource || entry.importSource !== replaceImportSource),
|
||||
...forDate,
|
||||
]
|
||||
);
|
||||
}
|
||||
return Array.from(byDate.keys()).sort();
|
||||
@@ -258,6 +267,7 @@ export async function updateMeal(login: string, id: unknown, input: unknown): Pr
|
||||
weight: normalized.weight,
|
||||
pricePer100g: normalized.pricePer100g,
|
||||
caloriesPer100g: normalized.caloriesPer100g,
|
||||
multiplier: normalized.multiplier,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
const entries = await storage.updateData<MealEntry[]>(
|
||||
@@ -295,6 +305,40 @@ export async function deleteMeal(login: string, id: unknown, date: unknown): Pro
|
||||
return buildDayRecord(date, entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nastaví násobek porce jednoho jídla. Ostatní údaje záznamu zůstanou beze změny,
|
||||
* takže jde násobek měnit přímo z přehledu dne bez otevření formuláře.
|
||||
*
|
||||
* @param login přihlašovací jméno uživatele
|
||||
* @param id identifikátor záznamu
|
||||
* @param date datum záznamu (YYYY-MM-DD)
|
||||
* @param multiplier nový násobek; 1 násobek zruší
|
||||
*/
|
||||
export async function setMealMultiplier(login: string, id: unknown, date: unknown, multiplier: unknown): Promise<string> {
|
||||
if (typeof id !== 'string' || !id.length) {
|
||||
throw new BadRequestError("Nebyl předán identifikátor záznamu");
|
||||
}
|
||||
if (!isValidIsoDate(date)) {
|
||||
throw new BadRequestError("Nebylo předáno platné datum ve formátu YYYY-MM-DD");
|
||||
}
|
||||
if (multiplier == null) {
|
||||
throw new BadRequestError("Nebyl předán násobek");
|
||||
}
|
||||
const normalized = normalizeMultiplier(multiplier);
|
||||
let found = false;
|
||||
await storage.updateData<MealEntry[]>(getDayKey(login, date), current => (current ?? []).map(entry => {
|
||||
if (entry.id !== id) return entry;
|
||||
found = true;
|
||||
const updated: MealEntry = { ...entry, multiplier: normalized, updatedAt: new Date().toISOString() };
|
||||
if (normalized == null) delete updated.multiplier;
|
||||
return updated;
|
||||
}));
|
||||
if (!found) {
|
||||
throw new NotFoundError("Záznam o jídle nebyl nalezen");
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Najde datum, pod kterým je záznam uložený.
|
||||
* Nejdřív zkusí očekávaný den, teprve pak prohledá ostatní klíče uživatele —
|
||||
@@ -317,21 +361,13 @@ async function findEntryDate(login: string, id: string, expectedDate: string): P
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí klíče importovaných záznamů uživatele v předaných dnech.
|
||||
* Slouží k rozpoznání duplicit při opakovaném importu stejného měsíce.
|
||||
* Vrátí všechna jídla uživatele v předaných dnech, seřazená dle dne a chodu.
|
||||
* Import podle nich rozpoznává duplicity a v náhledu je ukazuje uživateli.
|
||||
*
|
||||
* @param login přihlašovací jméno uživatele
|
||||
* @param dates dny, ve kterých se má hledat
|
||||
*/
|
||||
export async function getExistingImportKeys(login: string, dates: string[]): Promise<Set<string>> {
|
||||
const keys = new Set<string>();
|
||||
for (const date of dates) {
|
||||
const entries = await storage.getData<MealEntry[]>(getDayKey(login, date));
|
||||
for (const entry of entries ?? []) {
|
||||
if (entry.importKey) {
|
||||
keys.add(entry.importKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
export async function findMealsForDays(login: string, dates: string[]): Promise<MealEntry[]> {
|
||||
const days = await Promise.all([...dates].sort().map(date => getDay(login, date)));
|
||||
return days.flatMap(day => day.entries);
|
||||
}
|
||||
Reference in new issue
Block a user