diff --git a/backend/database.py b/backend/database.py index 9f5d99f..ed0d81f 100644 --- a/backend/database.py +++ b/backend/database.py @@ -482,6 +482,12 @@ async def set_baustein_score(topic: str, baustein: str, score: int) -> int: return score +async def delete_baustein_progress(topic: str, baustein: str) -> None: + """Fortschritt EINES Bausteins zurücksetzen: Zeile löschen (Score/Streak/Flags/offene Frage). + Fehlt die Zeile, liefert get_baustein_progress Defaults (0) — also voller Reset.""" + db = await get_db() + await db.execute("DELETE FROM baustein_progress WHERE topic = ? AND baustein = ?", (topic, baustein)) + await db.commit() async def set_baustein_verstanden(topic: str, baustein: str) -> bool: diff --git a/backend/routes.py b/backend/routes.py index d0cd114..c83f44a 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -14,7 +14,7 @@ from database import ( list_progress, set_progress, delete_progress, create_element, list_elements, get_element, update_element, delete_element, list_baustein_progress, get_baustein_progress, set_offene_frage, - set_baustein_score, set_baustein_absolviert, delete_baustein_daten, count_relevant_subs, subs_count_roh, + set_baustein_score, set_baustein_absolviert, delete_baustein_daten, delete_baustein_progress, count_relevant_subs, subs_count_roh, delete_topic_pipeline, delete_quelle, get_guide_content, delete_guide_content, ) from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, lade_quelle, lade_uebersicht, subbausteine_titel, lade_frage_muster @@ -179,6 +179,13 @@ async def remove_bausteine(topic: str): return {"ok": True} +@router.delete("/bausteine/fortschritt") +async def reset_baustein_fortschritt(topic: str, baustein: str): + """Lern-Fortschritt EINES Bausteins auf null (Score/Streak/Flags/offene Frage).""" + await delete_baustein_progress(topic, baustein) + return {"ok": True} + + def _validate_quelle(typ: str, ort: str) -> None: """Quellen-Eingabe prüfen (gleiche Regeln wie beim Erstellen).""" if typ in ("projekt", "uni"): diff --git a/frontend/src/api.js b/frontend/src/api.js index 933ba21..99b83b5 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -170,6 +170,14 @@ export async function uebernehmeBlock(id, { baustein, stelle, alt, neu, provider return jsonOrThrow(res) } +// Lern-Fortschritt eines Bausteins auf null setzen (Score/Streak/Flags/offene Frage). +export async function resetBausteinFortschritt(topic, baustein) { + const res = await fetch(`${BASE}/bausteine/fortschritt?topic=${encodeURIComponent(topic)}&baustein=${encodeURIComponent(baustein)}`, { + method: 'DELETE', + }) + return jsonOrThrow(res) +} + export async function fetchTopics() { const res = await fetch(`${BASE}/topics`) return res.json() diff --git a/frontend/src/components/BausteinFokus.vue b/frontend/src/components/BausteinFokus.vue index 517ccd9..3c2b124 100644 --- a/frontend/src/components/BausteinFokus.vue +++ b/frontend/src/components/BausteinFokus.vue @@ -2,7 +2,9 @@ import BausteinPanel from './BausteinPanel.vue' import { renderMarkdown, renderBloecke } from '../markdown.js' import { stufeFuer, STUFEN } from '../stufen.js' -import { pruefeBlock, uebernehmeBlock } from '../api.js' +import { pruefeBlock, uebernehmeBlock, resetBausteinFortschritt } from '../api.js' +import { clearPruef } from '../pruefungCache.js' +import { useConfirm } from '../composables/useConfirm.js' const props = defineProps({ baustein: { type: Object, required: true }, // { title, md, num } @@ -22,6 +24,27 @@ import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue' const emit = defineEmits(['close', 'prev', 'next', 'statusChanged', 'setAnsicht', 'openSidebar', 'sectionUpdated']) +// --- Reset pro Baustein: Fragen-Session (Slot) bzw. Fortschritt (Score) --- +const { isArmed, armOrRun } = useConfirm() +const resetN = ref(0) // Hochzählen → Panel-Remount (frischer Slot, Muster neu) +const pruefKey = computed(() => `${props.topic}::${props.baustein.title}`) + +// Frage-Session neu: Slot verwerfen → Remount lädt Muster + Pool frisch (behebt Erklären-only). +function fragenResetten() { + clearPruef(pruefKey.value) + resetN.value++ +} + +// Fortschritt auf 0: DB-Zeile löschen, Badge/Lernstand nullen, Session frisch. +async function fortschrittResetten() { + try { + await resetBausteinFortschritt(props.topic, props.baustein.title) + emit('statusChanged', { baustein: props.baustein.title, gute_antworten: 0, streak: 0, cap: props.cap }) + clearPruef(pruefKey.value) + resetN.value++ + } catch (e) { /* still bleiben — Reset fehlgeschlagen */ } +} + const guideEl = ref(null) // linke Guide-Spalte (Scroll-Ziel für ALT+↑/↓) const rightEl = ref(null) // rechte Spalte (Prüfungs-Panel mit Eingabefeld) @@ -134,6 +157,13 @@ watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa {{ baustein.title }} {{ stufe.kurz }} {{ stufe.label }} + + @@ -175,7 +205,7 @@ watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa
`${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa } .fokus-btn:hover { border-color: var(--accent); } .fokus-btn:disabled { opacity: 0.4; cursor: default; } +.fokus-btn.armed { border-color: var(--danger, #dc2626); color: var(--danger, #dc2626); background: color-mix(in srgb, var(--danger, #dc2626) 12%, var(--panel)); } /* Zwei abgehobene Karten auf grauem „Schreibtisch" (--bg-preview vom Overlay). */ .fokus-body { diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue index 0dd2326..f73571a 100644 --- a/frontend/src/components/BausteinPanel.vue +++ b/frontend/src/components/BausteinPanel.vue @@ -206,10 +206,10 @@ async function musterLaden() { try { const res = await fetchFrageMuster(props.topic, props.baustein) musterQuelle.value = (res.muster || []).map((m) => (m.frage || '').trim()).filter(Boolean) + musterGeladen.value = true // NUR bei Erfolg — sonst friert ein Fehlversuch die Session auf Erklären-only } catch { - musterQuelle.value = [] + musterQuelle.value = [] // musterGeladen bleibt false → nächster Zug versucht erneut } - musterGeladen.value = true } function mischen(arr) { // Fisher-Yates