refactor(frontend): unify CsvAnalysis state under buckets[]
This commit is contained in:
@@ -6,7 +6,12 @@ import RunSequenceView from './RunSequenceView';
|
|||||||
|
|
||||||
// ── Constants ──────────────────────────────────────────────────────────────────
|
// ── 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 = {
|
const CATEGORY_COLORS = {
|
||||||
Success: { bg: 'bg-green-100', border: 'border-green-300', text: 'text-green-800', dot: 'bg-green-500' },
|
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
|
// Classify phase
|
||||||
const [rows, setRows] = useState([]);
|
const [rows, setRows] = useState([]);
|
||||||
const [uniqueNotes, setUniqueNotes] = useState([]);
|
const [uniqueNotes, setUniqueNotes] = useState([]);
|
||||||
const [categories, setCategories] = useState(DEFAULT_CATEGORIES);
|
const [buckets, setBuckets] = useState(DEFAULT_BUCKETS);
|
||||||
const [customCategories, setCustomCategories] = useState([]);
|
|
||||||
const [classifications, setClassifications] = useState({});
|
|
||||||
const [selectedNote, setSelectedNote] = useState(null);
|
const [selectedNote, setSelectedNote] = useState(null);
|
||||||
const [draggedNote, setDraggedNote] = useState(null);
|
const [draggedNote, setDraggedNote] = useState(null);
|
||||||
const [overBucket, setOverBucket] = useState(null);
|
const [overBucket, setOverBucket] = useState(null);
|
||||||
const [newCatName, setNewCatName] = useState('');
|
const [newCatName, setNewCatName] = useState('');
|
||||||
const [showNewCat, setShowNewCat] = useState(false);
|
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
|
// Results phase
|
||||||
const [results, setResults] = useState(null);
|
const [results, setResults] = useState(null);
|
||||||
const [savedAnalysisId, setSavedAnalysisId] = useState(null);
|
const [savedAnalysisId, setSavedAnalysisId] = useState(null);
|
||||||
@@ -166,7 +178,7 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
|||||||
const uniq = getUniqueNoteValues(parsed, noteCol);
|
const uniq = getUniqueNoteValues(parsed, noteCol);
|
||||||
setRows(parsed);
|
setRows(parsed);
|
||||||
setUniqueNotes(uniq);
|
setUniqueNotes(uniq);
|
||||||
setClassifications({});
|
setBuckets((prev) => prev.map((b) => ({ ...b, notes: [] })));
|
||||||
setSelectedNote(null);
|
setSelectedNote(null);
|
||||||
if (frameCol) setFrameCheck(checkFrameConsecutive(parsed, frameCol));
|
if (frameCol) setFrameCheck(checkFrameConsecutive(parsed, frameCol));
|
||||||
setParsePending(false);
|
setParsePending(false);
|
||||||
@@ -177,19 +189,31 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
|||||||
// ── Classification helpers ───────────────────────────────────────────────────
|
// ── Classification helpers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
function assign(note, category) {
|
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);
|
setSelectedNote(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unassign(note) {
|
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() {
|
function addCategory() {
|
||||||
const name = newCatName.trim();
|
addBucket(newCatName, 'note');
|
||||||
if (!name || categories.includes(name)) return;
|
|
||||||
setCategories(prev => [...prev, name]);
|
|
||||||
setCustomCategories(prev => [...prev, name]);
|
|
||||||
setNewCatName('');
|
setNewCatName('');
|
||||||
setShowNewCat(false);
|
setShowNewCat(false);
|
||||||
}
|
}
|
||||||
@@ -266,13 +290,11 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
|||||||
setHeaders([]);
|
setHeaders([]);
|
||||||
setRows([]);
|
setRows([]);
|
||||||
setUniqueNotes([]);
|
setUniqueNotes([]);
|
||||||
setClassifications({});
|
setBuckets(DEFAULT_BUCKETS);
|
||||||
setResults(null);
|
setResults(null);
|
||||||
setSavedAnalysisId(null);
|
setSavedAnalysisId(null);
|
||||||
setSummaryPushed(false);
|
setSummaryPushed(false);
|
||||||
setSummaryError(null);
|
setSummaryError(null);
|
||||||
setCategories(DEFAULT_CATEGORIES);
|
|
||||||
setCustomCategories([]);
|
|
||||||
setFrameCheck(null);
|
setFrameCheck(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,18 +430,17 @@ export default function CsvAnalysis({ dailyStatusId, onSaved, onSummaryPushed })
|
|||||||
|
|
||||||
{/* Category buckets */}
|
{/* Category buckets */}
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
{categories.map(cat => {
|
{buckets.map((b) => {
|
||||||
const colors = getCategoryColors(cat, customCategories);
|
const colors = getCategoryColors(b.name, customCategories);
|
||||||
const notesInCat = uniqueNotes.filter(n => classifications[n] === cat);
|
|
||||||
return (
|
return (
|
||||||
<CategoryBucket key={cat} name={cat} notes={notesInCat} colors={colors}
|
<CategoryBucket key={b.name} name={b.name} notes={b.notes} colors={colors}
|
||||||
isOver={overBucket === cat}
|
isOver={overBucket === b.name}
|
||||||
onDragOver={() => setOverBucket(cat)}
|
onDragOver={() => setOverBucket(b.name)}
|
||||||
onDragLeave={() => setOverBucket(null)}
|
onDragLeave={() => setOverBucket(null)}
|
||||||
onDrop={() => {
|
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)}
|
onRemoveNote={(note) => unassign(note)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user