diff --git a/frontend/src/components/contacts/ContactFolderTree.tsx b/frontend/src/components/contacts/ContactFolderTree.tsx index cce55ff..b4511da 100644 --- a/frontend/src/components/contacts/ContactFolderTree.tsx +++ b/frontend/src/components/contacts/ContactFolderTree.tsx @@ -8,11 +8,25 @@ import { useDeleteContactFolder, useMoveContactToFolder, } from '@/api/hooks'; -import { buildFolderTree, type ContactFolderTreeNode } from '@/api/contactFolders'; +import { buildFolderTree, type ContactFolderTreeNode, type ContactFolder } from '@/api/contactFolders'; import { ChevronRight, Folder, MoreVertical, Palette, Pencil, Pin, Plus, Tag, Trash2, Users } from 'lucide-react'; export type ContactFilter = 'all' | 'company' | 'person' | `tag:${string}` | `folder:${string}`; +/** + * Returns true if `descendantId` is a descendant of (or equal to) `ancestorId` + * in the given flat folder list. Used to prevent circular folder moves. + */ +function isDescendantOrSelf(folders: ContactFolder[], descendantId: string, ancestorId: string): boolean { + if (descendantId === ancestorId) return true; + let current = folders.find((f) => f.id === descendantId); + while (current && current.parent_id) { + if (current.parent_id === ancestorId) return true; + current = folders.find((f) => f.id === current!.parent_id!); + } + return false; +} + export interface ContactFolderTreeProps { selectedFilter: ContactFilter; onSelect: (filter: ContactFilter) => void; @@ -83,13 +97,14 @@ function FolderDropdown({ ]; const top = state.anchorRect.bottom + 4; - const left = Math.max(4, state.anchorRect.right - 160); + const left = Math.max(4, Math.min(state.anchorRect.right - 160, window.innerWidth - 170)); + const maxHeight = Math.min(300, window.innerHeight - top - 8); return (
0 ? maxHeight : undefined, overflowY: 'auto' }} > {items.map((item, i) => (