This commit is contained in:
Team3
2026-05-29 18:37:45 +02:00
parent 067d7229de
commit 19280f7346
3 changed files with 100 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue' import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, reworkGuide as apiRework } from './api.js' import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, reworkGuide as apiRework, createBaustein as apiCreateBaustein } from './api.js'
import TopicSidebar from './components/TopicSidebar.vue' import TopicSidebar from './components/TopicSidebar.vue'
import TopicDetail from './components/TopicDetail.vue' import TopicDetail from './components/TopicDetail.vue'
import BausteineView from './components/BausteineView.vue' import BausteineView from './components/BausteineView.vue'
@@ -10,6 +10,7 @@ const manualTopics = ref([])
const selectedTopic = ref(null) const selectedTopic = ref(null)
const previewGuide = ref(null) const previewGuide = ref(null)
const showBausteine = ref(false) const showBausteine = ref(false)
const bausteineRefreshKey = ref(0)
const sidebarPinned = ref(localStorage.getItem('sidebarPinned') !== 'false') const sidebarPinned = ref(localStorage.getItem('sidebarPinned') !== 'false')
const sidebarSticky = ref(false) const sidebarSticky = ref(false)
let pollTimer = null let pollTimer = null
@@ -127,6 +128,14 @@ function handleShowBausteine() {
previewGuide.value = null previewGuide.value = null
} }
async function handleSidebarAddBaustein(title) {
if (!selectedTopic.value) return
showBausteine.value = true
previewGuide.value = null
await apiCreateBaustein(selectedTopic.value, title)
bausteineRefreshKey.value++
}
async function handleDeleteGuide(guideId) { async function handleDeleteGuide(guideId) {
await deleteGuide(guideId) await deleteGuide(guideId)
if (previewGuide.value?.id === guideId) { if (previewGuide.value?.id === guideId) {
@@ -211,12 +220,14 @@ onUnmounted(() => {
@preview="handlePreview" @preview="handlePreview"
@rework="handleRework" @rework="handleRework"
@showBausteine="handleShowBausteine" @showBausteine="handleShowBausteine"
@addBaustein="handleSidebarAddBaustein"
@togglePin="toggleSidebarPin" @togglePin="toggleSidebarPin"
@sidebarLeave="onSidebarLeave" @sidebarLeave="onSidebarLeave"
/> />
<BausteineView <BausteineView
v-if="selectedTopic && showBausteine" v-if="selectedTopic && showBausteine"
:topic="selectedTopic" :topic="selectedTopic"
:refreshKey="bausteineRefreshKey"
/> />
<TopicDetail <TopicDetail
v-else-if="selectedTopic" v-else-if="selectedTopic"

View File

@@ -16,6 +16,7 @@ import {
const props = defineProps({ const props = defineProps({
topic: { type: String, required: true }, topic: { type: String, required: true },
refreshKey: { type: Number, default: 0 },
}) })
const bausteine = ref([]) const bausteine = ref([])
@@ -192,6 +193,23 @@ watch(
}, },
) )
watch(
() => props.refreshKey,
async (newVal) => {
if (!newVal) return
await loadData()
let added = false
for (const b of bausteine.value) {
if (!b.description && !b.purpose && !reworkingIds.value.has(b.id)) {
reworkingSnapshots.set(b.id, b.updated_at)
reworkingIds.value = new Set([...reworkingIds.value, b.id])
added = true
}
}
if (added) startBausteinPolling()
},
)
onMounted(init) onMounted(init)
onUnmounted(stopPolling) onUnmounted(stopPolling)
</script> </script>

View File

@@ -11,7 +11,16 @@ const props = defineProps({
pinned: { type: Boolean, default: true }, pinned: { type: Boolean, default: true },
}) })
const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'cancelGuide', 'deleteGuide', 'preview', 'rework', 'showBausteine', 'togglePin', 'sidebarLeave']) const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'cancelGuide', 'deleteGuide', 'preview', 'rework', 'showBausteine', 'addBaustein', 'togglePin', 'sidebarLeave'])
const quickBausteinTitle = ref('')
function submitQuickAdd() {
const title = quickBausteinTitle.value.trim()
if (!title) return
emit('addBaustein', title)
quickBausteinTitle.value = ''
}
const formats = [ const formats = [
{ key: 'OnePager', label: 'OnePager' }, { key: 'OnePager', label: 'OnePager' },
@@ -137,18 +146,6 @@ function confirmDeleteTopic(topic) {
/> />
<button @click="submit" :disabled="!newTopic.trim()">+</button> <button @click="submit" :disabled="!newTopic.trim()">+</button>
</div> </div>
<ul class="topic-list">
<li
v-for="t in topics"
:key="t"
:class="{ active: t === selectedTopic }"
@click="emit('select', t)"
>
<span>{{ t }}</span>
<button class="delete-topic" @click.stop="confirmDeleteTopic(t)" title="Löschen">&times;</button>
</li>
</ul>
<div class="format-section" v-if="selectedTopic"> <div class="format-section" v-if="selectedTopic">
<div class="progress-info" v-if="activeGenerations.length"> <div class="progress-info" v-if="activeGenerations.length">
<div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div> <div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div>
@@ -201,8 +198,28 @@ function confirmDeleteTopic(topic) {
:class="{ active: bausteineActive }" :class="{ active: bausteineActive }"
@click="emit('showBausteine')" @click="emit('showBausteine')"
>Bausteine</button> >Bausteine</button>
<div class="quick-add">
<input
v-model="quickBausteinTitle"
placeholder="Neuer Baustein…"
@keyup.enter="submitQuickAdd"
/>
<button @click="submitQuickAdd" :disabled="!quickBausteinTitle.trim()">+</button>
</div> </div>
</div> </div>
</div>
<ul class="topic-list">
<li
v-for="t in topics"
:key="t"
:class="{ active: t === selectedTopic }"
@click="emit('select', t)"
>
<span>{{ t }}</span>
<button class="delete-topic" @click.stop="confirmDeleteTopic(t)" title="Löschen">&times;</button>
</li>
</ul>
</aside> </aside>
</template> </template>
@@ -269,9 +286,11 @@ function confirmDeleteTopic(topic) {
.topic-list { .topic-list {
list-style: none; list-style: none;
flex: 1;
min-height: 0;
overflow-y: auto; overflow-y: auto;
padding: 0.5rem 0; padding: 0.5rem 0;
border-bottom: 1px solid #e2e5e9; border-top: 1px solid #e2e5e9;
} }
.topic-list li { .topic-list li {
@@ -312,7 +331,8 @@ function confirmDeleteTopic(topic) {
/* Format section */ /* Format section */
.format-section { .format-section {
flex: 1; flex-shrink: 0;
max-height: 60vh;
overflow-y: auto; overflow-y: auto;
padding: 0.5rem 0; padding: 0.5rem 0;
} }
@@ -528,4 +548,39 @@ function confirmDeleteTopic(topic) {
border-color: #6366f1; border-color: #6366f1;
color: white; color: white;
} }
.quick-add {
display: flex;
gap: 4px;
margin-top: 0.5rem;
}
.quick-add input {
flex: 1;
padding: 6px 8px;
border: 1px solid #d8dde3;
border-radius: 6px;
font-size: 0.8rem;
outline: none;
}
.quick-add input:focus {
border-color: #6366f1;
}
.quick-add button {
padding: 6px 10px;
border: none;
background: #6366f1;
color: white;
border-radius: 6px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
}
.quick-add button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
</style> </style>