Swipeable calendar
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m15s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m15s
This commit is contained in:
@@ -3,16 +3,13 @@ import {
|
||||
CATEGORY_LABELS,
|
||||
} from '../data/catalog.js?v=9';
|
||||
import { loadPantry, getPantryTotal } from '../services/pantryShopping.js?v=2';
|
||||
import { loadPlans } from '../services/planStore.js?v=2';
|
||||
import { addDays, addMonths, sameDay, sameMonth, startOfDay, startOfMonth } from '../services/dateUtils.js';
|
||||
import { loadPlans, dateKey } from '../services/planStore.js?v=2';
|
||||
import { addDays, sameDay, startOfDay, startOfMonth } from '../services/dateUtils.js';
|
||||
import { aggregateRangeIngredientNeed, dayHasAnyMeal } from '../services/planIngredients.js?v=4';
|
||||
import {
|
||||
bindCalendarDayClicks,
|
||||
createCalendarTopbarHTML,
|
||||
createCalendarWeekdayHeaderHTML,
|
||||
renderCalendarGrid,
|
||||
syncCalendarTodayButton,
|
||||
} from '../ui/mealCalendar.js?v=11';
|
||||
createSwipePopoverCalendarHTML,
|
||||
initSwipePopoverCalendar,
|
||||
} from '../ui/swipePopoverCalendar.js';
|
||||
import { createIngredientCardController, getIngredientCardHTML } from '../ui/ingredientCard.js?v=20260417-116';
|
||||
|
||||
/* ── helpers ── */
|
||||
@@ -79,7 +76,6 @@ 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'];
|
||||
|
||||
const DEFAULT_HORIZON_DAYS = 7;
|
||||
const PANTRY_CALENDAR_DAY_ATTR = 'data-pantry-calendar-day';
|
||||
const SHORTFALL_ACCENT = 'rgb(var(--danger-rgb))';
|
||||
const PANTRY_CALENDAR_THEME = {
|
||||
bg: 'rgb(var(--app-bg-rgb))',
|
||||
@@ -105,6 +101,7 @@ let isCalendarOpen = false;
|
||||
let isFilterOpen = false;
|
||||
let calendarMonthAnchor = startOfMonth(horizonEndDate);
|
||||
let pantryGlobalListenersBound = false;
|
||||
let pantryCalendar = null;
|
||||
let pantryFilters = {
|
||||
categories: [],
|
||||
sections: [],
|
||||
@@ -140,10 +137,6 @@ function formatHorizonLabel(date) {
|
||||
return sameDay(date, getToday()) ? 'Do dziś' : `Do ${formatEndDate(date)}`;
|
||||
}
|
||||
|
||||
function formatRangeSummary(date) {
|
||||
return sameDay(date, getToday()) ? 'Zakres: tylko dziś' : `Zakres: dziś - ${formatEndDate(date)}`;
|
||||
}
|
||||
|
||||
function formatDayContext(dayStrings) {
|
||||
const dayNames = dayStrings.map((ds) => {
|
||||
const d = new Date(ds + 'T00:00:00');
|
||||
@@ -209,25 +202,8 @@ export function getPantryHTML() {
|
||||
</div>
|
||||
|
||||
|
||||
<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:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:var(--shadow-shell) !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-[rgb(var(--text-body-soft-rgb))] 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-[rgb(var(--text-body-soft-rgb))] 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-[rgb(var(--text-faint-rgb))] cursor-default',
|
||||
})}
|
||||
${createCalendarWeekdayHeaderHTML(undefined, {
|
||||
wrapperClass: 'grid grid-cols-7 gap-1.5 text-center text-[8px] font-medium text-[rgb(var(--text-dim-rgb))] 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:rgb(var(--card-strong-rgb));">
|
||||
<p id="pantry-cal-selection" class="min-w-0 text-[11px] leading-snug" style="color:rgb(var(--text-dim-rgb));"></p>
|
||||
</div>
|
||||
<div id="pantry-calendar-popover" class="absolute left-0 right-0 top-full mt-2 rounded-[1.35rem] py-3 transition-all duration-200 pointer-events-none" style="background:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:var(--shadow-shell) !important; opacity:0; transform:translateY(-6px) scale(0.98);">
|
||||
${createSwipePopoverCalendarHTML({ idPrefix: 'pantry-cal' })}
|
||||
</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:rgb(var(--sunken-rgb)) !important; border:1px solid rgb(var(--border-input-rgb)) !important; box-shadow:var(--shadow-shell) !important; opacity:0; transform:translateY(-2px) scale(0.98);">
|
||||
@@ -264,12 +240,8 @@ function syncHorizonUI() {
|
||||
const compactLabel = document.getElementById('pantry-horizon-compact-label');
|
||||
const compactPill = document.getElementById('pantry-horizon-compact');
|
||||
const chevron = document.getElementById('pantry-horizon-chevron');
|
||||
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 (selectionEl) selectionEl.textContent = formatRangeSummary(horizonEndDate);
|
||||
|
||||
const showCalendar = isCalendarOpen && !isSearchExpanded;
|
||||
const showDefault = !isSearchExpanded;
|
||||
@@ -313,22 +285,6 @@ function syncHorizonUI() {
|
||||
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()),
|
||||
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';
|
||||
searchShell.style.transform = isSearchExpanded ? 'translateY(0) scale(1)' : 'translateY(-2px) scale(0.98)';
|
||||
@@ -340,29 +296,38 @@ function syncHorizonUI() {
|
||||
}
|
||||
|
||||
function renderCalendarPopover() {
|
||||
const gridEl = document.getElementById('pantry-calendar-grid');
|
||||
if (!gridEl) return;
|
||||
pantryCalendar?.render();
|
||||
}
|
||||
|
||||
ensureValidHorizonDate();
|
||||
const today = getToday();
|
||||
const plans = loadPlans();
|
||||
|
||||
renderCalendarGrid({
|
||||
gridEl,
|
||||
mode: 'month',
|
||||
anchorDate: calendarMonthAnchor,
|
||||
selectedDate: horizonEndDate,
|
||||
resolveDayState: (day, meta) => {
|
||||
const isPast = day.getTime() < today.getTime();
|
||||
function bindPantryCalendarInteractions() {
|
||||
pantryCalendar = initSwipePopoverCalendar({
|
||||
idPrefix: 'pantry-cal',
|
||||
selectionMode: 'single',
|
||||
panelHandlePx: 10,
|
||||
panelHandleMin: 8,
|
||||
panelHandleMax: 12,
|
||||
getMonthAnchor: () => calendarMonthAnchor,
|
||||
setMonthAnchor: (nextMonth) => {
|
||||
const nextAnchor = startOfMonth(nextMonth);
|
||||
const minAnchor = startOfMonth(getToday());
|
||||
if (nextAnchor.getTime() < minAnchor.getTime()) return;
|
||||
calendarMonthAnchor = nextAnchor;
|
||||
},
|
||||
getSelectionKeys: () => dateKey(horizonEndDate),
|
||||
onSelectionCommit: (selectedKey) => {
|
||||
selectHorizonDate(new Date(`${selectedKey}T00:00:00`));
|
||||
},
|
||||
resolveDayState: (day, { inCurrentMonth }) => {
|
||||
const isPast = day.getTime() < getToday().getTime();
|
||||
return {
|
||||
disabled: isPast,
|
||||
dimmed: isPast || !meta.inCurrentMonth,
|
||||
showIndicator: dayHasAnyMeal(plans, day),
|
||||
dimmed: isPast || !inCurrentMonth,
|
||||
showDot: dayHasAnyMeal(loadPlans(), day),
|
||||
};
|
||||
},
|
||||
dayAttr: PANTRY_CALENDAR_DAY_ATTR,
|
||||
theme: PANTRY_CALENDAR_THEME,
|
||||
});
|
||||
pantryCalendar.render();
|
||||
}
|
||||
|
||||
function filterChipHtml(kind, value, label, active) {
|
||||
@@ -434,6 +399,7 @@ function openSearch() {
|
||||
function closeCalendar() {
|
||||
if (!isCalendarOpen) return;
|
||||
isCalendarOpen = false;
|
||||
pantryCalendar?.resetTrackPosition();
|
||||
syncHorizonUI();
|
||||
}
|
||||
|
||||
@@ -444,6 +410,10 @@ function openCalendar() {
|
||||
isFilterOpen = false;
|
||||
isCalendarOpen = true;
|
||||
syncHorizonUI();
|
||||
requestAnimationFrame(() => {
|
||||
pantryCalendar?.reapplyLayout();
|
||||
pantryCalendar?.resetTrackPosition();
|
||||
});
|
||||
}
|
||||
|
||||
function closeFilter() {
|
||||
@@ -767,23 +737,7 @@ export function setupPantry() {
|
||||
event.stopPropagation();
|
||||
isCalendarOpen ? closeCalendar() : openCalendar();
|
||||
});
|
||||
document.getElementById('pantry-cal-prev')?.addEventListener('click', () => {
|
||||
const prevMonth = addMonths(calendarMonthAnchor, -1);
|
||||
if (prevMonth.getTime() < startOfMonth(getToday()).getTime()) return;
|
||||
calendarMonthAnchor = prevMonth;
|
||||
syncHorizonUI();
|
||||
});
|
||||
document.getElementById('pantry-cal-next')?.addEventListener('click', () => {
|
||||
calendarMonthAnchor = addMonths(calendarMonthAnchor, 1);
|
||||
syncHorizonUI();
|
||||
});
|
||||
document.getElementById('pantry-cal-today')?.addEventListener('click', () => {
|
||||
calendarMonthAnchor = startOfMonth(getToday());
|
||||
syncHorizonUI();
|
||||
});
|
||||
bindCalendarDayClicks(document.getElementById('pantry-calendar-grid'), (date) => {
|
||||
selectHorizonDate(date);
|
||||
}, PANTRY_CALENDAR_DAY_ATTR);
|
||||
bindPantryCalendarInteractions();
|
||||
|
||||
if (!pantryGlobalListenersBound) {
|
||||
pantryGlobalListenersBound = true;
|
||||
|
||||
Reference in New Issue
Block a user