This commit is contained in:
team3 committed 2026-09-18 23:47:25 +02:00
1 parent db77c1f985
commit a90044b0a7
21 files changed
+1111 -253

No files matched your search

+65 -79
View File
@@ -1,5 +1,5 @@
<script setup>
import { computed, reactive, ref } from "vue";
import { computed, reactive, ref } from 'vue'
const props = defineProps({
items: { type: Array, default: () => [] },
@@ -7,111 +7,111 @@ const props = defineProps({
host: Boolean,
// Ohne gewählten Namen bleibt der Reiter zum Mitlesen da
angemeldet: Boolean,
});
})
const emit = defineEmits(["changed"]);
const emit = defineEmits(['changed'])
const entwurf = ref("");
const entwurf = ref('')
// Der Host darf jeden Wunsch anfassen, alle anderen nur die eigenen
const bearbeitet = ref(null);
const bearbeitung = ref("");
const bearbeitet = ref(null)
const bearbeitung = ref('')
function darfAendern(item) {
return props.host || item.mine;
return props.host || item.mine
}
function bearbeiten(item) {
bearbeitet.value = item.id;
bearbeitung.value = item.text;
bearbeitet.value = item.id
bearbeitung.value = item.text
}
async function speichern() {
const text = bearbeitung.value.trim();
const text = bearbeitung.value.trim()
if (text) {
await fetch(`/api/features/${bearbeitet.value}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
emit("changed");
})
emit('changed')
}
bearbeitet.value = null;
bearbeitet.value = null
}
// Die Liste kommt vom Server schon sortiert: offene nach Stimmen, dann
// die erledigten. Hier nur noch in zwei Abschnitte teilen.
const offene = computed(() => props.items.filter((w) => !w.done_at));
const erledigte = computed(() => props.items.filter((w) => w.done_at));
const offene = computed(() => props.items.filter((w) => !w.done_at))
const erledigte = computed(() => props.items.filter((w) => w.done_at))
async function anlegen() {
if (!entwurf.value.trim()) return;
await fetch("/api/features", {
method: "POST",
headers: { "Content-Type": "application/json" },
if (!entwurf.value.trim()) return
await fetch('/api/features', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: entwurf.value }),
});
entwurf.value = "";
emit("changed");
})
entwurf.value = ''
emit('changed')
}
async function loeschen(item) {
await fetch(`/api/features/${item.id}`, { method: "DELETE" });
emit("changed");
await fetch(`/api/features/${item.id}`, { method: 'DELETE' })
emit('changed')
}
async function abstimmen(item) {
await fetch(`/api/features/${item.id}/vote`, { method: "POST" });
emit("changed");
await fetch(`/api/features/${item.id}/vote`, { method: 'POST' })
emit('changed')
}
async function erledigen(item, done) {
await fetch(`/api/features/${item.id}/done`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ done }),
});
emit("changed");
})
emit('changed')
}
// Ergänzungen: aufgeklappt je Wunsch, mit eigenem Entwurf
const aufgeklappt = reactive(new Set());
const notizEntwurf = reactive({});
const aufgeklappt = reactive(new Set())
const notizEntwurf = reactive({})
function umschalten(id) {
if (aufgeklappt.has(id)) aufgeklappt.delete(id);
else aufgeklappt.add(id);
if (aufgeklappt.has(id)) aufgeklappt.delete(id)
else aufgeklappt.add(id)
}
function ergaenzungsLabel(item) {
const n = item.notes.length;
if (!n) return "Ergänzen";
return n === 1 ? "1 Ergänzung" : `${n} Ergänzungen`;
const n = item.notes.length
if (!n) return 'Ergänzen'
return n === 1 ? '1 Ergänzung' : `${n} Ergänzungen`
}
async function anmerken(item) {
const text = (notizEntwurf[item.id] || "").trim();
if (!text) return;
const text = (notizEntwurf[item.id] || '').trim()
if (!text) return
await fetch(`/api/features/${item.id}/notes`, {
method: "POST",
headers: { "Content-Type": "application/json" },
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
notizEntwurf[item.id] = "";
emit("changed");
})
notizEntwurf[item.id] = ''
emit('changed')
}
async function notizLoeschen(notiz) {
await fetch(`/api/feature-notes/${notiz.id}`, { method: "DELETE" });
emit("changed");
await fetch(`/api/feature-notes/${notiz.id}`, { method: 'DELETE' })
emit('changed')
}
function datum(iso) {
return new Date(iso).toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
return new Date(iso).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
</script>
@@ -129,9 +129,7 @@ function datum(iso) {
placeholder="Was fehlt dir auf der Seite?"
@keydown.ctrl.enter="anlegen"
></textarea>
<button type="button" :disabled="!entwurf.trim()" @click="anlegen">
Eintragen
</button>
<button type="button" :disabled="!entwurf.trim()" @click="anlegen">Eintragen</button>
</div>
<section
@@ -172,14 +170,8 @@ function datum(iso) {
@keydown.esc="bearbeitet = null"
></textarea>
<div class="zeile">
<button type="button" class="klein" @click="speichern">
Speichern
</button>
<button
type="button"
class="klein"
@click="bearbeitet = null"
>
<button type="button" class="klein" @click="speichern">Speichern</button>
<button type="button" class="klein" @click="bearbeitet = null">
Abbrechen
</button>
</div>
@@ -188,9 +180,7 @@ function datum(iso) {
<template v-else>
<p class="text">{{ w.text }}</p>
<p class="meta">
<span :class="{ eigen: w.mine }">{{
w.name || "Anonym"
}}</span>
<span :class="{ eigen: w.mine }">{{ w.name || 'Anonym' }}</span>
<span class="punkt">·</span>{{ datum(w.created_at) }}
<template v-if="w.done_at">
<span class="punkt">·</span>erledigt
@@ -210,9 +200,7 @@ function datum(iso) {
<div v-if="aufgeklappt.has(w.id)" class="notizen">
<p v-for="n in w.notes" :key="n.id" class="notiz">
<span class="wer" :class="{ eigen: n.mine }">{{
n.name || "Anonym"
}}</span>
<span class="wer" :class="{ eigen: n.mine }">{{ n.name || 'Anonym' }}</span>
<span class="notiz-text">{{ n.text }}</span>
<button
v-if="host"
@@ -250,12 +238,10 @@ function datum(iso) {
v-if="host"
type="button"
class="weg"
:title="
w.done_at ? 'Wieder öffnen' : 'Als erledigt markieren'
"
:title="w.done_at ? 'Wieder öffnen' : 'Als erledigt markieren'"
@click="erledigen(w, !w.done_at)"
>
{{ w.done_at ? "↺" : "✓" }}
{{ w.done_at ? '↺' : '✓' }}
</button>
<button
v-if="darfAendern(w) && bearbeitet !== w.id"
@@ -288,8 +274,8 @@ function datum(iso) {
.spalten {
display: grid;
grid-template-areas:
"neu erledigt"
"offen erledigt";
'neu erledigt'
'offen erledigt';
grid-template-columns: 1fr 1fr;
grid-template-rows: auto 1fr;
gap: 0 2.5rem;
@@ -558,9 +544,9 @@ h3 {
@media (max-width: 900px) {
.spalten {
grid-template-areas:
"neu"
"offen"
"erledigt";
'neu'
'offen'
'erledigt';
grid-template-columns: 1fr;
}
}