'use client'; import { useEffect, useState, useRef } from 'react'; import { getCalibrationEntries, deleteCalibrationEntry, updateCalibrationOrder, deleteAllCalibrationEntries } from '@/app/actions/calibration'; import Modal from '@/components/Modal'; import { ExperimentCalibration } from '@/lib/model/experiment_calibration'; import { useAudio } from './AudioProvider'; interface CalibrationListModalProps { experimentId: number; } export default function CalibrationListModal({ experimentId }: CalibrationListModalProps) { const [isOpen, setIsOpen] = useState(false); const [calibrationItems, setCalibrationItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [draggedItem, setDraggedItem] = useState(null); const { currentAudioId, setCurrentAudio } = useAudio(); const audioRef = useRef(null); useEffect(() => { if (isOpen) { loadCalibrationItems(); } }, [isOpen]); async function loadCalibrationItems() { setLoading(true); setError(null); try { const result = await getCalibrationEntries(experimentId); if (result.success) { const sorted = [...result.data].sort((a, b) => a.order - b.order); setCalibrationItems(sorted); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load calibration items'); } finally { setLoading(false); } } async function handleDelete(calibrationId: number) { if (!confirm('Are you sure you want to delete this calibration item?')) { return; } try { await deleteCalibrationEntry(calibrationId, experimentId); setCalibrationItems(items => items.filter(item => item.id !== calibrationId)); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete item'); } } async function handleDeleteAll() { if (!confirm('Are you sure you want to delete ALL calibration items? This cannot be undone.')) { return; } try { await deleteAllCalibrationEntries(experimentId); setCalibrationItems([]); if (audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; } setCurrentAudio(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete all items'); } } async function handleMoveUp(index: number) { if (index === 0) return; const reorderedItems = [...calibrationItems]; const temp = reorderedItems[index]; reorderedItems[index] = reorderedItems[index - 1]; reorderedItems[index - 1] = temp; setCalibrationItems(reorderedItems); // Prepare order map const orderMap: Record = {}; reorderedItems.forEach((item, idx) => { orderMap[item.id] = idx; }); try { await updateCalibrationOrder(experimentId, orderMap); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update order'); // Revert on error await loadCalibrationItems(); } } async function handleMoveDown(index: number) { if (index === calibrationItems.length - 1) return; const reorderedItems = [...calibrationItems]; const temp = reorderedItems[index]; reorderedItems[index] = reorderedItems[index + 1]; reorderedItems[index + 1] = temp; setCalibrationItems(reorderedItems); // Prepare order map const orderMap: Record = {}; reorderedItems.forEach((item, idx) => { orderMap[item.id] = idx; }); try { await updateCalibrationOrder(experimentId, orderMap); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update order'); // Revert on error await loadCalibrationItems(); } } function handlePlayAudio(calibrationId: number, filePath: string) { const audioId = `calibration-${calibrationId}`; if (currentAudioId === audioId && audioRef.current && !audioRef.current.paused) { // Stop playing audioRef.current.pause(); audioRef.current.currentTime = 0; setCurrentAudio(null); return; } // Stop any currently playing audio if (audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; } // Play new audio if (audioRef.current) { const fullPath = `/public${filePath}`; audioRef.current.src = fullPath; setCurrentAudio(audioId); audioRef.current.play().catch(err => console.error('Failed to play audio:', err)); } } function handleDragStart(index: number) { setDraggedItem(index); } function handleDragOver(e: React.DragEvent, index: number) { e.preventDefault(); if (draggedItem === null || draggedItem === index) return; const reorderedItems = [...calibrationItems]; const draggedItemContent = reorderedItems[draggedItem]; reorderedItems.splice(draggedItem, 1); reorderedItems.splice(index, 0, draggedItemContent); setCalibrationItems(reorderedItems); setDraggedItem(index); } async function handleDragEnd() { setDraggedItem(null); // Update order in database const orderMap: Record = {}; calibrationItems.forEach((item, idx) => { orderMap[item.id] = idx; }); try { await updateCalibrationOrder(experimentId, orderMap); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update order'); // Revert on error await loadCalibrationItems(); } } return ( <> { if (audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; } setCurrentAudio(null); setIsOpen(false); }} title="Calibration Items" actions={[ { label: 'Close', onClick: () => setIsOpen(false), variant: 'secondary', }, ]} > ); }