summaryrefslogtreecommitdiff
path: root/web/templates
diff options
context:
space:
mode:
Diffstat (limited to 'web/templates')
-rw-r--r--web/templates/partials/shopping-item-edit.html114
-rw-r--r--web/templates/partials/shopping-tab.html40
2 files changed, 152 insertions, 2 deletions
diff --git a/web/templates/partials/shopping-item-edit.html b/web/templates/partials/shopping-item-edit.html
new file mode 100644
index 0000000..e5220f5
--- /dev/null
+++ b/web/templates/partials/shopping-item-edit.html
@@ -0,0 +1,114 @@
+{{define "shopping-item-edit"}}
+<div id="shopping-edit-backdrop"
+ class="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm p-0 sm:p-4"
+ onclick="if(event.target === this) this.remove()">
+ <div class="w-full sm:max-w-md bg-slate-900 border border-white/10 rounded-t-2xl sm:rounded-2xl p-5 space-y-4">
+
+ <div class="flex items-start justify-between gap-3">
+ <h3 class="text-lg font-medium text-white">{{if .Editable}}Edit item{{else}}Item{{end}}</h3>
+ <button type="button" class="text-white/40 hover:text-white text-xl leading-none"
+ onclick="document.getElementById('shopping-edit-backdrop').remove()">&times;</button>
+ </div>
+
+ {{if .Editable}}
+ <form hx-post="/shopping/item/{{.ID}}"
+ hx-target="#shopping-items-list"
+ hx-swap="innerHTML"
+ hx-on::after-request="if(event.detail.successful) document.getElementById('shopping-edit-backdrop').remove()"
+ class="space-y-4">
+
+ <div>
+ <label class="block text-xs uppercase tracking-wide text-white/50 mb-1">Name</label>
+ <input type="text" name="name" value="{{.Name}}" required autocomplete="off"
+ class="w-full 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">
+ </div>
+
+ <div>
+ <label class="block text-xs uppercase tracking-wide text-white/50 mb-1">Store</label>
+ <select name="store" id="edit-store-select" required
+ class="w-full 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">
+ {{range .Stores}}
+ <option value="{{.Name}}" {{if .Selected}}selected{{end}}>{{.Name}}</option>
+ {{end}}
+ </select>
+ </div>
+
+ <div>
+ <label class="block text-xs uppercase tracking-wide text-white/50 mb-1">Section</label>
+ <select name="section" id="edit-section-select"
+ class="w-full 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">
+ <option value="">— none —</option>
+ </select>
+ <p class="text-xs text-white/30 mt-1">Sections belong to a store; changing store changes the options.</p>
+ </div>
+
+ <div class="flex gap-2 pt-1">
+ <button type="submit"
+ class="flex-1 bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
+ Save
+ </button>
+ <button type="button"
+ class="px-4 py-2 rounded-lg text-sm border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors"
+ hx-delete="/shopping/item/{{.ID}}"
+ hx-target="#shopping-items-list"
+ hx-swap="innerHTML"
+ hx-confirm="Delete {{.Name}}?"
+ hx-on::after-request="if(event.detail.successful) document.getElementById('shopping-edit-backdrop').remove()">
+ Delete
+ </button>
+ </div>
+ </form>
+
+ <script>
+ (function() {
+ // Section options are per-store, so they are rebuilt whenever the
+ // store changes rather than shipped as one flat list — otherwise
+ // you could file a Costco item under a Safeway aisle.
+ var sectionsByStore = {
+ {{- range .Stores}}
+ {{.Name}}: [{{range $i, $s := .Sections}}{{if $i}}, {{end}}{{$s.Name}}{{end}}],
+ {{- end}}
+ };
+ var current = {{.Section}};
+ var storeSel = document.getElementById('edit-store-select');
+ var sectionSel = document.getElementById('edit-section-select');
+
+ function rebuild() {
+ var opts = sectionsByStore[storeSel.value] || [];
+ sectionSel.innerHTML = '<option value="">— none —</option>';
+ opts.forEach(function(name) {
+ var o = document.createElement('option');
+ o.value = name;
+ o.textContent = name;
+ if (name === current) o.selected = true;
+ sectionSel.appendChild(o);
+ });
+ }
+ storeSel.addEventListener('change', function() { current = ''; rebuild(); });
+ rebuild();
+ })();
+ </script>
+
+ {{else}}
+ <div class="space-y-3">
+ <div>
+ <div class="text-white">{{.Name}}</div>
+ <div class="text-sm text-white/50">{{.Store}}</div>
+ </div>
+ <p class="text-sm text-amber-300/80 bg-amber-500/10 border border-amber-500/20 rounded-lg p-3">
+ {{.ReadOnlyWhy}}
+ </p>
+ <button type="button"
+ class="w-full px-4 py-2 rounded-lg text-sm border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors"
+ hx-delete="/shopping/item/{{.ID}}?source={{.Source}}"
+ hx-target="#shopping-items-list"
+ hx-swap="innerHTML"
+ hx-confirm="Remove {{.Name}} from the list?"
+ hx-on::after-request="if(event.detail.successful) document.getElementById('shopping-edit-backdrop').remove()">
+ Remove from list
+ </button>
+ </div>
+ {{end}}
+ </div>
+</div>
+{{end}}
diff --git a/web/templates/partials/shopping-tab.html b/web/templates/partials/shopping-tab.html
index 9d49122..4937b73 100644
--- a/web/templates/partials/shopping-tab.html
+++ b/web/templates/partials/shopping-tab.html
@@ -53,6 +53,21 @@
</div>
</form>
+ <!-- New store -->
+ <form class="flex gap-2"
+ hx-post="/shopping/stores"
+ hx-target="#shopping-items-list"
+ hx-swap="innerHTML"
+ hx-on::after-request="if(event.detail.successful) this.reset()">
+ <input type="hidden" name="grouped" value="{{.Grouped}}">
+ <input type="text" name="name" placeholder="New store..."
+ 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 autocomplete="off">
+ <button type="submit" class="bg-white/10 hover:bg-white/20 text-white/70 hover:text-white px-4 py-2 rounded-lg text-sm transition-colors">
+ Add store
+ </button>
+ </form>
+
<!-- Items List Container -->
<div id="shopping-items-list">
{{template "shopping-items-content" .}}
@@ -114,7 +129,10 @@
<ul class="space-y-2">
{{range .Items}}
{{if not .Checked}}
- <li class="flex items-center gap-3 p-3 bg-card bg-card-hover transition-colors rounded-lg border border-white/5">
+ <li class="flex items-center gap-3 p-3 bg-card bg-card-hover transition-colors rounded-lg border border-white/5 cursor-pointer"
+ hx-get="/shopping/item/{{.ID}}/edit?source={{.Source}}&name={{.Name}}&store={{.Store}}"
+ hx-target="body"
+ hx-swap="beforeend">
<span class="flex-1 text-white">{{.Name}}</span>
{{if .Quantity}}<span class="text-white/50 text-sm">{{.Quantity}}</span>{{end}}
<span class="text-xs px-2 py-0.5 rounded {{if eq .Source "user"}}bg-purple-900/50 text-purple-300{{else}}bg-green-900/50 text-green-300{{end}}">
@@ -126,6 +144,21 @@
</ul>
</div>
{{end}}
+ <!-- Add a section to this store -->
+ <form class="mt-3 flex gap-2"
+ hx-post="/shopping/sections"
+ hx-target="#shopping-items-list"
+ hx-swap="innerHTML scroll:#store-{{slug .Name}}:top">
+ <input type="hidden" name="store" value="{{.Name}}">
+ <input type="hidden" name="grouped" value="true">
+ <input type="text" name="name" placeholder="New section in {{.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-xs"
+ required autocomplete="off">
+ <button type="submit" class="bg-white/5 hover:bg-white/15 text-white/50 hover:text-white px-3 py-2 rounded-lg text-xs transition-colors">
+ + section
+ </button>
+ </form>
+
<!-- Inline Add Item -->
<!-- scroll: is not optional here. This form sits *inside* the swap
target, so submitting it replaces the form mid-request; without an
@@ -155,7 +188,10 @@
{{range .Categories}}
{{range .Items}}
{{if not .Checked}}
- <li class="flex items-center gap-3 p-3 bg-card bg-card-hover transition-colors rounded-lg border border-white/5">
+ <li class="flex items-center gap-3 p-3 bg-card bg-card-hover transition-colors rounded-lg border border-white/5 cursor-pointer"
+ hx-get="/shopping/item/{{.ID}}/edit?source={{.Source}}&name={{.Name}}&store={{.Store}}"
+ hx-target="body"
+ hx-swap="beforeend">
<span class="flex-1 text-white">{{.Name}}</span>
{{if .Quantity}}<span class="text-white/50 text-sm">{{.Quantity}}</span>{{end}}
<span class="text-xs px-2 py-0.5 rounded bg-white/10 text-white/50">