fix(ui): avoid freeze on notification click
This commit is contained in:
parent
62b381b844
commit
1b751ac16d
2 changed files with 97 additions and 31 deletions
|
|
@ -142,19 +142,19 @@ export default function NotificationCenter() {
|
||||||
}, [filter, notifications]);
|
}, [filter, notifications]);
|
||||||
|
|
||||||
async function handleOpenNotification(notification: OrchaiNotification) {
|
async function handleOpenNotification(notification: OrchaiNotification) {
|
||||||
if (!notification.read) {
|
|
||||||
try {
|
|
||||||
await markNotificationRead(notification.id);
|
|
||||||
setNotifications((prev) =>
|
|
||||||
prev.map((n) => (n.id === notification.id ? { ...n, read: true } : n))
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
||||||
|
if (!notification.read) {
|
||||||
|
setNotifications((prev) =>
|
||||||
|
prev.map((n) => (n.id === notification.id ? { ...n, read: true } : n))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Do not block navigation on read acknowledgement.
|
||||||
|
void markNotificationRead(notification.id).catch(() => {
|
||||||
|
// ignore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (notification.ticket_id) {
|
if (notification.ticket_id) {
|
||||||
navigate(`/tickets/${notification.ticket_id}`);
|
navigate(`/tickets/${notification.ticket_id}`);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,11 @@ export default function TicketDetail() {
|
||||||
const [targetBranch, setTargetBranch] = useState("");
|
const [targetBranch, setTargetBranch] = useState("");
|
||||||
const [availableBranches, setAvailableBranches] = useState<string[]>([]);
|
const [availableBranches, setAvailableBranches] = useState<string[]>([]);
|
||||||
const [branchInputMode, setBranchInputMode] = useState<"select" | "manual">("select");
|
const [branchInputMode, setBranchInputMode] = useState<"select" | "manual">("select");
|
||||||
|
const [branchesLoadedForWorktreeId, setBranchesLoadedForWorktreeId] = useState<string | null>(null);
|
||||||
const [branchesLoading, setBranchesLoading] = useState(false);
|
const [branchesLoading, setBranchesLoading] = useState(false);
|
||||||
const [branchesError, setBranchesError] = useState("");
|
const [branchesError, setBranchesError] = useState("");
|
||||||
|
const [diffLoading, setDiffLoading] = useState(false);
|
||||||
|
const [diffError, setDiffError] = useState("");
|
||||||
const [tab, setTab] = useState<"info" | "analyst" | "developer" | "diff">("info");
|
const [tab, setTab] = useState<"info" | "analyst" | "developer" | "diff">("info");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
@ -93,29 +96,24 @@ export default function TicketDetail() {
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
if (!ticketId) return;
|
if (!ticketId) return;
|
||||||
|
setError("");
|
||||||
try {
|
try {
|
||||||
const result = await getTicketResult(ticketId);
|
const result = await getTicketResult(ticketId);
|
||||||
setTicket(result.ticket);
|
setTicket(result.ticket);
|
||||||
setWorktree(result.worktree);
|
setWorktree(result.worktree);
|
||||||
|
setTab("info");
|
||||||
if (result.ticket.developer_report) setTab("developer");
|
setDiff(null);
|
||||||
else if (result.ticket.analyst_report) setTab("analyst");
|
setDiffError("");
|
||||||
|
setDiffLoading(false);
|
||||||
|
setAvailableBranches([]);
|
||||||
|
setBranchInputMode("select");
|
||||||
|
setTargetBranch("");
|
||||||
|
setBranchesError("");
|
||||||
|
setBranchesLoading(false);
|
||||||
|
setBranchesLoadedForWorktreeId(null);
|
||||||
|
|
||||||
if (result.worktree && result.worktree.status === "Active") {
|
if (result.worktree && result.worktree.status === "Active") {
|
||||||
await loadBranchOptions(result.worktree.id);
|
return;
|
||||||
try {
|
|
||||||
const d = await getWorktreeDiff(result.worktree.id);
|
|
||||||
setDiff(d);
|
|
||||||
} catch {
|
|
||||||
setDiff(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setDiff(null);
|
|
||||||
setAvailableBranches([]);
|
|
||||||
setBranchInputMode("select");
|
|
||||||
setTargetBranch("");
|
|
||||||
setBranchesError("");
|
|
||||||
setBranchesLoading(false);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(getErrorMessage(err));
|
setError(getErrorMessage(err));
|
||||||
|
|
@ -123,9 +121,61 @@ export default function TicketDetail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
void loadData();
|
||||||
}, [ticketId]);
|
}, [ticketId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tab !== "info") return;
|
||||||
|
if (!worktree || worktree.status !== "Active") return;
|
||||||
|
if (branchesLoading) return;
|
||||||
|
if (branchesLoadedForWorktreeId === worktree.id) return;
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
await loadBranchOptions(worktree.id);
|
||||||
|
if (!cancelled) {
|
||||||
|
setBranchesLoadedForWorktreeId(worktree.id);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [tab, worktree?.id, worktree?.status, branchesLoading, branchesLoadedForWorktreeId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tab !== "diff") return;
|
||||||
|
if (!worktree || worktree.status !== "Active") return;
|
||||||
|
if (diff !== null || diffLoading) return;
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
setDiffLoading(true);
|
||||||
|
setDiffError("");
|
||||||
|
|
||||||
|
void getWorktreeDiff(worktree.id)
|
||||||
|
.then((value) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setDiff(value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setDiff("");
|
||||||
|
setDiffError(getErrorMessage(err));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setDiffLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [tab, worktree?.id, worktree?.status, diff, diffLoading]);
|
||||||
|
|
||||||
async function handleRetry() {
|
async function handleRetry() {
|
||||||
if (!ticketId) return;
|
if (!ticketId) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -214,7 +264,11 @@ export default function TicketDetail() {
|
||||||
label: "Developer Report",
|
label: "Developer Report",
|
||||||
disabled: !ticket.developer_report,
|
disabled: !ticket.developer_report,
|
||||||
},
|
},
|
||||||
{ key: "diff" as const, label: "Diff", disabled: !diff && !worktree },
|
{
|
||||||
|
key: "diff" as const,
|
||||||
|
label: "Diff",
|
||||||
|
disabled: !worktree || worktree.status !== "Active",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -420,7 +474,19 @@ export default function TicketDetail() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tab === "diff" && <DiffViewer diff={diff || ""} />}
|
{tab === "diff" && (
|
||||||
|
<>
|
||||||
|
{diffLoading ? (
|
||||||
|
<div className="p-4 text-sm text-gray-500">Loading diff...</div>
|
||||||
|
) : diffError ? (
|
||||||
|
<div className={noticeClass("warning")}>
|
||||||
|
Could not load diff: {diffError}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<DiffViewer diff={diff || ""} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isDeleteWorktreeModalOpen}
|
isOpen={isDeleteWorktreeModalOpen}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue