Files
PaperClipAI/ui/src/hooks/useIssueLinkedDocuments.ts
T
DottaandPaperclip 2f23e4f1ca Add issue Documents tab + Open-in-Documents ergonomics (PAP-10569)
UX spec PAP-10520 §6. Inline IssueDocumentsSection plan editing/revision is
unchanged; it gains an "Open in Documents" (ExternalLink) button routing to
/documents/<id>?from=issue:<key> and a 💬N · ✎M feedback summary per doc.

New issue "Documents" tab (next to Chat/Activity/Related work) surfaces every
company document linked to the issue, reusing the library DocumentRow, pinning
the plan to the top, with scoped search and an Add document control (link
existing via picker / create new in Chat). Tab label shows the linked-doc count
plus an open-feedback dot.

Cross-issue document mention chip renders internal /<co>/documents/<id> links
in markdown using the chip-match-document-* tokens, with a hover summary of the
document title, status, and feedback counts.

Feedback counts come from one issue-scoped company-documents query shared by the
inline card and the tab (useIssueLinkedDocuments). Storybook stories added for
UX sign-off.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-07 01:55:08 +00:00

43 lines
1.4 KiB
TypeScript

import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import type { CompanyDocumentSummary } from "@paperclipai/shared";
import { documentsApi, type CompanyDocumentListFilters } from "../api/documents";
import { queryKeys } from "../lib/queryKeys";
/**
* Company documents linked to an issue (its inline issue documents plus any
* cross-linked company docs). Backs both the inline issue document card feedback
* counts and the issue "Documents" tab, so they stay in sync from one query.
*/
export function useIssueLinkedDocuments(
companyId: string | null | undefined,
issueId: string,
options: { search?: string; enabled?: boolean } = {},
) {
const filters: CompanyDocumentListFilters = useMemo(
() => ({
targetType: "issue",
targetId: issueId,
q: options.search?.trim() || undefined,
includeArchived: true,
limit: 200,
}),
[issueId, options.search],
);
const query = useQuery({
queryKey: queryKeys.documents.list(companyId ?? "", filters as Record<string, unknown>),
queryFn: () => documentsApi.list(companyId!, filters),
enabled: Boolean(companyId) && (options.enabled ?? true),
placeholderData: (previous) => previous,
});
const byId = useMemo(() => {
const map = new Map<string, CompanyDocumentSummary>();
for (const doc of query.data ?? []) map.set(doc.id, doc);
return map;
}, [query.data]);
return { ...query, documents: query.data ?? [], byId };
}