diff --git a/shaarli-pro/css/style.css b/shaarli-pro/css/style.css index 0ad2dac..b2329e7 100644 --- a/shaarli-pro/css/style.css +++ b/shaarli-pro/css/style.css @@ -2576,79 +2576,30 @@ select:focus { padding: 1.5rem 0 2.5rem; } -.daily-topbar { +.daily-nav-unified { display: flex; align-items: center; justify-content: space-between; - gap: 1rem; - margin-bottom: 1.5rem; - padding: 0.75rem 1rem; - border-radius: 16px; - background: var(--bg-card); + gap: 1.5rem; + padding: 1.1rem 1.4rem; + border-radius: 18px; + background: linear-gradient(135deg, rgba(15, 23, 42, 0.08), rgba(15, 23, 42, 0.03)); border: 1px solid var(--border); box-shadow: var(--shadow-sm); + margin-bottom: 1.6rem; + flex-wrap: wrap; } -.daily-topbar-left { +.daily-nav-left { display: inline-flex; align-items: center; - gap: 0.6rem; + gap: 0.65rem; font-weight: 600; color: var(--text-main); + white-space: nowrap; } -.daily-topbar-tabs { - display: inline-flex; - gap: 0.5rem; - background: var(--bg-body); - padding: 0.35rem; - border-radius: 999px; - border: 1px solid var(--border-light); -} - -.daily-tab { - padding: 0.45rem 0.95rem; - border-radius: 999px; - font-weight: 600; - font-size: 0.85rem; - color: var(--text-secondary); - transition: all 0.2s ease; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.daily-tab.is-active { - background: var(--primary); - color: #ffffff; - box-shadow: 0 8px 18px rgba(59, 130, 246, 0.35); -} - -.daily-tab.is-inactive:hover { - color: var(--text-main); - background: var(--bg-card-hover); -} - -.daily-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - margin-bottom: 1.75rem; - padding: 0.9rem 1.1rem; - border-radius: 16px; - background: linear-gradient(135deg, rgba(15, 23, 42, 0.05), rgba(15, 23, 42, 0.02)); - border: 1px solid var(--border); -} - -.daily-toolbar-label { - display: inline-flex; - align-items: center; - gap: 0.6rem; - font-weight: 600; - color: var(--text-main); -} - -.daily-toolbar-nav { +.daily-nav-center { display: flex; align-items: center; gap: 0.75rem; @@ -2657,56 +2608,352 @@ select:focus { flex-wrap: wrap; } -.daily-nav-btn { +.daily-nav-tabs { display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 0.95rem; - border-radius: 10px; - border: 1px solid var(--border); + gap: 0.4rem; + padding: 0.3rem; + border-radius: 999px; + background: var(--bg-body); + border: 1px solid var(--border-light); + white-space: nowrap; +} + +[data-theme="dark"] .daily-nav-tabs { + background: #0f172a; + border-color: #1f2937; +} + +.daily-tab { + padding: 0.45rem 1rem; + border-radius: 999px; + font-weight: 700; + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-secondary); + transition: all 0.2s ease; + border: 1px solid transparent; +} + +.daily-tab.is-active { + background: var(--primary); + color: #ffffff; + box-shadow: 0 12px 24px rgba(59, 130, 246, 0.3); +} + +.daily-tab.is-inactive:hover { + background: var(--bg-card-hover); + color: var(--text-main); + border-color: var(--border-light); +} + +.daily-date-pill { + border: 1px dashed var(--border); background: var(--bg-card); color: var(--text-main); font-weight: 600; - font-size: 0.85rem; + padding: 0.4rem 1.1rem; + border-radius: 999px; + cursor: pointer; + font-size: 0.9rem; transition: all 0.2s ease; } -.daily-nav-btn:hover { +.daily-date-pill:hover { background: var(--bg-card-hover); - transform: translateY(-1px); -} - -.daily-nav-btn[disabled] { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -.daily-current-date { - font-weight: 700; - color: var(--text-main); - padding: 0.35rem 0.9rem; - border-radius: 999px; - background: var(--bg-card); - border: 1px dashed var(--border); - font-size: 0.9rem; } .daily-calendar-btn { display: inline-flex; align-items: center; gap: 0.5rem; - padding: 0.5rem 0.95rem; + padding: 0.45rem 1.05rem; border-radius: 999px; - background: var(--primary-light); - color: var(--primary); + border: 1px solid var(--border-light); + background: var(--bg-card); + color: var(--text-main); font-weight: 600; font-size: 0.85rem; + cursor: pointer; + transition: all 0.2s ease; +} + +[data-theme="dark"] .daily-calendar-btn { + background: #0f172a; + border-color: #1f2937; + color: #e2e8f0; +} + +[data-theme="dark"] .daily-calendar-btn:hover { + background: #111827; + border-color: #334155; } .daily-calendar-btn:hover { - background: rgba(59, 130, 246, 0.2); - color: var(--primary-hover); + background: var(--bg-card-hover); + color: var(--text-main); + border-color: var(--border); +} + +.daily-calendar-wrap { + position: relative; +} + +.daily-calendar-panel { + position: absolute; + top: calc(100% + 12px); + right: 0; + z-index: 120; + display: none; +} + +.daily-calendar-panel.is-open { + display: block; +} + +.daily-calendar-shell { + display: flex; + width: min(640px, 92vw); + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 20px; + box-shadow: 0 25px 60px rgba(15, 23, 42, 0.35); + overflow: hidden; +} + +.daily-calendar-sidebar { + width: 190px; + padding: 1.1rem; + background: var(--bg-body); + border-right: 1px solid var(--border-light); + display: flex; + flex-direction: column; + gap: 0.6rem; +} + +.daily-calendar-title { + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); + margin-bottom: 0.5rem; +} + +.daily-calendar-shortcut { + text-align: left; + padding: 0.5rem 0.7rem; + border-radius: 10px; + border: 1px solid transparent; + background: transparent; + color: var(--text-secondary); + font-size: 0.85rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.daily-calendar-shortcut:hover { + background: var(--bg-card-hover); + color: var(--text-main); + border-color: var(--border); +} + +.daily-calendar-summary { + margin-top: auto; + padding-top: 1rem; + border-top: 1px solid var(--border-light); + font-size: 0.75rem; + color: var(--text-muted); + display: grid; + gap: 0.4rem; +} + +.daily-calendar-summary strong { + display: block; + color: var(--text-main); + font-size: 0.85rem; + margin-top: 0.2rem; +} + +.daily-calendar-main { + flex: 1; + padding: 1.5rem; + display: flex; + flex-direction: column; +} + +.daily-calendar-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; +} + +.daily-calendar-nav { + width: 36px; + height: 36px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--bg-body); + color: var(--text-secondary); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.daily-calendar-nav:hover { + color: var(--text-main); + background: var(--bg-card-hover); +} + +.daily-calendar-month { + font-weight: 700; + color: var(--text-main); + font-size: 1rem; + text-transform: capitalize; +} + +.daily-calendar-weekdays, +.daily-calendar-grid { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 0.35rem; + text-align: center; +} + +.daily-calendar-weekdays { + margin-bottom: 0.5rem; + font-size: 0.7rem; + text-transform: uppercase; + color: var(--text-muted); + letter-spacing: 0.05em; +} + +/* --- Styles Calendrier React-like --- */ +.daily-calendar-day { + width: 40px; + height: 40px; + border-radius: 999px; + border: none; + background: transparent; + color: var(--text-secondary); + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.15s ease; + display: inline-flex; + align-items: center; + justify-content: center; +} + +/* Hover: cercle visible comme dans React */ +.daily-calendar-day:hover { + background: rgba(59, 130, 246, 0.18); + color: var(--text-main); + border-radius: 999px; + box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.25); +} + +[data-theme="dark"] .daily-calendar-day:hover { + background: rgba(99, 102, 241, 0.25); + color: #ffffff; + box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.35); +} + +/* In range: fond bleu semi-transparent, pas d'arrondi */ +.daily-calendar-day.is-in-range { + background: rgba(59, 130, 246, 0.16); + color: var(--primary); + border-radius: 0; +} + +[data-theme="dark"] .daily-calendar-day.is-in-range { + background: rgba(59, 130, 246, 0.25); + color: #60a5fa; +} + +/* Start/End: fond bleu plein avec glow */ +.daily-calendar-day.is-range-start, +.daily-calendar-day.is-range-end { + background: var(--primary); + color: #ffffff; + box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); +} + +[data-theme="dark"] .daily-calendar-day.is-range-start, +[data-theme="dark"] .daily-calendar-day.is-range-end { + background: #2563eb; + box-shadow: 0 0 15px rgba(37, 99, 235, 0.5); +} + +/* Single date selection: cercle complet */ +.daily-calendar-day.is-range-single { + border-radius: 999px; +} + +/* Range start: arrondi gauche seulement */ +.daily-calendar-day.is-range-start:not(.is-range-end) { + border-radius: 999px 0 0 999px; +} + +/* Range end: arrondi droite seulement */ +.daily-calendar-day.is-range-end:not(.is-range-start) { + border-radius: 0 999px 999px 0; +} + +/* Hover sur in-range/start/end: pas de shadow supplémentaire */ +.daily-calendar-day.is-in-range:hover, +.daily-calendar-day.is-range-start:hover, +.daily-calendar-day.is-range-end:hover { + box-shadow: none; +} + +.daily-calendar-footer { + margin-top: auto; + padding-top: 1rem; + border-top: 1px solid var(--border-light); + display: flex; + justify-content: flex-end; + gap: 0.6rem; +} + +.daily-calendar-cancel, +.daily-calendar-apply { + padding: 0.45rem 1rem; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--bg-body); + color: var(--text-secondary); + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.daily-calendar-cancel:hover { + background: var(--bg-card-hover); + color: var(--text-main); +} + +.daily-calendar-apply { + background: var(--primary); + border-color: transparent; + color: #ffffff; + box-shadow: 0 12px 24px rgba(59, 130, 246, 0.25); +} + +.daily-calendar-apply:disabled { + opacity: 0.6; + cursor: not-allowed; + box-shadow: none; +} + +.daily-calendar-wrap.is-open .daily-calendar-btn { + background: rgba(59, 130, 246, 0.16); + color: var(--primary); + border-color: rgba(59, 130, 246, 0.35); } .daily-grid { @@ -2782,36 +3029,11 @@ select:focus { font-weight: 500; } -[data-theme="dark"] .daily-topbar, -[data-theme="dark"] .daily-toolbar { - background: #111827; +[data-theme="dark"] .daily-nav-unified { + background: linear-gradient(135deg, rgba(15, 23, 42, 0.8), rgba(15, 23, 42, 0.55)); border-color: #1f2937; } -[data-theme="dark"] .daily-topbar-tabs { - background: #0f172a; - border-color: #1f2937; -} - -[data-theme="dark"] .daily-tab.is-active { - box-shadow: 0 10px 20px rgba(96, 165, 250, 0.35); -} - -[data-theme="dark"] .daily-calendar-btn { - background: rgba(96, 165, 250, 0.15); - color: #cfe1ff; -} - -[data-theme="dark"] .daily-card { - background: #101a2c; - border-color: #1e2a44; - box-shadow: 0 18px 26px rgba(4, 12, 26, 0.45); -} - -[data-theme="dark"] .daily-item-footer { - border-top-color: #1e2a44; -} - @media (max-width: 1200px) { .daily-grid { column-count: 3; @@ -2823,14 +3045,8 @@ select:focus { column-count: 2; } - .daily-toolbar { - flex-direction: column; - align-items: flex-start; - } - - .daily-toolbar-nav { - width: 100%; - justify-content: flex-start; + .daily-nav-unified { + justify-content: center; } } @@ -2839,14 +3055,8 @@ select:focus { column-count: 1; } - .daily-topbar { - flex-direction: column; - align-items: flex-start; - } - - .daily-topbar-tabs { - width: 100%; - justify-content: space-between; + .daily-nav-unified { + padding: 1rem; } } diff --git a/shaarli-pro/daily.html b/shaarli-pro/daily.html index 645b88e..04f31d7 100644 --- a/shaarli-pro/daily.html +++ b/shaarli-pro/daily.html @@ -8,12 +8,67 @@ {include="page.header"}
-
-
- +
+
+ {'Time navigation'|t}
-
+
+ + + {function="t('Previous :type', '', 1, 'shaarli', [':type' => t($type)], true)"} + + + + {function="t('Next :type', '', 1, 'shaarli', [':type' => t($type)], true)"} + + +
+ + +
+
+
{'Daily'|t} {'Weekly'|t} {'Monthly'|t} @@ -25,28 +80,6 @@ {/loop}
- -
{loop="$daily_about_plugin"} {$value} diff --git a/shaarli-pro/js/script.js b/shaarli-pro/js/script.js index 5dee8b9..c25f4b9 100644 --- a/shaarli-pro/js/script.js +++ b/shaarli-pro/js/script.js @@ -435,8 +435,6 @@ document.addEventListener('DOMContentLoaded', () => { const currentVisibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : ''; const currentUntagged = (typeof shaarli !== 'undefined' && shaarli.untaggedonly) || false; - - let url = basePath + '/'; // Determine desired visibility @@ -464,7 +462,6 @@ document.addEventListener('DOMContentLoaded', () => { } } - window.location.href = url; } @@ -506,8 +503,6 @@ document.addEventListener('DOMContentLoaded', () => { // Initialize filter states from server-side variables (set in includes.html) (function initFilterStates() { - - // Get filter state from server-side rendered variables const visibility = (typeof shaarli !== 'undefined' && shaarli.visibility) ? shaarli.visibility : ''; const untaggedonly = (typeof shaarli !== 'undefined' && shaarli.untaggedonly) ? shaarli.untaggedonly : false; @@ -517,36 +512,28 @@ document.addEventListener('DOMContentLoaded', () => { let isPublicActive = visibility === 'public'; let isUntaggedActive = untaggedonly === true; - - // Set checkbox states if (filterPrivate && isPrivateActive) { filterPrivate.checked = true; - } if (filterPublic && isPublicActive) { filterPublic.checked = true; - } if (filterUntagged && isUntaggedActive) { filterUntagged.checked = true; - } const hasActiveFilter = isPrivateActive || isPublicActive || isUntaggedActive; - // Add/update filter indicator badge on the filter button if (filterToggleBtn && hasActiveFilter) { filterToggleBtn.classList.add('has-active-filter'); - // Add badge indicator if not exists if (!filterToggleBtn.querySelector('.filter-badge')) { const badge = document.createElement('span'); badge.className = 'filter-badge'; filterToggleBtn.appendChild(badge); - } } @@ -599,8 +586,6 @@ document.addEventListener('DOMContentLoaded', () => { } } - - if (message) { // Get base path safely const basePath = (typeof shaarli !== 'undefined' && shaarli.basePath) ? shaarli.basePath : ''; @@ -623,25 +608,18 @@ document.addEventListener('DOMContentLoaded', () => { const linklist = document.getElementById('linklist'); const emptyStateDiv = document.querySelector('.empty-state'); - - // Insert after the content-toolbar (pagination) if (contentToolbar && contentToolbar.parentNode) { contentToolbar.parentNode.insertBefore(banner, contentToolbar.nextSibling); - } else if (emptyStateDiv && emptyStateDiv.parentNode) { emptyStateDiv.parentNode.insertBefore(banner, emptyStateDiv); - } else if (linklist) { linklist.insertBefore(banner, linklist.firstChild); - } else { - } // Add click handler to clear button document.getElementById('filter-clear-btn')?.addEventListener('click', () => { - // Clear based on what was active if (isPrivateActive || isPublicActive) { window.location.href = basePath + '/admin/visibility/all'; @@ -658,7 +636,6 @@ document.addEventListener('DOMContentLoaded', () => { // Handle links per page options - // Handle custom value form submission const filterInput = document.querySelector('.filter-input'); if (filterInput) { @@ -887,7 +864,6 @@ document.addEventListener('DOMContentLoaded', () => { form.submit(); } - // ===== Thumbnail Update ===== const thumbnailsPage = document.querySelector('.page-thumbnails'); if (thumbnailsPage) { @@ -903,9 +879,6 @@ document.addEventListener('DOMContentLoaded', () => { let i = 0; const updateThumbnail = function (id) { - - - fetch(shaarli.basePath + '/admin/shaare/' + id + '/update-thumbnail', { method: 'PATCH', headers: { @@ -2195,6 +2168,12 @@ document.addEventListener('DOMContentLoaded', () => { lines.push(' audio.pause();'); lines.push(' audio.src = \'\';'); lines.push(' localStorage.removeItem(\'mediaPopupState\');'); + lines.push(' localStorage.removeItem(\'mediaPlayerUrl\');'); + lines.push(' localStorage.removeItem(\'mediaPlayerTitle\');'); + lines.push(' localStorage.removeItem(\'mediaPlayerPlaying\');'); + lines.push(' if (channel) {'); + lines.push(' try { channel.postMessage({ type: \'player-closed\' }); } catch(e) {}'); + lines.push(' }'); lines.push(' window.close();'); lines.push(' } else if (msg.type === \'player-seek\') {'); lines.push(' if (audio.duration && isFinite(audio.duration)) {'); @@ -2221,23 +2200,16 @@ document.addEventListener('DOMContentLoaded', () => { lines.push(''); lines.push(' // --- Also listen for localStorage changes (fallback for no BroadcastChannel) ---'); lines.push(' window.addEventListener(\'storage\', function(e) {'); - lines.push(' if (e.key === \'mediaPlayerCommand\' && e.newValue) {'); + lines.push(' if (e.key === \'mediaPopupState\' && e.newValue) {'); lines.push(' try {'); - lines.push(' var cmd = JSON.parse(e.newValue);'); - lines.push(' if (cmd.action === \'load\') {'); - lines.push(' loadAndPlay(cmd.url, cmd.title);'); - lines.push(' } else if (cmd.action === \'toggle\') {'); - lines.push(' if (audio.paused) audio.play().catch(function() {});'); - lines.push(' else audio.pause();'); - lines.push(' } else if (cmd.action === \'stop\') {'); - lines.push(' audio.pause();'); - lines.push(' audio.src = \'\';'); - lines.push(' localStorage.removeItem(\'mediaPopupState\');'); - lines.push(' window.close();'); + lines.push(' var state = JSON.parse(e.newValue);'); + lines.push(' if (state.url) {'); + lines.push(' loadAndPlay(state.url, state.title);'); lines.push(' }'); - lines.push(' // Clear the command'); - lines.push(' localStorage.removeItem(\'mediaPlayerCommand\');'); lines.push(' } catch(err) {}'); + lines.push(' } else if (e.key === \'mediaPlayerClosed\') {'); + lines.push(' if (playerBar) playerBar.classList.remove(\'show\');'); + lines.push(' playerPopup = null;'); lines.push(' }'); lines.push(' });'); lines.push(''); @@ -2276,6 +2248,8 @@ document.addEventListener('DOMContentLoaded', () => { * Open (or re-use) the popup player window using a Blob URL. * Blob URLs share the parent page's origin, so BroadcastChannel * and localStorage work seamlessly - and no server file is needed. + * Communication via BroadcastChannel API. + * The inline bar serves as a "Now Playing" indicator and control relay. */ function showPlayer(url, title) { // Save config to localStorage - the popup reads it on load @@ -2329,9 +2303,6 @@ document.addEventListener('DOMContentLoaded', () => { updateInlineBar(title, true); } - /** - * Update the inline "Now Playing" bar - */ function updateInlineBar(title, playing) { if (!playerBar) return; @@ -2522,4 +2493,249 @@ document.addEventListener('DOMContentLoaded', () => { } catch (err) { } } })(); + + // --- Daily calendar range picker --- + (function initDailyCalendar() { + var calendarToggle = document.getElementById('daily-calendar-toggle'); + var calendarPanel = document.getElementById('daily-calendar-panel'); + var calendarWrap = calendarPanel ? calendarPanel.closest('.daily-calendar-wrap') : null; + var calendarMonth = document.getElementById('daily-calendar-month'); + var calendarWeekdays = document.getElementById('daily-calendar-weekdays'); + var calendarGrid = document.getElementById('daily-calendar-grid'); + var calendarPrev = document.getElementById('daily-calendar-prev'); + var calendarNext = document.getElementById('daily-calendar-next'); + var calendarStart = document.getElementById('daily-calendar-start'); + var calendarEnd = document.getElementById('daily-calendar-end'); + var calendarCancel = document.getElementById('daily-calendar-cancel'); + var calendarApply = document.getElementById('daily-calendar-apply'); + var dateDisplay = document.getElementById('daily-date-display'); + var shortcuts = document.querySelectorAll('.daily-calendar-shortcut'); + + if (!calendarToggle || !calendarPanel || !calendarGrid || !calendarWeekdays || !calendarMonth) return; + + var MONTH_NAMES = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']; + var DAY_NAMES = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']; + + var state = { + viewDate: new Date(), + startDate: null, + endDate: null, + hoverDate: null + }; + + function getDaysInMonth(year, month) { + return new Date(year, month + 1, 0).getDate(); + } + + function getFirstDayOfMonth(year, month) { + var day = new Date(year, month, 1).getDay(); + return day === 0 ? 6 : day - 1; + } + + function formatDate(date) { + if (!date) return ''; + return new Intl.DateTimeFormat('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' }).format(date); + } + + function isSameDay(d1, d2) { + if (!d1 || !d2) return false; + return d1.getDate() === d2.getDate() && + d1.getMonth() === d2.getMonth() && + d1.getFullYear() === d2.getFullYear(); + } + + function isDateBetween(date, start, end) { + if (!start || !end || !date) return false; + return date > start && date < end; + } + + function normalizeDate(date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); + } + + function updateSummary() { + if (calendarStart) calendarStart.textContent = state.startDate ? formatDate(state.startDate) : '-'; + if (calendarEnd) calendarEnd.textContent = state.endDate ? formatDate(state.endDate) : '-'; + if (calendarApply) calendarApply.disabled = !(state.startDate && state.endDate); + if (dateDisplay) { + if (state.startDate && state.endDate) { + if (isSameDay(state.startDate, state.endDate)) { + dateDisplay.textContent = formatDate(state.startDate); + } else { + dateDisplay.textContent = formatDate(state.startDate) + ' - ' + formatDate(state.endDate); + } + } + } + } + + function renderWeekdays() { + calendarWeekdays.innerHTML = DAY_NAMES.map(function(day) { + return '' + day + ''; + }).join(''); + } + + function renderDays() { + var year = state.viewDate.getFullYear(); + var month = state.viewDate.getMonth(); + var daysInMonth = getDaysInMonth(year, month); + var firstDay = getFirstDayOfMonth(year, month); + + calendarMonth.textContent = MONTH_NAMES[month] + ' ' + year; + calendarGrid.innerHTML = ''; + + // Empty slots + for (var i = 0; i < firstDay; i++) { + var empty = document.createElement('span'); + empty.className = 'daily-calendar-empty'; + calendarGrid.appendChild(empty); + } + + // Days + for (var day = 1; day <= daysInMonth; day++) { + var currentDate = new Date(year, month, day); + var btn = document.createElement('button'); + btn.type = 'button'; + btn.textContent = day; + btn.className = 'daily-calendar-day'; + + var isSelectedStart = isSameDay(currentDate, state.startDate); + var isSelectedEnd = isSameDay(currentDate, state.endDate); + var rangeEnd = state.endDate || state.hoverDate; + var isInRange = rangeEnd && isDateBetween(currentDate, state.startDate, rangeEnd); + + // Apply classes like React version + if (isSelectedStart && isSelectedEnd) { + btn.classList.add('is-range-start', 'is-range-end', 'is-range-single'); + } else if (isSelectedStart) { + btn.classList.add('is-range-start'); + } else if (isSelectedEnd) { + btn.classList.add('is-range-end'); + } else if (isInRange) { + btn.classList.add('is-in-range'); + } + + // Event handlers with closure + (function(d, y, m) { + btn.addEventListener('mouseenter', function() { + if (state.startDate && !state.endDate) { + var newHoverDate = new Date(y, m, d); + if (!state.hoverDate || state.hoverDate.getTime() !== newHoverDate.getTime()) { + state.hoverDate = newHoverDate; + renderDays(); + } + } + }); + + btn.addEventListener('click', function(e) { + e.stopPropagation(); + var clickedDate = normalizeDate(new Date(y, m, d)); + console.log('Date clicked:', clickedDate); + console.log('Before click - Start:', state.startDate, 'End:', state.endDate); + + if (!state.startDate || (state.startDate && state.endDate)) { + // Start new selection + state.startDate = clickedDate; + state.endDate = null; + console.log('New selection started - Start:', state.startDate, 'End:', state.endDate); + } else if (state.startDate && !state.endDate) { + // Complete selection + if (clickedDate < state.startDate) { + state.endDate = state.startDate; + state.startDate = clickedDate; + console.log('Range completed (reverse) - Start:', state.startDate, 'End:', state.endDate); + } else { + state.endDate = clickedDate; + console.log('Range completed - Start:', state.startDate, 'End:', state.endDate); + } + } + state.hoverDate = null; + updateSummary(); + renderDays(); + }); + })(day, year, month); + + calendarGrid.appendChild(btn); + } + } + + function setOpen(isOpen) { + calendarPanel.classList.toggle('is-open', isOpen); + calendarPanel.setAttribute('aria-hidden', String(!isOpen)); + if (calendarWrap) calendarWrap.classList.toggle('is-open', isOpen); + if (calendarToggle) calendarToggle.setAttribute('aria-expanded', String(isOpen)); + } + + function selectPreset(days) { + var end = normalizeDate(new Date()); + var start = normalizeDate(new Date()); + start.setDate(end.getDate() - days); + state.startDate = start; + state.endDate = end; + state.viewDate = new Date(end.getFullYear(), end.getMonth(), 1); + updateSummary(); + renderDays(); + } + + // Event listeners + calendarToggle.addEventListener('click', function() { + var willOpen = !calendarPanel.classList.contains('is-open'); + setOpen(willOpen); + }); + + document.addEventListener('click', function(event) { + if (!calendarPanel.classList.contains('is-open')) return; + if (calendarWrap && !calendarWrap.contains(event.target) && event.target !== calendarToggle) { + setOpen(false); + } + }); + + calendarPanel.addEventListener('click', function(event) { + event.stopPropagation(); + }); + + if (calendarPrev) { + calendarPrev.addEventListener('click', function() { + state.viewDate = new Date(state.viewDate.getFullYear(), state.viewDate.getMonth() - 1, 1); + renderDays(); + }); + } + + if (calendarNext) { + calendarNext.addEventListener('click', function() { + state.viewDate = new Date(state.viewDate.getFullYear(), state.viewDate.getMonth() + 1, 1); + renderDays(); + }); + } + + shortcuts.forEach(function(shortcut) { + shortcut.addEventListener('click', function() { + var range = parseInt(this.dataset.range, 10); + selectPreset(range); + }); + }); + + if (calendarCancel) { + calendarCancel.addEventListener('click', function() { + setOpen(false); + }); + } + + if (calendarApply) { + calendarApply.addEventListener('click', function() { + if (!state.startDate || !state.endDate) return; + setOpen(false); + // Navigate to selected range + var startFormatted = state.startDate.toISOString().split('T')[0].replace(/-/g, ''); + var endFormatted = state.endDate.toISOString().split('T')[0].replace(/-/g, ''); + var url = window.location.pathname + '?start=' + startFormatted + '&end=' + endFormatted; + console.log('Navigating to range URL:', url); + window.location.href = url; + }); + } + + // Initialize + renderWeekdays(); + updateSummary(); + renderDays(); + })(); });