fix: oprava pádu při zdvojeném menu
CI / Generate TypeScript types (push) Successful in 13s
CI / Server unit tests (push) Successful in 23s
CI / Build server (push) Successful in 26s
CI / Build client (push) Successful in 38s
CI / Playwright E2E tests (push) Successful in 1m29s
CI / Build and push Docker image (push) Successful in 43s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 13s
CI / Server unit tests (push) Successful in 23s
CI / Build server (push) Successful in 26s
CI / Build client (push) Successful in 38s
CI / Playwright E2E tests (push) Successful in 1m29s
CI / Build and push Docker image (push) Successful in 43s
CI / Notify (push) Successful in 2s
This commit is contained in:
+4
-2
@@ -778,9 +778,11 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
{userChoices && userChoices.length > 0 && food && (
|
{userChoices && userChoices.length > 0 && food && (
|
||||||
<div className="food-choices">
|
<div className="food-choices">
|
||||||
{userChoices.map(foodIndex => {
|
{userChoices
|
||||||
|
.filter(foodIndex => food[key as Restaurant]?.food?.[foodIndex] != null)
|
||||||
|
.map(foodIndex => {
|
||||||
const restaurantKey = key as Restaurant;
|
const restaurantKey = key as Restaurant;
|
||||||
const foodName = food[restaurantKey]?.food?.[foodIndex].name;
|
const foodName = food[restaurantKey]?.food?.[foodIndex]?.name;
|
||||||
return <div key={foodIndex} className="food-choice-item">
|
return <div key={foodIndex} className="food-choice-item">
|
||||||
<span className="food-choice-name">{foodName}</span>
|
<span className="food-choice-name">{foodName}</span>
|
||||||
{login === auth.login && canChangeChoice &&
|
{login === auth.login && canChangeChoice &&
|
||||||
|
|||||||
+45
-1
@@ -4,7 +4,7 @@ import { getMenuSladovnicka, getMenuTechTower, getMenuZastavkaUmichala, getMenuS
|
|||||||
import { getTodayMock } from "./mock";
|
import { getTodayMock } from "./mock";
|
||||||
import { removeAllUserPizzas } from "./pizza";
|
import { removeAllUserPizzas } from "./pizza";
|
||||||
import { getStores } from "./stores";
|
import { getStores } from "./stores";
|
||||||
import { ClientData, DepartureTime, LunchChoice, MealSlot, PizzaDayState, Restaurant, RestaurantDayMenu, WeekMenu } from "../../types/gen/types.gen";
|
import { ClientData, DepartureTime, LunchChoice, LunchChoices, MealSlot, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, WeekMenu } from "../../types/gen/types.gen";
|
||||||
|
|
||||||
const storage = getStorage();
|
const storage = getStorage();
|
||||||
const MENU_PREFIX = 'menu';
|
const MENU_PREFIX = 'menu';
|
||||||
@@ -62,10 +62,54 @@ export async function getData(date?: Date, slot?: MealSlot): Promise<ClientData>
|
|||||||
ZASTAVKAUMICHALA: await getRestaurantMenu('ZASTAVKAUMICHALA', date),
|
ZASTAVKAUMICHALA: await getRestaurantMenu('ZASTAVKAUMICHALA', date),
|
||||||
SENKSERIKOVA: await getRestaurantMenu('SENKSERIKOVA', date),
|
SENKSERIKOVA: await getRestaurantMenu('SENKSERIKOVA', date),
|
||||||
}
|
}
|
||||||
|
// Očistí volby jídel odkazující na indexy, které v aktuálním menu již neexistují.
|
||||||
|
// Pokud se v odpovědi něco změnilo, promítne očistu i do uložených dat (self-heal).
|
||||||
|
if (pruneStaleFoodChoices(clientData.choices, clientData.menus)) {
|
||||||
|
await storage.updateData<ClientData>(getDataKey(date ?? getToday(), slot), (current) => {
|
||||||
|
const data = current ?? getEmptyData(date);
|
||||||
|
pruneStaleFoodChoices(data.choices, clientData.menus!);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return clientData;
|
return clientData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Odstraní z voleb uživatelů indexy jídel, které v aktuálním menu daného podniku již neexistují.
|
||||||
|
*
|
||||||
|
* Nastává typicky poté, co podnik ze svého webu odebere menu předchozího týdne: aplikace pak
|
||||||
|
* pro daný den vyparsuje méně jídel než dříve a dříve uložený index (např. z „budoucího“ týdne)
|
||||||
|
* se stane neplatným. Bez očištění by klient spadl při přístupu na neexistující jídlo a data
|
||||||
|
* by zůstala nekonzistentní.
|
||||||
|
*
|
||||||
|
* Očista se provádí pouze u podniků, které mají v menu alespoň jedno jídlo – prázdné menu
|
||||||
|
* (zavřeno / neúspěšné načtení) volby záměrně nemaže, aby nedošlo ke ztrátě platných voleb.
|
||||||
|
*
|
||||||
|
* @param choices volby uživatelů
|
||||||
|
* @param menus menu podniků pro daný den
|
||||||
|
* @returns true, pokud došlo k odebrání alespoň jednoho indexu
|
||||||
|
*/
|
||||||
|
export function pruneStaleFoodChoices(choices: LunchChoices, menus: RestaurantDayMenuMap): boolean {
|
||||||
|
let mutated = false;
|
||||||
|
for (const restaurant of Object.keys(Restaurant) as Restaurant[]) {
|
||||||
|
const foodCount = menus[restaurant]?.food?.length ?? 0;
|
||||||
|
if (foodCount === 0) continue; // prázdné menu volby nemažeme
|
||||||
|
const locationChoices = choices[restaurant];
|
||||||
|
if (!locationChoices) continue;
|
||||||
|
for (const userChoice of Object.values(locationChoices)) {
|
||||||
|
const selected = userChoice.selectedFoods;
|
||||||
|
if (!selected?.length) continue;
|
||||||
|
const filtered = selected.filter(index => index < foodCount);
|
||||||
|
if (filtered.length !== selected.length) {
|
||||||
|
userChoice.selectedFoods = filtered;
|
||||||
|
mutated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mutated;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vrátí klíč, pod kterým je uloženo menu pro týden příslušící předanému datu.
|
* Vrátí klíč, pod kterým je uloženo menu pro týden příslušící předanému datu.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { pruneStaleFoodChoices } from '../service';
|
||||||
|
import { LunchChoices, RestaurantDayMenuMap } from '../../../types/gen/types.gen';
|
||||||
|
|
||||||
|
// Pomocná fabrika menu s daným počtem jídel pro TECHTOWER.
|
||||||
|
const menusWithTechTowerFoods = (count: number): RestaurantDayMenuMap => ({
|
||||||
|
TECHTOWER: {
|
||||||
|
lastUpdate: 0,
|
||||||
|
closed: false,
|
||||||
|
food: Array.from({ length: count }, (_, i) => ({ name: `Jídlo ${i}` })),
|
||||||
|
},
|
||||||
|
} as unknown as RestaurantDayMenuMap);
|
||||||
|
|
||||||
|
describe('pruneStaleFoodChoices', () => {
|
||||||
|
test('odebere index jídla, který v aktuálním menu již neexistuje', () => {
|
||||||
|
const choices: LunchChoices = {
|
||||||
|
TECHTOWER: { alice: { selectedFoods: [0, 9] } },
|
||||||
|
};
|
||||||
|
const mutated = pruneStaleFoodChoices(choices, menusWithTechTowerFoods(5));
|
||||||
|
expect(mutated).toBe(true);
|
||||||
|
expect(choices.TECHTOWER!.alice.selectedFoods).toEqual([0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nechá platné indexy beze změny a vrátí false', () => {
|
||||||
|
const choices: LunchChoices = {
|
||||||
|
TECHTOWER: { alice: { selectedFoods: [0, 4] } },
|
||||||
|
};
|
||||||
|
const mutated = pruneStaleFoodChoices(choices, menusWithTechTowerFoods(5));
|
||||||
|
expect(mutated).toBe(false);
|
||||||
|
expect(choices.TECHTOWER!.alice.selectedFoods).toEqual([0, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('u prázdného menu (zavřeno / neúspěšné načtení) volby nemaže', () => {
|
||||||
|
const choices: LunchChoices = {
|
||||||
|
TECHTOWER: { alice: { selectedFoods: [3] } },
|
||||||
|
};
|
||||||
|
const mutated = pruneStaleFoodChoices(choices, menusWithTechTowerFoods(0));
|
||||||
|
expect(mutated).toBe(false);
|
||||||
|
expect(choices.TECHTOWER!.alice.selectedFoods).toEqual([3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('očistí volby více uživatelů najednou', () => {
|
||||||
|
const choices: LunchChoices = {
|
||||||
|
TECHTOWER: {
|
||||||
|
alice: { selectedFoods: [7] },
|
||||||
|
bob: { selectedFoods: [1, 2] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mutated = pruneStaleFoodChoices(choices, menusWithTechTowerFoods(5));
|
||||||
|
expect(mutated).toBe(true);
|
||||||
|
expect(choices.TECHTOWER!.alice.selectedFoods).toEqual([]);
|
||||||
|
expect(choices.TECHTOWER!.bob.selectedFoods).toEqual([1, 2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user