"use client"; import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react"; import { AtlasChat } from "@/components/AtlasChat"; import { useRouter, useParams } from "next/navigation"; import Link from "next/link"; import { ArrowUpDown, Filter, LayoutPanelLeft, Search } from "lucide-react"; import { JM, JV } from "@/components/project-creation/modal-theme"; import { type ChatContextRef, contextRefKey, } from "@/lib/chat-context-refs"; const DISCOVERY_PHASES = [ "big_picture", "users_personas", "features_scope", "business_model", "screens_data", "risks_questions", ] as const; const PHASE_DISPLAY: Record = { big_picture: "Big picture", users_personas: "Users & personas", features_scope: "Features & scope", business_model: "Business model", screens_data: "Screens & data", risks_questions: "Risks & questions", }; // Maps discovery phases → the PRD sections they populate const PRD_SECTIONS: { label: string; phase: string | null }[] = [ { label: "Executive Summary", phase: "big_picture" }, { label: "Problem Statement", phase: "big_picture" }, { label: "Vision & Success Metrics", phase: "big_picture" }, { label: "Users & Personas", phase: "users_personas" }, { label: "User Flows", phase: "users_personas" }, { label: "Feature Requirements", phase: "features_scope" }, { label: "Screen Specs", phase: "features_scope" }, { label: "Business Model", phase: "business_model" }, { label: "Integrations & Dependencies", phase: "screens_data" }, { label: "Non-Functional Reqs", phase: "features_scope" }, { label: "Risks & Mitigations", phase: "risks_questions" }, { label: "Open Questions", phase: "risks_questions" }, ]; type SidebarTab = "tasks" | "phases"; type GroupBy = "none" | "phase" | "status"; function sectionDone( phase: string | null, savedPhaseIds: Set, allDone: boolean ): boolean { return phase === null ? allDone : savedPhaseIds.has(phase); } interface FreshIdeaMainProps { projectId: string; projectName: string; } export function FreshIdeaMain({ projectId, projectName }: FreshIdeaMainProps) { const router = useRouter(); const params = useParams(); const workspace = params?.workspace as string; const [savedPhaseIds, setSavedPhaseIds] = useState>(new Set()); const [allDone, setAllDone] = useState(false); const [prdLoading, setPrdLoading] = useState(false); const [dismissed, setDismissed] = useState(false); const [hasPrd, setHasPrd] = useState(false); const [sidebarTab, setSidebarTab] = useState("tasks"); const [sectionSearch, setSectionSearch] = useState(""); const [phaseScope, setPhaseScope] = useState("all"); const [groupBy, setGroupBy] = useState("none"); const [pendingOnly, setPendingOnly] = useState(false); const [sortAlpha, setSortAlpha] = useState(false); const [chatContextRefs, setChatContextRefs] = useState([]); const addSectionToChat = useCallback((label: string, phase: string | null) => { setChatContextRefs(prev => { const next: ChatContextRef = { kind: "section", label, phaseId: phase }; const k = contextRefKey(next); if (prev.some(r => contextRefKey(r) === k)) return prev; return [...prev, next]; }); }, []); const addPhaseToChat = useCallback((phaseId: string, label: string) => { setChatContextRefs(prev => { const next: ChatContextRef = { kind: "phase", phaseId, label }; const k = contextRefKey(next); if (prev.some(r => contextRefKey(r) === k)) return prev; return [...prev, next]; }); }, []); const removeChatContextRef = useCallback((key: string) => { setChatContextRefs(prev => prev.filter(r => contextRefKey(r) !== key)); }, []); useEffect(() => { // Check if PRD already exists on the project fetch(`/api/projects/${projectId}`) .then(r => r.json()) .then(d => { if (d.project?.prd) setHasPrd(true); }) .catch(() => {}); const poll = () => { fetch(`/api/projects/${projectId}/save-phase`) .then(r => r.json()) .then(d => { const ids = new Set((d.phases ?? []).map((p: { phase: string }) => p.phase)); setSavedPhaseIds(ids); const done = DISCOVERY_PHASES.every(id => ids.has(id)); setAllDone(done); }) .catch(() => {}); }; poll(); const interval = setInterval(poll, 8_000); return () => clearInterval(interval); }, [projectId]); const handleGeneratePRD = async () => { if (prdLoading) return; setPrdLoading(true); try { router.push(`/${workspace}/project/${projectId}/tasks`); } finally { setPrdLoading(false); } }; const handleMVP = () => { router.push(`/${workspace}/project/${projectId}/mvp-setup/launch`); }; // PRD exists — show a thin notice bar at the top, then keep the chat fully accessible const completedSections = PRD_SECTIONS.filter(({ phase }) => phase === null ? allDone : savedPhaseIds.has(phase) ).length; const totalSections = PRD_SECTIONS.length; const filteredSections = useMemo(() => { const q = sectionSearch.trim().toLowerCase(); let rows = PRD_SECTIONS.map((s, index) => ({ ...s, index })); if (q) { rows = rows.filter(r => r.label.toLowerCase().includes(q)); } if (phaseScope !== "all") { rows = rows.filter(r => r.phase === phaseScope); } if (pendingOnly) { rows = rows.filter(r => !sectionDone(r.phase, savedPhaseIds, allDone)); } if (sortAlpha) { rows = [...rows].sort((a, b) => a.label.localeCompare(b.label)); } else { rows = [...rows].sort((a, b) => a.index - b.index); } return rows; }, [sectionSearch, phaseScope, pendingOnly, sortAlpha, savedPhaseIds, allDone]); const effectiveGroupBy: GroupBy = sidebarTab === "phases" ? "phase" : groupBy; return (
{/* ── Left: Atlas chat (Justine describe column) ── */}
{hasPrd && (
✦ PRD saved — keep refining here or open the full document.
View PRD →
)} {allDone && !dismissed && !hasPrd && (
✦ Discovery complete — what's next?
All 6 phases captured. Generate your PRD or open the MVP plan flow.
)}
{/* ── Right: Teams-style task rail (requirements = PRD sections as tasks) ── */}
{/* Tab bar */}
{([ { id: "tasks" as const, label: "Tasks" }, { id: "phases" as const, label: "Phases" }, ]).map(t => { const active = sidebarTab === t.id; return ( ); })}
{/* Search + tools */}
setSectionSearch(e.target.value)} placeholder="Search sections…" aria-label="Search sections" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", fontSize: 12, fontFamily: JM.fontSans, color: JM.ink, outline: "none", }} />
{/* Scope + group (Tasks tab only shows group pills; Phases tab locks grouping) */}
{sidebarTab === "tasks" && (
Group by {([ { id: "none" as const, label: "None" }, { id: "phase" as const, label: "Phase" }, { id: "status" as const, label: "Status" }, ]).map(opt => { const on = groupBy === opt.id; return ( ); })}
)} {sidebarTab === "phases" && (
Grouped by discovery phase
)}
{/* Progress summary */}
{completedSections} of {totalSections} sections · Requirements task
Click a section row or phase header to attach it to your next message.
{/* Task list */}
{(() => { const rows = filteredSections; if (rows.length === 0) { return (
No sections match your search or filters.
); } const renderRow = (label: string, phase: string | null, key: string) => { const isDone = sectionDone(phase, savedPhaseIds, allDone); const phaseSlug = phase ? phase.replace(/_/g, "-") : "prd"; const phaseLine = phase ? PHASE_DISPLAY[phase] ?? phase : "PRD"; return ( ); }; if (effectiveGroupBy === "none") { return rows.map(r => renderRow(r.label, r.phase, `${r.label}-${r.index}`)); } if (effectiveGroupBy === "phase") { const byPhase = new Map(); for (const r of rows) { const pk = r.phase ?? "null"; if (!byPhase.has(pk)) byPhase.set(pk, []); byPhase.get(pk)!.push(r); } const order = [...DISCOVERY_PHASES, "null"]; return order.flatMap(pk => { const list = byPhase.get(pk); if (!list?.length) return []; const header = pk === "null" ? "Final" : PHASE_DISPLAY[pk] ?? pk; const phaseClickable = pk !== "null"; return [ phaseClickable ? ( ) : (
{header}
), ...list.map(r => renderRow(r.label, r.phase, `${r.label}-${r.index}`)), ]; }); } const doneRows = rows.filter(r => sectionDone(r.phase, savedPhaseIds, allDone)); const todoRows = rows.filter(r => !sectionDone(r.phase, savedPhaseIds, allDone)); const statusBlocks: ReactNode[] = []; if (todoRows.length > 0) { statusBlocks.push(
To do
); todoRows.forEach(r => { statusBlocks.push(renderRow(r.label, r.phase, `todo-${r.label}-${r.index}`)); }); } if (doneRows.length > 0) { statusBlocks.push(
Done
); doneRows.forEach(r => { statusBlocks.push(renderRow(r.label, r.phase, `done-${r.label}-${r.index}`)); }); } return statusBlocks; })()}
{allDone && (
Open Tasks →
)}
); }