mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:45:24 +02:00
feat(experiments): add edit experiment functionality
This commit is contained in:
@@ -39,7 +39,7 @@ import {
|
|||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
// ============ Sub-view type ============
|
// ============ Sub-view type ============
|
||||||
type SubView = 'list' | 'detail' | 'create';
|
type SubView = 'list' | 'detail' | 'create' | 'edit';
|
||||||
|
|
||||||
// ============ Constants ============
|
// ============ Constants ============
|
||||||
const STATUS_OPTIONS = ['planned', 'active', 'completed', 'paused'];
|
const STATUS_OPTIONS = ['planned', 'active', 'completed', 'paused'];
|
||||||
@@ -71,15 +71,21 @@ const ExperimentsView: React.FC<ExperimentsViewProps> = ({ salesData = [], onExp
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const navigateToCreate = useCallback(() => {
|
const navigateToCreate = useCallback(() => {
|
||||||
|
setSelectedId(null);
|
||||||
setSubView('create');
|
setSubView('create');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const navigateToEdit = useCallback((id: string) => {
|
||||||
|
setSelectedId(id);
|
||||||
|
setSubView('edit');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const navigateToList = useCallback(() => {
|
const navigateToList = useCallback(() => {
|
||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
setSubView('list');
|
setSubView('list');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCreated = useCallback((id: string) => {
|
const handleSaved = useCallback((id: string) => {
|
||||||
setSelectedId(id);
|
setSelectedId(id);
|
||||||
setSubView('detail');
|
setSubView('detail');
|
||||||
onExperimentsFetch?.();
|
onExperimentsFetch?.();
|
||||||
@@ -99,14 +105,23 @@ const ExperimentsView: React.FC<ExperimentsViewProps> = ({ salesData = [], onExp
|
|||||||
experimentId={selectedId}
|
experimentId={selectedId}
|
||||||
salesData={salesData}
|
salesData={salesData}
|
||||||
onBack={navigateToList}
|
onBack={navigateToList}
|
||||||
|
onEdit={() => navigateToEdit(selectedId)}
|
||||||
onExperimentsFetch={onExperimentsFetch}
|
onExperimentsFetch={onExperimentsFetch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{subView === 'create' && (
|
{subView === 'create' && (
|
||||||
<ExperimentCreateView
|
<ExperimentFormView
|
||||||
salesData={salesData}
|
salesData={salesData}
|
||||||
onBack={navigateToList}
|
onBack={navigateToList}
|
||||||
onCreated={handleCreated}
|
onSaved={handleSaved}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{subView === 'edit' && selectedId && (
|
||||||
|
<ExperimentEditWrapper
|
||||||
|
experimentId={selectedId}
|
||||||
|
salesData={salesData}
|
||||||
|
onBack={() => navigateToDetail(selectedId)}
|
||||||
|
onSaved={handleSaved}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -355,8 +370,9 @@ const ExperimentDetailView: React.FC<{
|
|||||||
experimentId: string;
|
experimentId: string;
|
||||||
salesData: CombinedKPIs[];
|
salesData: CombinedKPIs[];
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
onEdit: () => void;
|
||||||
onExperimentsFetch?: () => void;
|
onExperimentsFetch?: () => void;
|
||||||
}> = ({ experimentId, salesData, onBack, onExperimentsFetch }) => {
|
}> = ({ experimentId, salesData, onBack, onEdit, onExperimentsFetch }) => {
|
||||||
const [experiment, setExperiment] = useState<Experiment | null>(null);
|
const [experiment, setExperiment] = useState<Experiment | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [analyzing, setAnalyzing] = useState(false);
|
const [analyzing, setAnalyzing] = useState(false);
|
||||||
@@ -505,6 +521,15 @@ const ExperimentDetailView: React.FC<{
|
|||||||
)}
|
)}
|
||||||
Run Analysis
|
Run Analysis
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onEdit}
|
||||||
|
className="p-2 text-slate-400 hover:text-indigo-400 hover:bg-indigo-500/10 rounded-lg transition-colors"
|
||||||
|
title="Edit experiment"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="p-2 text-slate-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors"
|
className="p-2 text-slate-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors"
|
||||||
@@ -841,29 +866,32 @@ const ExperimentDetailView: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// CREATE VIEW
|
// FORM VIEW
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
|
|
||||||
const ExperimentCreateView: React.FC<{
|
const ExperimentFormView: React.FC<{
|
||||||
salesData: CombinedKPIs[];
|
salesData: CombinedKPIs[];
|
||||||
|
initialData?: Experiment;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onCreated: (id: string) => void;
|
onSaved: (id: string) => void;
|
||||||
}> = ({ salesData, onBack, onCreated }) => {
|
}> = ({ salesData, initialData, onBack, onSaved }) => {
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState(initialData?.name || '');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState(initialData?.description || '');
|
||||||
const [type, setType] = useState<ExperimentType>('content');
|
const [type, setType] = useState<ExperimentType>(initialData?.type || 'content');
|
||||||
const [marketplace, setMarketplace] = useState('DE');
|
const [marketplace, setMarketplace] = useState(initialData?.marketplace || 'DE');
|
||||||
const [owner, setOwner] = useState('');
|
const [owner, setOwner] = useState(initialData?.owner || '');
|
||||||
const [hypothesis, setHypothesis] = useState('');
|
const [hypothesis, setHypothesis] = useState(initialData?.hypothesis || '');
|
||||||
const [primaryMetric, setPrimaryMetric] = useState<ExperimentMetric>('units');
|
const [primaryMetric, setPrimaryMetric] = useState<ExperimentMetric>(initialData?.primary_metric || 'units');
|
||||||
const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
|
const [startDate, setStartDate] = useState(initialData?.start_date?.split('T')[0] || new Date().toISOString().split('T')[0]);
|
||||||
const [endDate, setEndDate] = useState('');
|
const [endDate, setEndDate] = useState(initialData?.end_date?.split('T')[0] || '');
|
||||||
const [enableCustomBaseline, setEnableCustomBaseline] = useState(false);
|
const [enableCustomBaseline, setEnableCustomBaseline] = useState(!!(initialData?.baseline_start_date && initialData?.baseline_end_date));
|
||||||
const [baselineStartDate, setBaselineStartDate] = useState('');
|
const [baselineStartDate, setBaselineStartDate] = useState(initialData?.baseline_start_date?.split('T')[0] || '');
|
||||||
const [baselineEndDate, setBaselineEndDate] = useState('');
|
const [baselineEndDate, setBaselineEndDate] = useState(initialData?.baseline_end_date?.split('T')[0] || '');
|
||||||
const [treatmentAsins, setTreatmentAsins] = useState('');
|
const [treatmentAsins, setTreatmentAsins] = useState(initialData?.asins?.join(', ') || '');
|
||||||
const [controlAsins, setControlAsins] = useState('');
|
const [controlAsins, setControlAsins] = useState(initialData?.control_asins?.join(', ') || '');
|
||||||
const [changes, setChanges] = useState<ExperimentChangeAnnotation[]>([{ field: '', before_value: '', after_value: '' }]);
|
const [changes, setChanges] = useState<ExperimentChangeAnnotation[]>(
|
||||||
|
initialData?.changes?.length ? initialData.changes : [{ field: '', before_value: '', after_value: '' }]
|
||||||
|
);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
@@ -913,8 +941,13 @@ const ExperimentCreateView: React.FC<{
|
|||||||
changes: validChanges,
|
changes: validChanges,
|
||||||
owner: owner.trim() || undefined,
|
owner: owner.trim() || undefined,
|
||||||
};
|
};
|
||||||
|
if (initialData) {
|
||||||
|
const updated = await updateExperiment(initialData.id, input);
|
||||||
|
onSaved(updated.id);
|
||||||
|
} else {
|
||||||
const created = await createExperiment(input);
|
const created = await createExperiment(input);
|
||||||
onCreated(created.id);
|
onSaved(created.id);
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -931,7 +964,9 @@ const ExperimentCreateView: React.FC<{
|
|||||||
Back to experiments
|
Back to experiments
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h1 className="text-2xl font-bold text-white tracking-tight mb-8">New Experiment</h1>
|
<h1 className="text-2xl font-bold text-white tracking-tight mb-8">
|
||||||
|
{initialData ? "Edit Experiment" : "New Experiment"}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div className="max-w-3xl space-y-8">
|
<div className="max-w-3xl space-y-8">
|
||||||
{/* Basic Info */}
|
{/* Basic Info */}
|
||||||
@@ -1136,7 +1171,7 @@ const ExperimentCreateView: React.FC<{
|
|||||||
className="px-6 py-2.5 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white rounded-lg font-medium text-sm transition-colors flex items-center gap-2"
|
className="px-6 py-2.5 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white rounded-lg font-medium text-sm transition-colors flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{submitting && <div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent" />}
|
{submitting && <div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent" />}
|
||||||
Create Experiment
|
{initialData ? "Save Changes" : "Create Experiment"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={onBack} className="px-4 py-2.5 text-slate-400 hover:text-white text-sm transition-colors">
|
<button onClick={onBack} className="px-4 py-2.5 text-slate-400 hover:text-white text-sm transition-colors">
|
||||||
Cancel
|
Cancel
|
||||||
@@ -1147,6 +1182,23 @@ const ExperimentCreateView: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ExperimentEditWrapper: React.FC<{
|
||||||
|
experimentId: string;
|
||||||
|
salesData: CombinedKPIs[];
|
||||||
|
onBack: () => void;
|
||||||
|
onSaved: (id: string) => void;
|
||||||
|
}> = ({ experimentId, salesData, onBack, onSaved }) => {
|
||||||
|
const [experiment, setExperiment] = useState<Experiment | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getExperiment(experimentId).then(setExperiment);
|
||||||
|
}, [experimentId]);
|
||||||
|
|
||||||
|
if (!experiment) return <div className="flex items-center justify-center p-12 text-slate-400"><div className="animate-spin rounded-full h-8 w-8 border-2 border-indigo-500 border-t-transparent mr-4" /> Loading experiment data...</div>;
|
||||||
|
|
||||||
|
return <ExperimentFormView salesData={salesData} initialData={experiment} onBack={onBack} onSaved={onSaved} />;
|
||||||
|
};
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// SHARED UI COMPONENTS
|
// SHARED UI COMPONENTS
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user