refactor(frontend): unify CsvAnalysis state under buckets[]
This commit is contained in:
@@ -6,7 +6,12 @@ import RunSequenceView from './RunSequenceView';
|
||||
|
||||
// ── Constants ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const DEFAULT_CATEGORIES = ['Success', 'Failure', 'Other'];
|
||||
const DEFAULT_BUCKETS = [
|
||||
{ name: 'Success', type: 'note', notes: [] },
|
||||
{ name: 'Failure', type: 'note', notes: [] },
|
||||
{ name: 'Other', type: 'note', notes: [] },
|
||||
];
|
||||
const DEFAULT_BUCKET_NAMES = new Set(DEFAULT_BUCKETS.map((b) => b.name));
|
||||
|
||||
const CATEGORY_COLORS = {
|
||||
Success: { bg: 'bg-green-100', border: 'border-green-300', text: 'text-green-800', dot: 'bg-green-500' },
|
||||
@@ -106,15 +111,22 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
||||
// Classify phase
|
||||
const [rows, setRows] = useState([]);
|
||||
const [uniqueNotes, setUniqueNotes] = useState([]);
|
||||
const [categories, setCategories] = useState(DEFAULT_CATEGORIES);
|
||||
const [customCategories, setCustomCategories] = useState([]);
|
||||
const [classifications, setClassifications] = useState({});
|
||||
const [buckets, setBuckets] = useState(DEFAULT_BUCKETS);
|
||||
const [selectedNote, setSelectedNote] = useState(null);
|
||||
const [draggedNote, setDraggedNote] = useState(null);
|
||||
const [overBucket, setOverBucket] = useState(null);
|
||||
const [newCatName, setNewCatName] = useState('');
|
||||
const [showNewCat, setShowNewCat] = useState(false);
|
||||
|
||||
// Derived from buckets (recomputed every render — cheap)
|
||||
const categories = buckets.map((b) => b.name);
|
||||
const customCategories = buckets.filter((b) => !DEFAULT_BUCKET_NAMES.has(b.name)).map((b) => b.name);
|
||||
const noteBuckets = buckets.filter((b) => b.type === 'note');
|
||||
const numericalBuckets = buckets.filter((b) => b.type === 'numerical');
|
||||
const classifications = Object.fromEntries(
|
||||
noteBuckets.flatMap((b) => b.notes.map((n) => [n, b.name]))
|
||||
);
|
||||
|
||||
// Results phase
|
||||
const [results, setResults] = useState(null);
|
||||
const [savedAnalysisId, setSavedAnalysisId] = useState(null);
|
||||
@@ -166,7 +178,7 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
||||
const uniq = getUniqueNoteValues(parsed, noteCol);
|
||||
setRows(parsed);
|
||||
setUniqueNotes(uniq);
|
||||
setClassifications({});
|
||||
setBuckets((prev) => prev.map((b) => ({ ...b, notes: [] })));
|
||||
setSelectedNote(null);
|
||||
if (frameCol) setFrameCheck(checkFrameConsecutive(parsed, frameCol));
|
||||
setParsePending(false);
|
||||
@@ -177,19 +189,31 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
||||
// ── Classification helpers ───────────────────────────────────────────────────
|
||||
|
||||
function assign(note, category) {
|
||||
setClassifications(prev => ({ ...prev, [note]: category }));
|
||||
setBuckets((prev) => prev.map((b) => {
|
||||
if (b.name === category) {
|
||||
return b.notes.includes(note) ? b : { ...b, notes: [...b.notes, note] };
|
||||
}
|
||||
// Remove from any other bucket so a note belongs to at most one
|
||||
return b.notes.includes(note) ? { ...b, notes: b.notes.filter((n) => n !== note) } : b;
|
||||
}));
|
||||
setSelectedNote(null);
|
||||
}
|
||||
|
||||
function unassign(note) {
|
||||
setClassifications(prev => { const n = { ...prev }; delete n[note]; return n; });
|
||||
setBuckets((prev) => prev.map((b) =>
|
||||
b.notes.includes(note) ? { ...b, notes: b.notes.filter((n) => n !== note) } : b
|
||||
));
|
||||
}
|
||||
|
||||
function addBucket(name, type) {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed || buckets.some((b) => b.name === trimmed)) return;
|
||||
setBuckets((prev) => [...prev, { name: trimmed, type, notes: [] }]);
|
||||
}
|
||||
|
||||
// Back-compat shim so the existing "+ New category" UI keeps working until Task 9 swaps the picker
|
||||
function addCategory() {
|
||||
const name = newCatName.trim();
|
||||
if (!name || categories.includes(name)) return;
|
||||
setCategories(prev => [...prev, name]);
|
||||
setCustomCategories(prev => [...prev, name]);
|
||||
addBucket(newCatName, 'note');
|
||||
setNewCatName('');
|
||||
setShowNewCat(false);
|
||||
}
|
||||
@@ -266,13 +290,11 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
||||
setHeaders([]);
|
||||
setRows([]);
|
||||
setUniqueNotes([]);
|
||||
setClassifications({});
|
||||
setBuckets(DEFAULT_BUCKETS);
|
||||
setResults(null);
|
||||
setSavedAnalysisId(null);
|
||||
setSummaryPushed(false);
|
||||
setSummaryError(null);
|
||||
setCategories(DEFAULT_CATEGORIES);
|
||||
setCustomCategories([]);
|
||||
setFrameCheck(null);
|
||||
}
|
||||
|
||||
@@ -408,18 +430,17 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
||||
|
||||
{/* Category buckets */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{categories.map(cat => {
|
||||
const colors = getCategoryColors(cat, customCategories);
|
||||
const notesInCat = uniqueNotes.filter(n => classifications[n] === cat);
|
||||
{buckets.map((b) => {
|
||||
const colors = getCategoryColors(b.name, customCategories);
|
||||
return (
|
||||
<CategoryBucket key={cat} name={cat} notes={notesInCat} colors={colors}
|
||||
isOver={overBucket === cat}
|
||||
onDragOver={() => setOverBucket(cat)}
|
||||
<CategoryBucket key={b.name} name={b.name} notes={b.notes} colors={colors}
|
||||
isOver={overBucket === b.name}
|
||||
onDragOver={() => setOverBucket(b.name)}
|
||||
onDragLeave={() => setOverBucket(null)}
|
||||
onDrop={() => {
|
||||
if (draggedNote) { assign(draggedNote, cat); setDraggedNote(null); setOverBucket(null); }
|
||||
if (draggedNote) { assign(draggedNote, b.name); setDraggedNote(null); setOverBucket(null); }
|
||||
}}
|
||||
onClickAssign={() => { if (selectedNote) assign(selectedNote, cat); }}
|
||||
onClickAssign={() => { if (selectedNote) assign(selectedNote, b.name); }}
|
||||
onRemoveNote={(note) => unassign(note)}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user