import isEmpty from 'lodash/isEmpty' ; import union from 'lodash/union' ; import { DispatchCell , JsonFormsStateContext , useJsonForms } from '@jsonforms/react' ; import startCase from 'lodash/startCase' ; import range from 'lodash/range' ; import React , { Fragment , useMemo } from 'react' ; import { FormHelperText , Grid , Hidden , IconButton , Table , TableBody , TableCell , TableHead , TableRow , Typography } from '@mui/material' ; import { ArrayLayoutProps , ControlElement , errorsAt , formatErrorMessage , JsonSchema , Paths , Resolve , JsonFormsRendererRegistryEntry , JsonFormsCellRendererRegistryEntry } from '@jsonforms/core' ; import DeleteIcon from '@mui/icons-material/Delete' ; import ArrowDownward from '@mui/icons-material/ArrowDownward' ; import ArrowUpward from '@mui/icons-material/ArrowUpward' ; import { WithDeleteDialogSupport } from './DeleteDialog' ; import NoBorderTableCell from './NoBorderTableCell' ; import TableToolbar from './TableToolbar' ; import { ErrorObject } from 'ajv' ; import merge from 'lodash/merge' ; const styles = { fixedCell : { width : '150px' , height : '50px' , paddingLeft : 0 , paddingRight : 0 , textAlign : 'center' } , fixedCellSmall : { width : '50px' , height : '50px' , paddingLeft : 0 , paddingRight : 0 , textAlign : 'center' } } ; const generateCells = ( Cell , schema , rowPath , enabled , cells ? ) => { if ( schema . type === 'object' ) { return getValidColumnProps ( schema ) . map ( prop => { const cellPath = Paths . compose ( rowPath , prop ) ; const props = { propName : prop , schema , title : schema . properties ?. [ prop ] ?. title ?? startCase ( prop ) , rowPath , cellPath , enabled , cells } ; return < Cell key = { cellPath } { ... props } />; } ) ; } else { const props = { schema , rowPath , cellPath : rowPath , enabled } ; return < Cell key = { rowPath } { ... props } />; } } ; const getValidColumnProps = ( scopedSchema ) => { if ( scopedSchema . type === 'object' && typeof scopedSchema . properties === 'object' ) { return Object . keys ( scopedSchema . properties ) . filter ( prop => scopedSchema . properties [ prop ] . type !== 'array' ) ; } return [ '' ] ; } ; export interface EmptyTableProps { numColumns : number ; } const EmptyTable = ( { numColumns } ) => ( < TableRow > < NoBorderTableCell colSpan = { numColumns } > < Typography align = 'center' > No data < /Typography> < /NoBorderTableCell> < /TableRow>); interface TableHeaderCellProps { title : string ; } const TableHeaderCell = React . memo ( ( { title } ) => ( < TableCell > { title } < /TableCell>)); interface NonEmptyCellProps extends OwnPropsOfNonEmptyCell { rootSchema : JsonSchema ; errors : string ; path : string ; enabled : boolean ; } interface OwnPropsOfNonEmptyCell { rowPath : string ; propName ? : string ; schema : JsonSchema ; enabled : boolean ; renderers ? : JsonFormsRendererRegistryEntry [ ] ; cells ? : JsonFormsCellRendererRegistryEntry [ ] ; } const ctxToNonEmptyCellProps = ( ctx , ownProps ) : NonEmptyCellProps => { const path = ownProps . rowPath + ( ownProps . schema . type === 'object' ? '.' + ownProps . propName : '' ) ; const errors = formatErrorMessage ( union ( errorsAt ( path , ownProps . schema , p => p === path ) ( ctx . core . errors ) . map ( ( error ) => error . message ) ) ) ; return { rowPath : ownProps . rowPath , propName : ownProps . propName , schema : ownProps . schema , rootSchema : ctx . core . schema , errors , path , enabled : ownProps . enabled , cells : ownProps . cells || ctx . cells , renderers : ownProps . renderers || ctx . renderers } ; } ; const controlWithoutLabel = ( scope ) : ControlElement => ( { type : 'Control' , scope : scope , label : false } ) ; interface NonEmptyCellComponentProps { path : string ; propName ? : string ; schema : JsonSchema ; rootSchema : JsonSchema ; errors : string ; enabled : boolean ; renderers ? : JsonFormsRendererRegistryEntry [ ] ; cells ? : JsonFormsCellRendererRegistryEntry [ ] ; isValid : boolean ; } const NonEmptyCellComponent = React . memo ( ( { path , propName , schema , rootSchema , errors , enabled , renderers , cells , isValid } ) => { return ( < NoBorderTableCell > { schema . properties ? ( < DispatchCell schema = { Resolve . schema ( schema , `#/properties/${ propName }` , rootSchema ) } uischema = { controlWithoutLabel ( `#/properties/${ propName } `)} path={path} enabled={enabled} renderers={renderers} cells={cells}/>) : (<DispatchCell schema={schema} uischema={controlWithoutLabel('#')} path={path} enabled={enabled} renderers={renderers} cells={cells}/>)}
      <FormHelperText error={!isValid}>{!isValid && errors}</FormHelperText>
    </NoBorderTableCell>);
});
const NonEmptyCell = (ownProps) => {
    const ctx = useJsonForms();
    const emptyCellProps = ctxToNonEmptyCellProps(ctx, ownProps);
    const isValid = isEmpty(emptyCellProps.errors);
    return <NonEmptyCellComponent {...emptyCellProps} isValid={isValid}/>;
};
interface NonEmptyRowProps {
    childPath: string;
    schema: JsonSchema;
    rowIndex: number;
    moveUpCreator: (path: string, position: number) => () => void;
    moveDownCreator: (path: string, position: number) => () => void;
    enableUp: boolean;
    enableDown: boolean;
    showSortButtons: boolean;
    enabled: boolean;
    cells?: JsonFormsCellRendererRegistryEntry[];
    path: string;
}
const NonEmptyRowComponent = ({ childPath, schema, rowIndex, openDeleteDialog, moveUpCreator, moveDownCreator, enableUp, enableDown, showSortButtons, enabled, cells, path }) => {
    const moveUp = useMemo(() => moveUpCreator(path, rowIndex), [moveUpCreator, path, rowIndex]);
    const moveDown = useMemo(() => moveDownCreator(path, rowIndex), [moveDownCreator, path, rowIndex]);
    return (<TableRow key={childPath} hover>
        {generateCells(NonEmptyCell, schema, childPath, enabled, cells)}
        {enabled ? (<NoBorderTableCell style={showSortButtons ? styles.fixedCell : styles.fixedCellSmall}>
            <Grid container direction='row' justifyContent='flex-end' alignItems='center'>
              {showSortButtons ? (<Fragment>
                  <Grid item>
                    <IconButton aria-label={` Move up `} onClick={moveUp} disabled={!enableUp} size='large'>
                      <ArrowUpward />
                    </IconButton>
                  </Grid>
                  <Grid item>
                    <IconButton aria-label={` Move down `} onClick={moveDown} disabled={!enableDown} size='large'>
                      <ArrowDownward />
                    </IconButton>
                  </Grid>
                </Fragment>) : null}
              <Grid item>
                <IconButton aria-label={` Delete `} onClick={() => openDeleteDialog(childPath, rowIndex)} size='large'>
                  <DeleteIcon />
                </IconButton>
              </Grid>
            </Grid>
          </NoBorderTableCell>) : null}
      </TableRow>);
};
export const NonEmptyRow = React.memo(NonEmptyRowComponent);
interface TableRowsProp {
    data: number;
    path: string;
    schema: JsonSchema;
    uischema: ControlElement;
    config?: any;
    enabled: boolean;
    cells?: JsonFormsCellRendererRegistryEntry[];
    moveUp?(path, toMove): () => void;
    moveDown?(path, toMove): () => void;
}
const TableRows = ({ data, path, schema, openDeleteDialog, moveUp, moveDown, uischema, config, enabled, cells }) => {
    const isEmptyTable = data === 0;
    if (isEmptyTable) {
        return <EmptyTable numColumns={getValidColumnProps(schema).length + 1}/>;
    }
    const appliedUiSchemaOptions = merge({}, config, uischema.options);
    return (<React.Fragment>
      {range(data).map((index) => {
            const childPath = Paths.compose(path, ` $ { index } `);
            return (<NonEmptyRow key={childPath} childPath={childPath} rowIndex={index} schema={schema} openDeleteDialog={openDeleteDialog} moveUpCreator={moveUp} moveDownCreator={moveDown} enableUp={index !== 0} enableDown={index !== data - 1} showSortButtons={appliedUiSchemaOptions.showSortButtons} enabled={enabled} cells={cells} path={path}/>);
        })}
    </React.Fragment>);
};
export class MaterialTableControl extends React.Component<ArrayLayoutProps & WithDeleteDialogSupport, any> {
    addItem = (path, value) => this.props.addItem(path, value);
    render() {
        const { label, path, schema, rootSchema, uischema, errors, openDeleteDialog, visible, enabled, cells } = this.props;
        const controlElement = uischema as ControlElement;
        const isObjectSchema = schema.type === 'object';
        const headerCells = isObjectSchema
            ? generateCells(TableHeaderCell, schema, path, enabled, cells)
            : undefined;
        return (<Hidden xsUp={!visible}>
        <Table>
          <TableHead>
            <TableToolbar errors={errors} label={label} addItem={this.addItem} numColumns={isObjectSchema ? headerCells.length : 1} path={path} uischema={controlElement} schema={schema} rootSchema={rootSchema} enabled={enabled}/>
            {isObjectSchema && (<TableRow>
                {headerCells}
                {enabled ? <TableCell /> : null}
              </TableRow>)}
          </TableHead>
          <TableBody>
            <TableRows openDeleteDialog={openDeleteDialog} {...this.props}/>
          </TableBody>
        </Table>
      </Hidden>);
    }
}
