feat: Listen/Kalender/Adressbuch-Namen im 3-Punkte-Menue umbenennbar
Stift-Icon neben dem Namen oeffnet Inline-Editor (Eingabefeld + Check/X). Enter speichert, ESC bricht ab. Nur fuer Eigentuemer sichtbar. Backend-PUT-Endpunkte sind bereits vorhanden. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2ef186e262
commit
ed944339c4
|
|
@ -256,7 +256,22 @@
|
|||
<!-- Calendar Menu -->
|
||||
<Dialog v-model:visible="showCalMenu" header="Kalender-Optionen" modal :style="{ width: '480px' }">
|
||||
<div v-if="selectedCal" class="cal-menu-content">
|
||||
<p><strong>{{ selectedCal.name }}</strong></p>
|
||||
<div class="rename-row">
|
||||
<template v-if="!isRenamingCal">
|
||||
<strong>{{ selectedCal.name }}</strong>
|
||||
<Button v-if="selectedCal.permission === 'owner'"
|
||||
icon="pi pi-pencil" text size="small" title="Umbenennen"
|
||||
@click="startRenameCal" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<InputText v-model="renameCalValue" fluid autofocus
|
||||
@keyup.enter="saveRenameCal" @keyup.escape="isRenamingCal = false" />
|
||||
<Button icon="pi pi-check" text size="small" severity="success"
|
||||
title="Speichern" @click="saveRenameCal" />
|
||||
<Button icon="pi pi-times" text size="small"
|
||||
title="Abbrechen" @click="isRenamingCal = false" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>
|
||||
|
|
@ -1070,10 +1085,38 @@ function openCalendarMenu(cal) {
|
|||
icalPassword.value = ''
|
||||
shareUsername.value = ''
|
||||
shareSearchResults.value = []
|
||||
isRenamingCal.value = false
|
||||
showCalMenu.value = true
|
||||
loadShares()
|
||||
}
|
||||
|
||||
const isRenamingCal = ref(false)
|
||||
const renameCalValue = ref('')
|
||||
|
||||
function startRenameCal() {
|
||||
renameCalValue.value = selectedCal.value?.name || ''
|
||||
isRenamingCal.value = true
|
||||
}
|
||||
|
||||
async function saveRenameCal() {
|
||||
const newName = renameCalValue.value.trim()
|
||||
if (!newName || !selectedCal.value || newName === selectedCal.value.name) {
|
||||
isRenamingCal.value = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
await apiClient.put(`/calendars/${selectedCal.value.id}`, { name: newName })
|
||||
selectedCal.value.name = newName
|
||||
isRenamingCal.value = false
|
||||
await loadCalendars()
|
||||
refreshEvents()
|
||||
toast.add({ severity: 'success', summary: 'Umbenannt', life: 2000 })
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Fehler',
|
||||
detail: err.response?.data?.error || err.message, life: 4000 })
|
||||
}
|
||||
}
|
||||
|
||||
function onShareSearch() {
|
||||
clearTimeout(shareSearchTimer)
|
||||
const q = shareUsername.value.trim()
|
||||
|
|
@ -1307,6 +1350,8 @@ onUnmounted(() => {
|
|||
.user-result { padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.875rem; display: flex; gap: 0.5rem; align-items: center; }
|
||||
.user-result:hover { background: var(--p-primary-50); }
|
||||
.user-fullname { color: var(--p-text-muted-color); font-size: 0.75rem; margin-left: auto; }
|
||||
.rename-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||
.rename-row strong { font-size: 1rem; }
|
||||
.existing-shares { margin-top: 0.5rem; }
|
||||
.share-perm-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0; font-size: 0.875rem; flex-wrap: wrap; }
|
||||
.share-perm-item.editing { background: var(--p-surface-50); padding: 0.5rem; border-radius: 4px; }
|
||||
|
|
|
|||
|
|
@ -92,7 +92,22 @@
|
|||
<!-- Book Menu (3-dot) -->
|
||||
<Dialog v-model:visible="showBookMenu" header="Adressbuch-Optionen" modal :style="{ width: '560px' }">
|
||||
<div v-if="menuBook" class="book-menu-content">
|
||||
<p><strong>{{ menuBook.name }}</strong></p>
|
||||
<div class="rename-row">
|
||||
<template v-if="!isRenamingBook">
|
||||
<strong>{{ menuBook.name }}</strong>
|
||||
<Button v-if="menuBook.permission === 'owner'"
|
||||
icon="pi pi-pencil" text size="small" title="Umbenennen"
|
||||
@click="startRenameBook" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<InputText v-model="renameBookValue" fluid autofocus
|
||||
@keyup.enter="saveRenameBook" @keyup.escape="isRenamingBook = false" />
|
||||
<Button icon="pi pi-check" text size="small" severity="success"
|
||||
title="Speichern" @click="saveRenameBook" />
|
||||
<Button icon="pi pi-times" text size="small"
|
||||
title="Abbrechen" @click="isRenamingBook = false" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>
|
||||
|
|
@ -529,10 +544,37 @@ function openBookMenu(book) {
|
|||
shareUsername.value = ''
|
||||
shareSearchResults.value = []
|
||||
editingShareId.value = null
|
||||
isRenamingBook.value = false
|
||||
showBookMenu.value = true
|
||||
loadShares()
|
||||
}
|
||||
|
||||
const isRenamingBook = ref(false)
|
||||
const renameBookValue = ref('')
|
||||
|
||||
function startRenameBook() {
|
||||
renameBookValue.value = menuBook.value?.name || ''
|
||||
isRenamingBook.value = true
|
||||
}
|
||||
|
||||
async function saveRenameBook() {
|
||||
const newName = renameBookValue.value.trim()
|
||||
if (!newName || !menuBook.value || newName === menuBook.value.name) {
|
||||
isRenamingBook.value = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
await apiClient.put(`/addressbooks/${menuBook.value.id}`, { name: newName })
|
||||
menuBook.value.name = newName
|
||||
isRenamingBook.value = false
|
||||
await loadBooks()
|
||||
toast.add({ severity: 'success', summary: 'Umbenannt', life: 2000 })
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Fehler',
|
||||
detail: err.response?.data?.error || err.message, life: 4000 })
|
||||
}
|
||||
}
|
||||
|
||||
async function loadShares() {
|
||||
if (!menuBook.value || menuBook.value.permission !== 'owner') {
|
||||
bookShares.value = []
|
||||
|
|
@ -896,6 +938,8 @@ watch(selectedBookId, loadContacts)
|
|||
.multi-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.4rem; }
|
||||
.address-card { border: 1px solid var(--p-surface-200); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.75rem; }
|
||||
.share-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
||||
.rename-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||
.rename-row strong { font-size: 1rem; }
|
||||
.user-search-popup { position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
|
||||
background: white; border: 1px solid var(--p-surface-200);
|
||||
border-radius: 4px; max-height: 160px; overflow-y: auto;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,22 @@
|
|||
<!-- List Menu -->
|
||||
<Dialog v-model:visible="showListMenu" header="Listen-Optionen" modal :style="{ width: '480px' }">
|
||||
<div v-if="menuList">
|
||||
<p><strong>{{ menuList.name }}</strong></p>
|
||||
<div class="rename-row">
|
||||
<template v-if="!isRenaming">
|
||||
<strong>{{ menuList.name }}</strong>
|
||||
<Button v-if="menuList.permission === 'owner'"
|
||||
icon="pi pi-pencil" text size="small" title="Umbenennen"
|
||||
@click="startRename" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<InputText v-model="renameValue" fluid autofocus
|
||||
@keyup.enter="saveRename" @keyup.escape="isRenaming = false" />
|
||||
<Button icon="pi pi-check" text size="small" severity="success"
|
||||
title="Speichern" @click="saveRename" />
|
||||
<Button icon="pi pi-times" text size="small"
|
||||
title="Abbrechen" @click="isRenaming = false" />
|
||||
</template>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Farbe</label>
|
||||
<InputText :modelValue="menuList.color" @change="onListColor($event)" type="color" style="width:60px; height:36px" />
|
||||
|
|
@ -273,6 +288,31 @@ const listShares = ref([])
|
|||
const shareSearchResults = ref([])
|
||||
const editingShareId = ref(null)
|
||||
const editSharePermission = ref('read')
|
||||
const isRenaming = ref(false)
|
||||
const renameValue = ref('')
|
||||
|
||||
function startRename() {
|
||||
renameValue.value = menuList.value?.name || ''
|
||||
isRenaming.value = true
|
||||
}
|
||||
|
||||
async function saveRename() {
|
||||
const newName = renameValue.value.trim()
|
||||
if (!newName || !menuList.value || newName === menuList.value.name) {
|
||||
isRenaming.value = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
await apiClient.put(`/tasklists/${menuList.value.id}`, { name: newName })
|
||||
menuList.value.name = newName
|
||||
isRenaming.value = false
|
||||
await loadLists()
|
||||
toast.add({ severity: 'success', summary: 'Umbenannt', life: 2000 })
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Fehler',
|
||||
detail: err.response?.data?.error || err.message, life: 4000 })
|
||||
}
|
||||
}
|
||||
let shareSearchTimer = null
|
||||
|
||||
function startEditShare(s) {
|
||||
|
|
@ -423,6 +463,7 @@ function openListMenu(tl) {
|
|||
menuList.value = tl
|
||||
shareUsername.value = ''
|
||||
shareSearchResults.value = []
|
||||
isRenaming.value = false
|
||||
showListMenu.value = true
|
||||
loadShares()
|
||||
}
|
||||
|
|
@ -705,6 +746,8 @@ watch(selectedListId, loadTasks)
|
|||
.field-row { display: flex; gap: 0.75rem; }
|
||||
.field-row .field { flex: 1; }
|
||||
.share-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
||||
.rename-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||
.rename-row strong { font-size: 1rem; }
|
||||
.user-search-popup { position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
|
||||
background: white; border: 1px solid var(--p-surface-200);
|
||||
border-radius: 4px; max-height: 160px; overflow-y: auto;
|
||||
|
|
|
|||
Loading…
Reference in New Issue