"use client"; import { useCallback, useEffect, useMemo } from "react"; import { toast } from "sonner"; import { ChevronRight, ListTodo } from "lucide-react"; import type { IssueStatus } from "@multica/core/types"; import { Skeleton } from "@multica/ui/components/ui/skeleton"; import { useQuery } from "@tanstack/react-query"; import { useIssueViewStore, useClearFiltersOnWorkspaceChange } from "@multica/core/issues/stores/view-store"; import { useIssuesScopeStore } from "@multica/core/issues/stores/issues-scope-store"; import { ViewStoreProvider } from "@multica/core/issues/stores/view-store-context"; import { filterIssues } from "../utils/filter"; import { BOARD_STATUSES } from "@multica/core/issues/config"; import { useCurrentWorkspace } from "@multica/core/paths"; import { WorkspaceAvatar } from "../../workspace/workspace-avatar"; import { useWorkspaceId } from "@multica/core/hooks"; import { issueListOptions, childIssueProgressOptions } from "@multica/core/issues/queries"; import { useUpdateIssue } from "@multica/core/issues/mutations"; import { useIssueSelectionStore } from "@multica/core/issues/stores/selection-store"; import { PageHeader } from "../../layout/page-header"; 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 = useCurrentWorkspace(); 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); const projectFilters = useIssueViewStore((s) => s.projectFilters); const includeNoProject = useIssueViewStore((s) => s.includeNoProject); // Clear filter state when switching between workspaces (URL-driven). useClearFiltersOnWorkspaceChange(useIssueViewStore, wsId); 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, projectFilters, includeNoProject }), [scopedIssues, statusFilters, priorityFilters, assigneeFilters, includeNoAssignee, creatorFilters, projectFilters, includeNoProject], ); // Fetch sub-issue progress from the backend so counts are accurate // regardless of client-side pagination or filtering of done issues. const { data: childProgressMap = new Map() } = useQuery(childIssueProgressOptions(wsId)); 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 (
{viewMode === "list" ? (
{Array.from({ length: 4 }).map((_, i) => ( ))}
) : (
{Array.from({ length: 5 }).map((_, i) => (
))}
)}
); } return (
{/* Header 1: Workspace breadcrumb */} {workspace?.name ?? "Workspace"} Issues {/* Header 2: Scope tabs + filters */} {/* Content: scrollable */} {scopedIssues.length === 0 ? (

No issues yet

Create an issue to get started.

) : (
{viewMode === "board" ? ( ) : ( )}
)} {viewMode === "list" && }
); }