diff --git a/frontend/components/datagrids/applications/specieslimitsdatagrid.tsx b/frontend/components/datagrids/applications/specieslimitsdatagrid.tsx deleted file mode 100644 index b8fd12ac..00000000 --- a/frontend/components/datagrids/applications/specieslimitsdatagrid.tsx +++ /dev/null @@ -1,647 +0,0 @@ -'use client'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - GridActionsCellItem, - GridColDef, - GridEventListener, - GridFilterModel, - GridRowEditStopReasons, - GridRowId, - GridRowModel, - GridRowModes, - GridRowModesModel, - GridRowsProp, - GridToolbar, - GridToolbarContainer, - GridToolbarProps, - ToolbarPropsOverrides, - useGridApiRef -} from '@mui/x-data-grid'; -import { Alert, AlertProps, Button, Snackbar } from '@mui/material'; -import AddIcon from '@mui/icons-material/Add'; -import EditIcon from '@mui/icons-material/Edit'; -import DeleteIcon from '@mui/icons-material/DeleteOutlined'; -import SaveIcon from '@mui/icons-material/Save'; -import CancelIcon from '@mui/icons-material/Close'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import FileDownloadIcon from '@mui/icons-material/FileDownload'; -import Box from '@mui/joy/Box'; -import { Tooltip, Typography } from '@mui/joy'; -import { StyledDataGrid } from '@/config/styleddatagrid'; -import { - CellItemContainer, - createDeleteQuery, - createFetchQuery, - createPostPatchQuery, - EditToolbarCustomProps, - filterColumns, - getColumnVisibilityModel, - getGridID, - PendingAction -} from '@/config/datagridhelpers'; -import { useSession } from 'next-auth/react'; -import { useOrgCensusContext, usePlotContext, useQuadratContext, useSiteContext } from '@/app/contexts/userselectionprovider'; -import { HTTPResponses } from '@/config/macros'; -import { useLoading } from '@/app/contexts/loadingprovider'; -import ReEnterDataModal from '@/components/datagrids/reentrydatamodal'; -import ConfirmationDialog from '@/components/datagrids/confirmationdialog'; -import { randomId } from '@mui/x-data-grid-generator'; -import { SpeciesLimitsRDS } from '@/config/sqlrdsdefinitions/taxonomies'; -import { SpeciesLimitsGridColumns } from '@/components/client/datagridcolumns'; - -type EditToolbarProps = EditToolbarCustomProps & GridToolbarProps & ToolbarPropsOverrides; - -const EditToolbar = ({ handleAddNewRow, handleRefresh, handleExportAll, locked, filterModel }: EditToolbarProps) => { - const handleExportClick = async () => { - if (!handleExportAll) return; - const fullData = await handleExportAll(); - const blob = new Blob([JSON.stringify(fullData, null, 2)], { - type: 'application/json' - }); - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = 'data.json'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - - return ( - - - - - - - ); -}; - -export default function SpeciesLimitsDataGrid({ speciesID }: { speciesID: number }) { - const initialSpeciesLimitsRDSRow: SpeciesLimitsRDS = { - id: 0, - speciesLimitID: 0, - speciesID: 0, - limitType: '', - upperBound: 0, - lowerBound: 0, - unit: '' - }; - const [rows, setRows] = useState([initialSpeciesLimitsRDSRow] as GridRowsProp); - const [rowCount, setRowCount] = useState(0); - const [rowModesModel, setRowModesModel] = useState({}); - const [snackbar, setSnackbar] = React.useState | null>(null); - const [refresh, setRefresh] = useState(false); - const [paginationModel, setPaginationModel] = useState({ - page: 0, - pageSize: 10 - }); - const [isNewRowAdded, setIsNewRowAdded] = useState(false); - const [shouldAddRowAfterFetch, setShouldAddRowAfterFetch] = useState(false); - const [newLastPage, setNewLastPage] = useState(null); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const [pendingAction, setPendingAction] = useState({ - actionType: '', - actionId: null - }); - const [promiseArguments, setPromiseArguments] = useState<{ - resolve: (value: GridRowModel) => void; - reject: (reason?: any) => void; - newRow: GridRowModel; - oldRow: GridRowModel; - } | null>(null); - const [filterModel, setFilterModel] = useState({ - items: [] - }); - - const addNewRowToGrid = () => { - const id = randomId(); - const newRow = { - ...initialSpeciesLimitsRDSRow, - id, - isNew: true - }; - - setRows(oldRows => [...(oldRows ?? []), newRow]); - setRowModesModel(oldModel => ({ - ...oldModel, - [id]: { mode: GridRowModes.Edit, fieldToFocus: 'limitType' } - })); - }; - - const currentPlot = usePlotContext(); - const currentCensus = useOrgCensusContext(); - const currentQuadrat = useQuadratContext(); - const currentSite = useSiteContext(); - - const { setLoading } = useLoading(); - - useSession(); - - const apiRef = useGridApiRef(); - - useEffect(() => { - if (!isNewRowAdded) { - fetchPaginatedData(paginationModel.page).catch(console.error); - } - }, [paginationModel.page]); - - useEffect(() => { - if (currentPlot?.plotID || currentCensus?.plotCensusNumber) { - fetchPaginatedData(paginationModel.page).catch(console.error); - } - }, [currentPlot, currentCensus, paginationModel.page]); - - useEffect(() => { - if (refresh && currentSite) { - handleRefresh().then(() => { - setRefresh(false); - }); - } - }, [refresh, setRefresh]); - - useEffect(() => { - const initialRowModesModel = rows.reduce((acc, row) => { - acc[row.id] = { mode: GridRowModes.View }; - return acc; - }, {} as GridRowModesModel); - setRowModesModel(initialRowModesModel); - }, [rows]); - - const fetchFullData = async () => { - setLoading(true, 'Fetching full dataset...'); - const fullDataQuery = `/api/specieslimits/${speciesID}?schema=${currentSite?.schemaName}`; - - try { - const response = await fetch(fullDataQuery, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(filterModel) - }); - const data = await response.json(); - if (!response.ok) throw new Error(data.message || 'Error fetching full data'); - return data.output; - } catch (error) { - console.error('Error fetching full data:', error); - setSnackbar({ children: 'Error fetching full data', severity: 'error' }); - return []; - } finally { - setLoading(false); - } - }; - - const openConfirmationDialog = (actionType: 'save' | 'delete', actionId: GridRowId) => { - setPendingAction({ actionType, actionId }); - const row = rows.find(row => String(row.id) === String(actionId)); - if (row) { - if (actionType === 'delete') { - setIsDeleteDialogOpen(true); - } else { - setIsDialogOpen(true); - setRowModesModel(oldModel => ({ - ...oldModel, - [actionId]: { mode: GridRowModes.View } - })); - } - } - }; - - const handleConfirmAction = async (selectedRow?: GridRowModel) => { - setIsDialogOpen(false); - setIsDeleteDialogOpen(false); - if (pendingAction.actionType === 'save' && pendingAction.actionId !== null) { - await performSaveAction(pendingAction.actionId, selectedRow); - } else if (pendingAction.actionType === 'delete' && pendingAction.actionId !== null) { - await performDeleteAction(pendingAction.actionId); - } - setPendingAction({ actionType: '', actionId: null }); - setPromiseArguments(null); // Clear promise arguments after handling - }; - - const handleCancelAction = () => { - setIsDialogOpen(false); - setIsDeleteDialogOpen(false); - if (promiseArguments) { - promiseArguments.reject(new Error('Action cancelled by user')); - } - setPendingAction({ actionType: '', actionId: null }); - setPromiseArguments(null); // Clear promise arguments after handling - }; - - const performSaveAction = async (id: GridRowId, selectedRow?: GridRowModel) => { - if (!promiseArguments) return; - setLoading(true, 'Saving changes...'); - try { - const updatedRow = await updateRow( - 'specieslimits', - currentSite?.schemaName, - selectedRow ?? promiseArguments.newRow, - promiseArguments.oldRow, - setSnackbar, - setIsNewRowAdded, - setShouldAddRowAfterFetch, - fetchPaginatedData, - paginationModel - ); - promiseArguments.resolve(updatedRow); - } catch (error) { - promiseArguments.reject(error); - } - const row = rows.find(row => String(row.id) === String(id)); - if (row?.isNew) { - setIsNewRowAdded(false); - setShouldAddRowAfterFetch(false); - } - setLoading(false); - await fetchPaginatedData(paginationModel.page); - }; - - const performDeleteAction = async (id: GridRowId) => { - setLoading(true, 'Deleting...'); - const deletionID = rows.find(row => String(row.id) === String(id))?.id; - if (!deletionID) return; - const deleteQuery = createDeleteQuery(currentSite?.schemaName ?? '', 'specieslimits', getGridID('specieslimits'), deletionID); - const response = await fetch(deleteQuery, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - oldRow: undefined, - newRow: rows.find(row => String(row.id) === String(id))! - }) - }); - setLoading(false); - if (!response.ok) { - const error = await response.json(); - if (response.status === HTTPResponses.FOREIGN_KEY_CONFLICT) { - setSnackbar({ - children: `Error: Cannot delete row due to foreign key constraint in table ${error.referencingTable}`, - severity: 'error' - }); - } else { - setSnackbar({ - children: `Error: ${error.message || 'Deletion failed'}`, - severity: 'error' - }); - } - } else { - setSnackbar({ - children: 'Row successfully deleted', - severity: 'success' - }); - setRows(rows.filter(row => String(row.id) !== String(id))); - await fetchPaginatedData(paginationModel.page); - } - }; - - const handleSaveClick = (id: GridRowId) => () => { - openConfirmationDialog('save', id); - }; - - const handleDeleteClick = (id: GridRowId) => () => { - openConfirmationDialog('delete', id); - }; - - const handleAddNewRow = async () => { - if (isNewRowAdded) return; // Debounce double adds - const newRowCount = rowCount + 1; - const calculatedNewLastPage = Math.ceil(newRowCount / paginationModel.pageSize) - 1; - const existingLastPage = Math.ceil(rowCount / paginationModel.pageSize) - 1; - const isNewPageNeeded = newRowCount % paginationModel.pageSize === 1; - - setIsNewRowAdded(true); - setShouldAddRowAfterFetch(isNewPageNeeded); - setNewLastPage(calculatedNewLastPage); - - if (isNewPageNeeded) { - setPaginationModel({ ...paginationModel, page: calculatedNewLastPage }); - addNewRowToGrid(); - } else { - setPaginationModel({ ...paginationModel, page: existingLastPage }); - addNewRowToGrid(); - } - }; - - const handleRefresh = async () => { - await fetchPaginatedData(paginationModel.page); - }; - - const fetchPaginatedData = async (pageToFetch: number) => { - setLoading(true, 'Loading data...'); - const paginatedQuery = createFetchQuery( - currentSite?.schemaName ?? '', - 'specieslimits', - pageToFetch, - paginationModel.pageSize, - currentPlot?.plotID, - currentCensus?.plotCensusNumber, - currentQuadrat?.quadratID - ); - try { - const response = await fetch(paginatedQuery, { method: 'GET' }); - const data = await response.json(); - if (!response.ok) throw new Error(data.message || 'Error fetching data'); - setRows(data.output); - setRowCount(data.totalCount); - - if (isNewRowAdded && pageToFetch === newLastPage) { - addNewRowToGrid(); - setIsNewRowAdded(false); - } - } catch (error) { - console.error('Error fetching data:', error); - setSnackbar({ children: 'Error fetching data', severity: 'error' }); - } finally { - setLoading(false); - } - }; - - const updateRow = async ( - gridType: string, - schemaName: string | undefined, - newRow: GridRowModel, - oldRow: GridRowModel, - setSnackbar: (value: { children: string; severity: 'error' | 'success' }) => void, - setIsNewRowAdded: (value: boolean) => void, - setShouldAddRowAfterFetch: (value: boolean) => void, - fetchPaginatedData: (page: number) => Promise, - paginationModel: { page: number } - ): Promise => { - const gridID = getGridID('specieslimits'); - const fetchProcessQuery = createPostPatchQuery(schemaName ?? '', gridType, gridID); - - try { - const response = await fetch(fetchProcessQuery, { - method: oldRow.isNew ? 'POST' : 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ oldRow: oldRow, newRow: newRow }) - }); - - const responseJSON = await response.json(); - - if (!response.ok) { - setSnackbar({ - children: `Error: ${responseJSON.message}`, - severity: 'error' - }); - return Promise.reject(responseJSON.row); - } - - setSnackbar({ - children: oldRow.isNew ? 'New row added!' : 'Row updated!', - severity: 'success' - }); - - if (oldRow.isNew) { - setIsNewRowAdded(false); - setShouldAddRowAfterFetch(false); - await fetchPaginatedData(paginationModel.page); - } - // call refreshmeasurementssummary or viewfulltable if needed: await fetch(`/api/refresh/${gridType}`); - return newRow; - } catch (error: any) { - setSnackbar({ children: `Error: ${error.message}`, severity: 'error' }); - return Promise.reject(newRow); - } - }; - - const processRowUpdate = useCallback( - (newRow: GridRowModel, oldRow: GridRowModel) => - new Promise((resolve, reject) => { - setLoading(true, 'Processing changes...'); - if (newRow.id === '') { - setLoading(false); - return reject(new Error('Primary key id cannot be empty!')); - } - - setPromiseArguments({ resolve, reject, newRow, oldRow }); - setLoading(false); - }), - ['specieslimits', currentSite?.schemaName, setSnackbar, setIsNewRowAdded, setShouldAddRowAfterFetch, fetchPaginatedData, paginationModel] - ); - - const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { - console.log('new row modes model: ', newRowModesModel); - setRowModesModel(newRowModesModel); - }; - - const handleCloseSnackbar = () => setSnackbar(null); - - const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { - if (params.reason === GridRowEditStopReasons.rowFocusOut) { - event.defaultMuiPrevented = true; - } - }; - - const handleEditClick = (id: GridRowId) => () => { - setRowModesModel(prevModel => ({ - ...prevModel, - [id]: { mode: GridRowModes.Edit } - })); - // Auto-focus on the first editable cell when entering edit mode - setTimeout(() => { - const firstEditableColumn = columns.find(col => col.editable); - if (firstEditableColumn) { - apiRef.current.setCellFocus(id, firstEditableColumn.field); - } - }); - }; - - const handleCancelClick = (id: GridRowId, event?: React.MouseEvent | React.KeyboardEvent) => { - event?.preventDefault(); - const row = rows.find(row => String(row.id) === String(id)); - if (row?.isNew) { - setRows(oldRows => oldRows.filter(row => row.id !== id)); - setIsNewRowAdded(false); - if (rowCount % paginationModel.pageSize === 1 && isNewRowAdded) { - const newPage = paginationModel.page - 1 >= 0 ? paginationModel.page - 1 : 0; - setPaginationModel({ ...paginationModel, page: newPage }); - } - } else { - setRowModesModel(prevModel => ({ - ...prevModel, - [id]: { mode: GridRowModes.View, ignoreModifications: true } - })); - } - }; - - const getEnhancedCellAction = (type: string, icon: any, onClick: any) => ( - - - - - - ); - - const getGridActionsColumn = useCallback((): GridColDef => { - return { - field: 'actions', - type: 'actions', - headerName: 'Actions', - flex: 1, - cellClassName: 'actions', - getActions: ({ id }) => { - // Ensure that the mode is being accessed correctly - const mode = rowModesModel[id]?.mode; - - if (mode === GridRowModes.Edit) { - return [ - getEnhancedCellAction('Save', , handleSaveClick(id)), - getEnhancedCellAction('Cancel', , (e: any) => handleCancelClick(id, e)) - ]; - } - return [getEnhancedCellAction('Edit', , handleEditClick(id)), getEnhancedCellAction('Delete', , handleDeleteClick(id))]; - } - }; - }, [rowModesModel]); - - const columns = useMemo(() => { - return filterColumns(rows, [...SpeciesLimitsGridColumns, getGridActionsColumn()]); - }, [SpeciesLimitsGridColumns, rowModesModel, getGridActionsColumn]); - - const handleCellDoubleClick: GridEventListener<'cellDoubleClick'> = params => { - console.log('params: ', params); - setRowModesModel(prevModel => ({ - ...prevModel, - [params.id]: { mode: GridRowModes.Edit } - })); - }; - - const handleCellKeyDown: GridEventListener<'cellKeyDown'> = (params, event) => { - if (event.key === 'Enter') { - console.log('params: ', params); - setRowModesModel(prevModel => ({ - ...prevModel, - [params.id]: { mode: GridRowModes.Edit } - })); - } - if (event.key === 'Escape') { - console.log('params: ', params); - setRowModesModel(prevModel => ({ - ...prevModel, - [params.id]: { mode: GridRowModes.View, ignoreModifications: true } - })); - handleCancelClick(params.id, event); - } - }; - - return ( - - - Note: The Grid is filtered by your selected Plot and Plot ID - { - console.error('Row update error:', error); - setSnackbar({ - children: 'Error updating row', - severity: 'error' - }); - }} - loading={refresh} - paginationMode="server" - onPaginationModelChange={setPaginationModel} - paginationModel={paginationModel} - rowCount={rowCount} - pageSizeOptions={[paginationModel.pageSize]} - filterModel={filterModel} - onFilterModelChange={newFilterModel => setFilterModel(newFilterModel)} - initialState={{ - columns: { - columnVisibilityModel: getColumnVisibilityModel('specieslimits') - } - }} - slots={{ - toolbar: EditToolbar - }} - slotProps={{ - toolbar: { - handleAddNewRow: handleAddNewRow, - handleRefresh: handleRefresh, - handleExportAll: fetchFullData, - filterModel: filterModel - } - }} - getRowHeight={() => 'auto'} - /> - - {!!snackbar && ( - - - - )} - {isDialogOpen && promiseArguments && ( - ({ - value: limit, - label: limit - }))} - /> - )} - {isDeleteDialogOpen && ( - - )} - - ); -}