Reorganizacja górnych paneli i ujednolicenie stylu filtrów
- Kalendarz: data między strzałkami zamiast napisu "Dziś", nawigacja po prawej, mniejszy komponent dopasowany do wysokości dnia - MealPlanner/Pantry/RecipeList: spójne nagłówki z tytułem po lewej i kontrolkami po prawej - RecipeList: nowy top bar z przyciskami filtrów i wyszukiwania wzorowany na spiżarni - Filter popup: ujednolicony styl z popoverem spiżarni (ciemniejsze tło, jaśniejsze obramowanie, spójne chipy) - Usunięcie przyciemnienia otoczenia przy otwieraniu filtrów - Badge z liczbą aktywnych filtrów na przycisku, zachowujący stan po zamknięciu popupu - Usunięcie ikon kalendarza z pigułek w spiżarni Made-with: Cursor
This commit is contained in:
@@ -10,10 +10,9 @@ import {
|
||||
bindCalendarDayClicks,
|
||||
createCalendarTopbarHTML,
|
||||
createCalendarWeekdayHeaderHTML,
|
||||
formatCalendarMonthYear,
|
||||
renderCalendarGrid,
|
||||
syncCalendarTodayButton,
|
||||
} from '../ui/mealCalendar.js';
|
||||
} from '../ui/mealCalendar.js?v=11';
|
||||
import { createIngredientCardController, getIngredientCardHTML } from '../ui/ingredientCard.js?v=20260410-107';
|
||||
|
||||
/* ── helpers ── */
|
||||
@@ -35,6 +34,30 @@ function formatQty(n) {
|
||||
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1).replace(/\.0$/, '');
|
||||
}
|
||||
|
||||
function toggleStringFilter(list, value) {
|
||||
return list.includes(value)
|
||||
? list.filter((item) => item !== value)
|
||||
: [...list, value];
|
||||
}
|
||||
|
||||
function hasActivePantryFilters() {
|
||||
return pantryFilters.categories.length > 0 || pantryFilters.sections.length > 0;
|
||||
}
|
||||
|
||||
function getActivePantryFilterCount() {
|
||||
return pantryFilters.categories.length + pantryFilters.sections.length;
|
||||
}
|
||||
|
||||
function filterChipStyle(active) {
|
||||
const background = active ? '#393937' : '#2d2e2b';
|
||||
const color = active ? '#f2efe8' : '#d7d2c8';
|
||||
const borderRule = active ? 'border:1px solid #787876;' : 'border:none;';
|
||||
const shadow = active
|
||||
? 'box-shadow:inset 0 1px 0 rgba(255,255,255,0.04), 0 0 0 1px rgba(0,0,0,0.08);'
|
||||
: '';
|
||||
return `background:${background}; ${borderRule} color:${color}; ${shadow}`;
|
||||
}
|
||||
|
||||
const CATEGORY_ICONS = {
|
||||
pieczywo: 'fa-bread-slice',
|
||||
nabial: 'fa-cheese',
|
||||
@@ -45,6 +68,12 @@ const CATEGORY_ICONS = {
|
||||
przyprawy: 'fa-leaf',
|
||||
inne: 'fa-jar',
|
||||
};
|
||||
const CATEGORY_ORDER = ['pieczywo', 'nabial', 'mieso_ryby', 'warzywa', 'owoce', 'suche', 'przyprawy', 'inne'];
|
||||
const PANTRY_SECTION_FILTERS = [
|
||||
{ id: 'shortfalls', label: 'Potrzebne' },
|
||||
{ id: 'sufficient', label: 'W spiżarni' },
|
||||
{ id: 'notPlanned', label: 'Poza planem' },
|
||||
];
|
||||
|
||||
const DAY_NAMES_SHORT = ['nd.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'];
|
||||
const MONTHS_SHORT = ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'];
|
||||
@@ -52,6 +81,21 @@ const MONTHS_SHORT = ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'w
|
||||
const SEARCH_SHELL_SHADOW = '0 5px 10px rgba(0,0,0,0.16), 0 14px 22px rgba(0,0,0,0.24), 0 22px 34px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04)';
|
||||
const DEFAULT_HORIZON_DAYS = 7;
|
||||
const PANTRY_CALENDAR_DAY_ATTR = 'data-pantry-calendar-day';
|
||||
const SHORTFALL_ACCENT = '#CB4A48';
|
||||
const PANTRY_CALENDAR_THEME = {
|
||||
bg: '#272622',
|
||||
border: '#34312c',
|
||||
text: '#d7d2c8',
|
||||
dimText: '#5a5752',
|
||||
dimOpacity: 0.38,
|
||||
dimmedBg: 'transparent',
|
||||
dimmedBorder: 'transparent',
|
||||
dot: '#7d7a74',
|
||||
selectedBg: '#393937',
|
||||
selectedBorder: '#787876',
|
||||
selectedText: '#f2efe8',
|
||||
selectedDot: '#f2efe8',
|
||||
};
|
||||
|
||||
/* ── state ── */
|
||||
|
||||
@@ -59,8 +103,13 @@ let ingredientCard = null;
|
||||
let horizonEndDate = addDays(startOfDay(new Date()), DEFAULT_HORIZON_DAYS - 1);
|
||||
let isSearchExpanded = false;
|
||||
let isCalendarOpen = false;
|
||||
let isFilterOpen = false;
|
||||
let calendarMonthAnchor = startOfMonth(horizonEndDate);
|
||||
let pantryGlobalListenersBound = false;
|
||||
let pantryFilters = {
|
||||
categories: [],
|
||||
sections: [],
|
||||
};
|
||||
|
||||
/* ── date formatting ── */
|
||||
|
||||
@@ -127,32 +176,29 @@ export function getPantryHTML() {
|
||||
<!-- ── floating top bar ── -->
|
||||
<div class="pointer-events-none absolute inset-x-0 top-0 z-[12] px-4 pt-4" style="background:transparent !important; border:none !important;">
|
||||
<div class="pointer-events-auto relative z-[1] mx-auto" style="width:min(calc(100% - 0.5rem), 22.4rem);">
|
||||
<div id="pantry-topbar" class="relative h-11">
|
||||
<div id="pantry-compact-controls" class="flex items-center gap-2 transition-all duration-200" style="opacity:1; transform:translateY(0) scale(1);">
|
||||
<div id="pantry-horizon-wrap" class="relative flex-1 min-w-0">
|
||||
<button type="button" id="pantry-horizon-toggle" class="w-full h-11 rounded-full flex items-center gap-2 px-3 transition-all" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important;">
|
||||
<div class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center" style="background:#2f2f2d;">
|
||||
<i class="far fa-calendar text-[12px]" style="color:#9b978f;"></i>
|
||||
</div>
|
||||
<span id="pantry-horizon-label" class="flex-1 min-w-0 text-left text-[13px] font-normal truncate" style="color:#ddd6ca;"></span>
|
||||
<i id="pantry-horizon-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:#9b978f;"></i>
|
||||
<div id="pantry-topbar" class="relative min-h-12">
|
||||
<div id="pantry-default-row" class="flex min-h-12 items-center gap-2 transition-all duration-200" style="opacity:1; transform:translateY(0) scale(1);">
|
||||
<h1 class="flex-1 min-w-0 truncate" style="margin:0;padding:0;color:#f2efe8;font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Zapasy</h1>
|
||||
|
||||
<button type="button" id="pantry-horizon-compact" class="min-w-0 max-w-[12rem] h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all shrink" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important;">
|
||||
<span id="pantry-horizon-compact-label" class="min-w-0 flex-1 text-left text-[13px] font-normal truncate" style="color:#ddd6ca;"></span>
|
||||
<i class="fas fa-chevron-down text-[10px] shrink-0" style="color:#9b978f;"></i>
|
||||
</button>
|
||||
|
||||
<div id="pantry-filter-wrap" class="relative shrink-0">
|
||||
<button type="button" id="pantry-filter-toggle" class="relative w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937; border:1px solid #41423f; box-shadow:${SEARCH_SHELL_SHADOW}; color:#ddd6ca;">
|
||||
<i class="fas fa-sliders-h text-[12px]"></i>
|
||||
<span id="pantry-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:#23221e; border:1px solid #787876; color:#f2efe8;"></span>
|
||||
</button>
|
||||
|
||||
<div id="pantry-calendar-popover" class="absolute left-0 right-0 top-full mt-2 rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
|
||||
${createCalendarTopbarHTML({
|
||||
titleId: 'pantry-cal-title',
|
||||
prevId: 'pantry-cal-prev',
|
||||
todayId: 'pantry-cal-today',
|
||||
nextId: 'pantry-cal-next',
|
||||
wrapperClass: 'pb-3 flex items-center gap-3',
|
||||
titleClass: 'text-[13px] font-semibold text-[#ddd6ca] leading-none tracking-[-0.02em]',
|
||||
})}
|
||||
${createCalendarWeekdayHeaderHTML()}
|
||||
<div id="pantry-calendar-grid" class="grid grid-cols-7 gap-1.5"></div>
|
||||
|
||||
<div class="mt-3 pt-3 border-t" style="border-color:#444442;">
|
||||
<p id="pantry-cal-selection" class="min-w-0 text-[11px] leading-snug" style="color:#9b978f;"></p>
|
||||
<div id="pantry-filter-popover" class="absolute right-0 top-full mt-2 w-[19rem] max-w-[calc(100vw-2rem)] rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
|
||||
<div class="flex items-center justify-between gap-3 px-0.5 pb-3">
|
||||
<p class="text-[11px] font-semibold leading-none" style="color:#f2efe8;">Filtry</p>
|
||||
<button type="button" id="pantry-filter-clear" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:#b5afa5;">
|
||||
Wyczyść
|
||||
</button>
|
||||
</div>
|
||||
<div id="pantry-filter-panel-body" class="space-y-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -161,7 +207,37 @@ export function getPantryHTML() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="pantry-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
|
||||
<div id="pantry-horizon-expanded" class="absolute inset-0 transition-all duration-200 pointer-events-none" style="opacity:0; transform:translateY(-2px) scale(0.98);">
|
||||
<div id="pantry-horizon-wrap" class="relative">
|
||||
<button type="button" id="pantry-horizon-toggle" class="w-full h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important;">
|
||||
<span id="pantry-horizon-label" class="flex-1 min-w-0 text-left text-[13px] font-normal truncate" style="color:#ddd6ca;"></span>
|
||||
<i id="pantry-horizon-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:#9b978f;"></i>
|
||||
</button>
|
||||
|
||||
<div id="pantry-calendar-popover" class="absolute left-0 right-0 top-full mt-2 rounded-[1.35rem] px-3 py-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-6px) scale(0.98);">
|
||||
${createCalendarTopbarHTML({
|
||||
prevId: 'pantry-cal-prev',
|
||||
todayId: 'pantry-cal-today',
|
||||
nextId: 'pantry-cal-next',
|
||||
wrapperClass: 'pb-3 flex items-center justify-end gap-3',
|
||||
controlsStyle: `background:${PANTRY_CALENDAR_THEME.bg};border-color:${PANTRY_CALENDAR_THEME.border};`,
|
||||
navButtonClass: 'shrink-0 w-7 h-full flex items-center justify-center rounded-full border-0 bg-transparent text-[#d7d2c8] transition-colors',
|
||||
todayButtonActiveClass: 'h-full shrink-0 inline-flex min-w-[5.75rem] max-w-[9rem] items-center justify-center rounded-full bg-transparent px-1.5 text-[10px] font-semibold leading-none tabular-nums text-[#d7d2c8] active:bg-transparent whitespace-nowrap',
|
||||
todayButtonDimClass: 'h-full shrink-0 inline-flex items-center justify-center rounded-full px-2 text-[10px] font-semibold leading-none text-[#7d7a74] cursor-default',
|
||||
})}
|
||||
${createCalendarWeekdayHeaderHTML(undefined, {
|
||||
wrapperClass: 'grid grid-cols-7 gap-1.5 text-center text-[8px] font-medium text-[#9b978f] uppercase tracking-wide mb-1 leading-none',
|
||||
})}
|
||||
<div id="pantry-calendar-grid" class="grid grid-cols-7 gap-1.5"></div>
|
||||
|
||||
<div class="mt-3 pt-3 border-t" style="border-color:#444442;">
|
||||
<p id="pantry-cal-selection" class="min-w-0 text-[11px] leading-snug" style="color:#9b978f;"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pantry-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
|
||||
<i class="fas fa-search text-[13px] shrink-0" style="color:#9b978f;"></i>
|
||||
<input type="search" id="pantry-search" autocomplete="off" placeholder="Szukaj w spiżarni…"
|
||||
class="flex-1 min-w-0 h-full bg-transparent outline-none text-[15px] leading-none py-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:#ddd6ca; margin:0;">
|
||||
@@ -174,7 +250,7 @@ export function getPantryHTML() {
|
||||
</div>
|
||||
|
||||
<!-- ── scrollable content ── -->
|
||||
<div id="pantry-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-[5.25rem] pb-24" style="background:#2d2e2b !important;">
|
||||
<div id="pantry-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-[5.35rem] pb-24" style="background:#2d2e2b !important;">
|
||||
<div id="pantry-board"></div>
|
||||
</div>
|
||||
|
||||
@@ -190,21 +266,41 @@ export function getPantryHTML() {
|
||||
function syncHorizonUI() {
|
||||
ensureValidHorizonDate();
|
||||
|
||||
const compactControls = document.getElementById('pantry-compact-controls');
|
||||
const defaultRow = document.getElementById('pantry-default-row');
|
||||
const horizonExpanded = document.getElementById('pantry-horizon-expanded');
|
||||
const searchShell = document.getElementById('pantry-search-shell');
|
||||
const popover = document.getElementById('pantry-calendar-popover');
|
||||
const filterPopover = document.getElementById('pantry-filter-popover');
|
||||
const filterToggle = document.getElementById('pantry-filter-toggle');
|
||||
const filterCount = document.getElementById('pantry-filter-count');
|
||||
const compactLabel = document.getElementById('pantry-horizon-compact-label');
|
||||
const horizonLabel = document.getElementById('pantry-horizon-label');
|
||||
const chevron = document.getElementById('pantry-horizon-chevron');
|
||||
const titleEl = document.getElementById('pantry-cal-title');
|
||||
const selectionEl = document.getElementById('pantry-cal-selection');
|
||||
const prevBtn = document.getElementById('pantry-cal-prev');
|
||||
const todayBtn = document.getElementById('pantry-cal-today');
|
||||
|
||||
if (compactLabel) compactLabel.textContent = formatHorizonLabel(horizonEndDate);
|
||||
if (horizonLabel) horizonLabel.textContent = formatHorizonLabel(horizonEndDate);
|
||||
if (titleEl) titleEl.textContent = formatCalendarMonthYear(calendarMonthAnchor);
|
||||
if (selectionEl) selectionEl.textContent = formatRangeSummary(horizonEndDate);
|
||||
|
||||
const showCalendar = isCalendarOpen && !isSearchExpanded;
|
||||
const showDefault = !isSearchExpanded && !isCalendarOpen;
|
||||
const showFilter = isFilterOpen && showDefault;
|
||||
const activeFilterCount = getActivePantryFilterCount();
|
||||
|
||||
if (defaultRow) {
|
||||
defaultRow.style.opacity = showDefault ? '1' : '0';
|
||||
defaultRow.style.transform = showDefault ? 'translateY(0) scale(1)' : 'translateY(-2px) scale(0.98)';
|
||||
defaultRow.style.pointerEvents = showDefault ? 'auto' : 'none';
|
||||
}
|
||||
|
||||
if (horizonExpanded) {
|
||||
horizonExpanded.style.opacity = showCalendar ? '1' : '0';
|
||||
horizonExpanded.style.transform = showCalendar ? 'translateY(0) scale(1)' : 'translateY(-2px) scale(0.98)';
|
||||
horizonExpanded.style.pointerEvents = showCalendar ? 'auto' : 'none';
|
||||
}
|
||||
|
||||
if (popover) {
|
||||
popover.style.opacity = showCalendar ? '1' : '0';
|
||||
popover.style.transform = showCalendar ? 'translateY(0) scale(1)' : 'translateY(-6px) scale(0.98)';
|
||||
@@ -214,19 +310,38 @@ function syncHorizonUI() {
|
||||
chevron.style.transform = showCalendar ? 'rotate(180deg)' : 'rotate(0deg)';
|
||||
}
|
||||
|
||||
if (filterPopover) {
|
||||
filterPopover.style.opacity = showFilter ? '1' : '0';
|
||||
filterPopover.style.transform = showFilter ? 'translateY(0) scale(1)' : 'translateY(-6px) scale(0.98)';
|
||||
filterPopover.style.pointerEvents = showFilter ? 'auto' : 'none';
|
||||
}
|
||||
if (filterToggle) {
|
||||
const isActive = showFilter || hasActivePantryFilters();
|
||||
filterToggle.style.setProperty('background', isActive ? '#23221e' : '#393937', 'important');
|
||||
filterToggle.style.setProperty('border-color', isActive ? '#787876' : '#41423f', 'important');
|
||||
filterToggle.style.setProperty('color', isActive ? '#f2efe8' : '#ddd6ca', 'important');
|
||||
}
|
||||
if (filterCount) {
|
||||
filterCount.textContent = String(activeFilterCount);
|
||||
filterCount.classList.toggle('hidden', activeFilterCount === 0);
|
||||
filterCount.classList.toggle('flex', activeFilterCount > 0);
|
||||
}
|
||||
|
||||
if (prevBtn) {
|
||||
const isCurrentMonth = sameMonth(calendarMonthAnchor, getToday());
|
||||
prevBtn.disabled = isCurrentMonth;
|
||||
prevBtn.style.opacity = isCurrentMonth ? '0.45' : '1';
|
||||
prevBtn.style.cursor = isCurrentMonth ? 'default' : 'pointer';
|
||||
}
|
||||
syncCalendarTodayButton(todayBtn, sameMonth(calendarMonthAnchor, getToday()));
|
||||
|
||||
if (compactControls) {
|
||||
compactControls.style.opacity = isSearchExpanded ? '0' : '1';
|
||||
compactControls.style.transform = isSearchExpanded ? 'translateY(-2px) scale(0.98)' : 'translateY(0) scale(1)';
|
||||
compactControls.style.pointerEvents = isSearchExpanded ? 'none' : 'auto';
|
||||
}
|
||||
syncCalendarTodayButton(
|
||||
todayBtn,
|
||||
sameMonth(calendarMonthAnchor, getToday()),
|
||||
horizonEndDate,
|
||||
{
|
||||
ariaLabelGo: 'Pokaż bieżący miesiąc w kalendarzu',
|
||||
ariaLabelCurrent: 'Wyświetlany jest bieżący miesiąc',
|
||||
},
|
||||
);
|
||||
|
||||
if (searchShell) {
|
||||
searchShell.style.opacity = isSearchExpanded ? '1' : '0';
|
||||
@@ -235,6 +350,7 @@ function syncHorizonUI() {
|
||||
}
|
||||
|
||||
renderCalendarPopover();
|
||||
renderFilterPopover();
|
||||
}
|
||||
|
||||
function renderCalendarPopover() {
|
||||
@@ -259,9 +375,54 @@ function renderCalendarPopover() {
|
||||
};
|
||||
},
|
||||
dayAttr: PANTRY_CALENDAR_DAY_ATTR,
|
||||
theme: PANTRY_CALENDAR_THEME,
|
||||
});
|
||||
}
|
||||
|
||||
function filterChipHtml(kind, value, label, active) {
|
||||
return `<button
|
||||
type="button"
|
||||
class="px-3 py-2 rounded-full text-[11px] font-semibold transition-colors"
|
||||
style="${filterChipStyle(active)}"
|
||||
data-pantry-filter-kind="${esc(kind)}"
|
||||
data-pantry-filter-value="${esc(value)}"
|
||||
>${esc(label)}</button>`;
|
||||
}
|
||||
|
||||
function renderFilterPopover() {
|
||||
const body = document.getElementById('pantry-filter-panel-body');
|
||||
if (!body) return;
|
||||
|
||||
const categoryChips = CATEGORY_ORDER
|
||||
.map((category) => filterChipHtml(
|
||||
'category',
|
||||
category,
|
||||
CATEGORY_LABELS[category] || category,
|
||||
pantryFilters.categories.includes(category),
|
||||
))
|
||||
.join('');
|
||||
|
||||
const sectionChips = PANTRY_SECTION_FILTERS
|
||||
.map((item) => filterChipHtml(
|
||||
'section',
|
||||
item.id,
|
||||
item.label,
|
||||
pantryFilters.sections.includes(item.id),
|
||||
))
|
||||
.join('');
|
||||
|
||||
body.innerHTML = `
|
||||
<section>
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:#b5afa5;">Kategorie</p>
|
||||
<div class="flex flex-wrap gap-2">${categoryChips}</div>
|
||||
</section>
|
||||
<section>
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:#b5afa5;">Sekcje</p>
|
||||
<div class="flex flex-wrap gap-2">${sectionChips}</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
const input = document.getElementById('pantry-search');
|
||||
const hadQuery = Boolean(input?.value);
|
||||
@@ -277,6 +438,7 @@ function closeSearch() {
|
||||
function openSearch() {
|
||||
isSearchExpanded = true;
|
||||
isCalendarOpen = false;
|
||||
isFilterOpen = false;
|
||||
syncHorizonUI();
|
||||
window.requestAnimationFrame(() => {
|
||||
document.getElementById('pantry-search')?.focus();
|
||||
@@ -292,10 +454,25 @@ function closeCalendar() {
|
||||
function openCalendar() {
|
||||
ensureValidHorizonDate();
|
||||
calendarMonthAnchor = startOfMonth(horizonEndDate);
|
||||
isSearchExpanded = false;
|
||||
isFilterOpen = false;
|
||||
isCalendarOpen = true;
|
||||
syncHorizonUI();
|
||||
}
|
||||
|
||||
function closeFilter() {
|
||||
if (!isFilterOpen) return;
|
||||
isFilterOpen = false;
|
||||
syncHorizonUI();
|
||||
}
|
||||
|
||||
function toggleFilterPanel() {
|
||||
isCalendarOpen = false;
|
||||
isSearchExpanded = false;
|
||||
isFilterOpen = !isFilterOpen;
|
||||
syncHorizonUI();
|
||||
}
|
||||
|
||||
function selectHorizonDate(date) {
|
||||
const next = startOfDay(date);
|
||||
if (next.getTime() < getToday().getTime()) return;
|
||||
@@ -322,6 +499,8 @@ function classifyIngredients(searchQuery) {
|
||||
if (!q) return true;
|
||||
return name.toLowerCase().includes(q) || (CATEGORY_LABELS[category] || '').toLowerCase().includes(q);
|
||||
};
|
||||
const matchesCategory = (category) => pantryFilters.categories.length === 0 || pantryFilters.categories.includes(category);
|
||||
const matchesSection = (section) => pantryFilters.sections.length === 0 || pantryFilters.sections.includes(section);
|
||||
|
||||
const neededIds = new Set();
|
||||
const shortfalls = [];
|
||||
@@ -329,7 +508,7 @@ function classifyIngredients(searchQuery) {
|
||||
|
||||
for (const need of needs) {
|
||||
neededIds.add(need.ingredientId);
|
||||
if (!matchesSearch(need.name, need.category)) continue;
|
||||
if (!matchesSearch(need.name, need.category) || !matchesCategory(need.category)) continue;
|
||||
|
||||
const have = getPantryTotal(need.ingredientId, pantry);
|
||||
const def = INGREDIENTS[need.ingredientId];
|
||||
@@ -347,12 +526,14 @@ function classifyIngredients(searchQuery) {
|
||||
};
|
||||
|
||||
if (have < need.amount) {
|
||||
if (!matchesSection('shortfalls')) continue;
|
||||
shortfalls.push({
|
||||
...item,
|
||||
shortfall: Math.round((need.amount - have) * 10) / 10,
|
||||
fillPct: need.amount > 0 ? Math.min(100, Math.round((have / need.amount) * 100)) : 0,
|
||||
});
|
||||
} else {
|
||||
if (!matchesSection('sufficient')) continue;
|
||||
sufficient.push({
|
||||
...item,
|
||||
fillPct: 100,
|
||||
@@ -363,7 +544,9 @@ function classifyIngredients(searchQuery) {
|
||||
// "Poza planem": all ingredients NOT in any plan need
|
||||
const notPlanned = Object.keys(INGREDIENTS)
|
||||
.filter((id) => !neededIds.has(id))
|
||||
.filter(() => matchesSection('notPlanned'))
|
||||
.filter((id) => matchesSearch(INGREDIENTS[id].name, INGREDIENTS[id].category))
|
||||
.filter((id) => matchesCategory(INGREDIENTS[id].category))
|
||||
.map((id) => {
|
||||
const def = INGREDIENTS[id];
|
||||
return {
|
||||
@@ -385,13 +568,13 @@ function shortfallTileHtml(item) {
|
||||
return `
|
||||
<button type="button" class="pv2-tile w-full text-left rounded-2xl flex items-center gap-3 px-3 py-3 transition-all active:scale-[0.99] mb-2" style="background:#393937; border:none; box-shadow:0 2px 8px rgba(0,0,0,0.28);" data-id="${esc(item.ingredientId)}">
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center shrink-0" style="background:#2f2f2d;">
|
||||
<i class="fas ${item.icon} text-[17px]" style="color:#914945;"></i>
|
||||
<i class="fas ${item.icon} text-[17px]" style="color:${SHORTFALL_ACCENT};"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[13px] font-normal leading-tight truncate mb-1.5" style="color:#ddd6ca;">${esc(item.name)}</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 h-2 rounded-full overflow-hidden" style="background:#2a2a28;">
|
||||
<div class="h-full rounded-full" style="width:${item.fillPct}%; background:#914945;"></div>
|
||||
<div class="h-full rounded-full" style="width:${item.fillPct}%; background:${SHORTFALL_ACCENT};"></div>
|
||||
</div>
|
||||
<span class="text-[11px] font-semibold tabular-nums shrink-0" style="color:#ddd6ca;">${esc(formatQty(item.pantryQty))} <span class="font-medium text-[10px]" style="color:#9b978f;">/ ${esc(formatQty(item.needed))} ${esc(unitLabel(item.unit))}</span></span>
|
||||
</div>
|
||||
@@ -432,9 +615,6 @@ function notPlannedChipHtml(item) {
|
||||
function sectionHeaderHtml(icon, iconBg, iconColor, title, titleColor, count) {
|
||||
return `
|
||||
<div class="flex items-center gap-2 mb-2.5 px-0.5">
|
||||
<div class="w-6 h-6 rounded-lg flex items-center justify-center" style="background:${iconBg};">
|
||||
<i class="fas ${icon} text-[10px]" style="color:${iconColor};"></i>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider" style="color:${titleColor};">${esc(title)}</span>
|
||||
<span class="text-[10px]" style="color:#6d6c67;">${count}</span>
|
||||
</div>`;
|
||||
@@ -445,11 +625,12 @@ function renderBoard() {
|
||||
if (!root) return;
|
||||
|
||||
const q = document.getElementById('pantry-search')?.value || '';
|
||||
const hasFilters = hasActivePantryFilters();
|
||||
const { shortfalls, sufficient, notPlanned } = classifyIngredients(q);
|
||||
|
||||
const totalVisible = shortfalls.length + sufficient.length + notPlanned.length;
|
||||
if (totalVisible === 0 && q) {
|
||||
root.innerHTML = `<p class="text-sm text-center py-10" style="color:#9b978f;">Brak wyników — zmień wyszukiwanie.</p>`;
|
||||
if (totalVisible === 0 && (q || hasFilters)) {
|
||||
root.innerHTML = `<p class="text-sm text-center py-10" style="color:#9b978f;">Brak wyników — zmień filtry lub wyszukiwanie.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -458,7 +639,7 @@ function renderBoard() {
|
||||
// Section 1: Potrzebne (shortfalls)
|
||||
if (shortfalls.length > 0) {
|
||||
html += `<section class="mb-5">`;
|
||||
html += sectionHeaderHtml('fa-exclamation', '#914945', '#f2efe8', 'Potrzebne', '#7B756D', shortfalls.length);
|
||||
html += sectionHeaderHtml('fa-exclamation', '#2f2f2d', SHORTFALL_ACCENT, 'Potrzebne', '#7B756D', shortfalls.length);
|
||||
html += shortfalls.map(shortfallTileHtml).join('');
|
||||
html += `</section>`;
|
||||
}
|
||||
@@ -481,7 +662,7 @@ function renderBoard() {
|
||||
}
|
||||
|
||||
// Empty state: no plans at all
|
||||
if (shortfalls.length === 0 && sufficient.length === 0 && !q) {
|
||||
if (shortfalls.length === 0 && sufficient.length === 0 && !q && !hasFilters) {
|
||||
html = `
|
||||
<div class="flex flex-col items-center justify-center py-10 text-center mb-6">
|
||||
<div class="w-12 h-12 rounded-2xl flex items-center justify-center mb-3" style="background:#393937;">
|
||||
@@ -535,8 +716,45 @@ export function setupPantry() {
|
||||
});
|
||||
document.getElementById('pantry-search-toggle')?.addEventListener('click', () => openSearch());
|
||||
document.getElementById('pantry-search-close')?.addEventListener('click', () => closeSearch());
|
||||
document.getElementById('pantry-filter-toggle')?.addEventListener('click', () => toggleFilterPanel());
|
||||
document.getElementById('pantry-filter-clear')?.addEventListener('click', () => {
|
||||
pantryFilters = { categories: [], sections: [] };
|
||||
syncHorizonUI();
|
||||
renderBoard();
|
||||
});
|
||||
document.getElementById('pantry-filter-panel-body')?.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
|
||||
const chip = target.closest('[data-pantry-filter-kind]');
|
||||
if (!(chip instanceof Element)) return;
|
||||
|
||||
const kind = chip.getAttribute('data-pantry-filter-kind');
|
||||
const value = chip.getAttribute('data-pantry-filter-value');
|
||||
if (!kind || !value) return;
|
||||
|
||||
if (kind === 'category') {
|
||||
pantryFilters = {
|
||||
...pantryFilters,
|
||||
categories: toggleStringFilter(pantryFilters.categories, value),
|
||||
};
|
||||
}
|
||||
if (kind === 'section') {
|
||||
pantryFilters = {
|
||||
...pantryFilters,
|
||||
sections: toggleStringFilter(pantryFilters.sections, value),
|
||||
};
|
||||
}
|
||||
|
||||
syncHorizonUI();
|
||||
renderBoard();
|
||||
});
|
||||
|
||||
// Horizon pill + calendar
|
||||
document.getElementById('pantry-horizon-compact')?.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
openCalendar();
|
||||
});
|
||||
document.getElementById('pantry-horizon-toggle')?.addEventListener('click', () => {
|
||||
if (isCalendarOpen) {
|
||||
closeCalendar();
|
||||
@@ -567,9 +785,12 @@ export function setupPantry() {
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
if (isCalendarOpen && !target.closest('#pantry-horizon-wrap')) {
|
||||
if (isCalendarOpen && !target.closest('#pantry-horizon-wrap, #pantry-horizon-compact')) {
|
||||
closeCalendar();
|
||||
}
|
||||
if (isFilterOpen && !target.closest('#pantry-filter-wrap')) {
|
||||
closeFilter();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user