From 5b35c9e63b3c84e4ad2d106a1e78f84586b563cf Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Tue, 21 Apr 2026 16:41:20 +0200 Subject: [PATCH] feat(cospend): edit name and amount in list edit modal Long-press modal on /cospend/list now lets you change the item's name and quantity (e.g. "500g", "3x") alongside category and icon. The quantity is re-prepended to the name so the existing parser keeps picking it up. --- package.json | 2 +- src/lib/js/cospendI18n.ts | 3 ++ src/lib/js/shoppingSync.svelte.ts | 6 +++ .../list/+page.svelte | 54 +++++++++++++++++-- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d7e04f81..9dd296a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.42.1", + "version": "1.43.0", "private": true, "type": "module", "scripts": { diff --git a/src/lib/js/cospendI18n.ts b/src/lib/js/cospendI18n.ts index 92186b93..511041b3 100644 --- a/src/lib/js/cospendI18n.ts +++ b/src/lib/js/cospendI18n.ts @@ -178,6 +178,9 @@ const translations: Translations = { search_icon: { en: 'Search icon...', de: 'Icon suchen...' }, save: { en: 'Save', de: 'Speichern' }, saving: { en: 'Saving...', de: 'Speichern...' }, + edit_name: { en: 'Name', de: 'Name' }, + edit_qty: { en: 'Amount', de: 'Menge' }, + edit_qty_ph: { en: 'e.g. 3x, 500g, 1L', de: 'z.B. 3x, 500g, 1L' }, // EnhancedBalance your_balance: { en: 'Your Balance', de: 'Dein Saldo' }, diff --git a/src/lib/js/shoppingSync.svelte.ts b/src/lib/js/shoppingSync.svelte.ts index b49649bf..6654b509 100644 --- a/src/lib/js/shoppingSync.svelte.ts +++ b/src/lib/js/shoppingSync.svelte.ts @@ -219,6 +219,11 @@ export function createShoppingSync() { debouncedPush(); } + function updateItem(id: string, patch: Partial>) { + items = items.map(item => item.id === id ? { ...item, ...patch } : item); + debouncedPush(); + } + return { get items() { return items; }, get status() { return status; }, @@ -232,6 +237,7 @@ export function createShoppingSync() { removeItem, clearChecked, updateItemCategory, + updateItem, disconnect }; } diff --git a/src/routes/[cospendRoot=cospendRoot]/list/+page.svelte b/src/routes/[cospendRoot=cospendRoot]/list/+page.svelte index 90f7dfa7..65d2118d 100644 --- a/src/routes/[cospendRoot=cospendRoot]/list/+page.svelte +++ b/src/routes/[cospendRoot=cospendRoot]/list/+page.svelte @@ -205,6 +205,8 @@ let longPressTimer = $state(null); /** @type {import('$lib/js/shoppingSync.svelte').ShoppingItem | null} */ let editingItem = $state(null); + let editName = $state(''); + let editQty = $state(''); let editCategory = $state(''); let editIcon = $state(''); let iconSearch = $state(''); @@ -238,6 +240,9 @@ function startLongPress(item) { longPressTimer = window.setTimeout(() => { editingItem = item; + const parsed = parseQuantity(item.name); + editName = parsed.name; + editQty = parsed.qty || ''; editCategory = item.category; editIcon = item.icon || ''; iconSearch = ''; @@ -355,14 +360,21 @@ async function saveEdit() { if (!editingItem) return; editSaving = true; - const cleanName = parseQuantity(editingItem.name).name; + const trimmedName = editName.trim(); + const trimmedQty = editQty.trim(); + if (!trimmedName) { editSaving = false; return; } + const newRawName = trimmedQty ? `${trimmedQty} ${trimmedName}` : trimmedName; try { await fetch(sync.apiUrl('/api/cospend/list/categorize/override'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: cleanName, category: editCategory, icon: editIcon || null }) + body: JSON.stringify({ name: trimmedName, category: editCategory, icon: editIcon || null }) + }); + sync.updateItem(editingItem.id, { + name: newRawName, + category: editCategory, + icon: editIcon || null }); - sync.updateItemCategory(editingItem.id, editCategory, editIcon || null); closeEdit(); } catch (err) { console.error('[shopping] Save override error:', err); @@ -482,6 +494,19 @@
e.stopPropagation()}>

{parseQuantity(editingItem.name).name}

+
+
+ + + +
+
+ + + +
+
+
@@ -934,6 +959,29 @@ margin-bottom: 0.5rem; } + .name-qty-row { + display: flex; + gap: 0.6rem; + margin-bottom: 1rem; + } + .field { display: flex; flex-direction: column; } + .name-field { flex: 2; } + .qty-field { flex: 1; } + .edit-input { + padding: 0.5rem 0.65rem; + border: 1px solid var(--color-border); + border-radius: 8px; + background: var(--color-bg-tertiary); + color: var(--color-text-primary); + font-size: 0.9rem; + width: 100%; + box-sizing: border-box; + } + .edit-input:focus { + outline: none; + border-color: var(--nord10); + } + .category-picker { display: flex; flex-wrap: wrap;