import React, { useEffect, useMemo, useState } from 'react'; import { experimentsApi } from '../api/client'; import Button from './ui/Button'; import Alert from './ui/Alert'; import { listExportableParams, buildSubjectSeriesMatrix, toCSV, csvFilename } from '../lib/dataExport'; const PARAM_GROUP_LABELS = { metrics: 'Session metrics', daily: 'Daily record fields' }; function triggerDownload(filename, text) { const blob = new Blob([text], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 0); } export default function ExportDataModal({ experimentTitle, experimentId, dailyTemplate, animals, subjectTemplate = [], groupField = '__none__', onClose, }) { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [statuses, setStatuses] = useState([]); const [xField, setXField] = useState('__date__'); const [dataId, setDataId] = useState(null); 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; setLoading(true); setError(null); experimentsApi.getDailyStatuses(experimentId, {}) .then((data) => { if (alive) setStatuses(data); }) .catch((err) => { if (alive) setError(err.message); }) .finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, [experimentId]); const params = useMemo(() => listExportableParams(dailyTemplate ?? [], statuses), [dailyTemplate, statuses]); const effDataId = dataId ?? params[0]?.id ?? null; const dataParam = params.find((p) => p.id === effDataId) ?? params[0] ?? null; const activeDaily = useMemo(() => (dailyTemplate ?? []).filter((f) => f.active), [dailyTemplate]); const xOptions = useMemo(() => [ { id: '__date__', label: 'Date' }, { id: '__days__', label: '# Days Reach' }, ...activeDaily.map((f) => ({ id: f.fieldId, label: f.label })), ], [activeDaily]); const matrix = useMemo( () => (dataParam ? buildSubjectSeriesMatrix({ xField, dataParam, groupField: groupBy }, { statuses, animals, dailyTemplate }) : { columns: [], rows: [], context: { label: 'Data', value: '' }, corner: '', groupAxis: null }), [xField, dataParam, groupBy, statuses, animals, dailyTemplate], ); const hasData = matrix.rows.length > 0; const groupedParams = useMemo(() => { const out = []; for (const p of params) { let g = out.find((x) => x.group === p.group); if (!g) { g = { group: p.group, items: [] }; out.push(g); } g.items.push(p); } return out; }, [params]); function handleExport() { if (!hasData || !dataParam) return; triggerDownload(csvFilename(experimentTitle, dataParam.label), toCSV(matrix)); onClose(); } if (loading) return
Loading data…
; if (error) return (One value per subject over an X axis — each column is a subject, each row an X value. Blank where a subject has no value.
{hasData ? `${matrix.rows.length} row${matrix.rows.length !== 1 ? 's' : ''} × ${matrix.columns.length} subject${matrix.columns.length !== 1 ? 's' : ''}` : 'No data for this selection yet.'}