import React, { useEffect, useRef, useState, useCallback } from 'react'; import { useParams, useNavigate, Link, useLocation } from 'react-router-dom'; import { animalsApi, dailyStatusesApi, experimentsApi } from '../api/client'; import { format } from 'date-fns'; import Button from '../components/ui/Button'; import Modal from '../components/ui/Modal'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import DailyStatusForm from '../components/DailyStatusForm'; import SubjectInfoForm from '../components/SubjectInfoForm'; import Alert from '../components/ui/Alert'; import AuditLogSection from '../components/AuditLogSection'; import { SubjectAnalysisCharts } from '../components/AnalysisCharts'; /** Read the value for a template field from a status record. * Custom fields are keyed by fieldId, not by the display key. */ function getFieldValue(status, field) { if (field.builtin) return status[field.key]; return status.custom_fields?.[field.fieldId]; } // ── Inline cell editor ───────────────────────────────────────────────────────── function InlineCell({ statusId, field, value, currentStatus, onSaved }) { const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(''); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); const inputRef = useRef(null); const isTextarea = field.type === 'textarea'; function startEdit(e) { e.preventDefault(); e.stopPropagation(); setDraft(value ?? ''); setSaveError(null); setEditing(true); } useEffect(() => { if (editing && inputRef.current) { inputRef.current.focus(); if (inputRef.current.select) inputRef.current.select(); } }, [editing]); async function commit() { setEditing(false); const newValue = draft.trim() || null; if (newValue === (value?.trim() || null)) return; setSaving(true); setSaveError(null); try { let body; if (field.builtin) { body = { [field.key]: newValue }; } else { body = { custom_fields: { ...currentStatus.custom_fields, [field.fieldId]: newValue } }; } const updated = await dailyStatusesApi.update(statusId, body); onSaved(updated); } catch (err) { setSaveError(err.message ?? 'Save failed'); } finally { setSaving(false); } } function cancel() { setEditing(false); } if (editing) { const sharedProps = { ref: inputRef, value: draft, onChange: (e) => setDraft(e.target.value), onBlur: commit, onClick: (e) => e.stopPropagation(), onKeyDown: (e) => { if (e.key === 'Escape') { e.preventDefault(); cancel(); } if (e.key === 'Enter' && !isTextarea) { e.preventDefault(); commit(); } }, className: 'w-full border border-indigo-400 rounded px-1.5 py-0.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-300 bg-white', }; return isTextarea ?