update
This commit is contained in:
@@ -482,6 +482,12 @@ async def set_baustein_score(topic: str, baustein: str, score: int) -> int:
|
|||||||
return score
|
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:
|
async def set_baustein_verstanden(topic: str, baustein: str) -> bool:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from database import (
|
|||||||
list_progress, set_progress, delete_progress,
|
list_progress, set_progress, delete_progress,
|
||||||
create_element, list_elements, get_element, update_element, delete_element,
|
create_element, list_elements, get_element, update_element, delete_element,
|
||||||
list_baustein_progress, get_baustein_progress, set_offene_frage,
|
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,
|
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
|
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}
|
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:
|
def _validate_quelle(typ: str, ort: str) -> None:
|
||||||
"""Quellen-Eingabe prüfen (gleiche Regeln wie beim Erstellen)."""
|
"""Quellen-Eingabe prüfen (gleiche Regeln wie beim Erstellen)."""
|
||||||
if typ in ("projekt", "uni"):
|
if typ in ("projekt", "uni"):
|
||||||
|
|||||||
@@ -170,6 +170,14 @@ export async function uebernehmeBlock(id, { baustein, stelle, alt, neu, provider
|
|||||||
return jsonOrThrow(res)
|
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() {
|
export async function fetchTopics() {
|
||||||
const res = await fetch(`${BASE}/topics`)
|
const res = await fetch(`${BASE}/topics`)
|
||||||
return res.json()
|
return res.json()
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
import BausteinPanel from './BausteinPanel.vue'
|
import BausteinPanel from './BausteinPanel.vue'
|
||||||
import { renderMarkdown, renderBloecke } from '../markdown.js'
|
import { renderMarkdown, renderBloecke } from '../markdown.js'
|
||||||
import { stufeFuer, STUFEN } from '../stufen.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({
|
const props = defineProps({
|
||||||
baustein: { type: Object, required: true }, // { title, md, num }
|
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'])
|
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 guideEl = ref(null) // linke Guide-Spalte (Scroll-Ziel für ALT+↑/↓)
|
||||||
const rightEl = ref(null) // rechte Spalte (Prüfungs-Panel mit Eingabefeld)
|
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
|
|||||||
<span class="fokus-titel">{{ baustein.title }}</span>
|
<span class="fokus-titel">{{ baustein.title }}</span>
|
||||||
<span v-if="stufe" class="stand-badge" :style="{ color: stufe.farbe, borderColor: stufe.farbe }">{{ stufe.kurz }} {{ stufe.label }}</span>
|
<span v-if="stufe" class="stand-badge" :style="{ color: stufe.farbe, borderColor: stufe.farbe }">{{ stufe.kurz }} {{ stufe.label }}</span>
|
||||||
<span class="fokus-spacer"></span>
|
<span class="fokus-spacer"></span>
|
||||||
|
<button class="fokus-btn" title="Fragen resetten (neue Frage-Session)" @click="fragenResetten">↻</button>
|
||||||
|
<button
|
||||||
|
class="fokus-btn"
|
||||||
|
:class="{ armed: isArmed('reset-fortschritt') }"
|
||||||
|
:title="isArmed('reset-fortschritt') ? 'Nochmal klicken: Fortschritt auf 0 setzen' : 'Fortschritt resetten (Score auf 0)'"
|
||||||
|
@click="armOrRun('reset-fortschritt', fortschrittResetten)"
|
||||||
|
>⟲</button>
|
||||||
<button class="fokus-btn" title="Vollansicht beenden" @click="$emit('close')">✕</button>
|
<button class="fokus-btn" title="Vollansicht beenden" @click="$emit('close')">✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,7 +205,7 @@ watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa
|
|||||||
<div ref="rightEl" class="fokus-col right">
|
<div ref="rightEl" class="fokus-col right">
|
||||||
<BausteinPanel
|
<BausteinPanel
|
||||||
mode="full"
|
mode="full"
|
||||||
:key="baustein.title + '|' + tab"
|
:key="baustein.title + '|' + tab + '|' + resetN"
|
||||||
:initial-tab="tab"
|
:initial-tab="tab"
|
||||||
:topic="topic"
|
:topic="topic"
|
||||||
:baustein="baustein.title"
|
:baustein="baustein.title"
|
||||||
@@ -263,6 +293,7 @@ watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa
|
|||||||
}
|
}
|
||||||
.fokus-btn:hover { border-color: var(--accent); }
|
.fokus-btn:hover { border-color: var(--accent); }
|
||||||
.fokus-btn:disabled { opacity: 0.4; cursor: default; }
|
.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). */
|
/* Zwei abgehobene Karten auf grauem „Schreibtisch" (--bg-preview vom Overlay). */
|
||||||
.fokus-body {
|
.fokus-body {
|
||||||
|
|||||||
@@ -206,10 +206,10 @@ async function musterLaden() {
|
|||||||
try {
|
try {
|
||||||
const res = await fetchFrageMuster(props.topic, props.baustein)
|
const res = await fetchFrageMuster(props.topic, props.baustein)
|
||||||
musterQuelle.value = (res.muster || []).map((m) => (m.frage || '').trim()).filter(Boolean)
|
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 {
|
} catch {
|
||||||
musterQuelle.value = []
|
musterQuelle.value = [] // musterGeladen bleibt false → nächster Zug versucht erneut
|
||||||
}
|
}
|
||||||
musterGeladen.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mischen(arr) { // Fisher-Yates
|
function mischen(arr) { // Fisher-Yates
|
||||||
|
|||||||
Reference in New Issue
Block a user