///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2025, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import React, { forwardRef, useEffect } from 'react'; import { flexRender } from '@tanstack/react-table'; import { styled } from '@mui/material/styles'; import PropTypes from 'prop-types'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import EditRoundedIcon from '@mui/icons-material/EditRounded'; import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; import { PgIconButton } from './Buttons'; import CustomPropTypes from '../custom_prop_types'; import { InputSwitch } from './FormComponents'; import { Checkbox } from '@mui/material'; import { getEnterKeyHandler } from '../utils'; const StyledDiv = styled('div')(({theme})=>({ '&.pgrt': { display: 'grid', overflow: 'auto', position: 'relative', flexGrow: 1, }, // by default the table has no outer border. // the parent container has to take care of border. '& .pgrt-table': { borderSpacing: 0, borderRadius: theme.shape.borderRadius, display: 'grid', gridAutoRows: 'max-content', flexGrow: 1, flexDirection: 'column', '& .pgrt-header': { position: 'sticky', top: 0, zIndex: 1, '& .pgrt-header-row': { height: '34px', display: 'flex', '& .pgrt-header-cell': { position: 'relative', fontWeight: theme.typography.fontWeightBold, padding: theme.spacing(0.5), textAlign: 'left', alignContent: 'center', backgroundColor: theme.otherVars.tableBg, overflow: 'hidden', ...theme.mixins.panelBorder.bottom, ...theme.mixins.panelBorder.right, '& > div': { overflow: 'hidden', textOverflow: 'ellipsis', textWrap: 'nowrap' }, '& .pgrt-header-resizer': { display: 'inline-block', width: '5px', height: '100%', position: 'absolute', right: 0, top: 0, transform: 'translateX(50%)', zIndex: 1, cursor: 'col-resize', } } } }, '& .pgrt-body': { position: 'relative', flexGrow: 1, minHeight: 0, '& .pgrt-row': { display: 'flex', flexDirection: 'column', position: 'absolute', width: '100%', '& .pgrt-row-content': { display: 'flex', minHeight: 0, '& .pgrd-row-cell': { margin: 0, padding: theme.spacing(0.25, 0.5), ...theme.mixins.panelBorder.bottom, ...theme.mixins.panelBorder.right, position: 'relative', height: '30px', display: 'flex', alignItems: 'flex-start', backgroundColor: theme.otherVars.tableBg, '&.btn-cell': { textAlign: 'center', }, '&.expanded-icon-cell': { backgroundColor: theme.palette.grey[400], borderBottom: 'none', }, '&.row-warning': { backgroundColor: theme.palette.warning.main + '!important' }, '&.row-alert': { backgroundColor: theme.palette.error.main + '!important' }, '&.cell-with-icon': { paddingLeft: '1.8em', borderRadius: 0, backgroundPosition: '1%', }, '& .pgrd-row-cell-content': { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', userSelect: 'text', width: '100%', }, '& .reorder-cell': { cursor: 'move', padding: '4px 2px', }, '& .pgrt-cell-button': { border: 0, borderRadius: 0, padding: 0, minWidth: 0, backgroundColor: 'inherit', '&.Mui-disabled': { border: 0, }, } } }, '& .pgrt-expanded-content': { ...theme.mixins.panelBorder.all, margin: '8px', flexGrow: 1, } } }, } })); export const PgReactTableCell = forwardRef(({row, cell, children, className}, ref)=>{ let classNames = ['pgrd-row-cell']; if (typeof (cell.column.id) == 'string' && cell.column.id.startsWith('btn-')) { classNames.push('btn-cell'); } if (cell.column.id == 'btn-edit' && row.getIsExpanded()) { classNames.push('expanded-icon-cell'); } if (row.original.row_type === 'warning') { classNames.push('row-warning'); } if (row.original.row_type === 'alert') { classNames.push('row-alert'); } if(row.original.icon?.[cell.column.id]) { classNames.push(row.original.icon[cell.column.id], 'cell-with-icon'); } if(cell.column.columnDef.dataClassName){ classNames.push(cell.column.columnDef.dataClassName); } classNames.push(className); return (
{children}
); }); PgReactTableCell.displayName = 'PgReactTableCell'; PgReactTableCell.propTypes = { row: PropTypes.object, cell: PropTypes.object, children: CustomPropTypes.children, className: PropTypes.any, }; export const PgReactTableRow = forwardRef(({ children, className, ...props }, ref)=>{ return (
{children}
); }); PgReactTableRow.displayName = 'PgReactTableRow'; PgReactTableRow.propTypes = { children: CustomPropTypes.children, className: PropTypes.any, }; export const PgReactTableRowContent = forwardRef(({children, className, ...props}, ref)=>{ return (
{children}
); }); PgReactTableRowContent.displayName = 'PgReactTableRowContent'; PgReactTableRowContent.propTypes = { children: CustomPropTypes.children, className: PropTypes.any, }; export function PgReactTableRowExpandContent({row, children}) { if(!row.getIsExpanded()) { return <>; } return (
{children}
); } PgReactTableRowExpandContent.propTypes = { row: PropTypes.object, children: CustomPropTypes.children, }; export function PgReactTableHeader({table}) { return (
{table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => (
{flexRender(header.column.columnDef.header, header.getContext())} {header.column.getCanSort() && header.column.getIsSorted() && {header.column.getIsSorted() == 'desc' ? : } }
{header.column.getCanResize() && (
header.column.resetSize()} onMouseDown={header.getResizeHandler()} onTouchStart={header.getResizeHandler()} className='pgrt-header-resizer' /> )}
))}
))}
); } PgReactTableHeader.propTypes = { table: PropTypes.object, }; export function PgReactTableBody({children, style}) { return (
{children}
); } PgReactTableBody.propTypes = { style: PropTypes.object, children: CustomPropTypes.children, }; export const PgReactTable = forwardRef(({children, table, rootClassName, tableClassName, onScrollFunc, ...props}, ref)=>{ const columns = table.getAllColumns(); useEffect(()=>{ const setMaxExpandWidth = ()=>{ if(ref.current) { ref.current.style['--expand-width'] = (ref.current.getBoundingClientRect().width ?? 430) - 30; //margin,scrollbar,etc. } }; const tableResizeObserver = new ResizeObserver(()=>{ setMaxExpandWidth(); }); tableResizeObserver.observe(ref.current); }, []); const columnSizeVars = React.useMemo(() => { const headers = table.getFlatHeaders(); const colSizes = {}; for (let value of headers) { const header = value; colSizes[`--header-${header.id.replace(/\W/g, '_')}-size`] = header.getSize(); colSizes[`--col-${header.column.id.replace(/\W/g, '_')}-size`] = header.column.getSize(); } return colSizes; }, [columns, table.getState().columnSizingInfo]); return ( onScrollFunc?.(e.target)}>
{children}
); }); PgReactTable.displayName = 'PgReactTable'; PgReactTable.propTypes = { table: PropTypes.object, rootClassName: PropTypes.any, tableClassName: PropTypes.any, children: CustomPropTypes.children, onScrollFunc: PropTypes.any, }; export function getExpandCell({ onClick, title }) { const Cell = ({ row }) => { const onClickFinal = (e) => { e.preventDefault(); row.toggleExpanded(); onClick?.(row, e); }; return ( ) : ( ) } noBorder onClick={onClickFinal} aria-label={title} /> ); }; Cell.displayName = 'ExpandCell'; Cell.propTypes = { row: PropTypes.any, }; return Cell; } export function getSwitchCell() { const Cell = ({ getValue }) => { return ; }; Cell.displayName = 'SwitchCell'; Cell.propTypes = { getValue: PropTypes.func, }; return Cell; } export function getCheckboxCell({title}) { const Cell = ({ table }) => { return (
); }; Cell.displayName = 'CheckboxCell'; Cell.propTypes = { table: PropTypes.object, }; return Cell; } export function getCheckboxHeaderCell({title}) { const Cell = ({ row }) => { return (
); }; Cell.displayName = 'CheckboxHeaderCell'; Cell.propTypes = { row: PropTypes.object, }; return Cell; } export function getEditCell({isDisabled, title, onClick}) { const Cell = ({ row }) => { return } className='pgrt-cell-button' onClick={()=>{ onClick ? onClick(row) : row.toggleExpanded(); }} disabled={isDisabled?.(row)} />; }; Cell.displayName = 'EditCell'; Cell.propTypes = { row: PropTypes.any, }; return Cell; } export function getDeleteCell({isDisabled, title, onClick}) { const Cell = ({ row }) => ( } onClick={()=>onClick?.(row)} className='pgrt-cell-button' disabled={isDisabled?.(row)} /> ); Cell.displayName = 'DeleteCell'; Cell.propTypes = { row: PropTypes.any, }; return Cell; }