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:
selfrelease
2026-07-20 08:22:24 +08:00
parent c3232f73c8
commit 006fd7de7e
23 changed files with 112 additions and 6 deletions
@@ -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 && (
+6 -1
View File
@@ -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>
+6 -1
View File
@@ -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"}`}>
+6 -1
View File
@@ -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>
);
}
+14
View File
@@ -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);
};
}