Untitled

 avatar
unknown
plain_text
6 months ago
6.3 kB
3
Indexable
import React, { useState, ChangeEvent } from 'react';
import { useDrag, useDrop, DragSourceMonitor } from 'react-dnd';

const ItemType = 'TABLE_ITEM';

type DraggableTableProps = {
  columns: string[];
  rows: string[][];
  onRowChange: (updatedRows: string[][]) => void;
  onColumnChange: (updatedColumns: string[]) => void;
  onNewRow: (newRow: string[]) => void;
  enableDragAndDrop: boolean;
};

type DraggableCellProps = {
  children: React.ReactNode;
  columnIndex: number;
  rowIndex: number;
  moveRow: (fromIndex: number, toIndex: number, columnIndex: number) => void;
  moveColumn: (fromRowIndex: number, fromColumnIndex: number, toRowIndex: number, toColumnIndex: number) => void;
  enableDragAndDrop: boolean;
};

type DraggableHeaderProps = {
  children: React.ReactNode;
  columnIndex: number;
  moveColumn: (fromRowIndex: number, fromColumnIndex: number, toRowIndex: number, toColumnIndex: number) => void;
  enableDragAndDrop: boolean;
};

type DragItem = {
  rowIndex: number;
  columnIndex: number;
  isRow: boolean;
};

// Draggable Cell Component
const DraggableCell: React.FC<DraggableCellProps> = ({
  children,
  columnIndex,
  rowIndex,
  moveRow,
  moveColumn,
  enableDragAndDrop,
}) => {
  if (!enableDragAndDrop) {
    return <td>{children}</td>;
  }

  const [{ isDragging }, drag] = useDrag({
    type: ItemType,
    item: { rowIndex, columnIndex, isRow: false },
    collect: (monitor: DragSourceMonitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  const [, drop] = useDrop({
    accept: ItemType,
    hover: (item: DragItem) => {
      if (item.isRow) {
        moveRow(item.rowIndex, rowIndex, columnIndex);
      } else {
        moveColumn(item.rowIndex, item.columnIndex, rowIndex, columnIndex);
      }
    },
  });

  return (
    <td ref={(node) => drag(drop(node))} style={{ opacity: isDragging ? 0.5 : 1 }} className="draggable-cell">
      {children}
    </td>
  );
};

// Draggable Header Component
const DraggableHeader: React.FC<DraggableHeaderProps> = ({
  children,
  columnIndex,
  moveColumn,
  enableDragAndDrop,
}) => {
  if (!enableDragAndDrop) {
    return <th>{children}</th>;
  }

  const [, drag] = useDrag({
    type: ItemType,
    item: { rowIndex: -1, columnIndex, isRow: true },
  });

  return (
    <th ref={drag} className="draggable-header">
      {children}
    </th>
  );
};

// Reusable Table Component
const DraggableTable: React.FC<DraggableTableProps> = ({
  columns,
  rows,
  onRowChange,
  onColumnChange,
  onNewRow,
  enableDragAndDrop,
}) => {
  const [currentColumns, setColumns] = useState<string[]>(columns);
  const [currentRows, setRows] = useState<string[][]>(rows);
  const [newRow, setNewRow] = useState<string[]>(new Array(columns.length).fill(''));

  // Move a row to a new position
  const moveRow = (fromIndex: number, toIndex: number, columnIndex: number) => {
    const updatedRows = [...currentRows];
    const [movedRow] = updatedRows.splice(fromIndex, 1);
    updatedRows.splice(toIndex, 0, movedRow);
    setRows(updatedRows);
    onRowChange(updatedRows); // Notify parent of row change
  };

  // Move a column to a new position
  const moveColumn = (
    fromRowIndex: number,
    fromColumnIndex: number,
    toRowIndex: number,
    toColumnIndex: number
  ) => {
    const updatedColumns = [...currentColumns];
    const [movedColumn] = updatedColumns.splice(fromColumnIndex, 1);
    updatedColumns.splice(toColumnIndex, 0, movedColumn);
    setColumns(updatedColumns);

    // Reorder the rows based on the moved column
    const updatedRows = [...currentRows];
    updatedRows.forEach((row) => {
      const movedCell = row.splice(fromColumnIndex, 1);
      row.splice(toColumnIndex, 0, movedCell[0]);
    });

    setRows(updatedRows);
    onColumnChange(updatedColumns); // Notify parent of column change
  };

  // Handle input change for new row
  const handleNewRowChange = (e: ChangeEvent<HTMLInputElement>, columnIndex: number) => {
    const value = e.target.value;
    const updatedNewRow = [...newRow];
    updatedNewRow[columnIndex] = value;
    setNewRow(updatedNewRow);
  };

  // Handle adding the new row
  const handleAddNewRow = () => {
    if (newRow.some((cell) => cell !== '')) {
      const updatedRows = [...currentRows, newRow];
      setRows(updatedRows);
      onRowChange(updatedRows); // Notify parent of row change
      setNewRow(new Array(currentColumns.length).fill('')); // Clear the new row after adding
      onNewRow(newRow); // Notify parent about the new row added
    }
  };

  return (
    <div className="table-container">
      <table>
        <thead>
          <tr>
            {currentColumns.map((column, columnIndex) => (
              <DraggableHeader
                key={columnIndex}
                columnIndex={columnIndex}
                moveColumn={moveColumn}
                enableDragAndDrop={enableDragAndDrop}
              >
                {column}
              </DraggableHeader>
            ))}
          </tr>
        </thead>
        <tbody>
          {currentRows.map((row, rowIndex) => (
            <tr key={rowIndex}>
              {row.map((cell, columnIndex) => (
                <DraggableCell
                  key={columnIndex}
                  rowIndex={rowIndex}
                  columnIndex={columnIndex}
                  moveRow={moveRow}
                  moveColumn={moveColumn}
                  enableDragAndDrop={enableDragAndDrop}
                >
                  {cell}
                </DraggableCell>
              ))}
            </tr>
          ))}
          {/* Empty row at the bottom */}
          <tr>
            {newRow.map((cell, columnIndex) => (
              <td key={columnIndex}>
                <input
                  type="text"
                  value={cell}
                  onChange={(e) => handleNewRowChange(e, columnIndex)}
                  onBlur={handleAddNewRow} // Automatically add the row when focus leaves
                />
              </td>
            ))}
          </tr>
        </tbody>
      </table>
    </div>
  );
};

export default DraggableTable;
Editor is loading...
Leave a Comment