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 -->
|
<!-- Calendar Menu -->
|
||||||
<Dialog v-model:visible="showCalMenu" header="Kalender-Optionen" modal :style="{ width: '480px' }">
|
<Dialog v-model:visible="showCalMenu" header="Kalender-Optionen" modal :style="{ width: '480px' }">
|
||||||
<div v-if="selectedCal" class="cal-menu-content">
|
<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">
|
<div class="field">
|
||||||
<label>
|
<label>
|
||||||
|
|
@ -1070,10 +1085,38 @@ function openCalendarMenu(cal) {
|
||||||
icalPassword.value = ''
|
icalPassword.value = ''
|
||||||
shareUsername.value = ''
|
shareUsername.value = ''
|
||||||
shareSearchResults.value = []
|
shareSearchResults.value = []
|
||||||
|
isRenamingCal.value = false
|
||||||
showCalMenu.value = true
|
showCalMenu.value = true
|
||||||
loadShares()
|
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() {
|
function onShareSearch() {
|
||||||
clearTimeout(shareSearchTimer)
|
clearTimeout(shareSearchTimer)
|
||||||
const q = shareUsername.value.trim()
|
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 { 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-result:hover { background: var(--p-primary-50); }
|
||||||
.user-fullname { color: var(--p-text-muted-color); font-size: 0.75rem; margin-left: auto; }
|
.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; }
|
.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 { 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; }
|
.share-perm-item.editing { background: var(--p-surface-50); padding: 0.5rem; border-radius: 4px; }
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,22 @@
|
||||||
<!-- Book Menu (3-dot) -->
|
<!-- Book Menu (3-dot) -->
|
||||||
<Dialog v-model:visible="showBookMenu" header="Adressbuch-Optionen" modal :style="{ width: '560px' }">
|
<Dialog v-model:visible="showBookMenu" header="Adressbuch-Optionen" modal :style="{ width: '560px' }">
|
||||||
<div v-if="menuBook" class="book-menu-content">
|
<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">
|
<div class="field">
|
||||||
<label>
|
<label>
|
||||||
|
|
@ -529,10 +544,37 @@ function openBookMenu(book) {
|
||||||
shareUsername.value = ''
|
shareUsername.value = ''
|
||||||
shareSearchResults.value = []
|
shareSearchResults.value = []
|
||||||
editingShareId.value = null
|
editingShareId.value = null
|
||||||
|
isRenamingBook.value = false
|
||||||
showBookMenu.value = true
|
showBookMenu.value = true
|
||||||
loadShares()
|
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() {
|
async function loadShares() {
|
||||||
if (!menuBook.value || menuBook.value.permission !== 'owner') {
|
if (!menuBook.value || menuBook.value.permission !== 'owner') {
|
||||||
bookShares.value = []
|
bookShares.value = []
|
||||||
|
|
@ -896,6 +938,8 @@ watch(selectedBookId, loadContacts)
|
||||||
.multi-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.4rem; }
|
.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; }
|
.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; }
|
.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;
|
.user-search-popup { position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
|
||||||
background: white; border: 1px solid var(--p-surface-200);
|
background: white; border: 1px solid var(--p-surface-200);
|
||||||
border-radius: 4px; max-height: 160px; overflow-y: auto;
|
border-radius: 4px; max-height: 160px; overflow-y: auto;
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,22 @@
|
||||||
<!-- List Menu -->
|
<!-- List Menu -->
|
||||||
<Dialog v-model:visible="showListMenu" header="Listen-Optionen" modal :style="{ width: '480px' }">
|
<Dialog v-model:visible="showListMenu" header="Listen-Optionen" modal :style="{ width: '480px' }">
|
||||||
<div v-if="menuList">
|
<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">
|
<div class="field">
|
||||||
<label>Farbe</label>
|
<label>Farbe</label>
|
||||||
<InputText :modelValue="menuList.color" @change="onListColor($event)" type="color" style="width:60px; height:36px" />
|
<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 shareSearchResults = ref([])
|
||||||
const editingShareId = ref(null)
|
const editingShareId = ref(null)
|
||||||
const editSharePermission = ref('read')
|
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
|
let shareSearchTimer = null
|
||||||
|
|
||||||
function startEditShare(s) {
|
function startEditShare(s) {
|
||||||
|
|
@ -423,6 +463,7 @@ function openListMenu(tl) {
|
||||||
menuList.value = tl
|
menuList.value = tl
|
||||||
shareUsername.value = ''
|
shareUsername.value = ''
|
||||||
shareSearchResults.value = []
|
shareSearchResults.value = []
|
||||||
|
isRenaming.value = false
|
||||||
showListMenu.value = true
|
showListMenu.value = true
|
||||||
loadShares()
|
loadShares()
|
||||||
}
|
}
|
||||||
|
|
@ -705,6 +746,8 @@ watch(selectedListId, loadTasks)
|
||||||
.field-row { display: flex; gap: 0.75rem; }
|
.field-row { display: flex; gap: 0.75rem; }
|
||||||
.field-row .field { flex: 1; }
|
.field-row .field { flex: 1; }
|
||||||
.share-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
.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;
|
.user-search-popup { position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
|
||||||
background: white; border: 1px solid var(--p-surface-200);
|
background: white; border: 1px solid var(--p-surface-200);
|
||||||
border-radius: 4px; max-height: 160px; overflow-y: auto;
|
border-radius: 4px; max-height: 160px; overflow-y: auto;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue