Files
Brancheneinstufung2/general-market-intelligence/components/StepReport.tsx

234 lines
12 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;
onBack: () => void;
onStartOutreach: (company: AnalysisResult) => void;
language: Language;
}
export const StepReport: React.FC<StepReportProps> = ({ results, strategy, onRestart, onBack, 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 => {
const data = r.dynamicAnalysis[s.id];
if (!data) return '-';
let content = data.value || '-';
// Sanitize content pipes
content = content.replace(/\|/g, '\\|');
if (data.proof) {
// Sanitize proof pipes and newlines
const safeProof = data.proof.replace(/\|/g, '\\|').replace(/(\r\n|\n|\r)/gm, ' ');
content += `<br><sub>*Proof: ${safeProof}*</sub>`;
}
return content;
});
// Helper to sanitize other fields
const safe = (str: string) => (str || '').replace(/\|/g, '\\|').replace(/(\r\n|\n|\r)/gm, ' ');
return `| ${safe(r.companyName)} | ${r.tier} | ${safe(r.revenue)} / ${safe(r.employees)} | ${r.status} | ${signalValues.join(" | ")} | ${safe(r.recommendation)} |`;
});
const content = `
# Market Intelligence Report: ${strategy.productContext}
**Context:** ${strategy.idealCustomerProfile}
**Search Strategy ICP:** ${strategy.searchStrategyICP || 'N/A'}
**Digital Signals:** ${strategy.digitalSignals || 'N/A'}
**Target Pages:** ${strategy.targetPages || 'N/A'}
| ${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-xs text-slate-400 mb-1">Build Confirmation: {new Date().toLocaleString()}</p>
<p className="text-slate-500">Context: <span className="font-semibold text-indigo-600">{strategy.productContext}</span></p>
<div className="mt-2 text-xs text-slate-500 max-w-4xl">
<p><strong>Ideal Customer Profile:</strong> {strategy.idealCustomerProfile}</p>
<p><strong>Search Strategy ICP:</strong> {strategy.searchStrategyICP}</p>
<p><strong>Digital Signals:</strong> {strategy.digitalSignals}</p>
<p><strong>Target Pages:</strong> {strategy.targetPages}</p>
</div>
</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={onBack} 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>
);
};