- Integrated ICP-based lookalike sourcing. - Implemented Deep Tech Audit with automated evidence collection. - Enhanced processing terminal with real-time logs. - Refined daily logging and resolved all dependency issues. - Documented final status and next steps.
204 lines
11 KiB
TypeScript
204 lines
11 KiB
TypeScript
|
|
import React, { useMemo } from 'react';
|
|
import { LeadStatus, AnalysisResult, Language, Tier, SearchStrategy } from '../types';
|
|
import { ExternalLink, AlertCircle, CheckCircle, XCircle, RefreshCcw, ArrowLeft, Clock, Euro, Download, FileText, ShoppingBag, Star, TrendingUp, Users, Database, Send, Server, Truck, Eye, Search } from 'lucide-react';
|
|
|
|
interface StepReportProps {
|
|
results: AnalysisResult[];
|
|
strategy: SearchStrategy;
|
|
onRestart: () => void;
|
|
onStartOutreach: (company: AnalysisResult) => void;
|
|
language: Language;
|
|
}
|
|
|
|
export const StepReport: React.FC<StepReportProps> = ({ results, strategy, onRestart, onStartOutreach, language }) => {
|
|
|
|
const sortedResults = useMemo(() => {
|
|
return [...results].sort((a, b) => {
|
|
const tierOrder = { [Tier.TIER_1]: 3, [Tier.TIER_2]: 2, [Tier.TIER_3]: 1 };
|
|
const tierDiff = tierOrder[b.tier] - tierOrder[a.tier];
|
|
if (tierDiff !== 0) return tierDiff;
|
|
if (a.status === LeadStatus.POTENTIAL && b.status !== LeadStatus.POTENTIAL) return -1;
|
|
if (a.status !== LeadStatus.POTENTIAL && b.status === LeadStatus.POTENTIAL) return 1;
|
|
return 0;
|
|
});
|
|
}, [results]);
|
|
|
|
const getStatusColor = (status: LeadStatus) => {
|
|
switch (status) {
|
|
case LeadStatus.CUSTOMER: return 'bg-blue-50 text-blue-700 border-blue-200';
|
|
case LeadStatus.COMPETITOR: return 'bg-red-50 text-red-700 border-red-200';
|
|
case LeadStatus.POTENTIAL: return 'bg-emerald-50 text-emerald-700 border-emerald-200';
|
|
default: return 'bg-slate-50 text-slate-700 border-slate-200';
|
|
}
|
|
};
|
|
|
|
const getTierColor = (tier: Tier) => {
|
|
switch (tier) {
|
|
case Tier.TIER_1: return 'bg-purple-100 text-purple-800 border-purple-200';
|
|
case Tier.TIER_2: return 'bg-indigo-50 text-indigo-700 border-indigo-200';
|
|
case Tier.TIER_3: return 'bg-slate-50 text-slate-600 border-slate-200';
|
|
}
|
|
};
|
|
|
|
const downloadMarkdown = () => {
|
|
const signalHeaders = strategy.signals.map(s => s.name);
|
|
const headers = ["Company", "Prio", "Rev/Emp", "Status", ...signalHeaders, "Recommendation"];
|
|
|
|
const rows = sortedResults.map(r => {
|
|
const signalValues = strategy.signals.map(s => r.dynamicAnalysis[s.id]?.value || '-');
|
|
return `| ${r.companyName} | ${r.tier} | ${r.revenue} / ${r.employees} | ${r.status} | ${signalValues.join(" | ")} | ${r.recommendation} |`;
|
|
});
|
|
|
|
const content = `
|
|
# Market Intelligence Report: ${strategy.productContext}
|
|
**Context:** ${strategy.idealCustomerProfile}
|
|
|
|
| ${headers.join(" | ")} |
|
|
|${headers.map(() => "---").join("|")}|
|
|
${rows.join("\n")}
|
|
`;
|
|
|
|
const blob = new Blob([content], { type: 'text/markdown' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `report_${new Date().toISOString().slice(0,10)}.md`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
return (
|
|
<div className="h-[calc(100vh-64px)] flex flex-col max-w-7xl mx-auto px-4 py-6">
|
|
<div className="flex-none flex flex-col sm:flex-row sm:items-center justify-between mb-6 gap-4">
|
|
<div>
|
|
<h2 className="text-3xl font-bold text-slate-900">Analysis Report</h2>
|
|
<p className="text-slate-500">Context: <span className="font-semibold text-indigo-600">{strategy.productContext}</span></p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button onClick={downloadMarkdown} className="bg-white border border-slate-300 hover:bg-slate-50 text-slate-700 font-medium py-2 px-4 rounded-lg flex items-center gap-2 text-sm shadow-sm">
|
|
<FileText size={16} /> Export MD
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden flex flex-col min-h-0">
|
|
<div className="overflow-auto flex-1 w-full relative">
|
|
<table className="w-full text-left border-collapse relative">
|
|
<thead className="sticky top-0 z-10 bg-slate-50 shadow-sm border-b border-slate-200">
|
|
<tr>
|
|
<th className="px-6 py-4 font-semibold text-slate-500 text-xs uppercase tracking-wider w-[25%] bg-slate-50">Company Profile</th>
|
|
<th className="px-6 py-4 font-semibold text-slate-500 text-xs uppercase tracking-wider w-[65%] bg-slate-50">Strategic Intelligence & Audit</th>
|
|
<th className="px-6 py-4 font-semibold text-slate-500 text-xs uppercase tracking-wider w-[10%] bg-slate-50 text-center">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{sortedResults.map((row, idx) => (
|
|
<tr key={idx} className="hover:bg-slate-50/80 transition-colors align-top">
|
|
|
|
{/* Column 1: Firmographics */}
|
|
<td className="px-6 py-6">
|
|
<div className="flex flex-col gap-3">
|
|
<div>
|
|
<div className="font-bold text-slate-900 text-lg leading-tight">{row.companyName}</div>
|
|
|
|
<div className="flex flex-wrap gap-2 mt-2">
|
|
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold border uppercase tracking-wide ${getTierColor(row.tier)}`}>
|
|
{row.tier}
|
|
</span>
|
|
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${getStatusColor(row.status)}`}>
|
|
{row.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-sm text-slate-600 space-y-1 pt-2 border-t border-slate-100">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-slate-400 text-xs uppercase">Revenue</span>
|
|
<span className="font-medium text-slate-800">{row.revenue}</span>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-slate-400 text-xs uppercase">Employees</span>
|
|
<span className="font-medium text-slate-800">{row.employees}</span>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-slate-400 text-xs uppercase">Source</span>
|
|
<span className="text-xs text-slate-500 truncate max-w-[100px]" title={row.dataSource}>{row.dataSource}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
|
|
{/* Column 2: Intelligence (Stacked) */}
|
|
<td className="px-6 py-6">
|
|
<div className="space-y-5">
|
|
{/* Recommendation Block */}
|
|
<div className="bg-indigo-50/50 border border-indigo-100 rounded-lg p-3 relative">
|
|
<div className="absolute -top-2.5 left-3 bg-white px-1 text-[10px] font-bold text-indigo-600 uppercase tracking-wide border border-indigo-100 rounded">
|
|
AI Recommendation
|
|
</div>
|
|
<p className="text-sm text-slate-800 leading-relaxed italic mt-1">
|
|
"{row.recommendation}"
|
|
</p>
|
|
</div>
|
|
|
|
{/* Dynamic Signals Stacked */}
|
|
<div className="grid gap-4">
|
|
{strategy.signals.map((s, i) => {
|
|
const data = row.dynamicAnalysis && row.dynamicAnalysis[s.id];
|
|
return (
|
|
<div key={s.id} className="group">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<div className="w-5 h-5 rounded-full bg-slate-100 text-slate-500 flex items-center justify-center text-[10px] font-bold">
|
|
{i + 1}
|
|
</div>
|
|
<span className="text-xs font-bold text-slate-500 uppercase tracking-wide">{s.name}</span>
|
|
</div>
|
|
<div className="pl-7">
|
|
<div className={`text-sm font-medium leading-snug ${data ? 'text-slate-900' : 'text-slate-400 italic'}`}>
|
|
{data ? data.value : "Nicht geprüft / N/A"}
|
|
</div>
|
|
{data && data.proof && (
|
|
<div className="mt-1.5 inline-flex items-center gap-1.5 text-xs text-slate-500 bg-slate-50 px-2 py-1 rounded border border-slate-100 max-w-full">
|
|
<Search size={10} />
|
|
<span className="truncate max-w-[400px]">Evidence: {data.proof}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
|
|
{/* Column 3: Action */}
|
|
<td className="px-6 py-6 text-center align-middle">
|
|
<button
|
|
onClick={() => onStartOutreach(row)}
|
|
className="bg-indigo-600 hover:bg-indigo-700 text-white p-3 rounded-xl transition-all shadow-sm hover:shadow-lg active:scale-95 flex flex-col items-center justify-center gap-1 w-full group"
|
|
title="Generate Outreach"
|
|
>
|
|
<Send size={20} className="group-hover:-translate-y-0.5 transition-transform" />
|
|
<span className="text-[10px] font-medium opacity-80">Outreach</span>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-none flex justify-between items-center pt-6 bg-slate-50 border-t border-slate-200 mt-auto sticky bottom-0 z-20">
|
|
<button onClick={onRestart} className="text-slate-600 hover:text-indigo-600 font-medium flex items-center gap-2 px-4 py-2">
|
|
<ArrowLeft size={20} /> Back
|
|
</button>
|
|
<button onClick={onRestart} className="bg-slate-900 hover:bg-slate-800 text-white font-bold py-3 px-6 rounded-xl shadow-lg transition-all flex items-center gap-2">
|
|
<RefreshCcw size={18} /> New Search
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|