Dateien nach "competitor-analysis/components" hochladen
This commit is contained in:
131
competitor-analysis/components/Step1_Extraction.tsx
Normal file
131
competitor-analysis/components/Step1_Extraction.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { Product, TargetIndustry } from '../types';
|
||||
import { EditableCard } from './EditableCard';
|
||||
import EvidencePopover from './EvidencePopover';
|
||||
import { fetchProductDetails } from '../services/geminiService';
|
||||
|
||||
interface Step1ExtractionProps {
|
||||
products: Product[];
|
||||
industries: TargetIndustry[];
|
||||
onProductsChange: (products: Product[]) => void;
|
||||
onIndustriesChange: (industries: TargetIndustry[]) => void;
|
||||
t: any;
|
||||
lang: 'de' | 'en';
|
||||
}
|
||||
|
||||
const Step1Extraction: React.FC<Step1ExtractionProps> = ({ products, industries, onProductsChange, onIndustriesChange, t, lang }) => {
|
||||
const [newProductName, setNewProductName] = useState('');
|
||||
const [newProductUrl, setNewProductUrl] = useState('');
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [addError, setAddError] = useState<string | null>(null);
|
||||
|
||||
const handleAddProduct = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!newProductName || !newProductUrl) return;
|
||||
|
||||
setIsAdding(true);
|
||||
setAddError(null);
|
||||
try {
|
||||
const newProduct = await fetchProductDetails(newProductName, newProductUrl, lang);
|
||||
onProductsChange([...products, newProduct]);
|
||||
setNewProductName('');
|
||||
setNewProductUrl('');
|
||||
} catch (error) {
|
||||
console.error("Failed to add product:", error);
|
||||
setAddError(t.addProductError);
|
||||
} finally {
|
||||
setIsAdding(false);
|
||||
}
|
||||
};
|
||||
|
||||
const inputClasses = "w-full bg-light-primary dark:bg-brand-primary text-light-text dark:text-brand-text border border-light-accent dark:border-brand-accent rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-brand-highlight placeholder-light-subtle dark:placeholder-brand-light";
|
||||
|
||||
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="bg-light-secondary dark:bg-brand-secondary p-6 rounded-lg shadow-lg mb-6 border border-light-accent dark:border-brand-accent">
|
||||
<h3 className="text-xl font-bold mb-4">{t.addProductTitle}</h3>
|
||||
<form onSubmit={handleAddProduct} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="product_name" className="block text-sm font-medium text-light-subtle dark:text-brand-light mb-1">{t.productNameLabel}</label>
|
||||
<input
|
||||
id="product_name"
|
||||
type="text"
|
||||
value={newProductName}
|
||||
onChange={(e) => setNewProductName(e.target.value)}
|
||||
className={inputClasses}
|
||||
placeholder={t.productNamePlaceholder}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="product_url" className="block text-sm font-medium text-light-subtle dark:text-brand-light mb-1">{t.productUrlLabel}</label>
|
||||
<input
|
||||
id="product_url"
|
||||
type="url"
|
||||
value={newProductUrl}
|
||||
onChange={(e) => setNewProductUrl(e.target.value)}
|
||||
className={inputClasses}
|
||||
placeholder="https://..."
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{addError && <p className="text-red-500 text-sm">{addError}</p>}
|
||||
<div className="text-right">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isAdding}
|
||||
className="bg-brand-accent hover:bg-brand-light text-white font-bold py-2 px-4 rounded-md disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
{isAdding ? t.addingButton : t.addButton}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<EditableCard<Product>
|
||||
title={t.productsTitle}
|
||||
items={products}
|
||||
onItemsChange={onProductsChange}
|
||||
showAddButton={false}
|
||||
fieldConfigs={[
|
||||
{ key: 'name', label: t.productNameLabel, type: 'text' },
|
||||
{ key: 'purpose', label: t.purposeLabel, type: 'textarea' },
|
||||
]}
|
||||
newItemTemplate={{ name: '', purpose: '', evidence: [] }}
|
||||
renderDisplay={(item) => (
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<strong className="text-light-text dark:text-white">{item.name}</strong>
|
||||
<EvidencePopover evidence={item.evidence} />
|
||||
</div>
|
||||
<p className="text-light-subtle dark:text-brand-light text-sm mt-1">{item.purpose}</p>
|
||||
</div>
|
||||
)}
|
||||
t={t.editableCard}
|
||||
/>
|
||||
|
||||
<EditableCard<TargetIndustry>
|
||||
title={t.industriesTitle}
|
||||
items={industries}
|
||||
onItemsChange={onIndustriesChange}
|
||||
fieldConfigs={[{ key: 'name', label: t.industryNameLabel, type: 'text' }]}
|
||||
newItemTemplate={{ name: '', evidence: [] }}
|
||||
renderDisplay={(item) => (
|
||||
<div className="flex items-center">
|
||||
<strong className="text-light-text dark:text-white">{item.name}</strong>
|
||||
<EvidencePopover evidence={item.evidence} />
|
||||
</div>
|
||||
)}
|
||||
t={t.editableCard}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step1Extraction;
|
||||
Reference in New Issue
Block a user