diff --git a/frontend/app/api/formdownload/[dataType]/[[...slugs]]/route.ts b/frontend/app/api/formdownload/[dataType]/[[...slugs]]/route.ts index e12c17ab..fd36792b 100644 --- a/frontend/app/api/formdownload/[dataType]/[[...slugs]]/route.ts +++ b/frontend/app/api/formdownload/[dataType]/[[...slugs]]/route.ts @@ -143,46 +143,48 @@ export async function GET(_request: NextRequest, { params }: { params: { dataTyp })); return new NextResponse(JSON.stringify(formMappedResults), { status: HTTPResponses.OK }); case 'measurements': - query = `SELECT st.StemTag AS StemTag, - t.TreeTag AS TreeTag, - s.SpeciesCode AS SpeciesCode, - q.QuadratName AS QuadratName, - st.LocalX AS StartX, - st.LocalY AS StartY, - cm.MeasuredDBH AS MeasuredDBH, - cm.MeasuredHOM AS MeasuredHOM, - cm.MeasurementDate AS MeasurementDate, - (SELECT GROUP_CONCAT(ca.Code SEPARATOR '; ') - FROM ${schema}.cmattributes ca - WHERE ca.CoreMeasurementID = cm.CoreMeasurementID) AS Codes - FROM ${schema}.coremeasurements cm - JOIN ${schema}.stems st ON st.StemID = cm.StemID - JOIN ${schema}.trees t ON t.TreeID = st.TreeID - JOIN ${schema}.quadrats q ON q.QuadratID = st.QuadratID - JOIN ${schema}.plots p ON p.PlotID = q.PlotID - JOIN ${schema}.species s ON s.SpeciesID = t.SpeciesID - WHERE p.PlotID = ? - AND cm.CensusID = ? ${ - filterModel.visible.length > 0 - ? ` AND (${filterModel.visible - .map((v: string) => { - switch (v) { - case 'valid': - return `cm.IsValidated = TRUE`; - case 'errors': - return `cm.IsValidated = FALSE`; - case 'pending': - return `cm.IsValidated IS NULL`; - default: - return null; - } - }) - .filter(Boolean) - .join(' OR ')})` - : '' - } ${searchStub || filterStub ? ` AND (${[searchStub, filterStub].filter(Boolean).join(' OR ')})` : ''}`; + query = `SELECT st.StemTag AS StemTag, + t.TreeTag AS TreeTag, + s.SpeciesCode AS SpeciesCode, + q.QuadratName AS QuadratName, + st.LocalX AS StartX, + st.LocalY AS StartY, + cm.MeasuredDBH AS MeasuredDBH, + cm.MeasuredHOM AS MeasuredHOM, + cm.MeasurementDate AS MeasurementDate, + (SELECT GROUP_CONCAT(ca.Code SEPARATOR '; ') + FROM ${schema}.cmattributes ca + WHERE ca.CoreMeasurementID = cm.CoreMeasurementID) AS Codes, + (SELECT GROUP_CONCAT(CONCAT(vp.ProcedureName, ':', vp.Description) SEPARATOR ';') + FROM catalog.validationprocedures vp + JOIN ${schema}.cmverrors cmv ON cmv.ValidationErrorID = vp.ValidationID + WHERE cmv.CoreMeasurementID = cm.CoreMeasurementID) AS Errors + FROM ${schema}.coremeasurements cm + JOIN ${schema}.stems st ON st.StemID = cm.StemID + JOIN ${schema}.trees t ON t.TreeID = st.TreeID + JOIN ${schema}.quadrats q ON q.QuadratID = st.QuadratID + JOIN ${schema}.plots p ON p.PlotID = q.PlotID + JOIN ${schema}.species s ON s.SpeciesID = t.SpeciesID + WHERE p.PlotID = ? AND cm.CensusID = ? ${ + filterModel.visible.length > 0 + ? ` AND (${filterModel.visible + .map((v: string) => { + switch (v) { + case 'valid': + return `cm.IsValidated = TRUE`; + case 'errors': + return `cm.IsValidated = FALSE`; + case 'pending': + return `cm.IsValidated IS NULL`; + default: + return null; + } + }) + .filter(Boolean) + .join(' OR ')})` + : '' + } ${searchStub || filterStub ? ` AND (${[searchStub, filterStub].filter(Boolean).join(' OR ')})` : ''}`; results = await connectionManager.executeQuery(query, [plotID, censusID]); - // console.log('results: ', results); formMappedResults = results.map((row: any) => ({ tag: row.TreeTag, stemtag: row.StemTag, @@ -193,7 +195,8 @@ export async function GET(_request: NextRequest, { params }: { params: { dataTyp dbh: row.MeasuredDBH, hom: row.MeasuredHOM, date: row.MeasurementDate, - codes: row.Codes + codes: row.Codes, + errors: row.Errors })); return new NextResponse(JSON.stringify(formMappedResults), { status: HTTPResponses.OK }); default: diff --git a/frontend/components/datagrids/measurementscommons.tsx b/frontend/components/datagrids/measurementscommons.tsx index 14a2abf3..9582ec0a 100644 --- a/frontend/components/datagrids/measurementscommons.tsx +++ b/frontend/components/datagrids/measurementscommons.tsx @@ -19,9 +19,10 @@ import { GridToolbarProps, GridToolbarQuickFilter, ToolbarPropsOverrides, + useGridApiContext, useGridApiRef } from '@mui/x-data-grid'; -import { Alert, AlertColor, AlertProps, AlertPropsColorOverrides, Checkbox, Snackbar } from '@mui/material'; +import { Alert, AlertColor, AlertProps, AlertPropsColorOverrides, Snackbar } from '@mui/material'; import EditIcon from '@mui/icons-material/Edit'; import DeleteIcon from '@mui/icons-material/DeleteOutlined'; import SaveIcon from '@mui/icons-material/Save'; @@ -30,19 +31,25 @@ import RefreshIcon from '@mui/icons-material/Refresh'; import Box from '@mui/joy/Box'; import { Button, + Checkbox, + Chip, DialogActions, DialogContent, DialogTitle, Dropdown, + FormLabel, IconButton, Menu, MenuButton, MenuItem, Modal, ModalDialog, + Skeleton, Stack, + Switch, Tooltip, - Typography + Typography, + useTheme } from '@mui/joy'; import { StyledDataGrid } from '@/config/styleddatagrid'; import { @@ -59,13 +66,13 @@ import { sortRowsByMeasurementDate } from '@/config/datagridhelpers'; import { CMError, CoreMeasurementError, ErrorMap, ValidationPair } from '@/config/macros/uploadsystemmacros'; -import { useOrgCensusContext, usePlotContext, useQuadratContext, useSiteContext } from '@/app/contexts/userselectionprovider'; +import { useOrgCensusContext, usePlotContext, useSiteContext } from '@/app/contexts/userselectionprovider'; import { redirect } from 'next/navigation'; import moment from 'moment'; import CheckIcon from '@mui/icons-material/Check'; import ErrorIcon from '@mui/icons-material/Error'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; -import { HTTPResponses } from '@/config/macros'; +import { bitToBoolean, HTTPResponses } from '@/config/macros'; import { useLoading } from '@/app/contexts/loadingprovider'; import { useSession } from 'next-auth/react'; import ConfirmationDialog from './confirmationdialog'; @@ -78,6 +85,10 @@ import ValidationModal from '@/components/client/validationmodal'; import { MeasurementsSummaryViewGridColumns } from '@/components/client/datagridcolumns'; import { OverridableStringUnion } from '@mui/types'; import ValidationOverrideModal from '@/components/client/validationoverridemodal'; +import { MeasurementsSummaryResult } from '@/config/sqlrdsdefinitions/views'; +import Divider from '@mui/joy/Divider'; +import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; +import GridOnIcon from '@mui/icons-material/GridOn'; function debounce void>(fn: T, delay: number): T { let timeoutId: ReturnType; @@ -95,20 +106,49 @@ 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, - handleExportErrors, handleRefresh, - handleExportAll, - handleExportCSV, + handleExport, handleQuickFilterChange, filterModel, - dynamicButtons = [] + dynamicButtons = [], + currentSite, + currentPlot, + currentCensus, + gridColumns } = props; - if (!handleAddNewRow || !handleExportErrors || !handleRefresh || !handleQuickFilterChange || !handleExportAll) return <>; + if (!handleAddNewRow || !handleExport || !handleRefresh || !handleQuickFilterChange || !filterModel || !gridColumns) return <>; const [inputValue, setInputValue] = useState(''); const [isTyping, setIsTyping] = useState(false); + 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) => { setInputValue(event.target.value); @@ -156,64 +196,71 @@ const EditToolbar = (props: EditToolbarProps) => { URL.revokeObjectURL(url); } - const handleExportErrorsClick = async () => { - if (!handleExportErrors) return; - const errorData = await handleExportErrors(filterModel); - const blob = new Blob([JSON.stringify(errorData, null, 2)], { - type: 'application/json' + const downloadExportedData = async () => { + const blob = new Blob([await handleExport(exportVisibility, exportType)], { + type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; - link.download = 'error_data.json'; + link.download = `measurements_${exportType}_${currentSite?.schemaName ?? ''}_${currentPlot?.plotName ?? ''}_${currentCensus?.plotCensusNumber ?? 0}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; + const handleChipToggle = (type: string) => { + console.log('handle chip toggle: ', type); + setExportVisibility(prev => (prev.includes(type as VisibleFilter) ? prev.filter(t => t !== (type as VisibleFilter)) : [...prev, type as VisibleFilter])); + }; + + const csvHeaders = gridColumns + .filter(column => !Object.keys(apiRef.current.state.columns.columnVisibilityModel).includes(column.field)) + .map(column => column.field); + return ( - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Other + + {dynamicButtons.map((button: any, index: number) => ( + <> + {button.tooltip ? ( + <> + + + {button.label} + + + + ) : ( + + {button.label} + + )} + + ))} + + + + - - - - Other - - {dynamicButtons.map((button: any, index: number) => ( - <> - {button.tooltip ? ( - <> - - - {button.label} - - - - ) : ( - - {button.label} - - )} - - ))} - - - - + 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} + + )) + : getTableHeaders(FormType.measurements) + .map(header => header.label) + .map((label, index) => ( + + {label} + + ))} + + + Please choose your desired visibility settings using the provided legend: + + + Rows that have passed validation + + + Rows that have failed validation + + + 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 ?? '')} + > + + + ); + })} + + + + + + + + + + ); }; @@ -340,7 +557,6 @@ export default function MeasurementsCommons(props: Readonly { - const response = await fetch( - `/api/formdownload/measurements/${currentSite?.schemaName ?? ''}/${currentPlot?.plotID ?? 0}/${currentCensus?.dateRanges[0].censusID ?? 0}/${JSON.stringify(filterModel)}`, - { method: 'GET' } - ); - const data = await response.json(); - let csvRows = - getTableHeaders(FormType.measurements) - .map(row => row.label) - .join(',') + '\n'; - data.forEach((row: any) => { - const values = getTableHeaders(FormType.measurements) - .map(rowHeader => rowHeader.label) - .map(header => row[header]) - .map(value => { - if (value === undefined || value === null || value === '') { - return null; - } - const match = value.match(/(\d{4})[\/.-](\d{1,2})[\/.-](\d{1,2})|(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{4})/); - - if (match) { - let normalizedDate; - if (match[1]) { - normalizedDate = `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`; - } else { - normalizedDate = `${match[6]}-${match[5].padStart(2, '0')}-${match[4].padStart(2, '0')}`; - } - - const parsedDate = moment(normalizedDate, 'YYYY-MM-DD', true); - if (parsedDate.isValid()) { - return parsedDate.format('YYYY-MM-DD'); - } - } - if (/^0[0-9]+$/.test(value)) { - return value; // Return as a string if it has leading zeroes - } - // Attempt to parse as a float - const parsedValue = parseFloat(value); - if (!isNaN(parsedValue)) { - return parsedValue; - } - if (typeof value === 'string') { - value = value.replace(/"/g, '""'); - value = `"${value}"`; - } - - return value; - }); - csvRows += values.join(',') + '\n'; - }); - const blob = new Blob([csvRows], { - type: 'text/csv;charset=utf-8;' - }); - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = `measurementsform_${currentSite?.schemaName ?? ''}_${currentPlot?.plotName ?? ''}_${currentCensus?.plotCensusNumber ?? 0}.csv`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }, [currentPlot, currentCensus, currentSite, gridType]); - // helper functions for usage: const handleSortModelChange = (newModel: GridSortModel) => { setSortModel(newModel); @@ -455,9 +609,110 @@ export default function MeasurementsCommons(props: Readonly cellHasError(column.field, rowId)); }; - const fetchErrorRows = async () => { - if (!rows || rows.length === 0 || !validationErrors) return []; - return rows.filter(row => rowHasError(row.id)); + const fetchRowsForExport = async (visibility: VisibleFilter[], exportType: 'csv' | 'form') => { + const tempFilter: ExtendedGridFilterModel = { + ...filterModel, + visible: visibility + }; + if (exportType === 'form') { + const response = await fetch( + `/api/formdownload/measurements/${currentSite?.schemaName ?? ''}/${currentPlot?.plotID ?? 0}/${currentCensus?.dateRanges[0].censusID ?? 0}/${JSON.stringify(tempFilter)}`, + { method: 'GET' } + ); + const data = await response.json(); + let csvRows = + getTableHeaders(FormType.measurements) + .map(row => row.label) + .join(',') + '\n'; + data.forEach((row: any) => { + const values = getTableHeaders(FormType.measurements) + .map(rowHeader => rowHeader.label) + .map(header => row[header]) + .map(value => { + if (value === undefined || value === null || value === '') { + return null; + } + const match = value.match(/(\d{4})[\/.-](\d{1,2})[\/.-](\d{1,2})|(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{4})/); + + if (match) { + let normalizedDate; + if (match[1]) { + normalizedDate = `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`; + } else { + normalizedDate = `${match[6]}-${match[5].padStart(2, '0')}-${match[4].padStart(2, '0')}`; + } + + const parsedDate = moment(normalizedDate, 'YYYY-MM-DD', true); + if (parsedDate.isValid()) { + return parsedDate.format('YYYY-MM-DD'); + } + } + if (/^0[0-9]+$/.test(value)) { + return value; // Return as a string if it has leading zeroes + } + // Attempt to parse as a float + const parsedValue = parseFloat(value); + if (!isNaN(parsedValue)) { + return parsedValue; + } + if (typeof value === 'string') { + value = value.replace(/"/g, '""'); + value = `"${value}"`; + } + + return value; + }); + csvRows += values.join(',') + '\n'; + }); + return csvRows; + } else { + const tempQuery = createQFFetchQuery( + currentSite?.schemaName ?? '', + gridType, + paginationModel.page, + paginationModel.pageSize, + currentPlot?.plotID, + currentCensus?.plotCensusNumber + ); + + const results: MeasurementsSummaryResult[] = await ( + await fetch(`/api/runquery`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify( + ( + await ( + await fetch(tempQuery, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ filterModel: tempFilter }) + }) + ).json() + ).finishedQuery + .replace(/\bSQL_CALC_FOUND_ROWS\b\s*/i, '') + .replace(/\bLIMIT\s+\d+\s*,\s*\d+/i, '') + .trim() + ) + }) + ).json(); + const headers = Object.keys(results[0]).filter( + header => !['CoreMeasurementID', 'StemID', 'TreeID', 'SpeciesID', 'QuadratID', 'PlotID', 'CensusID'].includes(header) + ); + let csvRows = headers.join(',') + '\n'; + Object.entries(results).forEach(([_, row]) => { + const rowValues = headers.map(header => { + if (header === 'IsValidated') return bitToBoolean(row[header]); + if (header === 'MeasurementDate') return moment(new Date(row[header as keyof MeasurementsSummaryResult])).format('YYYY-MM-DD'); + if (Object.prototype.toString.call(row[header as keyof MeasurementsSummaryResult]) === '[object Object]') + return `"${JSON.stringify(row[header as keyof MeasurementsSummaryResult]).replace(/"/g, '""')}"`; + const value = row[header as keyof MeasurementsSummaryResult]; + if (typeof value === 'string' && value.includes(',')) return `"${value.replace(/"/g, '""')}"`; + return value ?? 'NULL'; + }); + csvRows += rowValues.join(',') + '\n'; + }); + return csvRows; + } }; const updateRow = async ( @@ -929,64 +1184,6 @@ export default function MeasurementsCommons(props: Readonly { - setLoading(true); - try { - const tempQuery = createQFFetchQuery( - currentSite?.schemaName ?? '', - gridType, - paginationModel.page, - paginationModel.pageSize, - currentPlot?.plotID, - currentCensus?.plotCensusNumber - ); - - const results = await ( - await fetch(`/api/runquery`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify( - ( - await ( - await fetch(tempQuery, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ filterModel }) - }) - ).json() - ).finishedQuery - .replace(/\bSQL_CALC_FOUND_ROWS\b\s*/i, '') - .replace(/\bLIMIT\s+\d+\s*,\s*\d+/i, '') - .trim() - ) - }) - ).json(); - - const jsonData = JSON.stringify(results, null, 2); - const blob = new Blob([jsonData], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - - const link = document.createElement('a'); - link.href = url; - link.download = 'results.json'; - link.click(); - - URL.revokeObjectURL(url); - } catch (error) { - console.error('Error fetching full data:', error); - setSnackbar({ children: 'Error fetching full data', severity: 'error' }); - } finally { - setLoading(false); - } - }, [usingQuery, filterModel, currentPlot, currentCensus, currentQuadrat, currentSite, gridType, setLoading]); - - const handleExportErrors = async () => { - const errorRows = await fetchErrorRows(); - return errorRows.map(row => { - return { ...row, errors: validationErrors[Number(row.coreMeasurementID)].errors ?? [] }; - }); - }; - // custom column formatting: const validationStatusColumn: GridColDef = useMemo( () => ({ @@ -1264,20 +1461,32 @@ export default function MeasurementsCommons(props: Readonly - setShowErrorRows(event.target.checked)} /> - Show rows failing validation: ({errorCount}) + setShowErrorRows(event.target.checked)} + label={`Show rows failing validation: (${errorCount})`} + /> - setShowValidRows(event.target.checked)} /> - Show rows passing validation: ({validCount}) + setShowValidRows(event.target.checked)} + label={`Show rows passing validation: (${validCount})`} + /> - setShowPendingRows(event.target.checked)} /> - Show rows pending validation: ({pendingCount}) + setShowPendingRows(event.target.checked)} + label={`Show rows pending validation: (${pendingCount})`} + /> - setHidingEmpty(event.target.checked)} /> - {hidingEmpty ? `Hiding Empty Columns` : `Hide Empty Columns`} + setHidingEmpty(event.target.checked)} + label={{hidingEmpty ? `Hiding Empty Columns` : `Hide Empty Columns`}} + /> @@ -1335,11 +1544,10 @@ export default function MeasurementsCommons(props: Readonly setRefresh(true), - handleExportAll: fetchFullData, - handleExportCSV: exportAllCSV, - handleExportErrors: handleExportErrors, + handleExport: fetchRowsForExport, handleQuickFilterChange: onQuickFilterChange, filterModel: filterModel, + gridColumns: gridColumns, dynamicButtons: [ ...dynamicButtons, { label: 'Run Validations', onClick: () => setIsValidationModalOpen(true) }, diff --git a/frontend/config/datagridhelpers.ts b/frontend/config/datagridhelpers.ts index cba7d0fe..aa4a4dff 100644 --- a/frontend/config/datagridhelpers.ts +++ b/frontend/config/datagridhelpers.ts @@ -2,7 +2,7 @@ * Defines templates for new rows in data grids */ // datagridhelpers.ts -import { getQuadratHCs } from '@/config/sqlrdsdefinitions/zones'; +import { getQuadratHCs, Plot, Site } from '@/config/sqlrdsdefinitions/zones'; import { getAllTaxonomiesViewHCs, getAllViewFullTableViewsHCs, getMeasurementsSummaryViewHCs } from '@/config/sqlrdsdefinitions/views'; import { getPersonnelHCs } from '@/config/sqlrdsdefinitions/personnel'; import { getCoreMeasurementsHCs } from '@/config/sqlrdsdefinitions/core'; @@ -12,6 +12,7 @@ import { AlertProps } from '@mui/material'; import styled from '@emotion/styled'; import { getSpeciesLimitsHCs } from '@/config/sqlrdsdefinitions/taxonomies'; import { GridApiCommunity } from '@mui/x-data-grid/internals'; +import { OrgCensus } from '@/config/sqlrdsdefinitions/timekeeping'; export interface FieldTemplate { type: 'string' | 'number' | 'boolean' | 'array' | 'date' | 'unknown'; @@ -174,19 +175,26 @@ export function getGridID(gridType: string): string { } } +type VisibleFilter = 'valid' | 'errors' | 'pending'; +interface ExtendedGridFilterModel extends GridFilterModel { + visible: VisibleFilter[]; +} + export interface EditToolbarCustomProps { handleAddNewRow?: () => Promise; handleRefresh?: () => Promise; - handleExportAll?: () => Promise; - handleExportCSV?: () => Promise; - handleRunValidations?: () => Promise; + handleExport?: (visibility: VisibleFilter[], exportType: 'csv' | 'form') => Promise; hidingEmptyColumns?: boolean; handleToggleHideEmptyColumns?: (checked: boolean) => void; handleQuickFilterChange?: (incomingFilterModel: GridFilterModel) => void; - filterModel?: GridFilterModel; + filterModel?: ExtendedGridFilterModel; apiRef?: MutableRefObject; dynamicButtons?: any; locked?: boolean; + currentSite?: Site; + currentPlot?: Plot; + currentCensus?: OrgCensus; + gridColumns?: GridColDef[]; } export interface IsolatedDataGridCommonProps { diff --git a/frontend/config/macros/formdetails.ts b/frontend/config/macros/formdetails.ts index 974b1f41..214db676 100644 --- a/frontend/config/macros/formdetails.ts +++ b/frontend/config/macros/formdetails.ts @@ -342,6 +342,10 @@ export function getTableHeaders(formType: FormType, _usesSubquadrats = false): { return TableHeadersByFormType[formType]; } +export function getGridHeaders(gridType: DatagridType): { label: string; explanation?: string; category?: 'required' | 'optional' }[] { + return HeadersByDatagridType[gridType]; +} + export const RequiredTableHeadersByFormType: Record = { [FormType.attributes]: TableHeadersByFormType[FormType.attributes].filter(header => header.category === 'required'), [FormType.personnel]: TableHeadersByFormType[FormType.personnel].filter(header => header.category === 'required'), diff --git a/frontend/config/poolmonitor.ts b/frontend/config/poolmonitor.ts index f946d857..d8f0be3c 100644 --- a/frontend/config/poolmonitor.ts +++ b/frontend/config/poolmonitor.ts @@ -132,15 +132,17 @@ export class PoolMonitor { setInterval(async () => { try { const { sleeping } = await this.logAndReturnConnections(); - console.log(chalk.cyan('Pool Health Check:')); - console.log(chalk.yellow(`Sleeping connections: ${sleeping.length}`)); - - if (sleeping.length > 50) { - // Example threshold for excessive sleeping connections - console.warn(chalk.red('Too many sleeping connections. Reinitializing pool.')); - await this.reinitializePool(); - } else { - await this.terminateSleepingConnections(); + if (sleeping.length > 0) { + console.log(chalk.cyan('Pool Health Check:')); + console.log(chalk.yellow(`Sleeping connections: ${sleeping.length}`)); + + if (sleeping.length > 50) { + // Example threshold for excessive sleeping connections + console.warn(chalk.red('Too many sleeping connections. Reinitializing pool.')); + await this.reinitializePool(); + } else { + await this.terminateSleepingConnections(); + } } } catch (error) { console.error(chalk.red('Error during pool health check:', error)); diff --git a/frontend/config/utils.ts b/frontend/config/utils.ts index 743a0dde..ab4e6039 100644 --- a/frontend/config/utils.ts +++ b/frontend/config/utils.ts @@ -286,52 +286,3 @@ export function getTransformedKeys(): string[] { const exampleObject: ResultType = {} as ResultType; return Object.keys(exampleObject) as string[]; } - -type FilterItem = { - field: string; - operator: string; - value: any; -}; - -type FilterModel = { - items: FilterItem[]; - logicOperator: 'and' | 'or'; -}; - -function buildWhereClause(filterModel: FilterModel): string { - const operatorMap: { [key: string]: string } = { - contains: 'LIKE', - equals: '=', - startsWith: 'LIKE', - endsWith: 'LIKE', - '>': '>', - '<': '<', - '>=': '>=', - '<=': '<=' - }; - - const conditions = filterModel.items.map(item => { - const sqlOperator = operatorMap[item.operator]; - const field = capitalizeAndTransformField(item.field); - let condition = ''; - - switch (item.operator) { - case 'contains': - condition = `${field} ${sqlOperator} '%${item.value}%'`; - break; - case 'startsWith': - condition = `${field} ${sqlOperator} '${item.value}%'`; - break; - case 'endsWith': - condition = `${field} ${sqlOperator} '%${item.value}'`; - break; - default: - condition = `${field} ${sqlOperator} '${item.value}'`; - } - - return condition; - }); - - const combinedConditions = conditions.join(` ${filterModel.logicOperator.toUpperCase()} `); - return combinedConditions ? `WHERE ${combinedConditions}` : ''; -}