summaryrefslogtreecommitdiff
path: root/web/templates/partials
diff options
context:
space:
mode:
Diffstat (limited to 'web/templates/partials')
-rw-r--r--web/templates/partials/shopping-tab.html74
-rw-r--r--web/templates/partials/timeline-tab.html172
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}}