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.
This commit is contained in:
2026-04-21 16:41:20 +02:00
parent b66c458a4d
commit 5b35c9e63b
4 changed files with 61 additions and 4 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.42.1",
"version": "1.43.0",
"private": true,
"type": "module",
"scripts": {
+3
View File
@@ -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' },
+6
View File
@@ -219,6 +219,11 @@ export function createShoppingSync() {
debouncedPush();
}
function updateItem(id: string, patch: Partial<Omit<ShoppingItem, 'id'>>) {
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
};
}
@@ -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 @@
<div class="edit-modal" onclick={(e) => e.stopPropagation()}>
<h3>{parseQuantity(editingItem.name).name}</h3>
<div class="name-qty-row">
<div class="field name-field">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="edit-label">{t('edit_name', lang)}</label>
<input class="edit-input" type="text" bind:value={editName} />
</div>
<div class="field qty-field">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="edit-label">{t('edit_qty', lang)}</label>
<input class="edit-input" type="text" bind:value={editQty} placeholder={t('edit_qty_ph', lang)} />
</div>
</div>
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="edit-label">{t('kategorie', lang)}</label>
<div class="category-picker">
@@ -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;