import { useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import type { LessonTreeNode } from '@flashcard/shared'; import { lessonsApi } from '../api/lessons.js'; import { useLessons } from '../stores/lessonsStore.js'; import { useAuth } from '../stores/authStore.js'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent, } from '@dnd-kit/core'; import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; function filterTree(nodes: LessonTreeNode[], q: string): LessonTreeNode[] { if (!q.trim()) return nodes; const term = q.trim().toLowerCase(); function visit(n: LessonTreeNode): LessonTreeNode | null { const matches = n.name.toLowerCase().includes(term); const kids = n.children.map(visit).filter((x): x is LessonTreeNode => x !== null); if (matches || kids.length > 0) return { ...n, children: kids }; return null; } return nodes.map(visit).filter((x): x is LessonTreeNode => x !== null); } function collectFlat(nodes: LessonTreeNode[]): LessonTreeNode[] { const out: LessonTreeNode[] = []; function walk(arr: LessonTreeNode[]) { for (const n of arr) { out.push(n); walk(n.children); } } walk(nodes); return out; } export function LessonTree({ nodes, filter = '' }: { nodes: LessonTreeNode[]; filter?: string }) { const filtered = useMemo(() => filterTree(nodes, filter), [nodes, filter]); const refresh = useLessons((s) => s.refresh); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), ); async function handleDragEnd(e: DragEndEvent) { const { active, over } = e; if (!over || active.id === over.id) return; const flat = collectFlat(filtered); const movedId = Number(active.id); const overId = Number(over.id); const moved = flat.find((x) => x.id === movedId); const overNode = flat.find((x) => x.id === overId); if (!moved || !overNode || moved.parentId !== overNode.parentId) return; const siblings = flat.filter((x) => x.parentId === moved.parentId); const newIdx = siblings.findIndex((x) => x.id === overId); if (newIdx < 0) return; try { await lessonsApi.move(movedId, { parentId: moved.parentId, position: newIdx }); await refresh(); } catch {/* ignore */} } return ( n.id)} strategy={verticalListSortingStrategy}> {filtered.map((n) => )} ); } function TreeRow({ n, depth }: { n: LessonTreeNode; depth: number }) { const refresh = useLessons((s) => s.refresh); const currentUserId = useAuth((s) => s.user?.id); const isOwner = n.ownerId === currentUserId; const [addingTo, setAddingTo] = useState(null); const [name, setName] = useState(''); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: n.id, disabled: !isOwner, }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1 }; async function addChild() { if (!name.trim()) return; await lessonsApi.create({ name: name.trim(), parentId: n.id }); setName(''); setAddingTo(null); await refresh(); } async function rename() { const next = prompt('Nieuwe naam', n.name); if (next && next.trim() && next !== n.name) { await lessonsApi.update(n.id, { name: next.trim() }); await refresh(); } } async function remove() { if (!confirm('Verwijder les en alle sublessen + kaarten?')) return; await lessonsApi.remove(n.id); await refresh(); } const visibilityBadge = n.isCurated ? '⭐ Curated' : n.visibility === 'shared' ? '🌍 Gedeeld' : '🔒 Privé'; return ( {isOwner && ( ⋮⋮ )} {n.name} {n.cardCount} {visibilityBadge} {!isOwner && ( 📥 Geabonneerd )} {isOwner && ( setAddingTo(n.id)}>+ subles rename delete )} {addingTo === n.id && ( setName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') addChild(); if (e.key === 'Escape') { setAddingTo(null); setName(''); } }} placeholder="Naam van subles" /> Toevoegen { setAddingTo(null); setName(''); }}>Annuleren )} {n.children.length > 0 && ( c.id)} strategy={verticalListSortingStrategy}> {n.children.map((c) => )} )} ); }