130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { CheckCircle2, Circle } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { db } from "@/lib/firebase/config";
|
|
import { doc, onSnapshot } from "firebase/firestore";
|
|
|
|
interface ExtractionReviewChecklistProps {
|
|
projectId: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function ExtractionReviewChecklist({ projectId, className }: ExtractionReviewChecklistProps) {
|
|
const [hasProblems, setHasProblems] = useState(false);
|
|
const [hasUsers, setHasUsers] = useState(false);
|
|
const [hasFeatures, setHasFeatures] = useState(false);
|
|
const [reviewedWithAI, setReviewedWithAI] = useState(false);
|
|
const [readyForVision, setReadyForVision] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!projectId) return;
|
|
|
|
const unsubscribe = onSnapshot(
|
|
doc(db, "projects", projectId),
|
|
(snapshot) => {
|
|
if (snapshot.exists()) {
|
|
const data = snapshot.data();
|
|
const extraction = data?.phaseData?.phaseHandoffs?.extraction;
|
|
|
|
if (extraction) {
|
|
// Check if we have key data
|
|
setHasProblems((extraction.confirmed?.problems?.length || 0) > 0);
|
|
setHasUsers((extraction.confirmed?.targetUsers?.length || 0) > 0);
|
|
setHasFeatures((extraction.confirmed?.features?.length || 0) > 0);
|
|
setReadyForVision(extraction.readyForNextPhase || false);
|
|
}
|
|
|
|
// Check if user has reviewed with AI (simplified: check if there are any messages in chat)
|
|
// This is a placeholder - you might track this differently
|
|
setReviewedWithAI(data?.lastChatAt ? true : false);
|
|
}
|
|
}
|
|
);
|
|
|
|
return () => unsubscribe();
|
|
}, [projectId]);
|
|
|
|
const checklist = [
|
|
{
|
|
id: "problems",
|
|
label: "Problems identified",
|
|
checked: hasProblems,
|
|
},
|
|
{
|
|
id: "users",
|
|
label: "Target users defined",
|
|
checked: hasUsers,
|
|
},
|
|
{
|
|
id: "features",
|
|
label: "Key features extracted",
|
|
checked: hasFeatures,
|
|
},
|
|
{
|
|
id: "reviewed",
|
|
label: "Reviewed with AI",
|
|
checked: reviewedWithAI,
|
|
},
|
|
];
|
|
|
|
const completedCount = checklist.filter((item) => item.checked).length;
|
|
const progress = (completedCount / checklist.length) * 100;
|
|
const isCompleted = completedCount === checklist.length || readyForVision;
|
|
|
|
return (
|
|
<div className={className}>
|
|
{/* Header */}
|
|
<div className="mb-3">
|
|
<h3 className="text-sm font-semibold">
|
|
{isCompleted ? '✓ Review Complete' : 'Extraction Review'}
|
|
</h3>
|
|
<div className="mt-2">
|
|
<div className="w-full bg-secondary h-2 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-primary transition-all duration-300"
|
|
style={{ width: `${isCompleted ? 100 : progress}%` }}
|
|
/>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{isCompleted ? `${checklist.length} of ${checklist.length} complete` : `${completedCount} of ${checklist.length} complete`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Checklist Items */}
|
|
<div className="space-y-2">
|
|
{checklist.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className={`flex items-center gap-2 py-2 px-3 rounded-md border ${
|
|
(item.checked || isCompleted)
|
|
? "bg-green-50 border-green-200"
|
|
: "bg-muted/50 border-muted text-muted-foreground opacity-60"
|
|
}`}
|
|
>
|
|
{(item.checked || isCompleted) ? (
|
|
<CheckCircle2 className="h-4 w-4 text-green-600 flex-shrink-0" />
|
|
) : (
|
|
<Circle className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-xs font-medium">{item.label}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Ready indicator */}
|
|
{readyForVision && (
|
|
<div className="mt-4 pt-4 border-t">
|
|
<p className="text-xs text-green-600 font-medium">
|
|
✓ Ready for Vision phase
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|