Podpora parsování a zobrazení alergenů
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
This commit is contained in:
parent
c2a001b7e5
commit
81f67c8424
@ -30,6 +30,24 @@ const EASTER_EGG_STYLE = {
|
|||||||
animationTimingFunction: "ease"
|
animationTimingFunction: "ease"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mapování čísel alergenů na jejich názvy
|
||||||
|
const ALLERGENS: { [key: number]: string } = {
|
||||||
|
1: "Obiloviny obsahující lepek",
|
||||||
|
2: "Korýši a výrobky z nich",
|
||||||
|
3: "Vejce a výrobky z nich",
|
||||||
|
4: "Ryby a výrobky z nich",
|
||||||
|
5: "Arašidy a výrobky z nich",
|
||||||
|
6: "Sója a výrobky z nich",
|
||||||
|
7: "Mléko a výrobky z nich (včetně laktózy)",
|
||||||
|
8: "Skořápkové plody",
|
||||||
|
9: "Celer a výrobky z něj",
|
||||||
|
10: "Hořčice a výrobky z ní",
|
||||||
|
11: "Sezamová semena a výrobky z nich",
|
||||||
|
12: "Oxid siřičitý a siřičitany",
|
||||||
|
13: "Vlčí bob (Lupina) a výrobky z něj",
|
||||||
|
14: "Měkkýši a výrobky z nich"
|
||||||
|
}
|
||||||
|
|
||||||
// Výchozí doba trvání animace v sekundách, pokud není přetíženo v konfiguračním JSONu
|
// Výchozí doba trvání animace v sekundách, pokud není přetíženo v konfiguračním JSONu
|
||||||
const EASTER_EGG_DEFAULT_DURATION = 0.75;
|
const EASTER_EGG_DEFAULT_DURATION = 0.75;
|
||||||
|
|
||||||
@ -352,7 +370,17 @@ function App() {
|
|||||||
(!hideSoups || !f.isSoup) &&
|
(!hideSoups || !f.isSoup) &&
|
||||||
<tr key={f.name} onClick={() => doAddClickFoodChoice(location, index)}>
|
<tr key={f.name} onClick={() => doAddClickFoodChoice(location, index)}>
|
||||||
<td>{f.amount}</td>
|
<td>{f.amount}</td>
|
||||||
<td>{f.name}</td>
|
<td>
|
||||||
|
{f.name}
|
||||||
|
{f.allergens && f.allergens.length > 0 && (
|
||||||
|
<> ({f.allergens.map((a, idx) => (
|
||||||
|
<span key={a}>
|
||||||
|
<span title={ALLERGENS[a]} style={{ cursor: 'help', textDecoration: 'underline' }}>{a}</span>
|
||||||
|
{idx < f.allergens!.length - 1 && ','}
|
||||||
|
</span>
|
||||||
|
))})</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
<td>{f.price}</td>
|
<td>{f.price}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
@ -412,8 +440,7 @@ function App() {
|
|||||||
<img alt="" src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} /> */}
|
<img alt="" src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} /> */}
|
||||||
Poslední změny:
|
Poslední změny:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Podpora ručního refresh týdne</li>
|
<li>Zobrazení alergenu při najetí myší</li>
|
||||||
<li>Úprava pro přepracovanou podobu stránek Sladovnická</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
{dayIndex != null &&
|
{dayIndex != null &&
|
||||||
|
@ -53,6 +53,28 @@ const sanitizeText = (text: string): string => {
|
|||||||
return text.replace('\t', '').replace(' , ', ', ').trim();
|
return text.replace('\t', '').replace(' , ', ', ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsuje čísla alergenů z názvu jídla a vrací vyčištěný název spolu s polem alergenů.
|
||||||
|
* Alergeny jsou očekávány na konci názvu ve formátu číslic oddělených čárkami.
|
||||||
|
*
|
||||||
|
* @param name původní název jídla
|
||||||
|
* @returns objekt obsahující vyčištěný název a pole alergenů
|
||||||
|
*/
|
||||||
|
const parseAllergens = (name: string): { cleanName: string, allergens: number[] } => {
|
||||||
|
// Regex pro nalezení čísel na konci řetězce oddělených čárkami a případnými mezerami
|
||||||
|
const regex = /\s+(\d+(?:\s*,\s*\d+)*)\s*$/;
|
||||||
|
const match = regex.exec(name);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const allergenString = match[1];
|
||||||
|
const allergens = allergenString.split(',').map(num => parseInt(num.trim(), 10)).filter(num => !isNaN(num));
|
||||||
|
const cleanName = name.replace(regex, '').trim();
|
||||||
|
return { cleanName, allergens };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { cleanName: name, allergens: [] };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stáhne a vrátí aktuální HTML z dané URL.
|
* Stáhne a vrátí aktuální HTML z dané URL.
|
||||||
*
|
*
|
||||||
@ -101,8 +123,9 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
|
|||||||
}
|
}
|
||||||
|
|
||||||
const soupAmount = sanitizeText($(soupCells.get(0)).text());
|
const soupAmount = sanitizeText($(soupCells.get(0)).text());
|
||||||
const soupName = sanitizeText($(soupCells.get(1)).text());
|
const soupNameRaw = sanitizeText($(soupCells.get(1)).text());
|
||||||
const soupPrice = sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0'));
|
const soupPrice = sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0'));
|
||||||
|
const soupParsed = parseAllergens(soupNameRaw);
|
||||||
|
|
||||||
// Parsování hlavních jídel
|
// Parsování hlavních jídel
|
||||||
const mainCourseElement = dayChildren.get(1);
|
const mainCourseElement = dayChildren.get(1);
|
||||||
@ -114,25 +137,28 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
|
|||||||
// Přidáme polévku do seznamu jídel
|
// Přidáme polévku do seznamu jídel
|
||||||
currentDayFood.push({
|
currentDayFood.push({
|
||||||
amount: soupAmount,
|
amount: soupAmount,
|
||||||
name: soupName,
|
name: soupParsed.cleanName,
|
||||||
price: soupPrice,
|
price: soupPrice,
|
||||||
isSoup: true,
|
isSoup: true,
|
||||||
|
allergens: soupParsed.allergens.length > 0 ? soupParsed.allergens : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Projdeme všechny řádky hlavních jídel
|
// Projdeme všechny řádky hlavních jídel
|
||||||
mainCourseRows.each((i, row) => {
|
mainCourseRows.each((i, row) => {
|
||||||
const cells = $(row).children('td');
|
const cells = $(row).children('td');
|
||||||
const amount = sanitizeText($(cells.get(0)).text());
|
const amount = sanitizeText($(cells.get(0)).text());
|
||||||
const name = sanitizeText($(cells.get(1)).text());
|
const nameRaw = sanitizeText($(cells.get(1)).text());
|
||||||
const price = sanitizeText($(cells.get(2)).text().replace(' ', '\xA0'));
|
const price = sanitizeText($(cells.get(2)).text().replace(' ', '\xA0'));
|
||||||
|
const parsed = parseAllergens(nameRaw);
|
||||||
|
|
||||||
// Přeskočíme prázdné řádky (první řádek může být prázdný)
|
// Přeskočíme prázdné řádky (první řádek může být prázdný)
|
||||||
if (name.trim().length > 0) {
|
if (parsed.cleanName.trim().length > 0) {
|
||||||
currentDayFood.push({
|
currentDayFood.push({
|
||||||
amount,
|
amount,
|
||||||
name,
|
name: parsed.cleanName,
|
||||||
price,
|
price,
|
||||||
isSoup: false,
|
isSoup: false,
|
||||||
|
allergens: parsed.allergens.length > 0 ? parsed.allergens : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -293,19 +319,25 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let price = 'na\xA0váhu';
|
let price = 'na\xA0váhu';
|
||||||
let name = text.replace('•', '');
|
let nameRaw = text.replace('•', '');
|
||||||
if (text.toLowerCase().endsWith('kč')) {
|
if (text.toLowerCase().endsWith('kč')) {
|
||||||
const tmp = text.replace('\xA0', ' ').split(' ');
|
const tmp = text.replace('\xA0', ' ').split(' ');
|
||||||
const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
|
const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
|
||||||
price = `${split.slice(1)[0]}\xA0Kč`
|
price = `${split.slice(1)[0]}\xA0Kč`
|
||||||
name = split[0].replace('•', '');
|
nameRaw = split[0].replace('•', '');
|
||||||
}
|
}
|
||||||
|
if (nameRaw.endsWith('–')) {
|
||||||
|
nameRaw = nameRaw.slice(0, -1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = parseAllergens(nameRaw);
|
||||||
result[currentDayIndex] ??= [];
|
result[currentDayIndex] ??= [];
|
||||||
result[currentDayIndex].push({
|
result[currentDayIndex].push({
|
||||||
amount: '-',
|
amount: '-',
|
||||||
name,
|
name: parsed.cleanName,
|
||||||
price,
|
price,
|
||||||
isSoup: isTextSoupName(name),
|
isSoup: isTextSoupName(parsed.cleanName),
|
||||||
|
allergens: parsed.allergens.length > 0 ? parsed.allergens : undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,9 +437,11 @@ export const getMenuSenkSerikova = async (firstDayOfWeek: Date, mock: boolean =
|
|||||||
$('.menicka').each((i, element) => {
|
$('.menicka').each((i, element) => {
|
||||||
const currentDayFood: Food[] = [];
|
const currentDayFood: Food[] = [];
|
||||||
$(element).find('.popup-gallery li').each((j, element) => {
|
$(element).find('.popup-gallery li').each((j, element) => {
|
||||||
|
const rawName = $(element).children('div.polozka').text();
|
||||||
|
const nameWithoutNumber = rawName.replace(/^\d+\.\s*/, '');
|
||||||
currentDayFood.push({
|
currentDayFood.push({
|
||||||
amount: '-',
|
amount: '-',
|
||||||
name: $(element).children('div.polozka').text(),
|
name: nameWithoutNumber,
|
||||||
price: $(element).children('div.cena').text().replace(/ /g, '\xA0'),
|
price: $(element).children('div.cena').text().replace(/ /g, '\xA0'),
|
||||||
isSoup: $(element).hasClass('polevka'),
|
isSoup: $(element).hasClass('polevka'),
|
||||||
});
|
});
|
||||||
|
@ -151,6 +151,12 @@ Food:
|
|||||||
isSoup:
|
isSoup:
|
||||||
description: Příznak, zda se jedná o polévku
|
description: Příznak, zda se jedná o polévku
|
||||||
type: boolean
|
type: boolean
|
||||||
|
allergens:
|
||||||
|
description: Seznam čísel alergenů obsažených v jídle
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
example: [1, 3, 7]
|
||||||
RestaurantDayMenu:
|
RestaurantDayMenu:
|
||||||
description: Menu restaurace na konkrétní den
|
description: Menu restaurace na konkrétní den
|
||||||
type: object
|
type: object
|
||||||
|
Loading…
x
Reference in New Issue
Block a user