From e76fca138d1676e4963617d3db2361ecadc42abc Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 08:24:24 -0500 Subject: [PATCH] feat: paginate issues in company export file tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show only 10 task entries at a time with a "Show more issues" button. Checked/selected tasks are always pinned visible regardless of the page limit. Search still works across all issues — matched results are pinned and the load-more button is hidden during search so all matches show. Co-Authored-By: Paperclip Co-Authored-By: Claude Opus 4.6 --- ui/src/pages/CompanyExport.tsx | 78 ++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/CompanyExport.tsx b/ui/src/pages/CompanyExport.tsx index 1684acfe..7dfa1d9d 100644 --- a/ui/src/pages/CompanyExport.tsx +++ b/ui/src/pages/CompanyExport.tsx @@ -164,6 +164,60 @@ function sortByChecked(nodes: FileTreeNode[], checkedFiles: Set): FileTr }); } +const TASKS_PAGE_SIZE = 10; + +/** + * Paginate children of `tasks/` directories: show up to `limit` entries, + * but always include children that are checked or match the search query. + * Returns the paginated tree and the total count of task children. + */ +function paginateTaskNodes( + nodes: FileTreeNode[], + limit: number, + checkedFiles: Set, + searchQuery: string, +): { nodes: FileTreeNode[]; totalTaskChildren: number; visibleTaskChildren: number } { + let totalTaskChildren = 0; + let visibleTaskChildren = 0; + + const result = nodes.map((node) => { + // Only paginate direct children of "tasks" directories + if (node.kind === "dir" && node.name === "tasks") { + totalTaskChildren = node.children.length; + + // Partition children: pinned (checked or search-matched) vs rest + const pinned: FileTreeNode[] = []; + const rest: FileTreeNode[] = []; + const lower = searchQuery.toLowerCase(); + + for (const child of node.children) { + const childFiles = collectAllPaths([child], "file"); + const isChecked = [...childFiles].some((p) => checkedFiles.has(p)); + const isSearchMatch = searchQuery && ( + child.name.toLowerCase().includes(lower) || + child.path.toLowerCase().includes(lower) || + [...childFiles].some((p) => p.toLowerCase().includes(lower)) + ); + if (isChecked || isSearchMatch) { + pinned.push(child); + } else { + rest.push(child); + } + } + + // Show pinned + up to `limit` from rest + const remaining = Math.max(0, limit - pinned.length); + const visible = [...pinned, ...rest.slice(0, remaining)]; + visibleTaskChildren = visible.length; + + return { ...node, children: visible }; + } + return node; + }); + + return { nodes: result, totalTaskChildren, visibleTaskChildren }; +} + // ── Tar helpers (reused from CompanySettings) ───────────────────────── function createTarArchive(files: Record, rootPath: string): Uint8Array { @@ -530,6 +584,7 @@ export function CompanyExport() { const [expandedDirs, setExpandedDirs] = useState>(new Set()); const [checkedFiles, setCheckedFiles] = useState>(new Set()); const [treeSearch, setTreeSearch] = useState(""); + const [taskLimit, setTaskLimit] = useState(TASKS_PAGE_SIZE); const savedExpandedRef = useRef | null>(null); useEffect(() => { @@ -586,11 +641,17 @@ export function CompanyExport() { [exportData], ); - const displayTree = useMemo(() => { + const { displayTree, totalTaskChildren, visibleTaskChildren } = useMemo(() => { let result = tree; if (treeSearch) result = filterTree(result, treeSearch); - return sortByChecked(result, checkedFiles); - }, [tree, treeSearch, checkedFiles]); + result = sortByChecked(result, checkedFiles); + const paginated = paginateTaskNodes(result, taskLimit, checkedFiles, treeSearch); + return { + displayTree: paginated.nodes, + totalTaskChildren: paginated.totalTaskChildren, + visibleTaskChildren: paginated.visibleTaskChildren, + }; + }, [tree, treeSearch, checkedFiles, taskLimit]); const totalFiles = useMemo(() => countFiles(tree), [tree]); const selectedCount = checkedFiles.size; @@ -767,6 +828,17 @@ export function CompanyExport() { onSelectFile={setSelectedFile} onToggleCheck={handleToggleCheck} /> + {totalTaskChildren > visibleTaskChildren && !treeSearch && ( +
+ +
+ )}