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 { 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 && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"}`}>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user