Reorganise the views and prepare summary
All checks were successful
Build and Deploy / build-and-push (push) Successful in 23s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 23s
This commit is contained in:
@@ -1,10 +1,106 @@
|
||||
import { RECIPES } from '../data/catalog.js';
|
||||
import { MEAL_SLOTS } from '../planner/mealSlots.js';
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
const slotLabelMap = Object.fromEntries(MEAL_SLOTS.map((s) => [s.id, s.label]));
|
||||
|
||||
function slotLabelsFor(recipe) {
|
||||
return (recipe.allowedSlots || [])
|
||||
.map((id) => slotLabelMap[id])
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
let filterState = {
|
||||
query: '',
|
||||
slots: [],
|
||||
tags: [],
|
||||
maxMinutes: 120,
|
||||
};
|
||||
|
||||
function matchesFilters(recipe) {
|
||||
const { query, slots, tags, maxMinutes } = filterState;
|
||||
|
||||
if (query) {
|
||||
const q = query.toLowerCase();
|
||||
const haystack = `${recipe.title} ${recipe.description || ''} ${(recipe.tags || []).join(' ')}`.toLowerCase();
|
||||
if (!haystack.includes(q)) return false;
|
||||
}
|
||||
|
||||
if (slots.length > 0) {
|
||||
if (!recipe.allowedSlots.some((s) => slots.includes(s))) return false;
|
||||
}
|
||||
|
||||
if (tags.length > 0) {
|
||||
const recipeTags = (recipe.tags || []).map((t) => t.toLowerCase());
|
||||
if (!tags.some((t) => recipeTags.includes(t.toLowerCase()))) return false;
|
||||
}
|
||||
|
||||
if (recipe.minutes > maxMinutes) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFilteredRecipes() {
|
||||
return Object.values(RECIPES).filter(matchesFilters);
|
||||
}
|
||||
|
||||
function renderRecipeCard(recipe) {
|
||||
const labels = slotLabelsFor(recipe);
|
||||
return `
|
||||
<div onclick="openRecipeDetail('${escapeHtml(recipe.id)}')" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">${escapeHtml(recipe.thumbLabel)}</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">${escapeHtml(recipe.title)}</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">${escapeHtml(recipe.description || '')}</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>${recipe.minutes} min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>${recipe.nutritionPerServing.kcal} kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
${labels.map((l) => `<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">${escapeHtml(l)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderGrid() {
|
||||
const grid = document.getElementById('recipe-grid');
|
||||
if (!grid) return;
|
||||
|
||||
const recipes = getFilteredRecipes();
|
||||
if (recipes.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="col-span-2 flex flex-col items-center justify-center py-16 text-center">
|
||||
<div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
|
||||
<i class="fas fa-search text-2xl text-gray-300"></i>
|
||||
</div>
|
||||
<p class="text-sm font-semibold text-gray-700">Brak wyników</p>
|
||||
<p class="text-xs text-gray-500 mt-1 max-w-[220px] leading-relaxed">Zmień kryteria wyszukiwania lub filtry</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = recipes.map(renderRecipeCard).join('');
|
||||
}
|
||||
|
||||
export function getRecipeListHTML() {
|
||||
return `
|
||||
<div id="main-view" class="flex flex-col h-full absolute inset-0 bg-gray-50 z-10">
|
||||
<div class="p-4 border-b border-gray-200 mt-4 bg-white">
|
||||
<div class="flex items-center w-full border border-gray-300 rounded-lg bg-white focus-within:border-gray-400 transition-colors">
|
||||
<div class="pl-3 pr-2 text-gray-400"><i class="fas fa-search"></i></div>
|
||||
<input type="text" placeholder="Szukaj przepisów..." class="flex-1 py-2.5 bg-transparent outline-none text-gray-600 placeholder-gray-400 text-sm">
|
||||
<input type="text" id="recipe-search-input" placeholder="Szukaj przepisów..." class="flex-1 py-2.5 bg-transparent outline-none text-gray-600 placeholder-gray-400 text-sm">
|
||||
<div class="w-px h-6 bg-gray-200"></div>
|
||||
<button onclick="openFilters()" class="px-4 text-gray-700 hover:text-black flex items-center justify-center transition-colors">
|
||||
<i class="fas fa-sliders-h"></i>
|
||||
@@ -13,162 +109,34 @@ export function getRecipeListHTML() {
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto px-4 pt-4 pb-24 bg-gray-50">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Placki</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Puszyste placki</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Klasyczne placki na śniadanie</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>15 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>320 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Śniadanie</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Sałatka</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Sałatka z kurczakiem</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Zielone warzywa z grillowanym kurczakiem</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>20 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>250 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Obiad</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Makaron</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Makaron z pomidorami i bazylią</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Aromatyczny sos pomidorowy z czosnkiem</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>30 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>450 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Kolacja</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Koktajl</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Koktajl owocowy</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Mix jagód i jogurtu</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>5 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>180 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Przekąska</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Tost z awokado</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Tost z awokado</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Chleb na zakwasie z rozgniecionym awokado</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>10 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>220 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Śniadanie</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Łosoś</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Grillowany łosoś</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Świeży łosoś z masłem cytrynowym</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>25 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>380 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Kolacja</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Tacos</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Tacos z wołowiną</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Pikantna mielona wołowina ze świeżą salsą</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>20 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>410 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Kolacja</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="openRecipeDetail()" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">Owsianka</span>
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">Miska owsianki</h3>
|
||||
<p class="text-gray-500 text-xs mb-3 line-clamp-2">Ciepła owsianka z miodem i orzechami</p>
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center justify-between text-[11px] text-gray-600 font-medium mb-2">
|
||||
<div class="flex items-center gap-1"><i class="fas fa-clock text-gray-400"></i><span>10 min</span></div>
|
||||
<div class="flex items-center gap-1"><i class="fas fa-fire text-gray-400"></i><span>210 kcal</span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-[10px] rounded-md font-medium">Śniadanie</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="recipe-grid" class="grid grid-cols-2 gap-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function getFilterState() {
|
||||
return filterState;
|
||||
}
|
||||
|
||||
export function applyFilters(newState) {
|
||||
Object.assign(filterState, newState);
|
||||
renderGrid();
|
||||
}
|
||||
|
||||
export function getFilteredCount() {
|
||||
return getFilteredRecipes().length;
|
||||
}
|
||||
|
||||
export function refreshRecipeList() {
|
||||
renderGrid();
|
||||
}
|
||||
|
||||
export function setupRecipeList() {
|
||||
renderGrid();
|
||||
|
||||
document.getElementById('recipe-search-input')?.addEventListener('input', (e) => {
|
||||
filterState.query = e.target.value.trim();
|
||||
renderGrid();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user