import type { CSSProperties, ReactNode } from "react";
import {
ArrowRight,
Bot,
BriefcaseBusiness,
CheckCircle2,
ChevronDown,
CirclePlus,
Code2,
FileText,
FlaskConical,
Maximize2,
Paperclip,
Send,
Settings2,
ShieldAlert,
TrendingUp,
X,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
import { usePMFlowOsStore } from "@/features/pmflow-os/store";
import type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowRiskLevel } from "@/features/pmflow-os/types";
import { cn } from "@/lib/utils";
const activityItems = [
["新增提取对象", "AI 从对话中提取了 3 个新对象:会议纪要分享、自动任务创建、跨时区支持", "10:24 · 由 PMFlow AI 提取", CirclePlus, "brand"],
["PR 已回写", "PR #128 已合并并回写 2 个需求与 1 个风险", "10:12 · 由 开发智能体", Code2, "success"],
["测试失败", "用例「会议纪要分享 - 权限校验」失败,影响发布门禁", "09:48 · 由 测试智能体", X, "danger"],
["发布风险升级", "风险「第三方转写服务稳定性」升级为高风险", "09:31 · 由 PMFlow AI", ShieldAlert, "warning"],
["决策已记录", "已记录决策:采用混合模型方案(本地 + 云端)", "昨天 18:22 · 由 张子墨", FileText, "brand"],
["用户反馈回写", "新增 12 条用户反馈,主要集中在「长会议跟踪助手」", "昨天 17:05 · 由 运营智能体", TrendingUp, "success"],
] as const;
const decisions = [
["范围调整", "是否将「AI 生成行动项」从 MVP 范围移出", "发起人:张子墨 · 截止:今天 18:00", "高"],
["发布时间确认", "确认 v1.0 版本发布时间窗口(建议 6.15 - 6.18)", "发起人:李文博 · 截止:明天 12:00", "中"],
["风险接受", "接受风险「第三方转写服务稳定性」并制定应对方案", "发起人:PMFlow AI · 截止:后天 17:00", "高"],
["资源冲突", "开发资源与「数据分析看板」项目冲突", "发起人:系统 · 截止:06-14 12:00", "中"],
] as const;
const inboxItems = [
["需求澄清", "会议纪要分享的权限范围需要进一步明确", "来自:PMFlow AI · 10:24", "需多确认", "brand"],
["用户反馈聚类", "长会议识别准确率问题较多,建议评估优化方案", "来自:运营智能体 · 09:40", "需多评估", "warning"],
["范围影响评估", "若移除「AI 生成行动项」,预计节省 2 周开发时间", "来自:开发智能体 · 昨天", "异步决策", "brand"],
] as const;
const agentCards = [
["产品智能体", Bot, "运行中", "梳理会议纪要分享需求细节", 60, "", [["提取 3 个新对象", "10:24"], ["更新需求 PRD-012", "昨天"], ["发起 1 项待决策", "昨天"]]],
["开发智能体", Code2, "运行中", "实现会议纪要分享 API", 75, "", [["PR #128 已合并并回写", "10:12"], ["创建 2 个开发任务", "昨天"], ["更新技术设计文档", "06-10"]]],
["测试智能体", FlaskConical, "阻塞中", "执行回归测试(纪要分享模块)", 40, "用例 2 失败,等待开发修复", [["执行 32 个用例", "09:48"], ["发现 2 个缺陷", "昨天"]]],
["运营智能体", TrendingUp, "运行中", "分析用户反馈与套用数据", 65, "", [["新增 12 条用户反馈", "09:31"], ["生成运营周报", "昨天"], ["更新增长实验建议", "06-10"]]],
] as const;
export default function TeamLayerPage() {
const navigate = useNavigate();
const { state, openReview, projectionWarnings } = usePMFlowOsStore();
const reviewerRouting = state.reviewerRouting || emptyReviewerRouting();
const pendingReviewCount = state.changeItems.filter((item) => item.status === "pending_review").length;
const reviewerWarnings = projectionWarnings.filter((warning) => warning.includes("reviewer_routing_warning"));
const handleOpenReview = (item: PMFlowReviewerRoutingItem) => {
openReview(item.changeSetId);
navigate(item.targetUrl || `/change-review?projectId=${item.projectId}&changeSetId=${item.changeSetId}`);
};
return (
);
}
function ReviewerRoutingPanel({
pendingReviewCount,
routings,
warnings,
onOpenReview,
}: {
pendingReviewCount: number;
routings: PMFlowReviewerRouting;
warnings: string[];
onOpenReview: (item: PMFlowReviewerRoutingItem) => void;
}) {
const visibleRoutings = routings.items.slice(0, 3);
return (
审查路由
待审 ChangeSet 审查路由
基于项目成员角色、待审变更风险和跨对象影响生成建议审查人;这里只展示建议,不自动分派审查人。
{routings.status === "warning" ? (
{reviewerRoutingWarningText(routings.warningCode, warnings)}
) : null}
{visibleRoutings.length > 0 ? (
{visibleRoutings.map((routing) => (
{riskLabel(routing.riskLevel)}
{sourceLabel(routing.sourceType)}
{priorityLabel(routing.priority)}
{routing.changeSetTitle}
{routing.reviewReason}
{routing.changeTypes.map((type) => (
{type}
))}
待审 {routing.pendingItemCount}
证据 {routing.evidenceCount}
建议审查人
{routing.suggestedReviewer ? (
{routing.suggestedReviewer.displayName}
{routing.suggestedReviewer.role}
{routing.suggestedReviewer.reason}
) : (
当前项目缺少 REVIEWER/OWNER,建议先补充项目审查人。兜底角色:{routing.fallbackReviewerRole || "OWNER"}
)}
))}
) : (
{routings.status === "empty" ? "当前没有待团队审查的变更。" : "团队审查路由暂时不可用,请稍后重试。"}
)}
);
}
function MetricPill({ label, value }: { label: string; value: string }) {
return (
);
}
function TeamActivityPanel() {
return (
}
/>
{activityItems.map(([title, detail, time, Icon, tone], index) => (
))}
查看全部动态
);
}
function DecisionPanel() {
return (
待拍板 6
}
/>
{decisions.map(([title, detail, meta, risk]) => (
{title}
{risk}
{detail}
{meta}
))}
查看全部待拍板
);
}
function RoleInboxPanel() {
return (
产品 3
开发 4
测试 2
运营 2
{inboxItems.map(([title, detail, meta, tag, tone]) => (
{title}
{detail}
{meta}
{tag}
))}
查看全部消息
);
}
function AgentWorkstationPanel() {
return (
智能体工位
{agentCards.map(([title, Icon, status, task, progress, blocked, outputs]) => (
当前任务
{task}
{blocked ? 阻塞原因:{blocked}
: null}
近期产出
{outputs.map(([label, time]) => (
{label}
{time}
))}
))}
);
}
function AICopilotPanel() {
return (
);
}
function Panel({ children, className, style }: { children: ReactNode; className?: string; style?: CSSProperties }) {
return ;
}
function PanelHeader({ title, action }: { title: string; action?: ReactNode }) {
return {title}
{action};
}
function LinkButton({ children, className }: { children: ReactNode; className?: string }) {
return ;
}
function CopilotBlock({ title, children }: { title: string; children: ReactNode }) {
return ;
}
function iconTone(tone: string) {
if (tone === "success") return "bg-emerald-600";
if (tone === "danger") return "bg-red-500";
if (tone === "warning") return "bg-orange-500";
return "bg-[#2463EB]";
}
function riskLabel(level: PMFlowRiskLevel) {
if (level === "high") return "高风险";
if (level === "medium") return "中风险";
return "低风险";
}
function riskTone(level: PMFlowRiskLevel) {
if (level === "high") return "bg-red-50 text-red-600";
if (level === "medium") return "bg-orange-50 text-orange-600";
return "bg-emerald-50 text-emerald-600";
}
function sourceLabel(sourceType: PMFlowReviewerRoutingItem["sourceType"]) {
if (sourceType === "iteration_recommendation") return "下一轮迭代";
if (sourceType === "ai_extraction") return "AI 提取";
if (sourceType === "manual") return "手动";
return "来源未知";
}
function priorityLabel(priority: PMFlowReviewerRoutingItem["priority"]) {
if (priority === "urgent") return "紧急";
if (priority === "elevated") return "需关注";
return "常规";
}
function reviewerRoutingWarningText(code: string | null | undefined, warnings: string[]) {
if (code === "reviewer_missing") return "当前项目缺少 REVIEWER/OWNER,建议先补充项目审查人。";
if (code === "evidence_missing") return "待审变更缺少足够证据,建议回到 Change Review 查看来源。";
if (warnings.length > 0) return "团队审查路由存在 warning,请优先检查 reviewer 和证据。";
return "团队审查路由暂时不可用,请稍后重试。";
}
function emptyReviewerRouting(): PMFlowReviewerRouting {
return {
status: "empty",
warningCode: null,
items: [],
summary: {
totalPendingReview: 0,
highRiskCount: 0,
missingReviewerCount: 0,
iterationDraftCount: 0,
},
};
}