mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
feat(experiments): add edit experiment functionality
This commit is contained in:
@@ -39,7 +39,7 @@ import {
|
||||
} from 'recharts';
|
||||
|
||||
// ============ Sub-view type ============
|
||||
type SubView = 'list' | 'detail' | 'create';
|
||||
type SubView = 'list' | 'detail' | 'create' | 'edit';
|
||||
|
||||
// ============ Constants ============
|
||||
const STATUS_OPTIONS = ['planned', 'active', 'completed', 'paused'];
|
||||
@@ -71,15 +71,21 @@ const ExperimentsView: React.FC<ExperimentsViewProps> = ({ salesData = [], onExp
|
||||
}, []);
|
||||
|
||||
const navigateToCreate = useCallback(() => {
|
||||
setSelectedId(null);
|
||||
setSubView('create');
|
||||
}, []);
|
||||
|
||||
const navigateToEdit = useCallback((id: string) => {
|
||||
setSelectedId(id);
|
||||
setSubView('edit');
|
||||
}, []);
|
||||
|
||||
const navigateToList = useCallback(() => {
|
||||
setSelectedId(null);
|
||||
setSubView('list');
|
||||
}, []);
|
||||
|
||||
const handleCreated = useCallback((id: string) => {
|
||||
const handleSaved = useCallback((id: string) => {
|
||||
setSelectedId(id);
|
||||
setSubView('detail');
|
||||
onExperimentsFetch?.();
|
||||
@@ -99,14 +105,23 @@ const ExperimentsView: React.FC<ExperimentsViewProps> = ({ salesData = [], onExp
|
||||
experimentId={selectedId}
|
||||
salesData={salesData}
|
||||
onBack={navigateToList}
|
||||
onEdit={() => navigateToEdit(selectedId)}
|
||||
onExperimentsFetch={onExperimentsFetch}
|
||||
/>
|
||||
)}
|
||||
{subView === 'create' && (
|
||||
<ExperimentCreateView
|
||||
<ExperimentFormView
|
||||
salesData={salesData}
|
||||
onBack={navigateToList}
|
||||
onCreated={handleCreated}
|
||||
onSaved={handleSaved}
|
||||
/>
|
||||
)}
|
||||
{subView === 'edit' && selectedId && (
|
||||
<ExperimentEditWrapper
|
||||
experimentId={selectedId}
|
||||
salesData={salesData}
|
||||
onBack={() => navigateToDetail(selectedId)}
|
||||
onSaved={handleSaved}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -355,8 +370,9 @@ const ExperimentDetailView: React.FC<{
|
||||
experimentId: string;
|
||||
salesData: CombinedKPIs[];
|
||||
onBack: () => void;
|
||||
onEdit: () => void;
|
||||
onExperimentsFetch?: () => void;
|
||||
}> = ({ experimentId, salesData, onBack, onExperimentsFetch }) => {
|
||||
}> = ({ experimentId, salesData, onBack, onEdit, onExperimentsFetch }) => {
|
||||
const [experiment, setExperiment] = useState<Experiment | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [analyzing, setAnalyzing] = useState(false);
|
||||
@@ -505,6 +521,15 @@ const ExperimentDetailView: React.FC<{
|
||||
)}
|
||||
Run Analysis
|
||||
</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
|
||||
onClick={handleDelete}
|
||||
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[];
|
||||
initialData?: Experiment;
|
||||
onBack: () => void;
|
||||
onCreated: (id: string) => void;
|
||||
}> = ({ salesData, onBack, onCreated }) => {
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [type, setType] = useState<ExperimentType>('content');
|
||||
const [marketplace, setMarketplace] = useState('DE');
|
||||
const [owner, setOwner] = useState('');
|
||||
const [hypothesis, setHypothesis] = useState('');
|
||||
const [primaryMetric, setPrimaryMetric] = useState<ExperimentMetric>('units');
|
||||
const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [endDate, setEndDate] = useState('');
|
||||
const [enableCustomBaseline, setEnableCustomBaseline] = useState(false);
|
||||
const [baselineStartDate, setBaselineStartDate] = useState('');
|
||||
const [baselineEndDate, setBaselineEndDate] = useState('');
|
||||
const [treatmentAsins, setTreatmentAsins] = useState('');
|
||||
const [controlAsins, setControlAsins] = useState('');
|
||||
const [changes, setChanges] = useState<ExperimentChangeAnnotation[]>([{ field: '', before_value: '', after_value: '' }]);
|
||||
onSaved: (id: string) => void;
|
||||
}> = ({ salesData, initialData, onBack, onSaved }) => {
|
||||
const [name, setName] = useState(initialData?.name || '');
|
||||
const [description, setDescription] = useState(initialData?.description || '');
|
||||
const [type, setType] = useState<ExperimentType>(initialData?.type || 'content');
|
||||
const [marketplace, setMarketplace] = useState(initialData?.marketplace || 'DE');
|
||||
const [owner, setOwner] = useState(initialData?.owner || '');
|
||||
const [hypothesis, setHypothesis] = useState(initialData?.hypothesis || '');
|
||||
const [primaryMetric, setPrimaryMetric] = useState<ExperimentMetric>(initialData?.primary_metric || 'units');
|
||||
const [startDate, setStartDate] = useState(initialData?.start_date?.split('T')[0] || new Date().toISOString().split('T')[0]);
|
||||
const [endDate, setEndDate] = useState(initialData?.end_date?.split('T')[0] || '');
|
||||
const [enableCustomBaseline, setEnableCustomBaseline] = useState(!!(initialData?.baseline_start_date && initialData?.baseline_end_date));
|
||||
const [baselineStartDate, setBaselineStartDate] = useState(initialData?.baseline_start_date?.split('T')[0] || '');
|
||||
const [baselineEndDate, setBaselineEndDate] = useState(initialData?.baseline_end_date?.split('T')[0] || '');
|
||||
const [treatmentAsins, setTreatmentAsins] = useState(initialData?.asins?.join(', ') || '');
|
||||
const [controlAsins, setControlAsins] = useState(initialData?.control_asins?.join(', ') || '');
|
||||
const [changes, setChanges] = useState<ExperimentChangeAnnotation[]>(
|
||||
initialData?.changes?.length ? initialData.changes : [{ field: '', before_value: '', after_value: '' }]
|
||||
);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
@@ -913,8 +941,13 @@ const ExperimentCreateView: React.FC<{
|
||||
changes: validChanges,
|
||||
owner: owner.trim() || undefined,
|
||||
};
|
||||
const created = await createExperiment(input);
|
||||
onCreated(created.id);
|
||||
if (initialData) {
|
||||
const updated = await updateExperiment(initialData.id, input);
|
||||
onSaved(updated.id);
|
||||
} else {
|
||||
const created = await createExperiment(input);
|
||||
onSaved(created.id);
|
||||
}
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
@@ -931,7 +964,9 @@ const ExperimentCreateView: React.FC<{
|
||||
Back to experiments
|
||||
</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">
|
||||
{/* 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"
|
||||
>
|
||||
{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 onClick={onBack} className="px-4 py-2.5 text-slate-400 hover:text-white text-sm transition-colors">
|
||||
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
|
||||
// ====================================================================
|
||||
|
||||
Reference in New Issue
Block a user