Files
Codewalkers/packages/web/src/components/review/ReviewTab.tsx
Lukas May da4152264c feat(web): Pipeline visualization, phase content editing, and review tab
Pipeline view groups phases by dependency depth with DAG visualization.
Phase detail panel with Tiptap rich content editor and auto-save.
Code review tab with diff viewer and comment threads (dummy data).
Centralized live updates hook replaces scattered subscription boilerplate.
Extract agent output parsing into shared utility. Inbox detail panel,
account cards, and agent action components.
2026-02-09 22:33:40 +01:00

105 lines
3.3 KiB
TypeScript

import { useState, useCallback, useRef } from "react";
import { toast } from "sonner";
import { DUMMY_REVIEW } from "./dummy-data";
import { DiffViewer } from "./DiffViewer";
import { ReviewSidebar } from "./ReviewSidebar";
import type { ReviewComment, ReviewStatus, DiffLine } from "./types";
interface ReviewTabProps {
initiativeId: string;
}
export function ReviewTab({ initiativeId: _initiativeId }: ReviewTabProps) {
const [comments, setComments] = useState<ReviewComment[]>(DUMMY_REVIEW.comments);
const [status, setStatus] = useState<ReviewStatus>(DUMMY_REVIEW.status);
const fileRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const handleAddComment = useCallback(
(filePath: string, lineNumber: number, lineType: DiffLine["type"], body: string) => {
const newComment: ReviewComment = {
id: `c${Date.now()}`,
filePath,
lineNumber,
lineType,
body,
author: "you",
createdAt: new Date().toISOString(),
resolved: false,
};
setComments((prev) => [...prev, newComment]);
toast.success("Comment added");
},
[]
);
const handleResolveComment = useCallback((commentId: string) => {
setComments((prev) =>
prev.map((c) => (c.id === commentId ? { ...c, resolved: true } : c))
);
}, []);
const handleUnresolveComment = useCallback((commentId: string) => {
setComments((prev) =>
prev.map((c) => (c.id === commentId ? { ...c, resolved: false } : c))
);
}, []);
const handleApprove = useCallback(() => {
setStatus("approved");
toast.success("Review approved");
}, []);
const handleRequestChanges = useCallback(() => {
setStatus("changes_requested");
toast("Changes requested", {
description: "The agent will be notified about the requested changes.",
});
}, []);
const handleFileClick = useCallback((filePath: string) => {
const el = fileRefs.current.get(filePath);
if (el) {
el.scrollIntoView({ behavior: "smooth", block: "start" });
}
}, []);
return (
<div className="grid grid-cols-1 gap-6 lg:grid-cols-[1fr_300px]">
{/* Left: Diff */}
<div className="min-w-0">
<div className="flex items-center justify-between border-b border-border pb-3 mb-4">
<h2 className="text-lg font-semibold">Review</h2>
<span className="text-xs text-muted-foreground">
{comments.filter((c) => !c.resolved).length} unresolved thread
{comments.filter((c) => !c.resolved).length !== 1 ? "s" : ""}
</span>
</div>
<DiffViewer
files={DUMMY_REVIEW.files}
comments={comments}
onAddComment={handleAddComment}
onResolveComment={handleResolveComment}
onUnresolveComment={handleUnresolveComment}
/>
</div>
{/* Right: Sidebar */}
<div className="w-full lg:w-[300px]">
<ReviewSidebar
title={DUMMY_REVIEW.title}
description={DUMMY_REVIEW.description}
author={DUMMY_REVIEW.author}
status={status}
sourceBranch={DUMMY_REVIEW.sourceBranch}
targetBranch={DUMMY_REVIEW.targetBranch}
files={DUMMY_REVIEW.files}
comments={comments}
onApprove={handleApprove}
onRequestChanges={handleRequestChanges}
onFileClick={handleFileClick}
/>
</div>
</div>
);
}