chore(frontend): remove obsolete Admin/AdminLesson pages
This commit is contained in:
@@ -1,54 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { lessonsApi } from '../api/lessons.js';
|
|
||||||
import { useLessons } from '../stores/lessonsStore.js';
|
|
||||||
import { LessonTree } from '../components/LessonTree.js';
|
|
||||||
|
|
||||||
export function AdminPage() {
|
|
||||||
const { tree, refresh, loading } = useLessons();
|
|
||||||
const [newRoot, setNewRoot] = useState('');
|
|
||||||
|
|
||||||
useEffect(() => { refresh(); }, [refresh]);
|
|
||||||
|
|
||||||
async function addRoot() {
|
|
||||||
if (!newRoot.trim()) return;
|
|
||||||
await lessonsApi.create({ name: newRoot.trim(), parentId: null });
|
|
||||||
setNewRoot('');
|
|
||||||
await refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx-auto max-w-3xl space-y-6">
|
|
||||||
<header>
|
|
||||||
<h1 className="font-display text-3xl font-bold">Lessen beheren</h1>
|
|
||||||
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">
|
|
||||||
Maak een hiërarchie van lessen en sublessen. Klik op een les voor de kaarten.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="surface flex flex-col gap-2 p-4 sm:flex-row">
|
|
||||||
<input
|
|
||||||
className="input-field"
|
|
||||||
placeholder="Nieuwe wortel-les…"
|
|
||||||
value={newRoot}
|
|
||||||
onChange={(e) => setNewRoot(e.target.value)}
|
|
||||||
onKeyDown={(e) => e.key === 'Enter' && addRoot()}
|
|
||||||
/>
|
|
||||||
<button className="btn-primary shrink-0" onClick={addRoot} disabled={!newRoot.trim()}>
|
|
||||||
+ Toevoegen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="surface p-4">
|
|
||||||
{loading ? (
|
|
||||||
<p className="text-sm text-slate-500">Laden…</p>
|
|
||||||
) : tree.length === 0 ? (
|
|
||||||
<div className="py-8 text-center text-sm text-slate-500">
|
|
||||||
Nog geen lessen. Voeg er hierboven een toe.
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<LessonTree nodes={tree} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Link, useParams } from 'react-router-dom';
|
|
||||||
import type { Card, LessonTreeNode } from '@flashcard/shared';
|
|
||||||
import { cardsApi } from '../api/cards.js';
|
|
||||||
import { lessonsApi } from '../api/lessons.js';
|
|
||||||
import { adminLessonsApi } from '../api/admin-lessons.js';
|
|
||||||
import { useAuth } from '../stores/authStore.js';
|
|
||||||
import { useLessons } from '../stores/lessonsStore.js';
|
|
||||||
import { CardTable } from '../components/CardTable.js';
|
|
||||||
import { ImportDialog } from '../components/ImportDialog.js';
|
|
||||||
import { ApiClientError } from '../api/client.js';
|
|
||||||
|
|
||||||
function findLesson(tree: LessonTreeNode[], id: number): LessonTreeNode | null {
|
|
||||||
for (const n of tree) {
|
|
||||||
if (n.id === id) return n;
|
|
||||||
const found = findLesson(n.children, id);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AdminLessonPage() {
|
|
||||||
const { id } = useParams();
|
|
||||||
const lessonId = Number(id);
|
|
||||||
const user = useAuth((s) => s.user);
|
|
||||||
const { tree, refresh: refreshTree } = useLessons();
|
|
||||||
const [cards, setCards] = useState<Card[]>([]);
|
|
||||||
const [showImport, setShowImport] = useState(false);
|
|
||||||
const [busy, setBusy] = useState(false);
|
|
||||||
|
|
||||||
const node = findLesson(tree, lessonId);
|
|
||||||
const isOwner = node?.ownerId === user?.id;
|
|
||||||
const visibility = node?.visibility ?? 'private';
|
|
||||||
const isCurated = node?.isCurated ?? false;
|
|
||||||
|
|
||||||
async function refresh() {
|
|
||||||
try { setCards(await cardsApi.list(lessonId)); }
|
|
||||||
catch (e) { if (e instanceof ApiClientError && e.status === 403) setCards([]); else throw e; }
|
|
||||||
}
|
|
||||||
useEffect(() => { refresh(); refreshTree(); }, [lessonId]);
|
|
||||||
|
|
||||||
async function toggleVisibility() {
|
|
||||||
setBusy(true);
|
|
||||||
try {
|
|
||||||
const next = visibility === 'shared' ? 'private' : 'shared';
|
|
||||||
await lessonsApi.setVisibility(lessonId, next);
|
|
||||||
await refreshTree();
|
|
||||||
} finally { setBusy(false); }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleCurated() {
|
|
||||||
if (!user || user.role !== 'sysadmin') return;
|
|
||||||
setBusy(true);
|
|
||||||
try {
|
|
||||||
await adminLessonsApi.setCurated(lessonId, !isCurated);
|
|
||||||
await refreshTree();
|
|
||||||
} finally { setBusy(false); }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function forkThis() {
|
|
||||||
setBusy(true);
|
|
||||||
try {
|
|
||||||
const fork = await lessonsApi.fork(lessonId);
|
|
||||||
await refreshTree();
|
|
||||||
window.location.href = `/admin/lessons/${fork.id}`;
|
|
||||||
} finally { setBusy(false); }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unsubscribeThis() {
|
|
||||||
setBusy(true);
|
|
||||||
try {
|
|
||||||
await lessonsApi.unsubscribe(lessonId);
|
|
||||||
await refreshTree();
|
|
||||||
window.location.href = '/admin';
|
|
||||||
} finally { setBusy(false); }
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx-auto max-w-5xl space-y-6">
|
|
||||||
<Link to="/admin" className="inline-flex items-center gap-1 text-sm font-medium text-brand-600 hover:text-brand-700 dark:text-brand-300">
|
|
||||||
← Terug naar lessen
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<header className="surface flex flex-col gap-3 p-5 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="font-display text-2xl font-bold">
|
|
||||||
Kaartenbeheer
|
|
||||||
{!isOwner && <span className="ml-2 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700 dark:bg-amber-900/30 dark:text-amber-200">📥 Geabonneerd</span>}
|
|
||||||
{isCurated && <span className="ml-2 rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-semibold text-yellow-700">⭐ Curated</span>}
|
|
||||||
</h1>
|
|
||||||
<p className="mt-1 text-xs text-slate-500">{cards.length} {cards.length === 1 ? 'kaart' : 'kaarten'} in deze les</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{isOwner ? (
|
|
||||||
<>
|
|
||||||
<button className="btn-ghost" onClick={toggleVisibility} disabled={busy}>
|
|
||||||
{visibility === 'shared' ? '🔒 Maak privé' : '🌍 Deel publiek'}
|
|
||||||
</button>
|
|
||||||
{user?.role === 'sysadmin' && visibility === 'shared' && (
|
|
||||||
<button className="btn-ghost" onClick={toggleCurated} disabled={busy}>
|
|
||||||
{isCurated ? '☆ Verwijder curated' : '⭐ Markeer als curated'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button className="btn-ghost" onClick={() => setShowImport(true)}>📥 Importeer</button>
|
|
||||||
<a className="btn-ghost" href={cardsApi.exportUrl(lessonId, false)}>📤 Exporteer</a>
|
|
||||||
<Link to={`/practice/${lessonId}/setup`} className="btn-success">Start oefenen →</Link>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<button className="btn-ghost" onClick={forkThis} disabled={busy}>🍴 Fork</button>
|
|
||||||
<button className="btn-ghost" onClick={unsubscribeThis} disabled={busy}>Abonnement opzeggen</button>
|
|
||||||
<Link to={`/practice/${lessonId}/setup`} className="btn-success">Start oefenen →</Link>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{!isOwner && (
|
|
||||||
<div className="rounded-2xl bg-amber-50 p-3 text-sm text-amber-800 dark:bg-amber-900/30 dark:text-amber-200">
|
|
||||||
Je bent geabonneerd op deze les en kunt kaarten alleen bekijken. Klik op <strong>🍴 Fork</strong> voor een eigen, bewerkbare kopie.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="surface overflow-hidden p-1">
|
|
||||||
<CardTable lessonId={lessonId} cards={cards} onChange={refresh} readOnly={!isOwner} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showImport && <ImportDialog lessonId={lessonId} onClose={() => setShowImport(false)} onDone={refresh} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user