3BOFJXKAMRCAQSJQD3AZWYG64A3IO54F4773ZOVWHJSVOQY34CSQC PY24MQAJYYL2IQCG5SIXN4J4DXYNG7QHNZMRBTCWKLJ7EK5DUFZAC YZMPDME2RWQGRPDXPSO7UVAZNK5LWVNCWRFWYU62ORL7MJX64UVAC DBDM3MJDJ5TRAH3FA7MLBGFUPZF4GIM4CPZAKFNS5MV3UYQOAFNQC UFYD7GLFT2JHHKDWCHDUUYS42SGIVLFLRHQLZC6UYHSDOCHYAJAAC 6W73J6WJQHALRF4ZLBJ4JH6H7UYT74KFPKFH537T2BRW2G6MCLRQC IUHUM6OZ5KYEAYQCIYNG5Q4QLQRAQNMBWKYGV2ZDJFNY5W4DOUNQC ADZREFCOSVSO6I66BOLGTZFJI4Y7J3WJO2PNHCHE2W7Y5PZPX4OAC DZURLJKGFFIAG4KUZTLCURERBXHFWNSNUFYYB7PXCXFUJWPIHWZQC YBJGFUTFTV4SBPUVC6LBETGMPMLS47LSQPL3OJAAPH7BROBQD7DQC NYBASAG4KWZNPSFBXWQJWQRBCGT7C5B24IYO2IBCXCE2WGJQJOEQC CWCD3MTTPIKDNDPLRTLU4S6JQ35YYRDW34NW25DQ5KHJYMQP3SNAC SFLRF3YSORJMAHAO3DVL7EXOKLAXFNMJKGJOBTYXPPIVM75GM5KAC 4SDYLIN2RF7RT4U6T4PKLXRQ7ON4SZOTDHMWFQQQE27E4EMFEQVQC LSAQ6ZM2NELU3FIWKEFBOXKVLSZS2ZOK2PHPHJRWPVZ5CVILSUYQC MQWZW46BIE5FJYVHV6QS34VGTWYQH65RMDHAYEKG67N5BAPKVDNAC VFENVAHOAERPOWJFWVQBS5GXK3F422I62ZGQGOJHALKZZK2Q2SJAC C4PJ5BYCHKYQVPCXNESI5AZELV3TXCWXIVU7IFJ2F3MZVR23SQ3AC HU4V2UA4CZNNWQ4RRMFYEKD44QRMLTRJHZMIONWIZGLTJU7CYU4AC // Fetch dataset owner if we don't have it yetif (!datasetOwnerId && requiredParams.datasetId) {
// Fetch dataset owner and permissions if we don't have them yetif ((!datasetOwnerId || datasetPermissions.length === 0) && requiredParams.datasetId) {
// Fetch dataset permissions when component mounts or dataset changesuseEffect(() => {const targetDatasetId = requiredParams.datasetId || datasetId;if (isAuthenticated && !authLoading && targetDatasetId && datasetPermissions.length === 0) {void fetchDatasetOwner(targetDatasetId).catch(console.error);}}, [isAuthenticated, authLoading, requiredParams.datasetId, datasetId, datasetPermissions.length, fetchDatasetOwner]);
}>;}interface EditableSelectionData {description: string;distance: string; // "", "close", "ok", "far"note: string;speciesId: string;speciesCertainty: number | null;callTypeId: string;callTypeCertainty: number | null;}interface SpeciesOption {id: string;label: string;callTypes: Array<{id: string;label: string;
}, []);// Update editing dataconst handleFieldChange = useCallback((field: keyof EditableSelectionData, value: string | number | null) => {setState(prev => ({...prev,editingData: prev.editingData ? {...prev.editingData,[field]: value} : null}));}, []);// Render editable or static field based on edit modeconst renderEditableField = useCallback((selection: SelectionData,fieldType: 'description' | 'distance' | 'note' | 'species' | 'callType',currentValue: string,fieldKey?: keyof EditableSelectionData) => {const isEditing = state.editingSelectionId === selection.id;if (!isEditing) {return currentValue;}if (!state.editingData || !fieldKey) {return currentValue;}switch (fieldType) {case 'description':case 'note':return (<inputtype="text"value={String(state.editingData[fieldKey] || "")}onChange={(e) => handleFieldChange(fieldKey, e.target.value)}className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"placeholder={fieldType === 'description' ? 'Enter description' : 'Enter comment'}/>);case 'distance':return (<selectvalue={String(state.editingData[fieldKey] || "")}onChange={(e) => handleFieldChange(fieldKey, e.target.value)}className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"><option value="">Select distance</option><option value="close">Close</option><option value="ok">OK</option><option value="far">Far</option></select>);case 'species':return (<selectvalue={String(state.editingData.speciesId || "")}onChange={(e) => {handleFieldChange('speciesId', e.target.value);// Reset call type when species changeshandleFieldChange('callTypeId', "");}}className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"><option value="">Select species</option>{state.speciesOptions.map(species => (<option key={species.id} value={species.id}>{species.label}</option>))}</select>);case 'callType': {const selectedSpecies = state.speciesOptions.find(s => s.id === state.editingData?.speciesId);const callTypeOptions = selectedSpecies?.callTypes || [];return (<selectvalue={String(state.editingData.callTypeId || "")}onChange={(e) => handleFieldChange('callTypeId', e.target.value)}className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"disabled={!state.editingData.speciesId}><option value="">Select call type</option>{callTypeOptions.map(callType => (<option key={callType.id} value={callType.id}>{callType.label}</option>))}</select>);}default:return currentValue;}}, [state.editingSelectionId, state.editingData, state.speciesOptions, handleFieldChange]);// Fetch species options for the datasetconst fetchSpeciesOptions = useCallback(async () => {if (!datasetId || !canEditSelections) return;setState(prev => ({ ...prev, speciesLoading: true }));try {const accessToken = await getAccessToken();const response = await fetch(`/api/species?datasetId=${encodeURIComponent(datasetId)}`, {headers: {Authorization: `Bearer ${accessToken}`,},});if (response.ok) {const data = await response.json();setState(prev => ({...prev,speciesOptions: data.data || [],speciesLoading: false}));}} catch (error) {console.error("Error fetching species options:", error);setState(prev => ({...prev,speciesOptions: [],speciesLoading: false}));}}, [datasetId, canEditSelections, getAccessToken]);// Start editing a selectionconst handleEditSelection = useCallback((selection: SelectionData) => {// Get current filter data for the selectionconst filterData = state.selectedFilterId? selection.filters.find(f => f.id === state.selectedFilterId): selection.filters[0];const firstSpecies = filterData?.species?.[0];const firstCallType = firstSpecies?.callTypes?.[0];setState(prev => ({...prev,editingSelectionId: selection.id,editingData: {description: selection.description || "",distance: getMetadataValue(selection.metadata, "distance"),note: getMetadataValue(selection.metadata, "note"),speciesId: firstSpecies?.id || "",speciesCertainty: firstSpecies?.certainty || null,callTypeId: firstCallType?.id || "",callTypeCertainty: firstCallType?.certainty || null,}}));}, [state.selectedFilterId, getMetadataValue]);// Cancel editingconst handleCancelEdit = useCallback(() => {setState(prev => ({...prev,editingSelectionId: null,editingData: null,}));
// Save selection changesconst handleSaveSelection = useCallback(async () => {if (!state.editingSelectionId || !state.editingData || !canEditSelections) return;setState(prev => ({ ...prev, savingSelection: true }));try {const accessToken = await getAccessToken();// Save metadata (distance, note)const metadataResponse = await fetch(`/api/files/selections/${state.editingSelectionId}/metadata`, {method: 'PATCH',headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json',},body: JSON.stringify({metadata: {distance: state.editingData.distance,note: state.editingData.note,}}),});if (!metadataResponse.ok) {throw new Error('Failed to update metadata');}// Save labels (species, call types) if species is selectedif (state.editingData.speciesId) {const labels = [{speciesId: state.editingData.speciesId,certainty: state.editingData.speciesCertainty,callTypes: state.editingData.callTypeId ? [{callTypeId: state.editingData.callTypeId,certainty: state.editingData.callTypeCertainty,}] : [],}];const labelsResponse = await fetch(`/api/files/selections/${state.editingSelectionId}/labels`, {method: 'PATCH',headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json',},body: JSON.stringify({labels,filterId: state.selectedFilterId,}),});if (!labelsResponse.ok) {throw new Error('Failed to update labels');}}// Refresh selections dataif (file) {await fetchSelections(file.id);}// Exit edit modehandleCancelEdit();} catch (error) {console.error("Error saving selection:", error);// TODO: Show error message to user} finally {setState(prev => ({ ...prev, savingSelection: false }));}}, [state.editingSelectionId, state.editingData, state.selectedFilterId, canEditSelections, getAccessToken, file, fetchSelections, handleCancelEdit]);
{canEditSelections && (<TableCell className="text-center"><div className="flex justify-center gap-2">{state.editingSelectionId === selection.id ? (<><ButtononClick={handleSaveSelection}variant="ghost"size="sm"className="p-1 h-8 w-8 text-green-600 hover:text-green-700 hover:bg-green-50"disabled={state.savingSelection}title="Save changes"><Check className="h-4 w-4" /></Button><ButtononClick={handleCancelEdit}variant="ghost"size="sm"className="p-1 h-8 w-8 text-red-600 hover:text-red-700 hover:bg-red-50"disabled={state.savingSelection}title="Cancel editing"><X className="h-4 w-4" /></Button></>) : (<ButtononClick={() => handleEditSelection(selection)}variant="ghost"size="sm"className="p-1 h-8 w-8"title="Edit selection"><Edit className="h-4 w-4" /></Button>)}</div></TableCell>)}
{canEditSelections && (<TableCell className="text-center"><div className="flex justify-center gap-2">{state.editingSelectionId === selection.id ? (<><ButtononClick={handleSaveSelection}variant="ghost"size="sm"className="p-1 h-8 w-8 text-green-600 hover:text-green-700 hover:bg-green-50"disabled={state.savingSelection}title="Save changes"><Check className="h-4 w-4" /></Button><ButtononClick={handleCancelEdit}variant="ghost"size="sm"className="p-1 h-8 w-8 text-red-600 hover:text-red-700 hover:bg-red-50"disabled={state.savingSelection}title="Cancel editing"><X className="h-4 w-4" /></Button></>) : (<ButtononClick={() => handleEditSelection(selection)}variant="ghost"size="sm"className="p-1 h-8 w-8"title="Edit selection"><Edit className="h-4 w-4" /></Button>)}</div></TableCell>)}
{canEditSelections && (<TableCell className="text-center"><div className="flex justify-center gap-2">{state.editingSelectionId === selection.id ? (<><ButtononClick={handleSaveSelection}variant="ghost"size="sm"className="p-1 h-8 w-8 text-green-600 hover:text-green-700 hover:bg-green-50"disabled={state.savingSelection}title="Save changes"><Check className="h-4 w-4" /></Button><ButtononClick={handleCancelEdit}variant="ghost"size="sm"className="p-1 h-8 w-8 text-red-600 hover:text-red-700 hover:bg-red-50"disabled={state.savingSelection}title="Cancel editing"><X className="h-4 w-4" /></Button></>) : (<ButtononClick={() => handleEditSelection(selection)}variant="ghost"size="sm"className="p-1 h-8 w-8"title="Edit selection"><Edit className="h-4 w-4" /></Button>)}</div></TableCell>)}