feat(frontend): 全局企业筛选同步 — 各页面显示企业名标签
- 新增 useCompanyName hook 和 CompanyNameTag 组件 - 20+ investor 页面添加 CompanyNameTag 显示企业名 - 表格型页面(agents/agreements/board/weak-signals/talents/reports)新增企业列 - 卡片型页面(risks/synergies/sentinels/alpha/exit-signals/okrs/aars/events/tasks/pre-mortems/nudges/product-diagnostics/peer-circles)添加企业名标签 - customer-success 改用 CompanyNameTag 替代手动 company_name 显示 - PlanCard 组件添加企业名标签 - 编译验证通过
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Sparkles } from "lucide-react";
|
import {Sparkles } from "lucide-react";
|
||||||
import { listAARs, generateAAR } from "@/lib/api-v2";
|
import { listAARs, generateAAR } from "@/lib/api-v2";
|
||||||
import { PageContainer, Card} from "@/components/shared/PageContainer";
|
import { PageContainer, Card} from "@/components/shared/PageContainer";
|
||||||
@@ -11,6 +12,7 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
/** AAR 复盘记录项。 */
|
/** AAR 复盘记录项。 */
|
||||||
interface AARItem {
|
interface AARItem {
|
||||||
|
company_id?: string;
|
||||||
trigger_event: string;
|
trigger_event: string;
|
||||||
gap_analysis?: string;
|
gap_analysis?: string;
|
||||||
lessons?: string[];
|
lessons?: string[];
|
||||||
@@ -85,6 +87,7 @@ export default function AARsPage() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<Card key={i}>
|
<Card key={i}>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="font-medium text-gray-900">{item.trigger_event}</h3>
|
<h3 className="font-medium text-gray-900">{item.trigger_event}</h3>
|
||||||
{item.gap_analysis && <p className="mt-1 text-sm text-gray-600">{item.gap_analysis}</p>}
|
{item.gap_analysis && <p className="mt-1 text-sm text-gray-600">{item.gap_analysis}</p>}
|
||||||
{Array.isArray(item.lessons) && (
|
{Array.isArray(item.lessons) && (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { listAgentExecutions, reviewAgentExecution } from "@/lib/api-v2";
|
import { listAgentExecutions, reviewAgentExecution } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
@@ -11,6 +12,7 @@ import { toast } from "sonner";
|
|||||||
/** Agent 执行记录项。 */
|
/** Agent 执行记录项。 */
|
||||||
interface AgentExecution {
|
interface AgentExecution {
|
||||||
id: string;
|
id: string;
|
||||||
|
company_id?: string;
|
||||||
agent_name: string;
|
agent_name: string;
|
||||||
autonomy_level: string;
|
autonomy_level: string;
|
||||||
output_summary: string;
|
output_summary: string;
|
||||||
@@ -52,6 +54,7 @@ export default function AgentsPage() {
|
|||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-500">企业</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">Agent</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">Agent</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">自治级别</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">自治级别</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">输出摘要</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">输出摘要</th>
|
||||||
@@ -63,6 +66,7 @@ export default function AgentsPage() {
|
|||||||
<tbody className="divide-y divide-gray-100 bg-white">
|
<tbody className="divide-y divide-gray-100 bg-white">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
|
<td className="px-4 py-2"><CompanyNameTag companyId={item.company_id} /></td>
|
||||||
<td className="px-4 py-2 text-gray-900">{item.agent_name}</td>
|
<td className="px-4 py-2 text-gray-900">{item.agent_name}</td>
|
||||||
<td className="px-4 py-2"><Badge color={item.autonomy_level === "L4" ? "red" : item.autonomy_level === "L3" ? "amber" : "blue"}>{item.autonomy_level}</Badge></td>
|
<td className="px-4 py-2"><Badge color={item.autonomy_level === "L4" ? "red" : item.autonomy_level === "L3" ? "amber" : "blue"}>{item.autonomy_level}</Badge></td>
|
||||||
<td className="px-4 py-2 text-gray-600 max-w-xs truncate">{item.output_summary}</td>
|
<td className="px-4 py-2 text-gray-600 max-w-xs truncate">{item.output_summary}</td>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Plus } from "lucide-react";
|
import {Plus } from "lucide-react";
|
||||||
import { listAgreements } from "@/lib/api-v2";
|
import { listAgreements } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge, TableEmpty } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge, TableEmpty } from "@/components/shared/PageContainer";
|
||||||
@@ -10,6 +11,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
|
|||||||
|
|
||||||
/** 投资协议项。 */
|
/** 投资协议项。 */
|
||||||
interface Agreement {
|
interface Agreement {
|
||||||
|
company_id?: string;
|
||||||
title: string;
|
title: string;
|
||||||
signed_at: string;
|
signed_at: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -46,6 +48,7 @@ export default function AgreementsPage() {
|
|||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-500">企业</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">协议名称</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">协议名称</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">签署日期</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">签署日期</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">状态</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">状态</th>
|
||||||
@@ -54,10 +57,11 @@ export default function AgreementsPage() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-100 bg-white">
|
<tbody className="divide-y divide-gray-100 bg-white">
|
||||||
{items.length === 0 ? (
|
{items.length === 0 ? (
|
||||||
<TableEmpty colSpan={4} />
|
<TableEmpty colSpan={5} />
|
||||||
) : (
|
) : (
|
||||||
items.map((item, i) => (
|
items.map((item, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
|
<td className="px-4 py-2"><CompanyNameTag companyId={item.company_id} /></td>
|
||||||
<td className="px-4 py-2 text-gray-900">{item.title}</td>
|
<td className="px-4 py-2 text-gray-900">{item.title}</td>
|
||||||
<td className="px-4 py-2 text-gray-600">{item.signed_at}</td>
|
<td className="px-4 py-2 text-gray-600">{item.signed_at}</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Plus } from "lucide-react";
|
import {Plus } from "lucide-react";
|
||||||
import { listInterventions } from "@/lib/api-v2";
|
import { listInterventions } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
|
||||||
@@ -10,6 +11,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
|
|||||||
|
|
||||||
/** 干预记录项。 */
|
/** 干预记录项。 */
|
||||||
interface Intervention {
|
interface Intervention {
|
||||||
|
company_id?: string;
|
||||||
intervention_type: string;
|
intervention_type: string;
|
||||||
title: string;
|
title: string;
|
||||||
executed_at: string;
|
executed_at: string;
|
||||||
@@ -48,6 +50,7 @@ export default function AlphaPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge color="blue">{item.intervention_type}</Badge>
|
<Badge color="blue">{item.intervention_type}</Badge>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-gray-400">{item.executed_at}</span>
|
<span className="text-xs text-gray-400">{item.executed_at}</span>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { listBoardMeetings } from "@/lib/api-v2";
|
import { listBoardMeetings } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
|
|||||||
|
|
||||||
/** 董事会会议项。 */
|
/** 董事会会议项。 */
|
||||||
interface BoardMeeting {
|
interface BoardMeeting {
|
||||||
|
company_id?: string;
|
||||||
title: string;
|
title: string;
|
||||||
meeting_at: string;
|
meeting_at: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -37,6 +39,7 @@ export default function BoardPage() {
|
|||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-500">企业</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">会议主题</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">会议主题</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">会议时间</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">会议时间</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">状态</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">状态</th>
|
||||||
@@ -46,6 +49,7 @@ export default function BoardPage() {
|
|||||||
<tbody className="divide-y divide-gray-100 bg-white">
|
<tbody className="divide-y divide-gray-100 bg-white">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
|
<td className="px-4 py-2"><CompanyNameTag companyId={item.company_id} /></td>
|
||||||
<td className="px-4 py-2 text-gray-900">{item.title}</td>
|
<td className="px-4 py-2 text-gray-900">{item.title}</td>
|
||||||
<td className="px-4 py-2 text-gray-600">{item.meeting_at}</td>
|
<td className="px-4 py-2 text-gray-600">{item.meeting_at}</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
@@ -191,7 +192,7 @@ export default function CustomerSuccessPage() {
|
|||||||
{churnRisks.map((risk, i) => (
|
{churnRisks.map((risk, i) => (
|
||||||
<div key={i} className="flex items-center justify-between rounded-md border border-[var(--border)] px-3 py-2">
|
<div key={i} className="flex items-center justify-between rounded-md border border-[var(--border)] px-3 py-2">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium">{risk.company_name || risk.company_id || "企业"}</span>
|
<CompanyNameTag companyId={risk.company_id} />
|
||||||
{risk.signals && <p className="text-xs text-muted-foreground">{risk.signals}</p>}
|
{risk.signals && <p className="text-xs text-muted-foreground">{risk.signals}</p>}
|
||||||
</div>
|
</div>
|
||||||
<span className={`rounded px-2 py-0.5 text-xs ${
|
<span className={`rounded px-2 py-0.5 text-xs ${
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { listMajorEvents, listInquiries } from "@/lib/api-v2";
|
import { listMajorEvents, listInquiries } from "@/lib/api-v2";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
|
|
||||||
/** 重大事项项。 */
|
/** 重大事项项。 */
|
||||||
interface MajorEvent {
|
interface MajorEvent {
|
||||||
|
company_id?: string;
|
||||||
event_type: string;
|
event_type: string;
|
||||||
title: string;
|
title: string;
|
||||||
severity: string;
|
severity: string;
|
||||||
@@ -17,6 +19,7 @@ interface MajorEvent {
|
|||||||
|
|
||||||
/** 追问清单项。 */
|
/** 追问清单项。 */
|
||||||
interface Inquiry {
|
interface Inquiry {
|
||||||
|
company_id?: string;
|
||||||
status: string;
|
status: string;
|
||||||
questions?: unknown[];
|
questions?: unknown[];
|
||||||
}
|
}
|
||||||
@@ -69,6 +72,7 @@ export default function EventsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge color="blue">{item.event_type}</Badge>
|
<Badge color="blue">{item.event_type}</Badge>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
<Badge color={item.severity === "critical" ? "red" : item.severity === "high" ? "amber" : "gray"}>
|
<Badge color={item.severity === "critical" ? "red" : item.severity === "high" ? "amber" : "gray"}>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Sparkles } from "lucide-react";
|
import {Sparkles } from "lucide-react";
|
||||||
import { listExitPredictions, predictExit } from "@/lib/api-v2";
|
import { listExitPredictions, predictExit } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
|
||||||
@@ -11,6 +12,7 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
/** 退出预测项。 */
|
/** 退出预测项。 */
|
||||||
interface ExitPrediction {
|
interface ExitPrediction {
|
||||||
|
company_id?: string;
|
||||||
exit_path?: string;
|
exit_path?: string;
|
||||||
expected_return?: string;
|
expected_return?: string;
|
||||||
confidence?: number;
|
confidence?: number;
|
||||||
@@ -80,6 +82,7 @@ export default function ExitSignalsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{item.exit_path && <Badge color="blue">{item.exit_path}</Badge>}
|
{item.exit_path && <Badge color="blue">{item.exit_path}</Badge>}
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<span className="text-sm text-gray-900">期望收益:{item.expected_return ?? "-"}</span>
|
<span className="text-sm text-gray-900">期望收益:{item.expected_return ?? "-"}</span>
|
||||||
</div>
|
</div>
|
||||||
{item.confidence != null && (
|
{item.confidence != null && (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
@@ -12,6 +13,7 @@ import { Lightbulb, Sparkles, Check, X, TrendingUp, BarChart3 } from "lucide-rea
|
|||||||
/** 助推记录项。 */
|
/** 助推记录项。 */
|
||||||
interface NudgeItem {
|
interface NudgeItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
company_id?: string;
|
||||||
nudge_type: string;
|
nudge_type: string;
|
||||||
message: string;
|
message: string;
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
@@ -117,7 +119,10 @@ export default function NudgesPage() {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Lightbulb size={16} className="text-amber-500" />
|
<Lightbulb size={16} className="text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium">{n.nudge_type}</div>
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium">{n.nudge_type}</span>
|
||||||
|
<CompanyNameTag companyId={n.company_id} />
|
||||||
|
</div>
|
||||||
<div className="text-xs text-muted-foreground">{n.message}</div>
|
<div className="text-xs text-muted-foreground">{n.message}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Plus } from "lucide-react";
|
import {Plus } from "lucide-react";
|
||||||
import { listOKRs } from "@/lib/api-v2";
|
import { listOKRs } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
|
||||||
@@ -17,6 +18,7 @@ interface KeyResult {
|
|||||||
|
|
||||||
/** OKR 项。 */
|
/** OKR 项。 */
|
||||||
interface OKRItem {
|
interface OKRItem {
|
||||||
|
company_id?: string;
|
||||||
quarter: string;
|
quarter: string;
|
||||||
objective: string;
|
objective: string;
|
||||||
alignment_score?: number;
|
alignment_score?: number;
|
||||||
@@ -56,6 +58,7 @@ export default function OKRsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<Badge color="blue">{item.quarter}</Badge>
|
<Badge color="blue">{item.quarter}</Badge>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="mt-1 font-medium text-gray-900">{item.objective}</h3>
|
<h3 className="mt-1 font-medium text-gray-900">{item.objective}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
@@ -12,6 +13,7 @@ import { Users, Sparkles, Circle } from "lucide-react";
|
|||||||
/** Circle 项。 */
|
/** Circle 项。 */
|
||||||
interface CircleItem {
|
interface CircleItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
company_id?: string;
|
||||||
topic: string;
|
topic: string;
|
||||||
status: string;
|
status: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -138,6 +140,7 @@ export default function PeerCirclesPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Circle size={16} className="text-[var(--investor-primary)]" />
|
<Circle size={16} className="text-[var(--investor-primary)]" />
|
||||||
|
<CompanyNameTag companyId={c.company_id} />
|
||||||
<h3 className="text-sm font-medium">{c.topic}</h3>
|
<h3 className="text-sm font-medium">{c.topic}</h3>
|
||||||
</div>
|
</div>
|
||||||
<span className={`rounded px-2 py-0.5 text-xs ${
|
<span className={`rounded px-2 py-0.5 text-xs ${
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Sparkles } from "lucide-react";
|
import {Sparkles } from "lucide-react";
|
||||||
import { listPreMortems, runPreMortem, listRedTeams, runRedTeam } from "@/lib/api-v2";
|
import { listPreMortems, runPreMortem, listRedTeams, runRedTeam } from "@/lib/api-v2";
|
||||||
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
|
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
|
||||||
@@ -11,6 +12,7 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
/** Pre-mortem 记录项。 */
|
/** Pre-mortem 记录项。 */
|
||||||
interface PreMortemItem {
|
interface PreMortemItem {
|
||||||
|
company_id?: string;
|
||||||
decision_context: string;
|
decision_context: string;
|
||||||
failure_paths?: FailurePath[];
|
failure_paths?: FailurePath[];
|
||||||
}
|
}
|
||||||
@@ -22,6 +24,7 @@ interface FailurePath {
|
|||||||
|
|
||||||
/** Red Team 记录项。 */
|
/** Red Team 记录项。 */
|
||||||
interface RedTeamItem {
|
interface RedTeamItem {
|
||||||
|
company_id?: string;
|
||||||
perspective: string;
|
perspective: string;
|
||||||
analysis: string;
|
analysis: string;
|
||||||
}
|
}
|
||||||
@@ -118,6 +121,7 @@ export default function PreMortemsPage() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{preMortems.map((item, i) => (
|
{preMortems.map((item, i) => (
|
||||||
<Card key={i}>
|
<Card key={i}>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="font-medium text-gray-900">{item.decision_context as string}</h3>
|
<h3 className="font-medium text-gray-900">{item.decision_context as string}</h3>
|
||||||
{Array.isArray(item.failure_paths) && (
|
{Array.isArray(item.failure_paths) && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
@@ -136,6 +140,7 @@ export default function PreMortemsPage() {
|
|||||||
{redTeams.map((item, i) => (
|
{redTeams.map((item, i) => (
|
||||||
<Card key={i}>
|
<Card key={i}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<Badge color="red">{item.perspective as string}</Badge>
|
<Badge color="red">{item.perspective as string}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-sm text-gray-600">{item.analysis as string}</p>
|
<p className="mt-2 text-sm text-gray-600">{item.analysis as string}</p>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
@@ -24,6 +25,7 @@ interface DiagnosticResult {
|
|||||||
/** 历史诊断项。 */
|
/** 历史诊断项。 */
|
||||||
interface DiagnosticHistory {
|
interface DiagnosticHistory {
|
||||||
id: string;
|
id: string;
|
||||||
|
company_id?: string;
|
||||||
product_name?: string;
|
product_name?: string;
|
||||||
dimensions?: DiagnosticDimension[];
|
dimensions?: DiagnosticDimension[];
|
||||||
}
|
}
|
||||||
@@ -159,7 +161,10 @@ export default function ProductDiagnosticsPage() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{diagnostics.map((d) => (
|
{diagnostics.map((d) => (
|
||||||
<div key={d.id} className="rounded-lg border border-[var(--border)] bg-white p-3">
|
<div key={d.id} className="rounded-lg border border-[var(--border)] bg-white p-3">
|
||||||
<h3 className="text-sm font-medium">{d.product_name || "未命名产品"}</h3>
|
<div className="flex items-center gap-2">
|
||||||
|
<CompanyNameTag companyId={d.company_id} />
|
||||||
|
<h3 className="text-sm font-medium">{d.product_name || "未命名产品"}</h3>
|
||||||
|
</div>
|
||||||
{d.dimensions && Array.isArray(d.dimensions) && (
|
{d.dimensions && Array.isArray(d.dimensions) && (
|
||||||
<div className="mt-1 flex flex-wrap gap-1">
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
{d.dimensions.map((dim, i: number) => (
|
{d.dimensions.map((dim, i: number) => (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState, useCallback } from "react";
|
|||||||
import {Plus, Trash2, Send } from "lucide-react";
|
import {Plus, Trash2, Send } from "lucide-react";
|
||||||
import { listReports, submitReport, deleteReport, STATUS_LABELS, STATUS_COLORS, type MonthlyReport } from "@/lib/reports";
|
import { listReports, submitReport, deleteReport, STATUS_LABELS, STATUS_COLORS, type MonthlyReport } from "@/lib/reports";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ export default function ReportsPage() {
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="border-b bg-muted/50">
|
<thead className="border-b bg-muted/50">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-muted-foreground">企业</th>
|
||||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">期间</th>
|
<th className="px-4 py-3 text-left font-medium text-muted-foreground">期间</th>
|
||||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">状态</th>
|
<th className="px-4 py-3 text-left font-medium text-muted-foreground">状态</th>
|
||||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">内容摘要</th>
|
<th className="px-4 py-3 text-left font-medium text-muted-foreground">内容摘要</th>
|
||||||
@@ -90,6 +92,7 @@ export default function ReportsPage() {
|
|||||||
<tbody className="divide-y">
|
<tbody className="divide-y">
|
||||||
{reports.map((report) => (
|
{reports.map((report) => (
|
||||||
<tr key={report.id} className="hover:bg-muted/30">
|
<tr key={report.id} className="hover:bg-muted/30">
|
||||||
|
<td className="px-4 py-3"><CompanyNameTag companyId={report.company_id} /></td>
|
||||||
<td className="px-4 py-3 font-medium">
|
<td className="px-4 py-3 font-medium">
|
||||||
{report.period_year}年{report.period_month}月
|
{report.period_year}年{report.period_month}月
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { listDecisionSentinels } from "@/lib/api-v2";
|
import { listDecisionSentinels } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
|
|||||||
|
|
||||||
/** 决策前哨项。 */
|
/** 决策前哨项。 */
|
||||||
interface DecisionSentinel {
|
interface DecisionSentinel {
|
||||||
|
company_id?: string;
|
||||||
decision_type: string;
|
decision_type: string;
|
||||||
title: string;
|
title: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -39,6 +41,7 @@ export default function SentinelsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge color="blue">{item.decision_type}</Badge>
|
<Badge color="blue">{item.decision_type}</Badge>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
<Badge color={item.status === "acted" ? "green" : item.status === "analyzed" ? "amber" : "gray"}>
|
<Badge color={item.status === "acted" ? "green" : item.status === "analyzed" ? "amber" : "gray"}>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { listSynergies, authorizeSynergy } from "@/lib/api-v2";
|
import { listSynergies, authorizeSynergy } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
@@ -11,6 +12,7 @@ import { toast } from "sonner";
|
|||||||
/** 协同机会项。 */
|
/** 协同机会项。 */
|
||||||
interface Synergy {
|
interface Synergy {
|
||||||
id: string;
|
id: string;
|
||||||
|
company_id?: string;
|
||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -63,6 +65,7 @@ export default function SynergiesPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge color="blue">{SYNERGY_TYPE_LABELS[item.type] ?? item.type}</Badge>
|
<Badge color="blue">{SYNERGY_TYPE_LABELS[item.type] ?? item.type}</Badge>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
<h3 className="font-medium text-gray-900">{item.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { listTalents } from "@/lib/api-v2";
|
import { listTalents } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
|
|||||||
|
|
||||||
/** 人才项。 */
|
/** 人才项。 */
|
||||||
interface Talent {
|
interface Talent {
|
||||||
|
company_id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
current_role: string;
|
current_role: string;
|
||||||
performance_rating?: string;
|
performance_rating?: string;
|
||||||
@@ -39,6 +41,7 @@ export default function TalentsPage() {
|
|||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-500">企业</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">姓名</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">姓名</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">当前职位</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">当前职位</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">绩效</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">绩效</th>
|
||||||
@@ -50,6 +53,7 @@ export default function TalentsPage() {
|
|||||||
<tbody className="divide-y divide-gray-100 bg-white">
|
<tbody className="divide-y divide-gray-100 bg-white">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
|
<td className="px-4 py-2"><CompanyNameTag companyId={item.company_id} /></td>
|
||||||
<td className="px-4 py-2 text-gray-900">{item.name}</td>
|
<td className="px-4 py-2 text-gray-900">{item.name}</td>
|
||||||
<td className="px-4 py-2 text-gray-600">{item.current_role}</td>
|
<td className="px-4 py-2 text-gray-600">{item.current_role}</td>
|
||||||
<td className="px-4 py-2 text-gray-600">{item.performance_rating ?? "-"}</td>
|
<td className="px-4 py-2 text-gray-600">{item.performance_rating ?? "-"}</td>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import {Plus } from "lucide-react";
|
import {Plus } from "lucide-react";
|
||||||
import { listTasks, updateTask } from "@/lib/api-v2";
|
import { listTasks, updateTask } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
import { PageContainer, Badge } from "@/components/shared/PageContainer";
|
||||||
@@ -12,6 +13,7 @@ import { toast } from "sonner";
|
|||||||
/** 任务项。 */
|
/** 任务项。 */
|
||||||
interface TaskItem {
|
interface TaskItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
company_id?: string;
|
||||||
title: string;
|
title: string;
|
||||||
status: string;
|
status: string;
|
||||||
priority: string;
|
priority: string;
|
||||||
@@ -80,7 +82,10 @@ export default function TasksPage() {
|
|||||||
{items.filter((i) => i.status === col).map((item, i) => (
|
{items.filter((i) => i.status === col).map((item, i) => (
|
||||||
<div key={i} className="rounded-md border border-gray-200 bg-white p-3">
|
<div key={i} className="rounded-md border border-gray-200 bg-white p-3">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<span className="text-sm font-medium text-gray-900">{item.title}</span>
|
<div>
|
||||||
|
<CompanyNameTag companyId={item.company_id} />
|
||||||
|
<span className="text-sm font-medium text-gray-900">{item.title}</span>
|
||||||
|
</div>
|
||||||
<Badge color={PRIORITY_COLORS[item.priority] ?? "gray"}>
|
<Badge color={PRIORITY_COLORS[item.priority] ?? "gray"}>
|
||||||
{item.priority}
|
{item.priority}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useScopeEffect } from "@/lib/company-scope";
|
import { useScopeEffect } from "@/lib/company-scope";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
import { listWeakSignals } from "@/lib/api-v2";
|
import { listWeakSignals } from "@/lib/api-v2";
|
||||||
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
import { PageContainer, Badge} from "@/components/shared/PageContainer";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
|
|||||||
|
|
||||||
/** 弱信号项。 */
|
/** 弱信号项。 */
|
||||||
interface WeakSignal {
|
interface WeakSignal {
|
||||||
|
company_id?: string;
|
||||||
signal_type: string;
|
signal_type: string;
|
||||||
content: string;
|
content: string;
|
||||||
confidence: number;
|
confidence: number;
|
||||||
@@ -45,6 +47,7 @@ export default function WeakSignalsPage() {
|
|||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-500">企业</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">类型</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">类型</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">内容</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">内容</th>
|
||||||
<th className="px-4 py-2 text-left font-medium text-gray-500">置信度</th>
|
<th className="px-4 py-2 text-left font-medium text-gray-500">置信度</th>
|
||||||
@@ -55,6 +58,7 @@ export default function WeakSignalsPage() {
|
|||||||
<tbody className="divide-y divide-gray-100 bg-white">
|
<tbody className="divide-y divide-gray-100 bg-white">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
|
<td className="px-4 py-2"><CompanyNameTag companyId={item.company_id} /></td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">
|
||||||
<Badge color="blue">{SIGNAL_TYPE_LABELS[item.signal_type] ?? item.signal_type}</Badge>
|
<Badge color="blue">{SIGNAL_TYPE_LABELS[item.signal_type] ?? item.signal_type}</Badge>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Target, ArrowRight, DollarSign, Users, Trophy } from "lucide-react";
|
import { Target, ArrowRight, DollarSign, Users, Trophy } from "lucide-react";
|
||||||
|
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
|
||||||
|
|
||||||
/** 决策链节点。 */
|
/** 决策链节点。 */
|
||||||
interface DecisionChainNode {
|
interface DecisionChainNode {
|
||||||
@@ -16,6 +17,7 @@ interface CompetitiveAnalysis {
|
|||||||
|
|
||||||
/** 客户获取方案。 */
|
/** 客户获取方案。 */
|
||||||
interface CustomerPlanData {
|
interface CustomerPlanData {
|
||||||
|
company_id?: string;
|
||||||
target_customer?: string;
|
target_customer?: string;
|
||||||
execution_status?: string;
|
execution_status?: string;
|
||||||
entry_angle?: string;
|
entry_angle?: string;
|
||||||
@@ -40,6 +42,7 @@ export function PlanCard({ plan }: { plan: CustomerPlanData }) {
|
|||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Target size={18} className="text-[var(--investor-primary)]" />
|
<Target size={18} className="text-[var(--investor-primary)]" />
|
||||||
|
<CompanyNameTag companyId={plan.company_id} />
|
||||||
<h3 className="text-sm font-medium">{plan.target_customer || "未指定目标客户"}</h3>
|
<h3 className="text-sm font-medium">{plan.target_customer || "未指定目标客户"}</h3>
|
||||||
</div>
|
</div>
|
||||||
<span className={`rounded px-2 py-0.5 text-xs ${plan.execution_status ? (statusColors[plan.execution_status] || "bg-muted") : "bg-muted"}`}>
|
<span className={`rounded px-2 py-0.5 text-xs ${plan.execution_status ? (statusColors[plan.execution_status] || "bg-muted") : "bg-muted"}`}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AlertTriangle, Trash2 } from "lucide-react";
|
import { AlertTriangle, Trash2, Building2 } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
RISK_STATUS_LABELS,
|
RISK_STATUS_LABELS,
|
||||||
RISK_STATUS_COLORS,
|
RISK_STATUS_COLORS,
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
RISK_TYPE_LABELS,
|
RISK_TYPE_LABELS,
|
||||||
type RiskEvent,
|
type RiskEvent,
|
||||||
} from "@/lib/risks";
|
} from "@/lib/risks";
|
||||||
|
import { useCompanyName } from "@/lib/company-scope";
|
||||||
|
|
||||||
/** 风险卡片组件 — 从风险工作台抽取的独立组件。 */
|
/** 风险卡片组件 — 从风险工作台抽取的独立组件。 */
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ interface RiskCardProps {
|
|||||||
* 风险事件卡片。
|
* 风险事件卡片。
|
||||||
*/
|
*/
|
||||||
export function RiskCard({ risk, onStatusChange, onDelete }: RiskCardProps) {
|
export function RiskCard({ risk, onStatusChange, onDelete }: RiskCardProps) {
|
||||||
|
const getCompanyName = useCompanyName();
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
@@ -38,6 +40,9 @@ export function RiskCard({ risk, onStatusChange, onDelete }: RiskCardProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex gap-3 text-xs text-muted-foreground">
|
<div className="mt-2 flex gap-3 text-xs text-muted-foreground">
|
||||||
|
<span className="flex items-center gap-1 font-medium text-[var(--investor-primary)]">
|
||||||
|
<Building2 size={12} aria-hidden="true" />{getCompanyName(risk.company_id)}
|
||||||
|
</span>
|
||||||
<span>{RISK_TYPE_LABELS[risk.type] || risk.type}</span>
|
<span>{RISK_TYPE_LABELS[risk.type] || risk.type}</span>
|
||||||
<span className={SEVERITY_COLORS[risk.severity]}>
|
<span className={SEVERITY_COLORS[risk.severity]}>
|
||||||
严重度: {SEVERITY_LABELS[risk.severity] || risk.severity}
|
严重度: {SEVERITY_LABELS[risk.severity] || risk.severity}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Building2 } from "lucide-react";
|
||||||
|
import { useCompanyName } from "@/lib/company-scope";
|
||||||
|
|
||||||
|
/** 企业名标签 — 在数据项旁显示所属企业名。 */
|
||||||
|
export function CompanyNameTag({ companyId, className = "" }: { companyId?: string | null; className?: string }) {
|
||||||
|
const getCompanyName = useCompanyName();
|
||||||
|
return (
|
||||||
|
<span className={`inline-flex items-center gap-1 text-xs font-medium text-[var(--investor-primary)] ${className}`}>
|
||||||
|
<Building2 size={12} aria-hidden="true" />
|
||||||
|
{getCompanyName(companyId)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -107,3 +107,17 @@ export function useScopeEffect(callback: () => void, deps: unknown[] = []) {
|
|||||||
}, [companyId, ...deps]);
|
}, [companyId, ...deps]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据企业 ID 查找企业名称。
|
||||||
|
*
|
||||||
|
* 在"全部企业"模式下,各页面数据项需要显示所属企业名,
|
||||||
|
* 此 hook 利用 Context 中已加载的企业列表进行映射。
|
||||||
|
*/
|
||||||
|
export function useCompanyName(): (companyId: string | undefined | null) => string {
|
||||||
|
const { companies } = useCompanyScope();
|
||||||
|
return (companyId: string | undefined | null) => {
|
||||||
|
if (!companyId) return "—";
|
||||||
|
return companies.find((c) => c.id === companyId)?.name ?? companyId.slice(0, 8);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user