feat(issues): load all open issues without limit, paginate closed (#459)
- Add ListOpenIssues SQL query (excludes done/cancelled, no LIMIT) - Add CountIssues SQL query for true total count - Backend: support open_only=true param, fix total to return real count - Frontend: two-phase fetch in issue store (all open + first 50 closed) - Add fetchMoreClosed action for paginated closed issue loading - Replace all hardcoded limit:200 with store.fetch() calls Resolves MUL-369 Co-authored-by: Devv <devv@Devvs-Mac-mini.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d1570b301
commit
abcc7bf3cd
8 changed files with 194 additions and 24 deletions
|
|
@ -53,9 +53,7 @@ export function BatchActionToolbar() {
|
|||
toast.success(`Updated ${count} issue${count > 1 ? "s" : ""}`);
|
||||
} catch {
|
||||
toast.error("Failed to update issues");
|
||||
api.listIssues({ limit: 200 }).then((res) => {
|
||||
useIssueStore.getState().setIssues(res.issues);
|
||||
}).catch(console.error);
|
||||
useIssueStore.getState().fetch().catch(console.error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -72,9 +70,7 @@ export function BatchActionToolbar() {
|
|||
toast.success(`Deleted ${count} issue${count > 1 ? "s" : ""}`);
|
||||
} catch {
|
||||
toast.error("Failed to delete issues");
|
||||
api.listIssues({ limit: 200 }).then((res) => {
|
||||
useIssueStore.getState().setIssues(res.issues);
|
||||
}).catch(console.error);
|
||||
useIssueStore.getState().fetch().catch(console.error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setDeleteOpen(false);
|
||||
|
|
|
|||
|
|
@ -82,9 +82,7 @@ export function IssuesPage() {
|
|||
|
||||
api.updateIssue(issueId, updates).catch(() => {
|
||||
toast.error("Failed to move issue");
|
||||
api.listIssues({ limit: 200 }).then((res) => {
|
||||
useIssueStore.getState().setIssues(res.issues);
|
||||
}).catch(console.error);
|
||||
useIssueStore.getState().fetch().catch(console.error);
|
||||
});
|
||||
},
|
||||
[]
|
||||
|
|
|
|||
|
|
@ -8,11 +8,16 @@ import { createLogger } from "@/shared/logger";
|
|||
|
||||
const logger = createLogger("issue-store");
|
||||
|
||||
const CLOSED_PAGE_SIZE = 50;
|
||||
|
||||
interface IssueState {
|
||||
issues: Issue[];
|
||||
loading: boolean;
|
||||
activeIssueId: string | null;
|
||||
hasMoreClosed: boolean;
|
||||
closedOffset: number;
|
||||
fetch: () => Promise<void>;
|
||||
fetchMoreClosed: () => Promise<void>;
|
||||
setIssues: (issues: Issue[]) => void;
|
||||
addIssue: (issue: Issue) => void;
|
||||
updateIssue: (id: string, updates: Partial<Issue>) => void;
|
||||
|
|
@ -24,15 +29,28 @@ export const useIssueStore = create<IssueState>((set, get) => ({
|
|||
issues: [],
|
||||
loading: true,
|
||||
activeIssueId: null,
|
||||
hasMoreClosed: false,
|
||||
closedOffset: 0,
|
||||
|
||||
fetch: async () => {
|
||||
logger.debug("fetch start");
|
||||
const isInitialLoad = get().issues.length === 0;
|
||||
if (isInitialLoad) set({ loading: true });
|
||||
try {
|
||||
const res = await api.listIssues({ limit: 200 });
|
||||
logger.info("fetched", res.issues.length, "issues");
|
||||
set({ issues: res.issues, loading: false });
|
||||
// Phase 1: fetch ALL open issues (no limit)
|
||||
// Phase 2: fetch first page of closed issues
|
||||
const [openRes, closedRes] = await Promise.all([
|
||||
api.listIssues({ open_only: true }),
|
||||
api.listIssues({ status: "done", limit: CLOSED_PAGE_SIZE, offset: 0 }),
|
||||
]);
|
||||
const allIssues = [...openRes.issues, ...closedRes.issues];
|
||||
logger.info("fetched", openRes.issues.length, "open +", closedRes.issues.length, "closed issues");
|
||||
set({
|
||||
issues: allIssues,
|
||||
loading: false,
|
||||
hasMoreClosed: closedRes.issues.length >= CLOSED_PAGE_SIZE,
|
||||
closedOffset: CLOSED_PAGE_SIZE,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("fetch failed", err);
|
||||
toast.error("Failed to load issues");
|
||||
|
|
@ -40,6 +58,28 @@ export const useIssueStore = create<IssueState>((set, get) => ({
|
|||
}
|
||||
},
|
||||
|
||||
fetchMoreClosed: async () => {
|
||||
const { closedOffset } = get();
|
||||
try {
|
||||
const res = await api.listIssues({
|
||||
status: "done",
|
||||
limit: CLOSED_PAGE_SIZE,
|
||||
offset: closedOffset,
|
||||
});
|
||||
set((s) => ({
|
||||
issues: [
|
||||
...s.issues,
|
||||
...res.issues.filter((ni) => !s.issues.some((ei) => ei.id === ni.id)),
|
||||
],
|
||||
closedOffset: closedOffset + CLOSED_PAGE_SIZE,
|
||||
hasMoreClosed: res.issues.length >= CLOSED_PAGE_SIZE,
|
||||
}));
|
||||
} catch (err) {
|
||||
logger.error("fetchMoreClosed failed", err);
|
||||
toast.error("Failed to load more issues");
|
||||
}
|
||||
},
|
||||
|
||||
setIssues: (issues) => set({ issues }),
|
||||
addIssue: (issue) =>
|
||||
set((s) => ({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue