diff --git a/frontend/app/api/clearcensus/route.ts b/frontend/app/api/clearcensus/route.ts new file mode 100644 index 00000000..4ea701f1 --- /dev/null +++ b/frontend/app/api/clearcensus/route.ts @@ -0,0 +1,57 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { HTTPResponses } from '@/config/macros'; +import ConnectionManager from '@/config/connectionmanager'; + +export async function GET(request: NextRequest) { + const schema = request.nextUrl.searchParams.get('schema'); + const censusIDParam = request.nextUrl.searchParams.get('censusID'); + if (!schema || !censusIDParam) { + return new NextResponse('Missing required parameters', { status: HTTPResponses.SERVICE_UNAVAILABLE }); + } + const censusID = parseInt(censusIDParam); + const connectionManager = ConnectionManager.getInstance(); + const transactionID = await connectionManager.beginTransaction(); + try { + let query = `DELETE FROM ${schema}.cmverrors WHERE CoreMeasurementID IN (SELECT CoreMeasurementID FROM ${schema}.coremeasurements WHERE CensusID = ${censusID});`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.cmattributes WHERE CoreMeasurementID IN (SELECT CoreMeasurementID FROM ${schema}.coremeasurements WHERE CensusID = ${censusID});`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.coremeasurements WHERE CensusID = ${censusID};`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.quadratpersonnel WHERE PersonnelID IN (SELECT PersonnelID FROM ${schema}.personnel WHERE CensusID = ${censusID});`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.personnel WHERE CensusID = ${censusID};`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.specieslimits WHERE CensusID = ${censusID};`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.censusquadrat WHERE CensusID = ${censusID};`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.quadrats WHERE QuadratID IN (SELECT QuadratID FROM ${schema}.censusquadrat WHERE CensusID = ${censusID});`; + await connectionManager.executeQuery(query); + query = `DELETE FROM ${schema}.census WHERE CensusID = ${censusID};`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.cmverrors AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.cmattributes AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.coremeasurements AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.quadratpersonnel AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.personnel AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.specieslimits AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.censusquadrat AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.quadrats AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + query = `ALTER TABLE ${schema}.census AUTO_INCREMENT = 1;`; + await connectionManager.executeQuery(query); + await connectionManager.commitTransaction(transactionID); + return NextResponse.json({ message: 'Census cleared successfully' }, { status: HTTPResponses.OK }); + } catch (e: any) { + await connectionManager.rollbackTransaction(transactionID); + return new NextResponse(e.message, { status: HTTPResponses.SERVICE_UNAVAILABLE }); + } +} diff --git a/frontend/components/datagrids/isolateddatagridcommons.tsx b/frontend/components/datagrids/isolateddatagridcommons.tsx index a7b98c0b..34f5ba0d 100644 --- a/frontend/components/datagrids/isolateddatagridcommons.tsx +++ b/frontend/components/datagrids/isolateddatagridcommons.tsx @@ -30,16 +30,33 @@ import { GridToolbarProps, GridToolbarQuickFilter, ToolbarPropsOverrides, + useGridApiContext, useGridApiRef } from '@mui/x-data-grid'; -import { Alert, AlertProps, Button, Checkbox, IconButton, Snackbar } from '@mui/material'; +import { Alert, AlertProps, Snackbar } from '@mui/material'; import RefreshIcon from '@mui/icons-material/Refresh'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useOrgCensusContext, usePlotContext, useQuadratContext, useSiteContext } from '@/app/contexts/userselectionprovider'; import { useDataValidityContext } from '@/app/contexts/datavalidityprovider'; import { useSession } from 'next-auth/react'; import { HTTPResponses, UnifiedValidityFlags } from '@/config/macros'; -import { Dropdown, Menu, MenuButton, MenuItem, Stack, Tooltip, Typography } from '@mui/joy'; +import { + Button, + Checkbox, + Chip, + DialogActions, + DialogContent, + DialogTitle, + FormLabel, + IconButton, + Modal, + ModalDialog, + Skeleton, + Stack, + Switch, + Tooltip, + Typography +} from '@mui/joy'; import SaveIcon from '@mui/icons-material/Save'; import CancelIcon from '@mui/icons-material/Close'; import EditIcon from '@mui/icons-material/Edit'; @@ -54,6 +71,9 @@ import { FormType, getTableHeaders } from '@/config/macros/formdetails'; import { applyFilterToColumns } from '@/components/datagrids/filtrationsystem'; import { ClearIcon } from '@mui/x-date-pickers'; import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; +import GridOnIcon from '@mui/icons-material/GridOn'; +import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; +import Divider from '@mui/joy/Divider'; type EditToolbarProps = EditToolbarCustomProps & GridToolbarProps & ToolbarPropsOverrides; @@ -67,11 +87,15 @@ const EditToolbar = (props: EditToolbarProps) => { handleToggleHideEmptyColumns, hidingEmptyColumns, filterModel, - dynamicButtons = [] + dynamicButtons = [], + gridColumns, + gridType } = props; if (!handleAddNewRow || !handleRefresh || !handleQuickFilterChange || !handleExportAll || !handleToggleHideEmptyColumns) return <>; const [inputValue, setInputValue] = useState(''); const [isTyping, setIsTyping] = useState(false); + const [openExportModal, setOpenExportModal] = useState(false); + const [exportType, setExportType] = useState<'csv' | 'form'>('csv'); const handleInputChange = (event: React.ChangeEvent) => { setInputValue(event.target.value); @@ -119,6 +143,16 @@ const EditToolbar = (props: EditToolbarProps) => { URL.revokeObjectURL(url); } + const apiRef = useGridApiContext(); + const csvHeaders = gridColumns + ?.filter(column => !Object.keys(apiRef.current.state.columns.columnVisibilityModel).includes(column.field)) + .map(column => column.field); + + let formHeaders: string[]; + if (gridType === 'alltaxonomiesview') { + formHeaders = getTableHeaders(FormType.species).map(header => header.label); + } else formHeaders = getTableHeaders(gridType as FormType).map(header => header.label); + return ( { sx={{ ml: 2 }} /> - + - - - - } - > - Export... - - - await handleExportAll()}> - All data as JSON - - - All Data as CSV - - - Filter Settings - - - + - handleToggleHideEmptyColumns(event.target.checked)} /> + handleToggleHideEmptyColumns(event.target.checked)} /> {hidingEmptyColumns ? `Hiding Empty Columns` : `Hide Empty Columns`} @@ -206,19 +221,132 @@ const EditToolbar = (props: EditToolbarProps) => { {button.tooltip ? ( <> - ) : ( - )} ))} + setOpenExportModal(false)} + sx={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }} + > + + + Exporting Data + + + + + Desired Format Type: + You can export data in either of these formats: + + + Table CSV + + + Form CSV + + + + ) => (event.target.checked ? setExportType('csv') : setExportType('form'))} + endDecorator={ + + + + + + CSV + + + } + startDecorator={ + + + + + + Form + + + } + sx={{ + marginRight: '1.5em', + transform: 'scale(1.25)', + transformOrigin: 'center' + }} + /> + + + Export Headers: + + {exportType === 'csv' + ? csvHeaders?.map((header, index) => ( + + {header} + + )) + : formHeaders.map((label, index) => ( + + {label} + + ))} + + + + + + + + ); }; @@ -1186,7 +1314,9 @@ export default function IsolatedDataGridCommons(props: Readonly 'auto'} diff --git a/frontend/components/datagrids/measurementscommons.tsx b/frontend/components/datagrids/measurementscommons.tsx index bff77631..3951677a 100644 --- a/frontend/components/datagrids/measurementscommons.tsx +++ b/frontend/components/datagrids/measurementscommons.tsx @@ -48,8 +48,7 @@ import { Stack, Switch, Tooltip, - Typography, - useTheme + Typography } from '@mui/joy'; import { StyledDataGrid } from '@/config/styleddatagrid'; import { @@ -106,27 +105,6 @@ interface ExtendedGridFilterModel extends GridFilterModel { visible: VisibleFilter[]; } -const rowCategories = [ - { - type: 'valid', - color: '#7fc180', // Dark mode - lightModeColor: '#4d814d', // Light mode (darker green for contrast) - label: 'Valid Rows' - }, - { - type: 'pending', - color: '#5570e1', // Dark mode - lightModeColor: '#2e4abf', // Light mode (darker blue for contrast) - label: 'Pending Rows' - }, - { - type: 'errors', - color: '#b10d01', // Dark mode - lightModeColor: '#8a0000', // Light mode (darker red for contrast) - label: 'Error Rows' - } -]; - const EditToolbar = (props: EditToolbarProps) => { const { handleAddNewRow, @@ -146,8 +124,6 @@ const EditToolbar = (props: EditToolbarProps) => { const [openExportModal, setOpenExportModal] = useState(false); const [exportType, setExportType] = useState<'csv' | 'form'>('csv'); const [exportVisibility, setExportVisibility] = useState(filterModel.visible); - const dataset = ['valid', 'pending', 'errors', 'pending', 'errors', 'valid', 'errors', 'pending', 'pending', 'valid', 'errors', 'errors', 'pending']; - const { palette } = useTheme(); const apiRef = useGridApiContext(); const handleInputChange = (event: React.ChangeEvent) => { @@ -422,6 +398,7 @@ const EditToolbar = (props: EditToolbarProps) => { whiteSpace: 'normal', wordBreak: 'break-word' }} + onClick={() => handleChipToggle('valid')} > Rows that have passed validation @@ -434,6 +411,7 @@ const EditToolbar = (props: EditToolbarProps) => { whiteSpace: 'normal', wordBreak: 'break-word' }} + onClick={() => handleChipToggle('errors')} > Rows that have failed validation @@ -446,37 +424,11 @@ const EditToolbar = (props: EditToolbarProps) => { whiteSpace: 'normal', wordBreak: 'break-word' }} + onClick={() => handleChipToggle('pending')} > Rows that have not yet been validated - - {dataset.map((row, index) => { - const rowType = rowCategories.find(rowC => rowC.type === row); - const isHighlighted = exportVisibility.includes(row as VisibleFilter); - const rowColor = palette.mode === 'dark' ? rowType?.color : rowType?.lightModeColor; - - return ( - handleChipToggle(rowType?.type ?? '')} - > - - - ); - })} - diff --git a/frontend/config/datagridhelpers.ts b/frontend/config/datagridhelpers.ts index aa4a4dff..199d4630 100644 --- a/frontend/config/datagridhelpers.ts +++ b/frontend/config/datagridhelpers.ts @@ -195,6 +195,7 @@ export interface EditToolbarCustomProps { currentPlot?: Plot; currentCensus?: OrgCensus; gridColumns?: GridColDef[]; + gridType?: string; } export interface IsolatedDataGridCommonProps { diff --git a/frontend/sqlscripting/storedprocedures.sql b/frontend/sqlscripting/storedprocedures.sql index b6b6fd57..4bb5eea8 100644 --- a/frontend/sqlscripting/storedprocedures.sql +++ b/frontend/sqlscripting/storedprocedures.sql @@ -6,6 +6,7 @@ CREATE PROCEDURE RefreshMeasurementsSummary() BEGIN SET foreign_key_checks = 0; + TRUNCATE measurementssummary; INSERT INTO measurementssummary (CoreMeasurementID, StemID, TreeID, @@ -91,7 +92,7 @@ CREATE BEGIN -- Disable foreign key checks temporarily SET foreign_key_checks = 0; - + TRUNCATE viewfulltable; -- Insert data with ON DUPLICATE KEY UPDATE to resolve conflicts INSERT INTO viewfulltable (CoreMeasurementID, MeasurementDate,