diff options
Diffstat (limited to 'web/templates/partials')
| -rw-r--r-- | web/templates/partials/shopping-tab.html | 74 | ||||
| -rw-r--r-- | web/templates/partials/timeline-tab.html | 172 |
2 files changed, 57 insertions, 189 deletions
diff --git a/web/templates/partials/shopping-tab.html b/web/templates/partials/shopping-tab.html index b09a54f..345549b 100644 --- a/web/templates/partials/shopping-tab.html +++ b/web/templates/partials/shopping-tab.html @@ -1,9 +1,5 @@ {{define "shopping-tab"}} -<div class="space-y-6 text-shadow-sm" - hx-get="/tabs/shopping{{if not .Grouped}}?grouped=false{{end}}" - hx-trigger="refresh-tasks from:body" - hx-target="#tab-content" - hx-swap="innerHTML"> +<div class="space-y-6 text-shadow-sm" id="shopping-tab-container"> <!-- Header with View Toggle --> <div class="flex items-center justify-between"> @@ -25,30 +21,74 @@ </div> <!-- Quick Add Form --> - <form hx-post="/shopping/add" - hx-target="#tab-content" - hx-swap="innerHTML" + <form id="shopping-quick-add" + hx-post="/shopping/add" + hx-target="#shopping-items-list" + hx-swap="innerHTML scroll:#shopping-quick-add:top" + hx-indicator="#shopping-add-indicator" class="bg-panel backdrop-blur-sm rounded-xl p-4 sm:p-5"> + <input type="hidden" name="grouped" value="{{.Grouped}}"> <div class="flex gap-2"> <input type="text" name="name" placeholder="Add item..." class="flex-1 bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-white/30 text-sm" - required> + required autocomplete="off"> <select name="store" class="bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:ring-2 focus:ring-white/30" required> {{range .Stores}} <option value="{{.Name}}">{{.Name}}</option> {{end}} </select> <button type="submit" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"> - Add + <span id="shopping-add-indicator" class="htmx-indicator">...</span> + <span class="htmx-hide-indicator">Add</span> </button> </div> </form> + <!-- Items List Container --> + <div id="shopping-items-list"> + {{template "shopping-items-content" .}} + </div> + + <script> + (function() { + document.body.addEventListener('htmx:afterRequest', function(evt) { + if (evt.detail.elt.id === 'shopping-quick-add' && evt.detail.successful) { + var form = evt.detail.elt; + var input = form.querySelector('input[name="name"]'); + if (input) { + input.value = ''; + input.focus(); + } + form.classList.add('flash-success'); + setTimeout(function() { form.classList.remove('flash-success'); }, 300); + } + // Handle inline add forms + if (evt.detail.elt.classList.contains('inline-add-form') && evt.detail.successful) { + var form = evt.detail.elt; + var input = form.querySelector('input[name="name"]'); + if (input) { + input.value = ''; + input.focus(); + } + } + }); + })(); + </script> + <style> + .flash-success { background: rgba(34, 197, 94, 0.2) !important; transition: background 0.3s; } + .htmx-indicator { display: none; } + .htmx-request .htmx-indicator { display: inline; } + .htmx-request .htmx-hide-indicator { display: none; } + </style> +</div> +{{end}} + +{{define "shopping-items-content"}} {{if .Stores}} {{if .Grouped}} <!-- Grouped View: Items by Store --> {{range .Stores}} - <section class="bg-panel backdrop-blur-sm rounded-xl p-4 sm:p-5"> + <section class="bg-panel backdrop-blur-sm rounded-xl p-4 sm:p-5 mb-6"> <div class="flex items-center justify-between mb-4"> <h2 class="text-xl font-medium text-white">{{.Name}}</h2> <a href="/shopping/mode/{{.Name}}" @@ -78,14 +118,15 @@ </div> {{end}} <!-- Inline Add Item --> - <form hx-post="/shopping/add" - hx-target="#tab-content" - hx-swap="innerHTML" - class="mt-3 flex gap-2"> + <form class="inline-add-form mt-3 flex gap-2" + hx-post="/shopping/add" + hx-target="#shopping-items-list" + hx-swap="innerHTML"> <input type="hidden" name="store" value="{{.Name}}"> + <input type="hidden" name="grouped" value="true"> <input type="text" name="name" placeholder="Add to {{.Name}}..." class="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white placeholder-white/30 focus:outline-none focus:ring-1 focus:ring-white/20 text-sm" - required> + required autocomplete="off"> <button type="submit" class="bg-white/10 hover:bg-white/20 text-white/70 hover:text-white px-3 py-2 rounded-lg text-sm transition-colors"> + </button> @@ -123,5 +164,4 @@ <p class="text-sm">Use the form above to add items quickly</p> </div> {{end}} -</div> {{end}} diff --git a/web/templates/partials/timeline-tab.html b/web/templates/partials/timeline-tab.html index 6775808..c73c1b5 100644 --- a/web/templates/partials/timeline-tab.html +++ b/web/templates/partials/timeline-tab.html @@ -199,98 +199,6 @@ {{end}} </div> - <script> - (function() { - const calendar = document.getElementById('today-calendar'); - if (!calendar) return; - const events = calendar.querySelectorAll('.calendar-event'); - const hourHeight = 40; - const startHour = parseInt(calendar.dataset.startHour) || 8; - const nowHour = parseInt(calendar.dataset.nowHour); - const nowMinute = parseInt(calendar.dataset.nowMinute); - - // Build event data for overlap detection - const eventData = []; - events.forEach(function(el) { - const hour = parseInt(el.dataset.hour); - const minute = parseInt(el.dataset.minute); - const endHourVal = parseInt(el.dataset.endHour); - const endMinute = parseInt(el.dataset.endMinute); - const startMin = hour * 60 + minute; - let endMin; - if (endHourVal > hour || (endHourVal === hour && endMinute > minute)) { - endMin = endHourVal * 60 + endMinute; - } else { - endMin = startMin + 55; - } - eventData.push({ el, startMin, endMin, column: 0 }); - }); - - // Assign columns for overlapping events - eventData.sort((a, b) => a.startMin - b.startMin); - for (let i = 0; i < eventData.length; i++) { - const ev = eventData[i]; - const overlaps = eventData.filter((other, j) => - j < i && other.endMin > ev.startMin && other.startMin < ev.endMin - ); - const usedCols = overlaps.map(o => o.column); - let col = 0; - while (usedCols.includes(col)) col++; - ev.column = col; - } - - // Position events with column-based indentation - eventData.forEach(function(ev) { - const el = ev.el; - const hour = parseInt(el.dataset.hour); - const minute = parseInt(el.dataset.minute); - const top = (hour - startHour) * hourHeight + (minute / 60) * hourHeight; - const durationMinutes = ev.endMin - ev.startMin; - const height = Math.max(28, (durationMinutes / 60) * hourHeight - 4); - - el.style.top = top + 'px'; - el.style.height = height + 'px'; - el.style.left = (8 + ev.column * 16) + 'px'; - el.style.display = 'block'; - - // Debounced hover effect (100ms delay) - let hoverTimeout; - el.addEventListener('mouseenter', function() { - hoverTimeout = setTimeout(() => el.classList.add('hover-active'), 100); - }); - el.addEventListener('mouseleave', function() { - clearTimeout(hoverTimeout); - el.classList.remove('hover-active'); - }); - - const url = el.dataset.url; - if (url) { - el.style.cursor = 'pointer'; - el.addEventListener('click', function(e) { - if (e.target.tagName !== 'INPUT') { - window.open(url, '_blank'); - } - }); - } - }); - - // Position the "now" line - const nowLine = document.getElementById('now-line'); - if (nowLine && !isNaN(nowHour)) { - const nowTop = (nowHour - startHour) * hourHeight + (nowMinute / 60) * hourHeight; - if (nowTop >= 0) { - nowLine.style.top = nowTop + 'px'; - nowLine.style.display = 'block'; - } - } - - // Hide untimed section if empty - const untimedSection = document.getElementById('untimed-section'); - if (untimedSection && untimedSection.querySelectorAll('.untimed-item').length === 0) { - untimedSection.style.display = 'none'; - } - })(); - </script> </details> {{end}} @@ -376,86 +284,6 @@ {{end}} </div> - <script> - (function() { - const calendar = document.getElementById('tomorrow-calendar'); - if (!calendar) return; - const events = calendar.querySelectorAll('.calendar-event'); - const hourHeight = 40; - const startHour = parseInt(calendar.dataset.startHour) || 8; - - // Build event data for overlap detection - const eventData = []; - events.forEach(function(el) { - const hour = parseInt(el.dataset.hour); - const minute = parseInt(el.dataset.minute); - const endHourVal = parseInt(el.dataset.endHour); - const endMinute = parseInt(el.dataset.endMinute); - const startMin = hour * 60 + minute; - let endMin; - if (endHourVal > hour || (endHourVal === hour && endMinute > minute)) { - endMin = endHourVal * 60 + endMinute; - } else { - endMin = startMin + 55; - } - eventData.push({ el, startMin, endMin, column: 0 }); - }); - - // Assign columns for overlapping events - eventData.sort((a, b) => a.startMin - b.startMin); - for (let i = 0; i < eventData.length; i++) { - const ev = eventData[i]; - const overlaps = eventData.filter((other, j) => - j < i && other.endMin > ev.startMin && other.startMin < ev.endMin - ); - const usedCols = overlaps.map(o => o.column); - let col = 0; - while (usedCols.includes(col)) col++; - ev.column = col; - } - - // Position events with column-based indentation - eventData.forEach(function(ev) { - const el = ev.el; - const hour = parseInt(el.dataset.hour); - const minute = parseInt(el.dataset.minute); - const top = (hour - startHour) * hourHeight + (minute / 60) * hourHeight; - const durationMinutes = ev.endMin - ev.startMin; - const height = Math.max(28, (durationMinutes / 60) * hourHeight - 4); - - el.style.top = top + 'px'; - el.style.height = height + 'px'; - el.style.left = (8 + ev.column * 16) + 'px'; - el.style.display = 'block'; - - // Debounced hover effect (100ms delay) - let hoverTimeout; - el.addEventListener('mouseenter', function() { - hoverTimeout = setTimeout(() => el.classList.add('hover-active'), 100); - }); - el.addEventListener('mouseleave', function() { - clearTimeout(hoverTimeout); - el.classList.remove('hover-active'); - }); - - const url = el.dataset.url; - if (url) { - el.style.cursor = 'pointer'; - el.addEventListener('click', function(e) { - if (e.target.tagName !== 'INPUT') { - window.open(url, '_blank'); - } - }); - } - }); - - // Hide untimed section if empty - const untimedSection = document.getElementById('tomorrow-untimed-section'); - if (untimedSection && untimedSection.querySelectorAll('.untimed-item').length === 0) { - untimedSection.style.display = 'none'; - } - })(); - </script> </details> {{end}} |
