"use client"; import { useState, useRef, useEffect } from "react"; import { useReviewStore } from "@/hooks/use-review-player"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { getInitials, formatRelativeDate } from "@/lib/utils"; import { frameToTimecode } from "@/lib/frame-utils"; import { CheckCircle2, Circle, MessageSquare, Send, ChevronDown, ChevronUp, Filter, } from "lucide-react"; import { addComment, addReply, resolveComment } from "@/actions/comments"; import { useToast } from "@/components/ui/use-toast"; import type { CommentWithReplies } from "@/types"; interface CommentPanelProps { versionId: string; fps: number; comments: CommentWithReplies[]; onCommentsChange?: () => void; pendingFrame?: number | null; onPendingFrameCleared?: () => void; onSeekToFrame?: (frame: number) => void; } export function CommentPanel({ versionId, fps, comments, onCommentsChange, pendingFrame, onPendingFrameCleared, onSeekToFrame, }: CommentPanelProps) { const { currentFrame, setCurrentFrame } = useReviewStore(); const [filterResolved, setFilterResolved] = useState<"all" | "unresolved" | "resolved">("all"); const [isSubmitting, setIsSubmitting] = useState(false); const [commentText, setCommentText] = useState(""); const [activeCommentFrame, setActiveCommentFrame] = useState(null); const { toast } = useToast(); const inputRef = useRef(null); // Handle pending frame from "Add Comment" button click useEffect(() => { if (pendingFrame !== null && pendingFrame !== undefined) { setActiveCommentFrame(pendingFrame); setTimeout(() => inputRef.current?.focus(), 50); onPendingFrameCleared?.(); } }, [pendingFrame, onPendingFrameCleared]); const filteredComments = comments.filter((c) => { if (filterResolved === "unresolved") return !c.isResolved; if (filterResolved === "resolved") return c.isResolved; return true; }); const handleSubmitComment = async () => { if (!commentText.trim() || activeCommentFrame === null) return; setIsSubmitting(true); try { const timestamp = activeCommentFrame / fps; await addComment({ versionId, frameNumber: activeCommentFrame, timestamp, text: commentText.trim(), }); setCommentText(""); setActiveCommentFrame(null); onCommentsChange?.(); toast({ title: "Comment added", variant: "default" }); } catch (err) { toast({ title: "Failed to add comment", variant: "destructive" }); } finally { setIsSubmitting(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); handleSubmitComment(); } if (e.key === "Escape") { setActiveCommentFrame(null); setCommentText(""); } }; return (
{/* Header */}
Comments {comments.length > 0 && ( ({comments.length}) )}
{(["all", "unresolved", "resolved"] as const).map((f) => ( ))}
{/* Comment Input */} {activeCommentFrame !== null ? (
Frame {activeCommentFrame} {frameToTimecode(activeCommentFrame, fps)}