Update planner search and planner editor

This commit is contained in:
2026-04-08 22:16:20 +02:00
parent 4706430316
commit 165f39d0b7
7 changed files with 693 additions and 301 deletions

View File

@@ -20,6 +20,27 @@ const PREP_TIME_MAX = 120;
const PREP_TIME_STEP = 5;
const PREP_TIME_MIN_GAP = PREP_TIME_STEP;
const FILTER_RECIPE_BLUR = 'blur(3px) saturate(0.94)';
const FILTER_CONTEXTS = {
recipes: {
anchorShellId: 'recipe-search-shell',
buttonId: 'recipe-filter-btn',
getState: () => getFilterState(),
applyState: (nextState) => applyFilters(nextState),
showSlots: true,
},
plannerPicker: {
anchorShellId: 'planner-picker-search-shell',
buttonId: 'planner-picker-filter-btn',
getState: () => window.getPlannerPickerFilterState?.() || ({
slots: [],
tags: [],
minMinutes: PREP_TIME_MIN,
maxMinutes: PREP_TIME_MAX,
}),
applyState: (nextState) => window.applyPlannerPickerFilters?.(nextState),
showSlots: false,
},
};
function escapeHtml(s) {
return String(s)
@@ -61,7 +82,7 @@ export function getFilterHTML() {
outline: none;
}
</style>
<div id="filter-view" class="absolute inset-0 z-[55] hidden opacity-0 transition-opacity duration-150" style="pointer-events:none; background:rgba(0,0,0,0.5) !important; background-image:none !important;" aria-hidden="true">
<div id="filter-view" class="absolute inset-0 z-[70] hidden opacity-0 transition-opacity duration-150" style="pointer-events:none; background:rgba(0,0,0,0.5) !important; background-image:none !important;" aria-hidden="true">
<div id="filter-panel" class="absolute flex flex-col overflow-hidden rounded-[1.5rem] border" style="background:${FILTER_SURFACE} !important; background-image:none !important; border-color:${FILTER_BORDER} !important; opacity:0; transform:translateY(-0.5rem) scale(0.98); transform-origin:top center; transition:${FILTER_PANEL_TRANSITION}; box-shadow:0 18px 40px rgba(0,0,0,0.34), 0 4px 12px rgba(0,0,0,0.18); width:min(calc(100% - 1.5rem), 22rem);">
<div class="pointer-events-none absolute inset-x-0 top-0 h-px" style="background:rgba(242,239,232,0.12);" aria-hidden="true"></div>
<div class="shrink-0 px-3.5 pt-3 pb-2 flex justify-end" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
@@ -71,7 +92,7 @@ export function getFilterHTML() {
</div>
<div id="filter-panel-body" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 pb-4 space-y-2.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
<section class="p-3.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
<section id="filter-slot-section" class="p-3.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
<p class="text-[10px] font-bold uppercase tracking-wider mb-3" style="color:${FILTER_TEXT_MUTED};">Pora posiłku</p>
<div id="filter-slot-chips" class="flex flex-wrap gap-2"></div>
</section>
@@ -132,6 +153,11 @@ let localTags = [];
let localMinMinutes = PREP_TIME_MIN;
let localMaxMinutes = PREP_TIME_MAX;
let closeTimer = null;
let activeFilterContext = 'recipes';
function getActiveFilterConfig() {
return FILTER_CONTEXTS[activeFilterContext] || FILTER_CONTEXTS.recipes;
}
function normalizeTimeRange(minMinutes, maxMinutes) {
let nextMin = snapTimeValue(minMinutes);
@@ -216,9 +242,10 @@ function positionFilterPanel() {
const view = document.getElementById('filter-view');
const panel = document.getElementById('filter-panel');
const body = document.getElementById('filter-panel-body');
const searchShell = document.getElementById('recipe-search-shell');
const button = document.getElementById('recipe-filter-btn');
if (!view || !panel || !button) return;
const { anchorShellId, buttonId } = getActiveFilterConfig();
const searchShell = document.getElementById(anchorShellId);
const button = document.getElementById(buttonId);
if (!view || !panel || (!searchShell && !button)) return;
const viewRect = view.getBoundingClientRect();
const anchorRect = (searchShell || button).getBoundingClientRect();
@@ -327,9 +354,16 @@ function renderTagChips() {
});
}
function syncFilterSections() {
const slotSection = document.getElementById('filter-slot-section');
if (!slotSection) return;
slotSection.classList.toggle('hidden', !getActiveFilterConfig().showSlots);
}
function syncLiveFilters() {
applyFilters({
slots: localSlots,
const config = getActiveFilterConfig();
config.applyState?.({
slots: config.showSlots ? localSlots : [],
tags: localTags,
minMinutes: localMinMinutes,
maxMinutes: localMaxMinutes,
@@ -465,15 +499,17 @@ export function setupFilter() {
syncLiveFilters();
});
window.openFilters = () => {
if (isFilterPanelOpen()) {
window.openFilters = (contextName = 'recipes') => {
if (isFilterPanelOpen() && activeFilterContext === contextName) {
window.closeFilters();
return;
}
const state = getFilterState();
localSlots = [...state.slots];
localTags = [...state.tags];
activeFilterContext = FILTER_CONTEXTS[contextName] ? contextName : 'recipes';
const config = getActiveFilterConfig();
const state = config.getState?.() || {};
localSlots = [...(state.slots || [])];
localTags = [...(state.tags || [])];
const normalized = normalizeTimeRange(
Number.isFinite(state.minMinutes) ? state.minMinutes : PREP_TIME_MIN,
Number.isFinite(state.maxMinutes) ? state.maxMinutes : PREP_TIME_MAX,
@@ -485,6 +521,7 @@ export function setupFilter() {
renderSlotChips();
renderTagChips();
syncFilterSections();
showFilterPanel();
};