fix(board): show total count in Done column and infinite scroll (#498)
* fix(board): show total count in Done column header and auto-load on scroll - Column header now shows server-side doneTotal instead of loaded count - Replace "Load more" button with IntersectionObserver sentinel for infinite scroll in the Done column Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(board): move sentinel below imports and stabilize observer - Move InfiniteScrollSentinel after all import statements - Use callback ref to avoid recreating IntersectionObserver on every render Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4bdb86057e
commit
76354cd968
3 changed files with 31 additions and 14 deletions
|
|
@ -23,11 +23,13 @@ export function BoardColumn({
|
|||
status,
|
||||
issueIds,
|
||||
issueMap,
|
||||
totalCount,
|
||||
footer,
|
||||
}: {
|
||||
status: IssueStatus;
|
||||
issueIds: string[];
|
||||
issueMap: Map<string, Issue>;
|
||||
totalCount?: number;
|
||||
footer?: ReactNode;
|
||||
}) {
|
||||
const cfg = STATUS_CONFIG[status];
|
||||
|
|
@ -54,7 +56,7 @@ export function BoardColumn({
|
|||
{cfg.label}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{issueIds.length}
|
||||
{totalCount ?? issueIds.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,30 @@ import { StatusIcon } from "./status-icon";
|
|||
import { BoardColumn } from "./board-column";
|
||||
import { BoardCardContent } from "./board-card";
|
||||
|
||||
/** Sentinel that triggers `onVisible` when scrolled into view. */
|
||||
function InfiniteScrollSentinel({ onVisible, loading }: { onVisible: () => void; loading: boolean }) {
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
const onVisibleRef = useRef(onVisible);
|
||||
onVisibleRef.current = onVisible;
|
||||
|
||||
useEffect(() => {
|
||||
const node = sentinelRef.current;
|
||||
if (!node) return;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => { if (entry.isIntersecting) onVisibleRef.current(); },
|
||||
{ rootMargin: "100px" },
|
||||
);
|
||||
observer.observe(node);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={sentinelRef} className="flex items-center justify-center py-2">
|
||||
{loading && <Loader2 className="size-3 animate-spin text-muted-foreground" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const COLUMN_IDS = new Set<string>(ALL_STATUSES);
|
||||
|
||||
const kanbanCollision: CollisionDetection = (args) => {
|
||||
|
|
@ -111,7 +135,7 @@ export function BoardView({
|
|||
}) {
|
||||
const sortBy = useViewStore((s) => s.sortBy);
|
||||
const sortDirection = useViewStore((s) => s.sortDirection);
|
||||
const { loadMore, hasMore, isLoading: loadingMore } = useLoadMoreDoneIssues();
|
||||
const { loadMore, hasMore, isLoading: loadingMore, doneTotal } = useLoadMoreDoneIssues();
|
||||
|
||||
// --- Drag state ---
|
||||
const [activeIssue, setActiveIssue] = useState<Issue | null>(null);
|
||||
|
|
@ -274,19 +298,10 @@ export function BoardView({
|
|||
status={status}
|
||||
issueIds={columns[status] ?? []}
|
||||
issueMap={issueMapRef.current}
|
||||
totalCount={status === "done" ? doneTotal : undefined}
|
||||
footer={
|
||||
status === "done" && hasMore ? (
|
||||
<button
|
||||
type="button"
|
||||
className="mt-1 flex w-full items-center justify-center gap-1.5 rounded-md py-2 text-xs text-muted-foreground hover:bg-accent/60 transition-colors disabled:opacity-50"
|
||||
onClick={loadMore}
|
||||
disabled={loadingMore}
|
||||
>
|
||||
{loadingMore ? (
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
) : null}
|
||||
{loadingMore ? "Loading..." : "Load more"}
|
||||
</button>
|
||||
<InfiniteScrollSentinel onVisible={loadMore} loading={loadingMore} />
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue