diff --git a/frontend/src/components/CsvAnalysis.jsx b/frontend/src/components/CsvAnalysis.jsx index c33d402..e92ace5 100644 --- a/frontend/src/components/CsvAnalysis.jsx +++ b/frontend/src/components/CsvAnalysis.jsx @@ -57,7 +57,11 @@ function NoteChip({ value, category, colors, selected, onSelect, draggable, onDr ); } -function CategoryBucket({ name, notes, colors, isOver, onDrop, onDragOver, onDragLeave, onRemoveNote, onClickAssign }) { +function CategoryBucket({ + name, type, notes, colors, isOver, isCustom, unassignedCount, + onDrop, onDragOver, onDragLeave, onRemoveNote, onClickAssign, + onBulkAddUnassigned, onRemoveBucket, +}) { return (
{ e.preventDefault(); onDragOver(); }} @@ -65,11 +69,35 @@ function CategoryBucket({ name, notes, colors, isOver, onDrop, onDragOver, onDra onDrop={(e) => { e.preventDefault(); onDrop(); }} onClick={onClickAssign} className={` - rounded-lg border-2 border-dashed p-3 min-h-[80px] transition-all cursor-pointer + relative group rounded-lg border-2 border-dashed p-3 min-h-[80px] transition-all cursor-pointer ${isOver ? `${colors.border} ${colors.bg} scale-[1.02]` : 'border-gray-200 hover:border-gray-300'} `} > -

{name}

+
+

+ {name} + {type === 'numerical' && ( + + numerical + + )} +

+ {isCustom && ( + + )} +
+ + {type === 'numerical' && unassignedCount > 0 && ( + + )} +
{notes.length === 0 && ( Drop here @@ -116,6 +144,7 @@ export default function CsvAnalysis({ dailyStatusId, animal, onSaved, onSummaryP const [draggedNote, setDraggedNote] = useState(null); const [overBucket, setOverBucket] = useState(null); const [newCatName, setNewCatName] = useState(''); + const [newCatType, setNewCatType] = useState('note'); const [showNewCat, setShowNewCat] = useState(false); // Derived from buckets (recomputed every render — cheap) @@ -211,13 +240,6 @@ export default function CsvAnalysis({ dailyStatusId, animal, onSaved, onSummaryP 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() { - addBucket(newCatName, 'note'); - setNewCatName(''); - setShowNewCat(false); - } - const unassignedNotes = uniqueNotes.filter(n => !classifications[n]); const allAssigned = uniqueNotes.length > 0 && unassignedNotes.length === 0; @@ -432,9 +454,12 @@ export default function CsvAnalysis({ dailyStatusId, animal, onSaved, onSummaryP
{buckets.map((b) => { const colors = getCategoryColors(b.name, customCategories); + const isCustom = !DEFAULT_BUCKET_NAMES.has(b.name); return ( - setOverBucket(b.name)} onDragLeave={() => setOverBucket(null)} onDrop={() => { @@ -442,26 +467,50 @@ export default function CsvAnalysis({ dailyStatusId, animal, onSaved, onSummaryP }} onClickAssign={() => { if (selectedNote) assign(selectedNote, b.name); }} onRemoveNote={(note) => unassign(note)} + onBulkAddUnassigned={() => { + for (const n of unassignedNotes) assign(n, b.name); + }} + onRemoveBucket={() => setBuckets((prev) => prev.filter((x) => x.name !== b.name))} /> ); })}
- {/* Add new category */} -
+ {/* Add new bucket — note or numerical */} +
{showNewCat ? ( <> - setNewCatName(e.target.value)} - onKeyDown={(e) => { if (e.key === 'Enter') addCategory(); if (e.key === 'Escape') setShowNewCat(false); }} - placeholder="Category name…" - className="text-sm border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-400 w-40" /> - - + setNewCatName(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Escape') setShowNewCat(false); }} + placeholder="Field name…" + className="text-sm border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-400 w-40" + /> + + + + ) : ( )}