Files
Brancheneinstufung2/competitor-analysis-app/components/Step4_Analysis.tsx

179 lines
10 KiB
TypeScript

import React, { useState } from 'react';
import type { Analysis, AppState } from '../types';
import EvidencePopover from './EvidencePopover';
import { reanalyzeCompetitor } from '../services/geminiService';
interface Step4AnalysisProps {
analyses: Analysis[];
company: AppState['company'];
onAnalysisUpdate: (index: number, analysis: Analysis) => void;
t: any;
}
const DownloadIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>);
const RefreshIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>);
const downloadJSON = (data: any, filename: string) => {
const jsonStr = JSON.stringify(data, null, 2);
const blob = new Blob([jsonStr], { type: "application/json" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const OverlapBar: React.FC<{ score: number }> = ({ score }) => (
<div className="w-full bg-light-accent dark:bg-brand-accent rounded-full h-2.5">
<div className="bg-brand-highlight h-2.5 rounded-full" style={{ width: `${score}%` }}></div>
</div>
);
const Step4Analysis: React.FC<Step4AnalysisProps> = ({ analyses, company, onAnalysisUpdate, t }) => {
const sortedAnalyses = [...analyses].sort((a, b) => b.overlap_score - a.overlap_score);
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const [manualUrls, setManualUrls] = useState<string>("");
const [isReanalyzing, setIsReanalyzing] = useState<boolean>(false);
const handleEditStart = (index: number) => {
setEditingIndex(index);
setManualUrls(""); // Reset
};
const handleReanalyze = async (index: number, competitor: Analysis['competitor']) => {
setIsReanalyzing(true);
try {
const urls = manualUrls.split('\n').map(u => u.trim()).filter(u => u);
// Construct a partial CompetitorCandidate object as expected by the service
const candidate = {
name: competitor.name,
url: competitor.url,
confidence: 0, // Not needed for re-analysis
why: "",
evidence: []
};
const updatedAnalysis = await reanalyzeCompetitor(company, candidate, urls);
// Find the original index in the unsorted 'analyses' array to update correctly
const originalIndex = analyses.findIndex(a => a.competitor.name === competitor.name);
if (originalIndex !== -1) {
onAnalysisUpdate(originalIndex, updatedAnalysis);
}
setEditingIndex(null);
} catch (error) {
console.error("Re-analysis failed:", error);
alert("Fehler bei der Re-Analyse. Bitte Logs prüfen.");
} finally {
setIsReanalyzing(false);
}
};
return (
<div>
<h2 className="text-2xl font-bold mb-4">{t.title}</h2>
<p className="text-light-subtle dark:text-brand-light mb-6">
{t.subtitle}
</p>
<div className="space-y-6">
{sortedAnalyses.map((analysis, index) => (
<div key={index} className="bg-light-secondary dark:bg-brand-secondary p-6 rounded-lg shadow-lg">
<div className="flex justify-between items-start mb-4">
<div>
<h3 className="text-xl font-bold text-light-text dark:text-white">{analysis.competitor.name}</h3>
<a href={analysis.competitor.url} target="_blank" rel="noopener noreferrer" className="text-blue-500 dark:text-blue-400 hover:underline text-sm">
{analysis.competitor.url}
</a>
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => handleEditStart(index)}
title="Daten anreichern / Korrigieren"
className="text-light-subtle dark:text-brand-light hover:text-blue-500 p-1 rounded-full"
>
<RefreshIcon />
</button>
<button
onClick={() => downloadJSON(analysis, `analysis_${analysis.competitor.name.replace(/ /g, '_')}.json`)}
title={t.downloadJson_title}
className="text-light-subtle dark:text-brand-light hover:text-light-text dark:hover:text-white p-1 rounded-full"
>
<DownloadIcon />
</button>
<EvidencePopover evidence={analysis.evidence} />
</div>
</div>
{/* Re-Analysis UI */}
{editingIndex === index && (
<div className="mb-6 bg-light-primary dark:bg-brand-primary p-4 rounded-md border border-brand-highlight">
<h4 className="font-bold text-sm mb-2">Manuelle Produkt-URLs ergänzen (optional)</h4>
<p className="text-xs text-light-subtle dark:text-brand-light mb-2">
Falls Produkte fehlen, fügen Sie hier direkte Links zu den Produktseiten ein (eine pro Zeile).
</p>
<textarea
className="w-full bg-light-secondary dark:bg-brand-secondary text-light-text dark:text-brand-text border border-light-accent dark:border-brand-accent rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-highlight mb-2"
rows={3}
placeholder="https://example.com/product-a&#10;https://example.com/product-b"
value={manualUrls}
onChange={(e) => setManualUrls(e.target.value)}
/>
<div className="flex justify-end space-x-2">
<button
onClick={() => setEditingIndex(null)}
className="px-3 py-1 text-sm bg-gray-500 hover:bg-gray-600 text-white rounded"
disabled={isReanalyzing}
>
Abbrechen
</button>
<button
onClick={() => handleReanalyze(index, analysis.competitor)}
className="px-3 py-1 text-sm bg-brand-highlight hover:bg-blue-600 text-white rounded flex items-center"
disabled={isReanalyzing}
>
{isReanalyzing ? "Analysiere..." : "Neu scrapen & Analysieren"}
</button>
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-semibold mb-2">{t.portfolio}</h4>
<ul className="list-disc list-inside text-light-subtle dark:text-brand-light text-sm space-y-1">
{analysis.portfolio.map((p, i) => <li key={i}><strong>{p.product}:</strong> {p.purpose}</li>)}
</ul>
</div>
<div>
<h4 className="font-semibold mb-2">{t.differentiators}</h4>
<ul className="list-disc list-inside text-light-subtle dark:text-brand-light text-sm space-y-1">
{analysis.differentiators.map((d, i) => <li key={i}>{d}</li>)}
</ul>
</div>
<div>
<h4 className="font-semibold mb-2">{t.targetIndustries}</h4>
<div className="flex flex-wrap gap-2 mb-2">
{analysis.target_industries.map((ind, i) => (
<span key={i} className="bg-light-accent dark:bg-brand-accent text-xs font-medium px-2.5 py-0.5 rounded-full">{ind}</span>
))}
</div>
<span className="bg-gray-500 dark:bg-gray-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{analysis.delivery_model}</span>
</div>
<div>
<h4 className="font-semibold mb-2">{t.overlapScore}: {analysis.overlap_score}%</h4>
<OverlapBar score={analysis.overlap_score} />
</div>
</div>
</div>
))}
</div>
</div>
);
};
export default Step4Analysis;