diff --git a/edu/api/dashboard.php b/edu/api/dashboard.php new file mode 100644 index 0000000..1756350 --- /dev/null +++ b/edu/api/dashboard.php @@ -0,0 +1,40 @@ +prepare("SELECT COUNT(*) FROM card_progress WHERE user_id = :uid AND last_reviewed_at IS NOT NULL"); +$stmt->execute([':uid' => $user_id]); +$cards_learned = (int) $stmt->fetchColumn(); + +$stmt = $pdo->prepare("SELECT COUNT(*) FROM card_progress WHERE user_id = :uid AND next_review <= CURRENT_DATE"); +$stmt->execute([':uid' => $user_id]); +$cards_due = (int) $stmt->fetchColumn(); + +$stmt = $pdo->prepare(" + SELECT COUNT(*) FILTER (WHERE last_rating IN ('easy','medium')) AS correct, COUNT(*) AS total + FROM card_progress WHERE user_id = :uid AND last_reviewed_at IS NOT NULL +"); +$stmt->execute([':uid' => $user_id]); +$acc = $stmt->fetch(); +$correct_pct = ($acc['total'] > 0) ? round(($acc['correct'] / $acc['total']) * 100) : 0; + +$total_cards = (int) $pdo->query("SELECT COUNT(*) FROM cards")->fetchColumn(); + +$stmt = $pdo->prepare("SELECT COUNT(*) FROM tutorial_progress WHERE user_id = :uid AND completed = true"); +$stmt->execute([':uid' => $user_id]); +$tutorials_done = (int) $stmt->fetchColumn(); +$tutorials_total = (int) $pdo->query("SELECT COUNT(*) FROM tutorials")->fetchColumn(); + +$deck_count = (int) $pdo->query("SELECT COUNT(*) FROM decks")->fetchColumn(); +$cheatsheet_count = (int) $pdo->query("SELECT COUNT(*) FROM cheatsheets")->fetchColumn(); + +$stmt = $pdo->prepare("SELECT score_percent FROM quiz_results WHERE user_id = :uid ORDER BY created_at DESC LIMIT 1"); +$stmt->execute([':uid' => $user_id]); +$last_quiz = $stmt->fetchColumn(); + +json_ok([ + 'cards_learned' => $cards_learned, 'cards_due' => $cards_due, 'cards_total' => $total_cards, + 'correct_pct' => $correct_pct, 'tutorials_done' => $tutorials_done, 'tutorials_total' => $tutorials_total, + 'deck_count' => $deck_count, 'cheatsheet_count' => $cheatsheet_count, + 'last_quiz_score' => $last_quiz !== false ? (int) $last_quiz : null +]); diff --git a/edu/api/tutorials.php b/edu/api/tutorials.php new file mode 100644 index 0000000..e86b4a3 --- /dev/null +++ b/edu/api/tutorials.php @@ -0,0 +1,47 @@ +prepare(" + SELECT t.id, t.slug, t.title, t.description, t.duration_min, t.sort_order, + COALESCE(tp.completed, false) AS completed, tp.completed_at + FROM tutorials t + LEFT JOIN tutorial_progress tp ON tp.tutorial_id = t.id AND tp.user_id = :uid + ORDER BY t.sort_order + "); + $stmt->execute([':uid' => $user_id]); + json_ok(['tutorials' => $stmt->fetchAll()]); +} + +// GET /api/tutorials/{slug} +if (get_method() === 'GET' && !empty($segments[1]) && ($segments[2] ?? '') !== 'complete') { + $stmt = $pdo->prepare(" + SELECT t.*, COALESCE(tp.completed, false) AS completed + FROM tutorials t + LEFT JOIN tutorial_progress tp ON tp.tutorial_id = t.id AND tp.user_id = :uid + WHERE t.slug = :slug + "); + $stmt->execute([':slug' => $segments[1], ':uid' => $user_id]); + $tutorial = $stmt->fetch(); + if (!$tutorial) json_error('Tutorial nicht gefunden', 404); + json_ok(['tutorial' => $tutorial]); +} + +// POST /api/tutorials/{slug}/complete +if (get_method() === 'POST' && !empty($segments[1]) && ($segments[2] ?? '') === 'complete') { + $stmt = $pdo->prepare("SELECT id FROM tutorials WHERE slug = :slug"); + $stmt->execute([':slug' => $segments[1]]); + $tutorial = $stmt->fetch(); + if (!$tutorial) json_error('Tutorial nicht gefunden', 404); + + $stmt = $pdo->prepare(" + INSERT INTO tutorial_progress (user_id, tutorial_id, completed, completed_at) + VALUES (:uid, :tid, true, NOW()) + ON CONFLICT (user_id, tutorial_id) DO UPDATE SET completed = true, completed_at = NOW() + "); + $stmt->execute([':uid' => $user_id, ':tid' => $tutorial['id']]); + json_ok(['ok' => true]); +} + +json_error('Unbekannter Tutorials-Endpunkt', 404); diff --git a/edu/js/dashboard.js b/edu/js/dashboard.js new file mode 100644 index 0000000..a8d63eb --- /dev/null +++ b/edu/js/dashboard.js @@ -0,0 +1,50 @@ +'use strict'; +// NOTE: innerHTML safe - only own API data (numbers, names) + +var Dashboard = { + render: function() { + var el = document.getElementById('content'); + el.textContent = 'Lade Dashboard...'; + + API.get('/dashboard').then(function(d) { + var cardPct = d.cards_total > 0 ? Math.round((d.cards_learned / d.cards_total) * 100) : 0; + var tutPct = d.tutorials_total > 0 ? Math.round((d.tutorials_done / d.tutorials_total) * 100) : 0; + var dueText = d.cards_due > 0 + ? '' + d.cards_due + ' Karten faellig heute' + : 'Keine faelligen Karten — gut gemacht!'; + + var h = '
' + dueText + '
' + done + '/' + tutorials.length + ' abgeschlossen
✓ Abgeschlossen
'; + } else { + h += ''; + } + h += '