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 { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Sparkles } from "lucide-react";
import { listAARs, generateAAR } from "@/lib/api-v2";
import { PageContainer, Card} from "@/components/shared/PageContainer";
@@ -11,6 +12,7 @@ import { toast } from "sonner";
/** AAR 复盘记录项。 */
interface AARItem {
company_id?: string;
trigger_event: string;
gap_analysis?: string;
lessons?: string[];
@@ -85,6 +87,7 @@ export default function AARsPage() {
<div className="space-y-3">
{items.map((item, i) => (
<Card key={i}>
<CompanyNameTag companyId={item.company_id} />
<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>}
{Array.isArray(item.lessons) && (
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { listAgentExecutions, reviewAgentExecution } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -11,6 +12,7 @@ import { toast } from "sonner";
/** Agent 执行记录项。 */
interface AgentExecution {
id: string;
company_id?: string;
agent_name: string;
autonomy_level: string;
output_summary: string;
@@ -52,6 +54,7 @@ export default function AgentsPage() {
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<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"></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">
{items.map((item, 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"><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>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Plus } from "lucide-react";
import { listAgreements } from "@/lib/api-v2";
import { PageContainer, Badge, TableEmpty } from "@/components/shared/PageContainer";
@@ -10,6 +11,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
/** 投资协议项。 */
interface Agreement {
company_id?: string;
title: string;
signed_at: string;
status: string;
@@ -46,6 +48,7 @@ export default function AgreementsPage() {
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<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>
@@ -54,10 +57,11 @@ export default function AgreementsPage() {
</thead>
<tbody className="divide-y divide-gray-100 bg-white">
{items.length === 0 ? (
<TableEmpty colSpan={4} />
<TableEmpty colSpan={5} />
) : (
items.map((item, 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-600">{item.signed_at}</td>
<td className="px-4 py-2">
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Plus } from "lucide-react";
import { listInterventions } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -10,6 +11,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
/** 干预记录项。 */
interface Intervention {
company_id?: string;
intervention_type: string;
title: string;
executed_at: string;
@@ -48,6 +50,7 @@ export default function AlphaPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge color="blue">{item.intervention_type}</Badge>
<CompanyNameTag companyId={item.company_id} />
<h3 className="font-medium text-gray-900">{item.title}</h3>
</div>
<span className="text-xs text-gray-400">{item.executed_at}</span>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { listBoardMeetings } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
/** 董事会会议项。 */
interface BoardMeeting {
company_id?: string;
title: string;
meeting_at: string;
status: string;
@@ -37,6 +39,7 @@ export default function BoardPage() {
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<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>
@@ -46,6 +49,7 @@ export default function BoardPage() {
<tbody className="divide-y divide-gray-100 bg-white">
{items.map((item, 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-600">{item.meeting_at}</td>
<td className="px-4 py-2">
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -191,7 +192,7 @@ export default function CustomerSuccessPage() {
{churnRisks.map((risk, i) => (
<div key={i} className="flex items-center justify-between rounded-md border border-[var(--border)] px-3 py-2">
<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>}
</div>
<span className={`rounded px-2 py-0.5 text-xs ${
@@ -3,12 +3,14 @@
import { useState } from "react";
import { listMajorEvents, listInquiries } from "@/lib/api-v2";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
/** 重大事项项。 */
interface MajorEvent {
company_id?: string;
event_type: string;
title: string;
severity: string;
@@ -17,6 +19,7 @@ interface MajorEvent {
/** 追问清单项。 */
interface Inquiry {
company_id?: string;
status: string;
questions?: unknown[];
}
@@ -69,6 +72,7 @@ export default function EventsPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge color="blue">{item.event_type}</Badge>
<CompanyNameTag companyId={item.company_id} />
<h3 className="font-medium text-gray-900">{item.title}</h3>
</div>
<Badge color={item.severity === "critical" ? "red" : item.severity === "high" ? "amber" : "gray"}>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Sparkles } from "lucide-react";
import { listExitPredictions, predictExit } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -11,6 +12,7 @@ import { toast } from "sonner";
/** 退出预测项。 */
interface ExitPrediction {
company_id?: string;
exit_path?: string;
expected_return?: string;
confidence?: number;
@@ -80,6 +82,7 @@ export default function ExitSignalsPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{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>
</div>
{item.confidence != null && (
+6 -1
View File
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -12,6 +13,7 @@ import { Lightbulb, Sparkles, Check, X, TrendingUp, BarChart3 } from "lucide-rea
/** 助推记录项。 */
interface NudgeItem {
id: string;
company_id?: string;
nudge_type: string;
message: string;
accepted: boolean;
@@ -117,7 +119,10 @@ export default function NudgesPage() {
<div className="flex items-center gap-3">
<Lightbulb size={16} className="text-amber-500" />
<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>
</div>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Plus } from "lucide-react";
import { listOKRs } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -17,6 +18,7 @@ interface KeyResult {
/** OKR 项。 */
interface OKRItem {
company_id?: string;
quarter: string;
objective: string;
alignment_score?: number;
@@ -56,6 +58,7 @@ export default function OKRsPage() {
<div className="flex items-center justify-between">
<div>
<Badge color="blue">{item.quarter}</Badge>
<CompanyNameTag companyId={item.company_id} />
<h3 className="mt-1 font-medium text-gray-900">{item.objective}</h3>
</div>
<div className="flex items-center gap-2">
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -12,6 +13,7 @@ import { Users, Sparkles, Circle } from "lucide-react";
/** Circle 项。 */
interface CircleItem {
id: string;
company_id?: string;
topic: string;
status: string;
description?: string;
@@ -138,6 +140,7 @@ export default function PeerCirclesPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Circle size={16} className="text-[var(--investor-primary)]" />
<CompanyNameTag companyId={c.company_id} />
<h3 className="text-sm font-medium">{c.topic}</h3>
</div>
<span className={`rounded px-2 py-0.5 text-xs ${
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Sparkles } from "lucide-react";
import { listPreMortems, runPreMortem, listRedTeams, runRedTeam } from "@/lib/api-v2";
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
@@ -11,6 +12,7 @@ import { toast } from "sonner";
/** Pre-mortem 记录项。 */
interface PreMortemItem {
company_id?: string;
decision_context: string;
failure_paths?: FailurePath[];
}
@@ -22,6 +24,7 @@ interface FailurePath {
/** Red Team 记录项。 */
interface RedTeamItem {
company_id?: string;
perspective: string;
analysis: string;
}
@@ -118,6 +121,7 @@ export default function PreMortemsPage() {
<div className="space-y-3">
{preMortems.map((item, i) => (
<Card key={i}>
<CompanyNameTag companyId={item.company_id} />
<h3 className="font-medium text-gray-900">{item.decision_context as string}</h3>
{Array.isArray(item.failure_paths) && (
<div className="mt-2 space-y-1">
@@ -136,6 +140,7 @@ export default function PreMortemsPage() {
{redTeams.map((item, i) => (
<Card key={i}>
<div className="flex items-center gap-2">
<CompanyNameTag companyId={item.company_id} />
<Badge color="red">{item.perspective as string}</Badge>
</div>
<p className="mt-2 text-sm text-gray-600">{item.analysis as string}</p>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -24,6 +25,7 @@ interface DiagnosticResult {
/** 历史诊断项。 */
interface DiagnosticHistory {
id: string;
company_id?: string;
product_name?: string;
dimensions?: DiagnosticDimension[];
}
@@ -159,7 +161,10 @@ export default function ProductDiagnosticsPage() {
<div className="space-y-2">
{diagnostics.map((d) => (
<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) && (
<div className="mt-1 flex flex-wrap gap-1">
{d.dimensions.map((dim, i: number) => (
@@ -4,6 +4,7 @@ import { useState, useCallback } from "react";
import {Plus, Trash2, Send } from "lucide-react";
import { listReports, submitReport, deleteReport, STATUS_LABELS, STATUS_COLORS, type MonthlyReport } from "@/lib/reports";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -80,6 +81,7 @@ export default function ReportsPage() {
<table className="w-full text-sm">
<thead className="border-b bg-muted/50">
<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>
@@ -90,6 +92,7 @@ export default function ReportsPage() {
<tbody className="divide-y">
{reports.map((report) => (
<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">
{report.period_year}{report.period_month}
</td>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { listDecisionSentinels } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
/** 决策前哨项。 */
interface DecisionSentinel {
company_id?: string;
decision_type: string;
title: string;
status: string;
@@ -39,6 +41,7 @@ export default function SentinelsPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge color="blue">{item.decision_type}</Badge>
<CompanyNameTag companyId={item.company_id} />
<h3 className="font-medium text-gray-900">{item.title}</h3>
</div>
<Badge color={item.status === "acted" ? "green" : item.status === "analyzed" ? "amber" : "gray"}>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { listSynergies, authorizeSynergy } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -11,6 +12,7 @@ import { toast } from "sonner";
/** 协同机会项。 */
interface Synergy {
id: string;
company_id?: string;
type: string;
title: string;
status: string;
@@ -63,6 +65,7 @@ export default function SynergiesPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<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>
</div>
<div className="flex items-center gap-2">
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { listTalents } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
/** 人才项。 */
interface Talent {
company_id?: string;
name: string;
current_role: string;
performance_rating?: string;
@@ -39,6 +41,7 @@ export default function TalentsPage() {
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<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>
@@ -50,6 +53,7 @@ export default function TalentsPage() {
<tbody className="divide-y divide-gray-100 bg-white">
{items.map((item, 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-600">{item.current_role}</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 { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import {Plus } from "lucide-react";
import { listTasks, updateTask } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
@@ -12,6 +13,7 @@ import { toast } from "sonner";
/** 任务项。 */
interface TaskItem {
id: string;
company_id?: string;
title: string;
status: string;
priority: string;
@@ -80,7 +82,10 @@ export default function TasksPage() {
{items.filter((i) => i.status === col).map((item, i) => (
<div key={i} className="rounded-md border border-gray-200 bg-white p-3">
<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"}>
{item.priority}
</Badge>
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
import { listWeakSignals } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -9,6 +10,7 @@ import { EmptyState } from "@/components/shared/EmptyState";
/** 弱信号项。 */
interface WeakSignal {
company_id?: string;
signal_type: string;
content: string;
confidence: number;
@@ -45,6 +47,7 @@ export default function WeakSignalsPage() {
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<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>
@@ -55,6 +58,7 @@ export default function WeakSignalsPage() {
<tbody className="divide-y divide-gray-100 bg-white">
{items.map((item, i) => (
<tr key={i}>
<td className="px-4 py-2"><CompanyNameTag companyId={item.company_id} /></td>
<td className="px-4 py-2">
<Badge color="blue">{SIGNAL_TYPE_LABELS[item.signal_type] ?? item.signal_type}</Badge>
</td>
@@ -1,6 +1,7 @@
"use client";
import { Target, ArrowRight, DollarSign, Users, Trophy } from "lucide-react";
import { CompanyNameTag } from "@/components/shared/CompanyNameTag";
/** 决策链节点。 */
interface DecisionChainNode {
@@ -16,6 +17,7 @@ interface CompetitiveAnalysis {
/** 客户获取方案。 */
interface CustomerPlanData {
company_id?: string;
target_customer?: string;
execution_status?: 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-center gap-2">
<Target size={18} className="text-[var(--investor-primary)]" />
<CompanyNameTag companyId={plan.company_id} />
<h3 className="text-sm font-medium">{plan.target_customer || "未指定目标客户"}</h3>
</div>
<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";
import { AlertTriangle, Trash2 } from "lucide-react";
import { AlertTriangle, Trash2, Building2 } from "lucide-react";
import {
RISK_STATUS_LABELS,
RISK_STATUS_COLORS,
@@ -9,6 +9,7 @@ import {
RISK_TYPE_LABELS,
type RiskEvent,
} from "@/lib/risks";
import { useCompanyName } from "@/lib/company-scope";
/** 风险卡片组件 — 从风险工作台抽取的独立组件。 */
@@ -22,6 +23,7 @@ interface RiskCardProps {
* 风险事件卡片。
*/
export function RiskCard({ risk, onStatusChange, onDelete }: RiskCardProps) {
const getCompanyName = useCompanyName();
return (
<div className="rounded-lg border bg-white p-4 shadow-sm">
<div className="flex items-start justify-between">
@@ -38,6 +40,9 @@ export function RiskCard({ risk, onStatusChange, onDelete }: RiskCardProps) {
</span>
</div>
<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 className={SEVERITY_COLORS[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]);
}
/**
* 根据企业 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);
};
}