diff --git a/App.tsx b/App.tsx index 731f517..b768c9b 100644 --- a/App.tsx +++ b/App.tsx @@ -65,6 +65,7 @@ const App: React.FC = () => { const [showCreateExperiment, setShowCreateExperiment] = useState(false); const [preselectedAsins, setPreselectedAsins] = useState([]); const [preselectedMarketplace, setPreselectedMarketplace] = useState(''); + const [preselectedLine, setPreselectedLine] = useState(''); // Modal State const [isDataModalOpen, setIsDataModalOpen] = useState(false); @@ -340,6 +341,14 @@ const App: React.FC = () => { setView('ads'); }, []); + // Create experiment for a specific line + const handleCreateExperimentForLine = useCallback((line: string) => { + if (!line) return; + setPreselectedLine(line); + setPreselectedMarketplace(filters.customer[0] || ''); + setShowCreateExperiment(true); + }, [filters.customer]); + // 1. Initial Load from Cache (IndexedDB) or Auto-Fetch Permanent URL useEffect(() => { const initApp = async () => { @@ -1034,12 +1043,14 @@ const App: React.FC = () => { setShowCreateExperiment(false); setPreselectedAsins([]); setPreselectedMarketplace(''); + setPreselectedLine(''); }} onSuccess={() => { handleExperimentsFetch(); }} initialAsins={preselectedAsins} initialMarketplace={preselectedMarketplace} + initialLine={preselectedLine} /> )} diff --git a/components/ExperimentForm.tsx b/components/ExperimentForm.tsx index 024e40f..75d0dc4 100644 --- a/components/ExperimentForm.tsx +++ b/components/ExperimentForm.tsx @@ -7,13 +7,15 @@ interface ExperimentFormProps { onSuccess: () => void; initialAsins?: string[]; initialMarketplace?: string; + initialLine?: string; } -const ExperimentForm: React.FC = ({ - onClose, +const ExperimentForm: React.FC = ({ + onClose, onSuccess, initialAsins = [], - initialMarketplace = 'DE' + initialMarketplace = 'DE', + initialLine = '' }) => { const [formData, setFormData] = useState({ name: '', @@ -31,6 +33,42 @@ const ExperimentForm: React.FC = ({ const [loading, setLoading] = useState(false); const [asinInput, setAsinInput] = useState(''); + const [targetType, setTargetType] = useState<'asin' | 'line'>(initialAsins.length > 0 ? 'asin' : initialLine ? 'line' : 'asin'); + const [selectedLine, setSelectedLine] = useState(initialLine); + + // Get available product lines from localStorage (sales data) + const availableLines = useMemo(() => { + try { + const cachedData = localStorage.getItem('craze_sales_data'); + if (cachedData) { + const data = JSON.parse(cachedData); + const lines = new Set(data.map((r: any) => r.line).filter(Boolean)); + return Array.from(lines).sort(); + } + } catch (e) { + console.error('Failed to load product lines:', e); + } + return []; + }, []); + + const handleAddAsin = () => { + // Support both comma and space separated ASINs + const newAsins = asinInput + .split(/[\s,]+/) // Split by space or comma + .map(a => a.trim().toUpperCase()) + .filter(a => a.length > 0 && /^[A-Z0-9]{9,10}$/.test(a)); // Validate ASIN format + + const uniqueAsins = Array.from(new Set([...formData.asins, ...newAsins])); + setFormData({ ...formData, asins: uniqueAsins }); + setAsinInput(''); + }; + + const handleSelectLine = (line: string) => { + setSelectedLine(line); + // For line targeting, we don't pre-populate ASINs + // The backend will handle filtering by line + setFormData({ ...formData, asins: [`LINE:${line}`] }); + }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -62,13 +100,6 @@ const ExperimentForm: React.FC = ({ setAsinInput(''); }; - const handleRemoveAsin = (asinToRemove: string) => { - setFormData({ - ...formData, - asins: formData.asins.filter(a => a !== asinToRemove), - }); - }; - const typeOptions: { value: ExperimentType; label: string; icon: string }[] = [ { value: 'pricing', label: 'Pricing', icon: '💰' }, { value: 'advertising', label: 'Advertising', icon: '📢' }, @@ -153,51 +184,122 @@ const ExperimentForm: React.FC = ({ - {/* ASINs */} + {/* Target Type Selector */}
-