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,41 +1,48 @@
|
||||
import { RECIPES } from '../data/catalog.js';
|
||||
import { MEAL_SLOTS } from '../planner/mealSlots.js';
|
||||
import { applyFilters, getFilterState, getFilteredCount, refreshRecipeList } from './RecipeList.js';
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function collectAllTags() {
|
||||
const tags = new Set();
|
||||
for (const r of Object.values(RECIPES)) {
|
||||
for (const t of (r.tags || [])) tags.add(t);
|
||||
}
|
||||
return [...tags].sort((a, b) => a.localeCompare(b, 'pl'));
|
||||
}
|
||||
|
||||
export function getFilterHTML() {
|
||||
return `
|
||||
<div id="filter-view" class="absolute inset-0 bg-white z-50 hidden flex-col">
|
||||
<div class="p-4 border-b border-gray-200 flex items-center justify-between mt-4">
|
||||
<button onclick="closeFilters()" class="w-10 h-10 flex items-center justify-center text-gray-600 hover:bg-gray-100 rounded-full transition-colors"><i class="fas fa-arrow-left text-lg"></i></button>
|
||||
<button id="filter-close-btn" class="w-10 h-10 flex items-center justify-center text-gray-600 hover:bg-gray-100 rounded-full transition-colors"><i class="fas fa-arrow-left text-lg"></i></button>
|
||||
<h2 class="text-lg font-semibold text-black">Filtry</h2>
|
||||
<button class="px-2 text-sm font-medium text-gray-500 hover:text-black transition-colors">Wyczyść</button>
|
||||
<button id="filter-clear-btn" class="px-2 text-sm font-medium text-gray-500 hover:text-black transition-colors">Wyczyść</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 space-y-8">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-black mb-4">Pora posiłku</h3>
|
||||
<div class="flex flex-wrap gap-2.5">
|
||||
<button class="px-4 py-2 bg-gray-900 text-white rounded-full text-sm font-medium transition-colors">Śniadanie</button>
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Drugie śniadanie</button>
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Obiad</button>
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Podwieczorek</button>
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Kolacja</button>
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Przekąska</button>
|
||||
</div>
|
||||
<div id="filter-slot-chips" class="flex flex-wrap gap-2.5"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-black mb-4">Dieta i tagi</h3>
|
||||
<div class="flex flex-wrap gap-2.5">
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Wegetariańska</button>
|
||||
<button class="px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors">Wegańska</button>
|
||||
<button class="px-4 py-2 bg-gray-900 text-white rounded-full text-sm font-medium transition-colors">Wysokobiałkowe</button>
|
||||
</div>
|
||||
<div id="filter-tag-chips" class="flex flex-wrap gap-2.5"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-base font-semibold text-black">Maks. czas przygotowania</h3>
|
||||
<span id="time-display" class="text-sm font-medium text-gray-600">30 min</span>
|
||||
<span id="time-display" class="text-sm font-medium text-gray-600">120 min</span>
|
||||
</div>
|
||||
<div class="px-1">
|
||||
<input type="range" id="prep-time-slider" min="5" max="120" step="5" value="30" class="w-full appearance-none bg-transparent">
|
||||
<input type="range" id="prep-time-slider" min="5" max="120" step="5" value="120" class="w-full appearance-none bg-transparent">
|
||||
<div class="flex justify-between text-xs text-gray-400 mt-3 font-medium">
|
||||
<span>5 min</span><span>30 min</span><span>1 godz.</span><span>2 godz.+</span>
|
||||
</div>
|
||||
@@ -44,20 +51,128 @@ export function getFilterHTML() {
|
||||
</div>
|
||||
|
||||
<div class="p-4 border-t border-gray-200 bg-white mt-auto">
|
||||
<button onclick="closeFilters()" class="w-full bg-gray-900 hover:bg-black text-white py-3.5 rounded-xl font-semibold shadow-sm transition-colors text-sm">Pokaż 12 wyników</button>
|
||||
<button id="filter-apply-btn" class="w-full bg-gray-900 hover:bg-black text-white py-3.5 rounded-xl font-semibold shadow-sm transition-colors text-sm"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let localSlots = [];
|
||||
let localTags = [];
|
||||
let localMaxMinutes = 120;
|
||||
|
||||
function renderSlotChips() {
|
||||
const wrap = document.getElementById('filter-slot-chips');
|
||||
if (!wrap) return;
|
||||
|
||||
wrap.innerHTML = MEAL_SLOTS.map((slot) => {
|
||||
const active = localSlots.includes(slot.id);
|
||||
const cls = active
|
||||
? 'px-4 py-2 bg-gray-900 text-white rounded-full text-sm font-medium transition-colors'
|
||||
: 'px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors';
|
||||
return `<button type="button" data-filter-slot="${escapeHtml(slot.id)}" class="${cls}">${escapeHtml(slot.label)}</button>`;
|
||||
}).join('');
|
||||
|
||||
wrap.querySelectorAll('[data-filter-slot]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.filterSlot;
|
||||
const idx = localSlots.indexOf(id);
|
||||
if (idx >= 0) localSlots.splice(idx, 1);
|
||||
else localSlots.push(id);
|
||||
renderSlotChips();
|
||||
updateResultCount();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderTagChips() {
|
||||
const wrap = document.getElementById('filter-tag-chips');
|
||||
if (!wrap) return;
|
||||
|
||||
const allTags = collectAllTags();
|
||||
wrap.innerHTML = allTags.map((tag) => {
|
||||
const active = localTags.includes(tag.toLowerCase());
|
||||
const cls = active
|
||||
? 'px-4 py-2 bg-gray-900 text-white rounded-full text-sm font-medium transition-colors'
|
||||
: 'px-4 py-2 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-full text-sm font-medium transition-colors';
|
||||
return `<button type="button" data-filter-tag="${escapeHtml(tag)}" class="${cls}">${escapeHtml(tag)}</button>`;
|
||||
}).join('');
|
||||
|
||||
wrap.querySelectorAll('[data-filter-tag]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tag = btn.dataset.filterTag.toLowerCase();
|
||||
const idx = localTags.indexOf(tag);
|
||||
if (idx >= 0) localTags.splice(idx, 1);
|
||||
else localTags.push(tag);
|
||||
renderTagChips();
|
||||
updateResultCount();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateResultCount() {
|
||||
applyFilters({ slots: localSlots, tags: localTags, maxMinutes: localMaxMinutes });
|
||||
const count = getFilteredCount();
|
||||
const applyBtn = document.getElementById('filter-apply-btn');
|
||||
if (applyBtn) applyBtn.textContent = `Pokaż ${count} wyników`;
|
||||
}
|
||||
|
||||
export function setupFilter() {
|
||||
const timeSlider = document.getElementById('prep-time-slider');
|
||||
const timeDisplay = document.getElementById('time-display');
|
||||
|
||||
if(timeSlider) {
|
||||
if (timeSlider) {
|
||||
timeSlider.addEventListener('input', (e) => {
|
||||
const val = e.target.value;
|
||||
const val = Number(e.target.value);
|
||||
localMaxMinutes = val;
|
||||
timeDisplay.textContent = val >= 120 ? 'ponad 120 min' : `${val} min`;
|
||||
updateResultCount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('filter-close-btn')?.addEventListener('click', () => {
|
||||
window.closeFilters();
|
||||
});
|
||||
|
||||
document.getElementById('filter-apply-btn')?.addEventListener('click', () => {
|
||||
applyFilters({ slots: localSlots, tags: localTags, maxMinutes: localMaxMinutes });
|
||||
refreshRecipeList();
|
||||
window.closeFilters();
|
||||
});
|
||||
|
||||
document.getElementById('filter-clear-btn')?.addEventListener('click', () => {
|
||||
localSlots = [];
|
||||
localTags = [];
|
||||
localMaxMinutes = 120;
|
||||
if (timeSlider) timeSlider.value = '120';
|
||||
if (timeDisplay) timeDisplay.textContent = '120 min';
|
||||
renderSlotChips();
|
||||
renderTagChips();
|
||||
applyFilters({ slots: [], tags: [], maxMinutes: 120 });
|
||||
updateResultCount();
|
||||
});
|
||||
|
||||
window.openFilters = () => {
|
||||
const state = getFilterState();
|
||||
localSlots = [...state.slots];
|
||||
localTags = [...state.tags];
|
||||
localMaxMinutes = state.maxMinutes;
|
||||
|
||||
if (timeSlider) timeSlider.value = String(localMaxMinutes);
|
||||
if (timeDisplay) timeDisplay.textContent = localMaxMinutes >= 120 ? 'ponad 120 min' : `${localMaxMinutes} min`;
|
||||
|
||||
renderSlotChips();
|
||||
renderTagChips();
|
||||
updateResultCount();
|
||||
|
||||
const fv = document.getElementById('filter-view');
|
||||
fv.classList.remove('hidden');
|
||||
fv.classList.add('flex');
|
||||
};
|
||||
|
||||
window.closeFilters = () => {
|
||||
const fv = document.getElementById('filter-view');
|
||||
fv.classList.add('hidden');
|
||||
fv.classList.remove('flex');
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user