This commit is contained in:
Team3
2026-05-25 18:43:17 +02:00
parent 145b3b25d5
commit 1cef392892
29 changed files with 3482 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
topics: { type: Array, required: true },
selectedTopic: { type: String, default: null },
guidesByFormat: { type: Object, default: () => ({}) },
allGuides: { type: Array, default: () => [] },
})
const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'cancelGuide', 'deleteGuide', 'preview'])
const formats = [
{ key: 'OnePager', label: 'OnePager' },
{ key: 'Cheatsheet', label: 'Cheatsheet' },
{ key: 'MiniGuide', label: 'MiniGuide' },
{ key: 'BeginnerGuide', label: 'BeginnerGuide' },
{ key: 'IntermediateGuide', label: 'IntermediateGuide' },
{ key: 'ExtendedGuide', label: 'ExtendedGuide' },
]
const activeGenerations = computed(() => {
return props.allGuides
.filter((g) => g.status === 'generating' || g.status === 'queued')
.map((g) => `${g.topic} ${g.format}: ${g.progress || 'Wartend…'}`)
})
function guideStatus(format) {
const guide = props.guidesByFormat[format]
if (!guide) return 'none'
return guide.status
}
function handleFormatClick(format) {
const guide = props.guidesByFormat[format]
if (guide?.status === 'done') {
emit('preview', guide)
}
}
function handlePlay(format) {
const guide = props.guidesByFormat[format]
if (guide?.status === 'done') {
if (!confirm('Guide überschreiben?')) return
}
emit('formatClick', format)
}
function handleDelete(format) {
const guide = props.guidesByFormat[format]
if (!guide) return
if (guide.status === 'generating' || guide.status === 'queued') {
if (!confirm('Generierung abbrechen?')) return
emit('cancelGuide', guide.id)
} else {
if (!confirm('Guide löschen?')) return
emit('deleteGuide', guide.id)
}
}
const newTopic = ref('')
function submit() {
const t = newTopic.value.trim()
if (!t) return
emit('create', t)
newTopic.value = ''
}
</script>
<template>
<aside class="sidebar">
<div class="new-topic">
<input
v-model="newTopic"
placeholder="Neues Thema…"
@keyup.enter="submit"
/>
<button @click="submit" :disabled="!newTopic.trim()">+</button>
</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="emit('deleteTopic', t)" title="Löschen">&times;</button>
</li>
</ul>
<div class="format-section" v-if="selectedTopic">
<div class="progress-info" v-if="activeGenerations.length">
<div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div>
</div>
<div
v-for="f in formats"
:key="f.key"
:class="['format-row', 'fmt-' + guideStatus(f.key)]"
>
<button class="format-name" @click="handleFormatClick(f.key)">
{{ f.label }}
</button>
<div class="format-actions">
<button
v-if="guideStatus(f.key) !== 'generating' && guideStatus(f.key) !== 'queued'"
class="action-btn play"
:title="guideStatus(f.key) === 'done' ? 'Neu generieren' : 'Generieren'"
@click="handlePlay(f.key)"
>
{{ guideStatus(f.key) === 'done' ? '↻' : '▶' }}
</button>
<button
v-if="guideStatus(f.key) !== 'none'"
class="action-btn delete"
:title="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued' ? 'Abbrechen' : 'Löschen'"
@click="handleDelete(f.key)"
>
&times;
</button>
</div>
</div>
</div>
</aside>
</template>
<style scoped>
.sidebar {
width: 300px;
min-width: 300px;
background: #fff;
border-right: 1px solid #e2e5e9;
display: flex;
flex-direction: column;
height: 100vh;
}
.new-topic {
display: flex;
gap: 4px;
padding: 0.75rem;
border-bottom: 1px solid #e2e5e9;
}
.new-topic input {
flex: 1;
padding: 6px 8px;
border: 1px solid #d8dde3;
border-radius: 6px;
font-size: 0.85rem;
outline: none;
}
.new-topic input:focus {
border-color: #6366f1;
}
.new-topic button {
padding: 6px 10px;
border: none;
background: #6366f1;
color: white;
border-radius: 6px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
}
.new-topic button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.topic-list {
list-style: none;
overflow-y: auto;
padding: 0.5rem 0;
border-bottom: 1px solid #e2e5e9;
}
.topic-list li {
padding: 0.6rem 1rem;
cursor: pointer;
font-size: 0.9rem;
color: #333;
transition: background 0.15s;
display: flex;
justify-content: space-between;
align-items: center;
}
.topic-list li:hover {
background: #f5f3ff;
}
.topic-list li.active {
background: #ede9fe;
color: #4f46e5;
font-weight: 600;
}
.delete-topic {
display: none;
background: none;
border: none;
color: #991b1b;
font-size: 1.1rem;
cursor: pointer;
padding: 0 2px;
line-height: 1;
}
.topic-list li:hover .delete-topic {
display: block;
}
/* Format section */
.format-section {
flex: 1;
overflow-y: auto;
padding: 0.5rem 0;
}
.progress-info {
padding: 0.4rem 0.75rem;
font-size: 0.75rem;
color: #92400e;
background: #fef3c7;
margin-bottom: 0.25rem;
animation: pulse 1.5s ease-in-out infinite;
}
.format-row {
display: flex;
align-items: center;
padding: 0.4rem 0.75rem;
transition: background 0.15s;
}
.format-row:hover {
background: #f5f5f5;
}
.format-name {
flex: 1;
background: none;
border: none;
text-align: left;
font-size: 0.85rem;
padding: 4px 8px;
border-radius: 4px;
cursor: default;
color: #999;
}
.fmt-done .format-name {
color: #065f46;
font-weight: 600;
cursor: pointer;
background: #d1fae5;
border: 1px solid #34d399;
}
.fmt-done .format-name:hover {
background: #a7f3d0;
}
.fmt-generating .format-name,
.fmt-queued .format-name {
color: #92400e;
background: #fef3c7;
border: 1px solid #fbbf24;
animation: pulse 1.5s ease-in-out infinite;
}
.fmt-error .format-name {
color: #991b1b;
background: #fee2e2;
border: 1px solid #f87171;
}
.format-actions {
display: flex;
gap: 2px;
margin-left: 6px;
}
.action-btn {
background: none;
border: 1px solid transparent;
border-radius: 4px;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.15s;
}
.action-btn.play {
color: #059669;
}
.action-btn.play:hover {
background: #d1fae5;
border-color: #34d399;
}
.action-btn.delete {
color: #991b1b;
font-size: 1.1rem;
}
.action-btn.delete:hover {
background: #fee2e2;
border-color: #f87171;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.65; }
}
</style>