feat(tasks): Benutzer-Suche beim Teilen (statt Freitext)
Analog zu Kontakten/Kalender: ab 2 Zeichen werden Vorschlaege per /users/search eingeblendet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9bb22eb17b
commit
3eb038abd8
|
|
@ -113,7 +113,16 @@
|
||||||
<div v-if="menuList.permission === 'owner'" class="field">
|
<div v-if="menuList.permission === 'owner'" class="field">
|
||||||
<label>Mit Benutzer teilen</label>
|
<label>Mit Benutzer teilen</label>
|
||||||
<div class="share-row">
|
<div class="share-row">
|
||||||
<InputText v-model="shareUsername" placeholder="Benutzername" fluid />
|
<div style="position: relative; flex: 1;">
|
||||||
|
<InputText v-model="shareUsername" placeholder="Benutzername suchen..."
|
||||||
|
fluid @input="onShareSearch" />
|
||||||
|
<div v-if="shareSearchResults.length" class="user-search-popup">
|
||||||
|
<div v-for="u in shareSearchResults" :key="u.id" class="user-result"
|
||||||
|
@click="shareUsername = u.username; shareSearchResults = []">
|
||||||
|
<i class="pi pi-user"></i> {{ u.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Select v-model="sharePermission" :options="permOptions" optionLabel="label" optionValue="value" />
|
<Select v-model="sharePermission" :options="permOptions" optionLabel="label" optionValue="value" />
|
||||||
<Button label="Teilen" size="small" @click="doShare" />
|
<Button label="Teilen" size="small" @click="doShare" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -238,6 +247,20 @@ const menuList = ref(null)
|
||||||
const shareUsername = ref('')
|
const shareUsername = ref('')
|
||||||
const sharePermission = ref('read')
|
const sharePermission = ref('read')
|
||||||
const listShares = ref([])
|
const listShares = ref([])
|
||||||
|
const shareSearchResults = ref([])
|
||||||
|
let shareSearchTimer = null
|
||||||
|
|
||||||
|
function onShareSearch() {
|
||||||
|
clearTimeout(shareSearchTimer)
|
||||||
|
const q = shareUsername.value.trim()
|
||||||
|
if (q.length < 2) { shareSearchResults.value = []; return }
|
||||||
|
shareSearchTimer = setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const res = await apiClient.get('/users/search', { params: { q } })
|
||||||
|
shareSearchResults.value = res.data
|
||||||
|
} catch { shareSearchResults.value = [] }
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
const permOptions = [
|
const permOptions = [
|
||||||
{ label: 'Lesen', value: 'read' },
|
{ label: 'Lesen', value: 'read' },
|
||||||
{ label: 'Lesen+Schreiben', value: 'readwrite' },
|
{ label: 'Lesen+Schreiben', value: 'readwrite' },
|
||||||
|
|
@ -353,6 +376,7 @@ async function createList() {
|
||||||
function openListMenu(tl) {
|
function openListMenu(tl) {
|
||||||
menuList.value = tl
|
menuList.value = tl
|
||||||
shareUsername.value = ''
|
shareUsername.value = ''
|
||||||
|
shareSearchResults.value = []
|
||||||
showListMenu.value = true
|
showListMenu.value = true
|
||||||
loadShares()
|
loadShares()
|
||||||
}
|
}
|
||||||
|
|
@ -373,6 +397,7 @@ async function doShare() {
|
||||||
})
|
})
|
||||||
toast.add({ severity: 'success', summary: 'Geteilt', life: 2500 })
|
toast.add({ severity: 'success', summary: 'Geteilt', life: 2500 })
|
||||||
shareUsername.value = ''
|
shareUsername.value = ''
|
||||||
|
shareSearchResults.value = []
|
||||||
await loadShares()
|
await loadShares()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.add({ severity: 'error', summary: err.response?.data?.error || 'Fehler', life: 4000 })
|
toast.add({ severity: 'error', summary: err.response?.data?.error || 'Fehler', life: 4000 })
|
||||||
|
|
@ -621,7 +646,14 @@ watch(selectedListId, loadTasks)
|
||||||
.field label { display: block; margin-bottom: 0.25rem; font-weight: 500; font-size: 0.875rem; }
|
.field label { display: block; margin-bottom: 0.25rem; font-weight: 500; font-size: 0.875rem; }
|
||||||
.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; }
|
.share-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
||||||
|
.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;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||||
|
.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); }
|
||||||
.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; }
|
.share-perm-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0; font-size: 0.875rem; }
|
||||||
.perm-label { color: var(--p-text-muted-color); font-size: 0.75rem; }
|
.perm-label { color: var(--p-text-muted-color); font-size: 0.75rem; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue