diff options
Diffstat (limited to 'web/templates')
| -rw-r--r-- | web/templates/partials/shopping-item-edit.html | 114 | ||||
| -rw-r--r-- | web/templates/partials/shopping-tab.html | 40 |
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()">×</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"> |
