feat: paginate issues in company export file tree
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 <noreply@paperclip.ing> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
45df62652b
commit
e76fca138d
1 changed files with 75 additions and 3 deletions
|
|
@ -164,6 +164,60 @@ function sortByChecked(nodes: FileTreeNode[], checkedFiles: Set<string>): 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<string>,
|
||||||
|
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) ─────────────────────────
|
// ── Tar helpers (reused from CompanySettings) ─────────────────────────
|
||||||
|
|
||||||
function createTarArchive(files: Record<string, string>, rootPath: string): Uint8Array {
|
function createTarArchive(files: Record<string, string>, rootPath: string): Uint8Array {
|
||||||
|
|
@ -530,6 +584,7 @@ export function CompanyExport() {
|
||||||
const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());
|
const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());
|
||||||
const [checkedFiles, setCheckedFiles] = useState<Set<string>>(new Set());
|
const [checkedFiles, setCheckedFiles] = useState<Set<string>>(new Set());
|
||||||
const [treeSearch, setTreeSearch] = useState("");
|
const [treeSearch, setTreeSearch] = useState("");
|
||||||
|
const [taskLimit, setTaskLimit] = useState(TASKS_PAGE_SIZE);
|
||||||
const savedExpandedRef = useRef<Set<string> | null>(null);
|
const savedExpandedRef = useRef<Set<string> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -586,11 +641,17 @@ export function CompanyExport() {
|
||||||
[exportData],
|
[exportData],
|
||||||
);
|
);
|
||||||
|
|
||||||
const displayTree = useMemo(() => {
|
const { displayTree, totalTaskChildren, visibleTaskChildren } = useMemo(() => {
|
||||||
let result = tree;
|
let result = tree;
|
||||||
if (treeSearch) result = filterTree(result, treeSearch);
|
if (treeSearch) result = filterTree(result, treeSearch);
|
||||||
return sortByChecked(result, checkedFiles);
|
result = sortByChecked(result, checkedFiles);
|
||||||
}, [tree, treeSearch, 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 totalFiles = useMemo(() => countFiles(tree), [tree]);
|
||||||
const selectedCount = checkedFiles.size;
|
const selectedCount = checkedFiles.size;
|
||||||
|
|
@ -767,6 +828,17 @@ export function CompanyExport() {
|
||||||
onSelectFile={setSelectedFile}
|
onSelectFile={setSelectedFile}
|
||||||
onToggleCheck={handleToggleCheck}
|
onToggleCheck={handleToggleCheck}
|
||||||
/>
|
/>
|
||||||
|
{totalTaskChildren > visibleTaskChildren && !treeSearch && (
|
||||||
|
<div className="px-4 py-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setTaskLimit((prev) => prev + TASKS_PAGE_SIZE)}
|
||||||
|
className="w-full rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent/30 hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
Show more issues ({visibleTaskChildren} of {totalTaskChildren})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<div className="min-w-0 overflow-y-auto pl-6">
|
<div className="min-w-0 overflow-y-auto pl-6">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue