diff --git a/frontend/src/components/ExportDataModal.jsx b/frontend/src/components/ExportDataModal.jsx index d676521..d8cc000 100644 --- a/frontend/src/components/ExportDataModal.jsx +++ b/frontend/src/components/ExportDataModal.jsx @@ -31,7 +31,11 @@ export default function ExportDataModal({ const [rowDim, setRowDim] = useState('date'); const [colDim, setColDim] = useState('subject'); const [pinnedId, setPinnedId] = useState(null); - const [groupBy, setGroupBy] = useState(groupField ?? '__none__'); + const [groupBy, setGroupBy] = useState(() => { + const valid = groupField === '__none__' || groupField === '__name__' || groupField === '__id__' + || subjectTemplate.some((f) => f.active && f.fieldId === groupField); + return valid ? groupField : '__none__'; + }); useEffect(() => { let alive = true; diff --git a/frontend/src/lib/dataExport.js b/frontend/src/lib/dataExport.js index 173db8a..a44c268 100644 --- a/frontend/src/lib/dataExport.js +++ b/frontend/src/lib/dataExport.js @@ -1,4 +1,6 @@ -// Pure helpers for exporting daily-parameter data as a date×subject CSV matrix. +// Pure helpers for exporting daily-parameter data as a CSV matrix. buildMatrix is +// dimension-agnostic: rows and columns are assignable to Date/Subject/Parameter, +// with the leftover dimension pinned to a single value. // No React, no I/O — mirrors the crossSubjectChart.js pure-helper pattern. // Read a single parameter's raw value from one daily status. @@ -112,7 +114,7 @@ export function listSubjectMembers(animals, groupField) { const members = list.map((a) => { const name = a?.animal_name ?? ''; const label = nameCounts.get(name) > 1 ? `${name} (${a?.animal_id_string ?? ''})` : name; - return { id: a.id, label, animalId: a.id, group: subjectGroupValue(a, groupField) }; + return { id: a?.id, label, animalId: a?.id, group: subjectGroupValue(a, groupField) }; }); const grouped = !!groupField && groupField !== '__none__'; members.sort((x, y) => { diff --git a/frontend/src/pages/ExperimentDetail.jsx b/frontend/src/pages/ExperimentDetail.jsx index 11a1183..76f8503 100644 --- a/frontend/src/pages/ExperimentDetail.jsx +++ b/frontend/src/pages/ExperimentDetail.jsx @@ -417,7 +417,7 @@ export default function ExperimentDetail() { dailyTemplate={dailyTemplate} animals={animals} subjectTemplate={subjectTemplate} - groupField={groupField} + groupField={localStorage.getItem(`exp-subject-group-${id}`) ?? '__none__'} onClose={() => setShowExport(false)} /> diff --git a/frontend/tests/ExportDataModal.test.jsx b/frontend/tests/ExportDataModal.test.jsx index f8924a9..7c866c9 100644 --- a/frontend/tests/ExportDataModal.test.jsx +++ b/frontend/tests/ExportDataModal.test.jsx @@ -22,6 +22,44 @@ beforeEach(() => { client.experimentsApi.getDailyStatuses.mockResolvedValue(statuses); }); +// Intercepts the Blob passed to URL.createObjectURL during handleExport so tests +// can inspect the actual CSV text produced, rather than inferring it from the +// 's DOM .value here would be misleading — React's + // controlled