This commit is contained in:
team3
2026-06-29 17:11:23 +02:00
parent 87fc9b209a
commit 12f97031e8
7 changed files with 330 additions and 38 deletions

View File

@@ -4,8 +4,41 @@ import { fetchBausteineUebersicht } from '../api.js'
const props = defineProps({
topic: { type: String, required: true },
steps: { type: Array, default: () => [] }, // feine Teilschritte {label, phase, state}
generating: { type: Boolean, default: false },
progress: { type: String, default: null },
ready: { type: Boolean, default: false },
partial: { type: Boolean, default: false },
})
const emit = defineEmits(['close'])
const emit = defineEmits(['close', 'restartFrom', 'resetFrom', 'restartAll', 'removeAll', 'cancel'])
// Teilschritte nach Phase gruppieren, globalen Index für den Re-Run mitführen.
const phasenGruppen = computed(() => {
const out = []
props.steps.forEach((s, i) => {
const last = out[out.length - 1]
if (last && last.phase === s.phase) last.schritte.push({ ...s, idx: i })
else out.push({ phase: s.phase, schritte: [{ ...s, idx: i }] })
})
return out
})
const gewaehlt = ref(null) // markierter Startpunkt (Schritt-Index)
const confirm = ref(null) // welche destruktive Aktion gerade „Sicher?" zeigt
const gewaehltLabel = computed(() => props.steps[gewaehlt.value]?.label || '')
function stepKlick(idx) {
if (props.generating) return
gewaehlt.value = gewaehlt.value === idx ? null : idx
confirm.value = null
}
// 2-Klick-Bestätigung für destruktive Aktionen: erster Klick „armt", zweiter führt aus.
function arm(aktion, fn) {
if (confirm.value === aktion) { confirm.value = null; fn() }
else confirm.value = aktion
}
function neuAbHier() { const i = gewaehlt.value; gewaehlt.value = null; confirm.value = null; emit('restartFrom', i) }
function loeschAbHier() { const i = gewaehlt.value; gewaehlt.value = null; confirm.value = null; emit('resetFrom', i) }
const items = ref([])
const loading = ref(true)
@@ -62,6 +95,46 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subbausteine
<button class="bk-close" title="Schließen" @click="emit('close')"></button>
</header>
<section v-if="steps.length" class="bk-steps">
<div class="bk-steps-top">
<div v-if="progress" class="bk-progress"><span class="bk-progress-dot"></span>{{ progress }}</div>
<div v-if="!generating" class="bk-global-actions">
<button class="bk-act play" @click="emit('restartAll')">{{ partial ? 'Fortsetzen' : ready ? 'Neu generieren' : 'Generieren' }}</button>
<button
v-if="ready || partial"
class="bk-act danger"
:class="{ armed: confirm === 'remove' }"
@click="arm('remove', () => emit('removeAll'))"
>{{ confirm === 'remove' ? 'Sicher?' : 'Entfernen' }}</button>
</div>
<div v-else class="bk-global-actions">
<button class="bk-act danger" @click="emit('cancel')">Abbrechen</button>
</div>
</div>
<div class="bk-phasen">
<div v-for="g in phasenGruppen" :key="g.phase" class="bk-phase">
<span class="bk-phase-label">{{ g.phase }}</span>
<div class="bk-schritte">
<button
v-for="s in g.schritte"
:key="s.idx"
class="bk-step"
:class="[s.state, { sel: gewaehlt === s.idx }]"
:disabled="generating"
:title="`Startpunkt «${s.label}» wählen`"
@click="stepKlick(s.idx)"
>{{ s.label }}</button>
</div>
</div>
</div>
<div v-if="gewaehlt !== null && !generating" class="bk-step-actions">
<span class="bk-step-actions-label">Ab «{{ gewaehltLabel }}»:</span>
<button class="bk-act play" @click="neuAbHier"> neu generieren</button>
<button class="bk-act danger" :class="{ armed: confirm === 'reset' }" @click="arm('reset', loeschAbHier)">{{ confirm === 'reset' ? 'Sicher?' : ' alles löschen' }}</button>
<button class="bk-act ghost" @click="gewaehlt = null; confirm = null">Abbrechen</button>
</div>
</section>
<div v-if="loading" class="bk-empty-state">Lade</div>
<div v-else-if="error" class="bk-empty-state">{{ error }}</div>
<div v-else-if="!items.length" class="bk-empty-state">Noch keine Bausteine.</div>
@@ -126,6 +199,93 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subbausteine
}
.bk-close:hover { border-color: var(--accent); }
/* Schritt-Übersicht über den Bausteinen */
.bk-steps {
padding: 0.85rem 2rem;
border-bottom: 1px solid var(--border);
background: var(--panel-soft);
}
.bk-progress {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.84rem;
color: var(--accent);
font-weight: 600;
margin-bottom: 0.7rem;
}
.bk-progress-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
animation: bk-pulse 1.2s ease-in-out infinite;
}
@keyframes bk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.bk-phasen { display: flex; flex-wrap: wrap; gap: 0.5rem 1.1rem; }
.bk-phase { display: flex; flex-direction: column; gap: 0.3rem; }
.bk-phase-label {
font-size: 0.62rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-faint);
}
.bk-schritte { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.bk-step {
display: inline-flex;
align-items: center;
gap: 0.3rem;
border: 1px solid var(--border-strong);
border-radius: 6px;
background: var(--panel);
color: var(--text-muted);
font-size: 0.74rem;
padding: 0.22rem 0.5rem;
cursor: pointer;
white-space: nowrap;
}
.bk-step:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.bk-step:disabled { cursor: default; opacity: 0.7; }
.bk-step.done { border-color: var(--success-border); color: var(--success); }
.bk-step.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.bk-step.pending { color: var(--text-faint); }
.bk-step.sel { border-color: var(--accent); color: var(--on-accent); background: var(--accent); font-weight: 700; box-shadow: 0 0 0 2px var(--accent-soft); }
/* Kopf: Fortschritt links, globale Buttons rechts */
.bk-steps-top { display: flex; align-items: center; gap: 1rem; min-height: 1.9rem; margin-bottom: 0.7rem; }
.bk-steps-top .bk-progress { margin-bottom: 0; }
.bk-global-actions { margin-left: auto; display: flex; gap: 0.4rem; }
/* Aktions-Leiste bei gewähltem Startpunkt */
.bk-step-actions {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.75rem;
padding-top: 0.7rem;
border-top: 1px dashed var(--border-strong);
}
.bk-step-actions-label { font-size: 0.8rem; font-weight: 600; color: var(--text-muted); }
.bk-act {
border: 1px solid var(--border-strong);
border-radius: 6px;
background: var(--panel);
color: var(--text);
font-size: 0.8rem;
padding: 0.3rem 0.7rem;
cursor: pointer;
font-weight: 600;
}
.bk-act:hover { border-color: var(--accent); }
.bk-act.play { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.bk-act.play:hover { background: var(--accent-hover); }
.bk-act.danger { color: var(--danger); border-color: var(--danger); background: transparent; }
.bk-act.danger.armed { background: var(--danger); color: #fff; }
.bk-act.ghost { color: var(--text-muted); }
.bk-empty-state {
flex: 1;
display: flex;