"use client"; import { useCallback, useEffect, useMemo } from "react"; import { toast } from "sonner"; import { ChevronRight, ListTodo } from "lucide-react"; import type { IssueStatus } from "@/shared/types"; import { Skeleton } from "@/components/ui/skeleton"; import { useQuery } from "@tanstack/react-query"; import { useIssueViewStore, initFilterWorkspaceSync } from "@/features/issues/stores/view-store"; import { useIssuesScopeStore } from "@/features/issues/stores/issues-scope-store"; import { ViewStoreProvider } from "@/features/issues/stores/view-store-context"; import { filterIssues } from "@/features/issues/utils/filter"; import { BOARD_STATUSES } from "@/features/issues/config"; import { useWorkspaceStore } from "@/features/workspace"; import { WorkspaceAvatar } from "@/features/workspace"; import { useWorkspaceId } from "@core/hooks"; import { issueListOptions } from "@core/issues/queries"; import { useUpdateIssue } from "@core/issues/mutations"; import { useIssueSelectionStore } from "@/features/issues/stores/selection-store"; import { IssuesHeader } from "./issues-header"; import { BoardView } from "./board-view"; import { ListView } from "./list-view"; import { BatchActionToolbar } from "./batch-action-toolbar"; export function IssuesPage() { const wsId = useWorkspaceId(); const { data: allIssues = [], isLoading: loading } = useQuery(issueListOptions(wsId)); const workspace = useWorkspaceStore((s) => s.workspace); const scope = useIssuesScopeStore((s) => s.scope); const viewMode = useIssueViewStore((s) => s.viewMode); const statusFilters = useIssueViewStore((s) => s.statusFilters); const priorityFilters = useIssueViewStore((s) => s.priorityFilters); const assigneeFilters = useIssueViewStore((s) => s.assigneeFilters); const includeNoAssignee = useIssueViewStore((s) => s.includeNoAssignee); const creatorFilters = useIssueViewStore((s) => s.creatorFilters); useEffect(() => { initFilterWorkspaceSync(); }, []); useEffect(() => { useIssueSelectionStore.getState().clear(); }, [viewMode, scope]); // Scope pre-filter: narrow by assignee type const scopedIssues = useMemo(() => { if (scope === "members") return allIssues.filter((i) => i.assignee_type === "member"); if (scope === "agents") return allIssues.filter((i) => i.assignee_type === "agent"); return allIssues; }, [allIssues, scope]); const issues = useMemo( () => filterIssues(scopedIssues, { statusFilters, priorityFilters, assigneeFilters, includeNoAssignee, creatorFilters }), [scopedIssues, statusFilters, priorityFilters, assigneeFilters, includeNoAssignee, creatorFilters], ); const visibleStatuses = useMemo(() => { if (statusFilters.length > 0) return BOARD_STATUSES.filter((s) => statusFilters.includes(s)); return BOARD_STATUSES; }, [statusFilters]); const hiddenStatuses = useMemo(() => { return BOARD_STATUSES.filter((s) => !visibleStatuses.includes(s)); }, [visibleStatuses]); const updateIssueMutation = useUpdateIssue(); const handleMoveIssue = useCallback( (issueId: string, newStatus: IssueStatus, newPosition?: number) => { // Auto-switch to manual sort so drag ordering is preserved const viewState = useIssueViewStore.getState(); if (viewState.sortBy !== "position") { viewState.setSortBy("position"); viewState.setSortDirection("asc"); } const updates: Partial<{ status: IssueStatus; position: number }> = { status: newStatus, }; if (newPosition !== undefined) updates.position = newPosition; updateIssueMutation.mutate( { id: issueId, ...updates }, { onError: () => toast.error("Failed to move issue") }, ); }, [updateIssueMutation], ); if (loading) { return (
No issues yet
Create an issue to get started.