mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 19:20:39 +09:00
fix(ui): polish issue detail timelines and attachments
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
36376968af
commit
bd6d07d0b4
25 changed files with 2020 additions and 82 deletions
|
|
@ -29,7 +29,7 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Check, ChevronDown, ChevronRight, Copy, Download, FileText, MoreHorizontal, Plus, Trash2, X } from "lucide-react";
|
||||
import { Check, ChevronDown, ChevronRight, Copy, Download, FilePenLine, FileText, MoreHorizontal, Plus, Trash2, X } from "lucide-react";
|
||||
|
||||
type DraftState = {
|
||||
key: string;
|
||||
|
|
@ -106,6 +106,25 @@ function documentHasUnsavedChanges(doc: IssueDocument, draft: DraftState | null)
|
|||
return draft.body !== doc.body || (doc.title ?? "") !== draft.title;
|
||||
}
|
||||
|
||||
function toDocumentSummary(document: IssueDocument) {
|
||||
return {
|
||||
id: document.id,
|
||||
companyId: document.companyId,
|
||||
issueId: document.issueId,
|
||||
key: document.key,
|
||||
title: document.title,
|
||||
format: document.format,
|
||||
latestRevisionId: document.latestRevisionId,
|
||||
latestRevisionNumber: document.latestRevisionNumber,
|
||||
createdByAgentId: document.createdByAgentId,
|
||||
createdByUserId: document.createdByUserId,
|
||||
updatedByAgentId: document.updatedByAgentId,
|
||||
updatedByUserId: document.updatedByUserId,
|
||||
createdAt: document.createdAt,
|
||||
updatedAt: document.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function IssueDocumentsSection({
|
||||
issue,
|
||||
canDeleteDocuments,
|
||||
|
|
@ -181,6 +200,36 @@ export function IssueDocumentsSection({
|
|||
});
|
||||
}, [issue.id, queryClient]);
|
||||
|
||||
const syncDocumentCaches = useCallback((document: IssueDocument) => {
|
||||
queryClient.setQueryData<IssueDocument[] | undefined>(
|
||||
queryKeys.issues.documents(issue.id),
|
||||
(current) => {
|
||||
if (!current) return [document];
|
||||
const existingIndex = current.findIndex((entry) => entry.key === document.key);
|
||||
if (existingIndex === -1) return [...current, document];
|
||||
return current.map((entry, index) => index === existingIndex ? document : entry);
|
||||
},
|
||||
);
|
||||
queryClient.setQueryData<Issue | undefined>(
|
||||
queryKeys.issues.detail(issue.id),
|
||||
(current) => {
|
||||
if (!current) return current;
|
||||
const nextSummaries = (() => {
|
||||
const summary = toDocumentSummary(document);
|
||||
const existingIndex = (current.documentSummaries ?? []).findIndex((entry) => entry.key === document.key);
|
||||
if (existingIndex === -1) return [...(current.documentSummaries ?? []), summary];
|
||||
return (current.documentSummaries ?? []).map((entry, index) => index === existingIndex ? summary : entry);
|
||||
})();
|
||||
return {
|
||||
...current,
|
||||
planDocument: document.key === "plan" ? document : current.planDocument ?? null,
|
||||
documentSummaries: nextSummaries,
|
||||
legacyPlanDocument: document.key === "plan" ? null : current.legacyPlanDocument ?? null,
|
||||
};
|
||||
},
|
||||
);
|
||||
}, [issue.id, queryClient]);
|
||||
|
||||
const upsertDocument = useMutation({
|
||||
mutationFn: async (nextDraft: DraftState) =>
|
||||
issuesApi.upsertDocument(issue.id, nextDraft.key, {
|
||||
|
|
@ -206,7 +255,8 @@ export function IssueDocumentsSection({
|
|||
const restoreDocumentRevision = useMutation({
|
||||
mutationFn: ({ key, revisionId }: { key: string; revisionId: string }) =>
|
||||
issuesApi.restoreDocumentRevision(issue.id, key, revisionId),
|
||||
onSuccess: (_document, variables) => {
|
||||
onSuccess: (document, variables) => {
|
||||
syncDocumentCaches(document);
|
||||
setSelectedRevisionIds((current) => ({ ...current, [variables.key]: null }));
|
||||
setDraft((current) => current?.key === variables.key ? null : current);
|
||||
setDocumentConflict((current) => current?.key === variables.key ? null : current);
|
||||
|
|
@ -369,6 +419,7 @@ export function IssueDocumentsSection({
|
|||
isNew: false,
|
||||
};
|
||||
});
|
||||
syncDocumentCaches(saved);
|
||||
invalidateIssueDocuments();
|
||||
};
|
||||
|
||||
|
|
@ -408,7 +459,7 @@ export function IssueDocumentsSection({
|
|||
setError(err instanceof Error ? err.message : "Failed to save document");
|
||||
return false;
|
||||
}
|
||||
}, [documentConflict, invalidateIssueDocuments, issue.id, resetAutosaveState, runSave, sortedDocuments, upsertDocument]);
|
||||
}, [documentConflict, invalidateIssueDocuments, issue.id, resetAutosaveState, runSave, sortedDocuments, syncDocumentCaches, upsertDocument]);
|
||||
|
||||
const reloadDocumentFromServer = useCallback((key: string) => {
|
||||
if (documentConflict?.key !== key) return;
|
||||
|
|
@ -864,7 +915,14 @@ export function IssueDocumentsSection({
|
|||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuContent align="end">
|
||||
{!isHistoricalPreview ? (
|
||||
<DropdownMenuItem onClick={() => beginEdit(doc.key)}>
|
||||
<FilePenLine className="h-3.5 w-3.5" />
|
||||
Edit document
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{!isHistoricalPreview ? <DropdownMenuSeparator /> : null}
|
||||
<DropdownMenuItem
|
||||
onClick={() => downloadDocumentFile(doc.key, displayedBody)}
|
||||
>
|
||||
|
|
@ -889,13 +947,6 @@ export function IssueDocumentsSection({
|
|||
{!isFolded ? (
|
||||
<div
|
||||
className="mt-3 space-y-3"
|
||||
onFocusCapture={!isHistoricalPreview
|
||||
? () => {
|
||||
if (!activeDraft) {
|
||||
beginEdit(doc.key);
|
||||
}
|
||||
}
|
||||
: undefined}
|
||||
onBlurCapture={!isHistoricalPreview
|
||||
? async (event) => {
|
||||
if (activeDraft) {
|
||||
|
|
@ -1026,7 +1077,7 @@ export function IssueDocumentsSection({
|
|||
<div className="rounded-md border border-amber-500/20 bg-background/50 p-3">
|
||||
{renderBody(displayedBody, documentBodyContentClassName)}
|
||||
</div>
|
||||
) : (
|
||||
) : activeDraft ? (
|
||||
<MarkdownEditor
|
||||
value={displayedBody}
|
||||
onChange={(body) => {
|
||||
|
|
@ -1035,13 +1086,7 @@ export function IssueDocumentsSection({
|
|||
if (current && current.key === doc.key && !current.isNew) {
|
||||
return { ...current, body };
|
||||
}
|
||||
return {
|
||||
key: doc.key,
|
||||
title: doc.title ?? "",
|
||||
body,
|
||||
baseRevisionId: doc.latestRevisionId,
|
||||
isNew: false,
|
||||
};
|
||||
return current;
|
||||
});
|
||||
}}
|
||||
placeholder="Markdown body"
|
||||
|
|
@ -1052,6 +1097,10 @@ export function IssueDocumentsSection({
|
|||
imageUploadHandler={imageUploadHandler}
|
||||
onSubmit={() => void commitDraft(activeDraft ?? draft, { clearAfterSave: false, trackAutosave: true })}
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded-md border border-border/60 bg-background/40 p-3">
|
||||
{renderBody(displayedBody, documentBodyContentClassName)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex min-h-4 items-center justify-end px-1">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue