silevis / reactgrid

Add spreadsheet-like behavior to your React app
https://reactgrid.com
MIT License
1.19k stars 131 forks source link

When dragging a column, the columns array has changed, but it has no effect #411

Closed Iswen closed 3 months ago

Iswen commented 3 months ago

import {useState} from "react"; import { ReactGrid, Column, Row,DefaultCellTypes,CellChange,TextCell } from "@silevis/reactgrid"; import "@silevis/reactgrid/styles.css";

interface Person { id: number; component: string; taskType: string; remark:string; }

const columnMap = { component: 'Component', taskType: 'Task Type', remark: 'Remark', }

//reorderable 排序 ; resizable调整宽度 const getColumns = ()=>[ { columnId: "component", width: 200 ,resizable: true,reorderable: true}, { columnId: "taskType", width: 200 ,reorderable: true}, { columnId: "remark", width: 400,reorderable: true }, ];

const getPeople = (): Person[] => [ { id:1, component: 'c11', taskType: "Story" , remark:'000000000' }, { id:2, component: 'c22',
taskType: "Work" , remark:'2323232323232' }, { id:3, component:'c33',
taskType: "Bug" , remark:'6666666666666' }, { id:4, component:'c44',
taskType: "Bug" , remark:'6666666666666' }, { id:5, component:'c55', taskType: "Bug" , remark:'6666666666666' } ];

const getRows = (people: Person[],columnsOrder: any[]): Row[] => { return ( [ { rowId: "header", cells: [ { type: "header", text: columnMap[columnsOrder[0]] }, { type: "header", text: columnMap[columnsOrder[1]] }, { type: "header", text: columnMap[columnsOrder[2]] }, ] }, ...people.map<Row>((person, idx) => ({ rowId: idx, reorderable: true, cells: [ { type: "text", text: person[columnsOrder[0]] }, { type: "text", text: person[columnsOrder[1]] }, { type: "text", text: person[columnsOrder[2]] }, ] })) ] ) };

export default function ReactGridDemo() { const [cellChangesIndex, setCellChangesIndex] = useState(() => -1); const [cellChanges, setCellChanges] = useState<CellChange[][]>(() => []); const [columns, setColumns] = useState<Column[]>(getColumns()); const [rows,setRows] = useState(getRows(getPeople(),columns.map(c => c.columnId)));

const applyChangesToPeople = ( changes: CellChange[], prevPeople: Person[] ): Person[] => { const updated = applyNewValue(changes, prevPeople); setCellChanges([...cellChanges.slice(0, cellChangesIndex + 1), changes]); setCellChangesIndex(cellChangesIndex + 1); return updated; };

const handleChanges = (changes:CellChange[]) => { setRows((prevPeople) => applyChangesToPeople(changes, prevPeople)); };

const applyNewValue = ( changes: CellChange[], prevPeople: Person[], usePrevValue: boolean = false ): Person[] => { changes.forEach((change) => { const changeRowIdx = prevPeople.findIndex( (el) => el.rowId === change.rowId ); const changeColumnIdx = columns.findIndex( (el) => el.columnId === change.columnId ); const cell = usePrevValue ? change.previousCell : change.newCell; prevPeople[changeRowIdx].cells[changeColumnIdx] = cell; }); return [...prevPeople]; };

const handleColumnResize = (ci: Id, width: number) => { setColumns((prevColumns) => { const columnIndex = prevColumns.findIndex(el => el.columnId === ci); const resizedColumn = prevColumns[columnIndex]; const updatedColumn = { ...resizedColumn, width }; prevColumns[columnIndex] = updatedColumn; return [...prevColumns]; }); }

//排序 const reorderArray = <T extends {}>(arr: T[], idxs: number[], to: number) => { const movedElements = arr.filter((, idx) => idxs.includes(idx)); const targetIdx = Math.min(...idxs) < to ? to += 1 : to -= idxs.filter(idx => idx < to).length; const leftSide = arr.filter((, idx) => idx < targetIdx && !idxs.includes(idx)); const rightSide = arr.filter((_, idx) => idx >= targetIdx && !idxs.includes(idx)); return [...leftSide, ...movedElements, ...rightSide]; }

const handleColumnsReorder = (targetColumnId: Id, columnIds: Id[]) => { setColumns(prevColumns => { const to = columns.findIndex((column) => column.columnId === targetColumnId); const columnIdxs = columnIds.map((columnId) => columns.findIndex((c) => c.columnId === columnId)); return reorderArray(prevColumns, columnIdxs, to) }); }

const handleRowsReorder = (targetRowId: Id, rowIds: Id[]) => { setRows((prevPeople) => { const to = rows.findIndex(person => person.rowId === targetRowId); const rowsIds = rowIds.map((id) => rows.findIndex(person => person.rowId === id)); return reorderArray(prevPeople, rowsIds, to); }); }

const handleCanReorderRows = (targetRowId: Id, rowIds: Id[]): boolean => { return targetRowId !== 'header'; }

console.log('columns',columns);

return ( <ReactGrid rows={rows} columns={columns} onCellsChanged={handleChanges} onColumnResized={handleColumnResize} onColumnsReordered={handleColumnsReorder} onRowsReordered={handleRowsReorder} canReorderRows={handleCanReorderRows}//删除行会被调用 enableFillHandle//粘贴 enableRangeSelection//选择区域 enableRowSelection//点击第一个选择行 enableColumnSelection//选择列 /> ) }

image

Iswen commented 3 months ago

I used a simple case, but I don't know if it's a code problem or other reasons, dragging the column doesn't work. Can you please help me find out what the problem is?

webloopbox commented 3 months ago

Hi @Iswen, it seems you've forgotten to reorder the row cells in handleColumnsReorder.

The corrected handler function should look like this:


const handleColumnsReorder = (targetColumnId: Id, columnIds: Id[]) => {
    const to = columns.findIndex(
      (column: Column) => column.columnId === targetColumnId
    );
    const columnIdxs = columnIds.map((id: Id, idx: number) =>
      columns.findIndex((c: Column) => c.columnId === id)
    );

    setRows(
      rows.map((row) => ({
        ...row,
        cells: reorderArray(row.cells, columnIdxs, to),
      }))
    );

    setColumns((prevColumns) => {
      return reorderArray(prevColumns, columnIdxs, to);
    });
  };
Iswen commented 2 months ago

Thank you for your answer. For the current simple case, adding row sorting to column sorting does solve my problem, but I found a new problem. When adding a custom template to the case, such as DropdownNumberCellTemplate, the code for this is in the reactgrid-samples project. Since useState is used in this template to store the isOpen status, an error will occur when superimposing column sorting. The error message is Rendered fewer hooks than expected. This may be caused by an accidental early return statement. If useState is removed from the template, the sorting is normal, but I generally need to use certain state changes in custom templates, so is there any good solution?

代码示例如下:

import {useState} from "react";
import { ReactGrid, Column, Row,DefaultCellTypes ,CellChange,TextCell } from "@silevis/reactgrid";
import "@silevis/reactgrid/styles.css";
import {DropdownNumberCellTemplate,DropdownNumberCell} from '../Template/dropdownNumberCell/DropdownNumberCellTemplate.tsx';

interface Person {
  id: number;
  component: any;
  taskType: string;
  remark:string;
}

const columnMap = {
  component: 'Component',
  taskType: 'Task Type',
  dispatch: 'Dispatch To',
  point: 'Frontend Developer(Point: Estimate/Actual)',
  tag: 'Tag',
  remark: 'Remark',
  flag: 'Flag',
}

//reorderable 排序 ; resizable调整宽度
const getColumns = ()=>[
  { columnId: "component", width: 200 ,resizable: true,reorderable: true},
  { columnId: "taskType", width: 200 ,reorderable: true},
  { columnId: "remark", width: 300,reorderable: true },
  { columnId: "flag", width: 150 ,reorderable: true },
];

const getPeople = (): Person[] => [
  { 
    id:1,
    component: {
      name:'c11',
      componentId:'111',
      number:'CXX01'
    }, 
    taskType: "Story" ,
    remark:'000000000'
  },
  { 
    id:2,
    component: {
      name:'c22',
      componentId:'222',
      number:'CXX02'
    },  
    taskType: "Work" ,
    remark:'2323232323232'
  },
  { 
    id:3,
    component:{
      name:'c33',
      componentId:'333',
      number:'CXX3'
    },  
    taskType: "Bug" ,
    remark:'6666666666666'
  },
  { 
    id:4,
    component:{
      name:'c44',
      componentId:'444',
      number:'CXX4'
    },  
    taskType: "Bug" ,
    remark:'6666666666666'
  },
  { 
    id:5,
    component:{
      name:'c55',
      componentId:'555',
      number:'CXX5'
    },  
    taskType: "Bug" ,
    remark:'6666666666666'
  }
];

const getRows = (people: Person[],columnsOrder: ColumnId[]): Row<DefaultCellTypes  | DropdownNumberCell>[] => {
  return (
    [
      {
        rowId: "header",
        cells: [
          { type: "header", text: columnMap[columnsOrder[0]] },
          { type: "header", text: columnMap[columnsOrder[1]] },
          { type: "header", text: columnMap[columnsOrder[2]] },
          { type: "header", text: columnMap[columnsOrder[3]] },
        ]
      },
      ...people.map<Row<DefaultCellTypes | DropdownNumberCell>>((person, idx) => ({
        rowId: idx,
        reorderable: true,
        cells: [
          { type: "text", text: person[columnsOrder[0]]?.name },
          { type: "text", text: person[columnsOrder[1]] },
          { type: "text", text: person[columnsOrder[2]] },
          { type: 'dropdownNumber', value: 50, isOpened: false },
        ]
      }))
    ]
  )
};

export default function ReactGridDemo() {
  const [cellChangesIndex, setCellChangesIndex] = useState(() => -1);
  const [cellChanges, setCellChanges] = useState<CellChange<TextCell>[][]>(() => []);
  const [columns, setColumns] = useState<Column[]>(getColumns());
  const [rows,setRows] = useState<any>(getRows(getPeople(),columns.map(c => c.columnId)));

  const applyChangesToPeople = (
    changes: CellChange<TextCell>[],
    prevPeople: Person[]
  ): Person[] => {
    const updated = applyNewValue(changes, prevPeople);
    setCellChanges([...cellChanges.slice(0, cellChangesIndex + 1), changes]);
    setCellChangesIndex(cellChangesIndex + 1);
    return updated;
  };

  const handleChanges = (changes:CellChange<TextCell>[]) => {
    setRows((prevPeople) => applyChangesToPeople(changes, prevPeople));
  };

  const applyNewValue = (
    changes: CellChange<TextCell>[],
    prevPeople: Person[],
    usePrevValue: boolean = false
  ): Person[] => {
    changes.forEach((change) => {
      const changeRowIdx = prevPeople.findIndex(
        (el) => el.rowId === change.rowId
      );
      const changeColumnIdx = columns.findIndex(
        (el) => el.columnId === change.columnId
      );
      const cell = usePrevValue ? change.previousCell : change.newCell;
      prevPeople[changeRowIdx].cells[changeColumnIdx] = cell;
    });
    return [...prevPeople];
  };

  const handleColumnResize = (ci: Id, width: number) => {
    setColumns((prevColumns) => {
        const columnIndex = prevColumns.findIndex(el => el.columnId === ci);
        const resizedColumn = prevColumns[columnIndex];
        const updatedColumn = { ...resizedColumn, width };
        prevColumns[columnIndex] = updatedColumn;
        return [...prevColumns];
    });
  }

  //排序
  const reorderArray = <T extends {}>(arr: T[], idxs: number[], to: number) => {
    const movedElements = arr.filter((_, idx) => idxs.includes(idx));
    const targetIdx = Math.min(...idxs) < to ? to += 1 : to -= idxs.filter(idx => idx < to).length;
    const leftSide = arr.filter((_, idx) => idx < targetIdx && !idxs.includes(idx));
    const rightSide = arr.filter((_, idx) => idx >= targetIdx && !idxs.includes(idx));
    return [...leftSide, ...movedElements, ...rightSide];
  }

  const handleColumnsReorder = (targetColumnId: Id, columnIds: Id[]) => {
    const to = columns.findIndex((column) => column.columnId === targetColumnId);
    const columnIdxs = columnIds.map((columnId) => columns.findIndex((c) => c.columnId === columnId));
    setColumns(prevColumns => reorderArray(prevColumns, columnIdxs, to));
    const newArr = rows.map((row) => ({...row,cells: reorderArray(row.cells, columnIdxs, to)}));
    console.log('newArr',newArr);
    setRows(rows.map((row) => ({...row,cells: reorderArray(row.cells, columnIdxs, to)})));
  }

  const handleRowsReorder = (targetRowId: Id, rowIds: Id[]) => {
    setRows((prevPeople) => {
      const to = rows.findIndex(person => person.rowId === targetRowId);
      const rowsIds = rowIds.map((id) => rows.findIndex(person => person.rowId === id));
      return reorderArray(prevPeople, rowsIds, to);
    });
  }

  const handleCanReorderRows = (targetRowId: Id, rowIds: Id[]): boolean => {
    return targetRowId !== 'header';
  }

  return (
    <ReactGrid 
      rows={rows} 
      columns={columns} 
      customCellTemplates={{ 
        'dropdownNumber': new DropdownNumberCellTemplate(),
      }}
      onCellsChanged={handleChanges} 
      onColumnResized={handleColumnResize}
      onColumnsReordered={handleColumnsReorder}
      onRowsReordered={handleRowsReorder}
      canReorderRows={handleCanReorderRows}//删除行会被调用
      enableFillHandle//粘贴
      enableRangeSelection//选择区域
      enableRowSelection//点击第一个选择行
      enableColumnSelection//选择列
    />
  )
}

img:

image

2024-06-28 10-15-19.zip

Iswen commented 2 months ago

Sorry to bother you.@webloopbox