)
}
function RecursiveSortableList({ items, level, selectedId, onSelect, textbookId, onDelete, onCreateSub, canEdit = true }: {
items: Chapter[],
level: number,
selectedId?: string,
onSelect: (c: Chapter) => void,
textbookId: string,
onDelete: (c: Chapter) => void,
onCreateSub: (pid: string) => void,
canEdit?: boolean
}) {
return (
i.id)} strategy={verticalListSortingStrategy}>
{items.map((chapter) => (
))}
)
}
interface ChapterSidebarListProps {
chapters: Chapter[]
selectedChapterId?: string
onSelectChapter: (chapter: Chapter) => void
textbookId: string
canEdit?: boolean
}
export function ChapterSidebarList({ chapters, selectedChapterId, onSelectChapter, textbookId, canEdit = true }: ChapterSidebarListProps) {
const [showCreateDialog, setShowCreateDialog] = useState(false)
const [createParentId, setCreateParentId] = useState(undefined)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [deleteTarget, setDeleteTarget] = useState(null)
const [isDeleting, setIsDeleting] = useState(false)
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
)
const handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event
if (!over || active.id === over.id) return
// Find which list the items belong to
// Since we only support sibling reordering for now, we assume active and over are in the same list
// We need a helper to find the parent of an item in the tree
const findParent = (items: Chapter[], id: string): Chapter | null => {
for (const item of items) {
if (item.children?.some(c => c.id === id)) return item
if (item.children) {
const found = findParent(item.children, id)
if (found) return found
}
}
return null
}
const activeParent = findParent(chapters, active.id as string)
const overParent = findParent(chapters, over.id as string)
// If parents don't match (and neither is root), we can't reorder easily in this simplified version
// But actually, we need to check if they are in the same list.
// If both are root items (activeParent is null), they are siblings.
const getSiblings = (parentId: string | null) => {
if (!parentId) return chapters
const parent = chapters.find(c => c.id === parentId) // This only finds root parents, we need recursive find
const findNode = (nodes: Chapter[], id: string): Chapter | null => {
for (const node of nodes) {
if (node.id === id) return node
if (node.children) {
const found = findNode(node.children, id)
if (found) return found
}
}
return null
}
return findNode(chapters, parentId)?.children || []
}
// Simplified logic: We trust dnd-kit's SortableContext to only allow valid drops if we restricted it?
// No, dnd-kit allows dropping anywhere by default unless restricted.
// We need to find the list that contains the 'active' item
// And the list that contains the 'over' item.
// If they are the same list, we reorder.
let activeList: Chapter[] = chapters
let activeParentId: string | null = null
if (activeParent) {
activeList = activeParent.children || []
activeParentId = activeParent.id
} else {
// Check if active is in root
if (!chapters.some(c => c.id === active.id)) {
// Should not happen if tree is consistent
return
}
}
// Check if over is in the same list
if (activeList.some(c => c.id === over.id)) {
const oldIndex = activeList.findIndex((item) => item.id === active.id)
const newIndex = activeList.findIndex((item) => item.id === over.id)
await reorderChaptersAction(active.id as string, newIndex, activeParentId, textbookId)
toast.success("Order updated")
}
}
const handleDelete = async () => {
if (!deleteTarget) return
setIsDeleting(true)
const res = await deleteChapterAction(deleteTarget.id, textbookId)
setIsDeleting(false)
if (res.success) {
toast.success(res.message)
setShowDeleteDialog(false)
setDeleteTarget(null)
} else {
toast.error(res.message)
}
}
const handleDeleteRequest = (chapter: Chapter) => {
if (chapter.children && chapter.children.length > 0) {
toast.error("Cannot delete chapter with subchapters")
return
}
setDeleteTarget(chapter)
setShowDeleteDialog(true)
}
const handleCreateSubRequest = (parentId: string) => {
setCreateParentId(parentId)
setShowCreateDialog(true)
}
// If not editable, we can skip dnd logic
if (!canEdit) {
return (
)
}
return (
{
setShowCreateDialog(open)
if (!open) setCreateParentId(undefined)
}}
/>
Delete Chapter?
This will permanently delete {deleteTarget?.title}.
This action cannot be undone.
Cancel
{isDeleting ? "Deleting..." : "Delete"}
)
}