mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.14k stars 1.29k forks source link

[data grid] is there anyway to avoid copy paste functionality from one column to another? #13847

Open JahanzaibAbdullah opened 3 months ago

JahanzaibAbdullah commented 3 months ago

The problem in depth

Hi there,

I want to avoid copy and pasting the data from one column to another in datagrid and i am unable to do it as there is no prop to do it.

find some attached Screenshots i am sharing with you.

image

in the above picture you can see, different columns, and it has different data, i want to make sure that Verified RAD center data should not be pasted in anu other column and likewise for other columns as well,

Screenshot 2024-07-15 184145

i am providing you my code snippet as well,

const ResponsesColumns: GridColDef[] = [
  {
    field: 'locationId',
    headerName: 'Location ID',
    minWidth: 140,
    flex: 0.1,
    display: 'flex',
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              LOCATION ID
            </Typography>
          )}
        </>
      );
    },
    renderCell: (params: any) => (
      <>
        {loading ? (
          <Skeleton width={130} />
        ) : (
          <Box
            sx={{
              '& a': {
                color: 'text.primary',
                textDecoration: 'none',
                fontWeight: '500',
                '&:hover': {
                  textDecoration: 'underline',
                },
              },
            }}
            onClick={() => {
              TaskDetailsDialog.handleOpen();
            }}
          >
            <NextLink href="">{params.value}</NextLink>
          </Box>
        )}
      </>
    ),
  },
  {
    field: 'verifiedRADCenter',
    headerName: 'Verified RAD Center',
    minWidth: 220,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Verified RAD Center
            </Typography>
          )}
        </>
      );
    },
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'structuralAnalysisReceived',
    headerName: 'Structural Analysis Received',
    minWidth: 260,
    flex: 0.1,
    editable: true,
    display: 'flex',
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Structural Analysis Received
            </Typography>
          )}
        </>
      );
    },
    renderEditCell: (params) => (
      <Select
        value={params.value}
        onChange={(e) =>
          params.api.setEditCellValue({
            id: params.id,
            field: params.field,
            value: e.target.value,
          })
        }
        fullWidth
      >
        <MenuItem value="Yes">Yes</MenuItem>
        <MenuItem value="No">No</MenuItem>
      </Select>
    ),
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'utilityAppSubmitted',
    headerName: 'Utility App Submitted',
    minWidth: 220,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Utility App Submitted
            </Typography>
          )}
        </>
      );
    },
    renderEditCell: (params) => (
      <Select
        value={params.value}
        onChange={(e) =>
          params.api.setEditCellValue({
            id: params.id,
            field: params.field,
            value: e.target.value,
          })
        }
        fullWidth
      >
        <MenuItem value="Yes">Yes</MenuItem>
        <MenuItem value="No">No</MenuItem>
      </Select>
    ),
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'powerWalkRequired',
    headerName: 'Power Walk Required',
    minWidth: 220,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Power Walk Required
            </Typography>
          )}
        </>
      );
    },
    renderEditCell: (params) => (
      <Select
        value={params.value}
        onChange={(e) =>
          params.api.setEditCellValue({
            id: params.id,
            field: params.field,
            value: e.target.value,
          })
        }
        fullWidth
      >
        <MenuItem value="Yes">Yes</MenuItem>
        <MenuItem value="No">No</MenuItem>
      </Select>
    ),
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'leaseReviewed',
    headerName: 'Lease Reviewed',
    minWidth: 180,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Lease Reviewed
            </Typography>
          )}
        </>
      );
    },
    renderEditCell: (params) => (
      <Select
        value={params.value}
        onChange={(e) =>
          params.api.setEditCellValue({
            id: params.id,
            field: params.field,
            value: e.target.value,
          })
        }
        fullWidth
      >
        <MenuItem value="Yes">Yes</MenuItem>
        <MenuItem value="No">No</MenuItem>
      </Select>
    ),
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'routedforExecution',
    headerName: 'Routed For Execution',
    minWidth: 220,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Routed For Execution
            </Typography>
          )}
        </>
      );
    },
    renderEditCell: (params) => (
      <Select
        value={params.value}
        onChange={(e) =>
          params.api.setEditCellValue({
            id: params.id,
            field: params.field,
            value: e.target.value,
          })
        }
        fullWidth
      >
        <MenuItem value="Yes">Yes</MenuItem>
        <MenuItem value="No">No</MenuItem>
      </Select>
    ),
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'typeofZoning',
    headerName: 'Type of Zoning',
    minWidth: 180,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Type of Zoning
            </Typography>
          )}
        </>
      );
    },
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'zoningDuration',
    headerName: 'Zoning Duration',
    minWidth: 180,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              Zoning Duration
            </Typography>
          )}
        </>
      );
    },
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'GCBidDate',
    headerName: 'GC Bid Date',
    editable: true,
    minWidth: 160,
    flex: 0.1,
    display: 'flex',
    type: 'date',
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              GC Bid Date
            </Typography>
          )}
        </>
      );
    },
    renderCell: (params: any) => (
      <>
        {loading ? (
          <Skeleton
            animation="wave"
            variant="rounded"
            width={150}
          />
        ) : (
          <Box>{formatCustomCompleteDate(params.value, storedDateFormat)}</Box>
        )}
      </>
    ),
  },
  {
    field: 'GC_WO_Issued',
    headerName: 'GC WO Issued',
    minWidth: 160,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              GC WO Issued
            </Typography>
          )}
        </>
      );
    },
    renderEditCell: (params) => (
      <Select
        value={params.value}
        onChange={(e) =>
          params.api.setEditCellValue({
            id: params.id,
            field: params.field,
            value: e.target.value,
          })
        }
        fullWidth
      >
        <MenuItem value="Yes">Yes</MenuItem>
        <MenuItem value="No">No</MenuItem>
      </Select>
    ),
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
  {
    field: 'cost',
    headerName: 'Labour Cost ($)',
    minWidth: 160,
    flex: 0.1,
    display: 'flex',
    editable: true,
    renderHeader: () => {
      return (
        <>
          {loading ? (
            <Skeleton
              animation="wave"
              variant="rounded"
              width={150}
            />
          ) : (
            <Typography
              variant="subtitle2"
              fontWeight={600}
            >
              {'Labour Cost ($)'}
            </Typography>
          )}
        </>
      );
    },
    renderCell: (params: any) => <>{loading ? <Skeleton width={130} /> : params.value}</>,
  },
];
<StyledDataGrid
  rows={rowsResponses}
  columns={ResponsesColumns}
  columnGroupingModel={columnResponsesGroup}
  onBeforeClipboardPasteStart={confirmPaste}
  density="compact"
  disableClipboardPaste
  // onDensityChange={(newDensity) => setDensity(newDensity)}
  className="editableDrawerTables multiple-headers"
  sx={dataGridStylesListView}
  pagination
  initialState={{
    pagination: {
      paginationModel: {
        pageSize: 100,
      },
    },
    sorting: { sortModel: [{ field: 'date', sort: 'desc' }] },
    pinnedColumns: {
      left: [GRID_CHECKBOX_SELECTION_COL_DEF.field, 'locationId'],
    },
  }}
  disableVirtualization
  cellSelection
  getRowClassName={(params) => `super-app-theme--${params.row.status}`}
  pageSizeOptions={[25, 50, 100]}
  checkboxSelection={true}
  disableRowSelectionOnClick
  disableAggregation
  disableRowGrouping
  onCellEditStop={(params: GridCellEditStopParams, event: MuiEvent) => {
    if (params.reason === GridCellEditStopReasons.cellFocusOut) {
      event.defaultMuiPrevented = true;
    }
  }}
  slots={{
    toolbar: () => (
      <CustomToolbar
        loadingState={loading}
        showCreateButton={false}
        showTrashButton={false}
        buttonText="Create"
        showCustomIconButtons={true}
        showCustomMenuAutoComplete={false}
        showEllipsisMenuButton={false}
        showEllipsisButton={false}
        menuItems={[
          {
            text: 'Change Status',
            onClick: () => {
              handleClose();
            },
          },
          {
            text: 'Change Due Date',
            onClick: () => {
              handleClose();
            },
          },
        ]}
        showEllipsisButtonNoCheck={true}
        menuItemsNoCheck={[
          {
            text: 'Set as default view',
            onClick: () => {
              handleSetDefaultView();
            },
          },
        ]}
        showCustomMenuButton2={true}
        customMenuButtonProps2={{
          endIcon: faChevronDown,
          buttonColor: 'primary',
          buttonText: 'Responses',
          buttonVariant: 'outlined',
          menuItems: [
            {
              title: 'Task Assignment',
              onClick: () => {
                handleClose();
                setTaskAssignment(true);
                setMilestoneView(false);
                setDateView(false);
                setResponsesView(false);
                setListView(false);
                setBoardView(false);
                setToggleButtonOption(null);
              },
            },
            {
              title: 'Responses',
              onClick: () => {
                handleClose();
                setResponsesView(true);
                setTaskAssignment(false);
                setMilestoneView(false);
                setDateView(false);
                setListView(false);
                setBoardView(false);
                setToggleButtonOption(null);
              },
            },
            {
              title: 'Dates',
              onClick: () => {
                handleClose();
                setDateView(true);
                setTaskAssignment(false);
                setMilestoneView(false);
                setResponsesView(false);
                setListView(false);
                setBoardView(false);
                setToggleButtonOption(null);
              },
            },
            {
              title: 'divider',
              onClick: () => {
                console.log('Divider');
              },
            },
            {
              title: 'Custom',
              onClick: () => {
                DrawerOpen();
                setActiveStep(0);
                handleClose();
                setToggleButtonOption(null);
              },
            },
          ],
        }}
        customMenuButtonPropsAutoComplete={{
          endIcon: faChevronDown,
          buttonColor: 'primary',
          buttonText: 'Responses',
          buttonVariant: 'outlined',
          customComponent: (
            <Autocomplete
              options={[
                // 'Bulk Update',
                'Task Assignment',
                'Responses',
                'Dates',
                'Custom',
              ]}
              value={selectedBulkLocation}
              onChange={(event, newValue) => {
                if (newValue === 'Task Assignment') {
                  setTaskAssignment(true);
                  setMilestoneView(false);
                  setDateView(false);
                  setResponsesView(false);
                  setListView(false);
                  setBoardView(false);
                  setToggleButtonOption(null);
                } else if (newValue === 'Responses') {
                  setResponsesView(true);
                  setTaskAssignment(false);
                  setMilestoneView(false);
                  setDateView(false);
                  setListView(false);
                  setBoardView(false);
                  setToggleButtonOption(null);
                } else if (newValue === 'Dates') {
                  setDateView(true);
                  setTaskAssignment(false);
                  setMilestoneView(false);
                  setResponsesView(false);
                  setListView(false);
                  setBoardView(false);
                  setToggleButtonOption(null);
                } else if (newValue === 'Custom') {
                  DrawerOpen();
                  setActiveStep(0);
                  setCustomView(true);
                  setDateView(false);
                  setTaskAssignment(false);
                  setMilestoneView(false);
                  setResponsesView(false);
                  setListView(false);
                  setBoardView(false);
                  setToggleButtonOption(null);
                }
                setSelectedBulkLocation(newValue ?? '');
              }}
              renderInput={(params) => (
                <TextField
                  {...params}
                  label="Bulk Update"
                  variant="outlined"
                  size="small"
                  sx={{
                    width: '180px',
                    height: '42px',
                    '& .MuiOutlinedInput-root.MuiInputBase-sizeSmall': {
                      paddingTop: '9px',
                      paddingBottom: '8px',
                    },
                  }}
                />
              )}
              clearOnEscape
              disableClearable={!selectedBulkLocation} // disable clear button when no value is selected
            />
          ),
        }}
        showExportButton={false}
        showDensitySelector={false}
        showColumnsButton={false}
        toolbarSwitchView={
          <>
            <ToggleButtonGroup
              color="primary"
              value={toggleButtonOption}
              exclusive
              onChange={handleChange}
              aria-label="Platform"
              sx={{ height: '42px' }}
            >
              <ToggleButton
                sx={{ textTransform: 'none' }}
                value="Milestone"
                onClick={() => {
                  setBoardView(false);
                  setDateView(false);
                  setTaskAssignment(false);
                  setMilestoneView(true);
                  setResponsesView(false);
                  setListView(false);
                  setSelectedBulkLocation('');
                }}
              >
                Milestone
              </ToggleButton>
              <ToggleButton
                sx={{ textTransform: 'none' }}
                value="List"
                onClick={() => {
                  setBoardView(false);
                  setDateView(false);
                  setTaskAssignment(false);
                  setMilestoneView(false);
                  setResponsesView(false);
                  setListView(true);
                  setSelectedBulkLocation('');
                }}
              >
                List
              </ToggleButton>
              <ToggleButton
                sx={{ textTransform: 'none' }}
                value="Board"
                onClick={() => {
                  setBoardView(true);
                  setDateView(false);
                  setTaskAssignment(false);
                  setMilestoneView(false);
                  setResponsesView(false);
                  setListView(false);
                  setSelectedBulkLocation('');
                }}
              >
                Board
              </ToggleButton>
            </ToggleButtonGroup>
          </>
        }
        RowSelectionModel={rowSelectionModel}
      />
    ),
    noRowsOverlay: CustomNoRowsOverlay,
    noResultsOverlay: CustomNoResultOverlay,
  }}
  slotProps={{
    columnsPanel: {
      sx: {
        [`& .MuiDataGrid-columnsManagement > label:first-child`]: {
          display: 'none',
        },
      },
    },
  }}
  onRowSelectionModelChange={(newRowSelectionModel) => {
    {
      newRowSelectionModel.length > 0 ? setRowSelectionModel(true) : setRowSelectionModel(false);
    }
  }}
/>;

Thank you for your support. I look forward to your prompt response.

Regards, Jahanzaib Malik

Your environment

`npx @mui/envinfo` ``` Don't forget to mention which browser you used. Output from `npx @mui/envinfo` goes here. ```

Search keywords: Data Grid Premium, copy paste Order ID: 91010

michelengelen commented 3 months ago

Hey @JahanzaibAbdullah and thanks for opening this issue. I am not sure if you can do this with the current way the pasting works, since only raw values are being copied to the clipBoard. To make this work we would need to identify where the data came from and where it will get pasted. The latter should not be the problem, but the former could be impossible to do. This is mainly because the source of the data could also be another table that is not a data grid.

@MBilalShafi do you have an idea if we could support this the way it works now? Or should we treat this as a feature request?

MBilalShafi commented 3 months ago

Yes, currently the clipboard copy and paste works like any other grid application such as Excel or Sheets. Technically, you could copy data from anywhere and paste into the Data Grid.

@JahanzaibAbdullah I am not sure if we have received a similar request before. May I know the motivation behind it? If it only relates to the data type safety (e.g. you want to avoid a string being pasted in a number column), you could handle that using processRowUpdate.

Does that fulfill your requirement by any chance?

JahanzaibAbdullah commented 3 months ago

Hi Mui/Mui-X,

I have tried using it as well but it does not fulfil my requirements,as what i need is to not let paste a copied value into the other column of different data type as there is an editable date column where i can only past copied date value from one row to another, likewise a dropdown menu for 'yes' and 'no' in which only yes or no can be pasted, no other value other than that. This is just the demo data that I shared with you earlier, later on there will be a massive amount of data in which more of the columns will be added for which we would want the same functionality.

I hope you understand my query.

Regards, Jahanzaib Malik.

On Wed, Jul 17, 2024 at 12:05 AM Bilal Shafi @.***> wrote:

Yes, currently the clipboard copy and paste works like any other grid application like Excel or Sheets. Technically, you could copy data from anywhere and paste into the Data Grid.

@JahanzaibAbdullah https://github.com/JahanzaibAbdullah I am not sure if we have received a similar request before. May I know the motivation behind it? If it only relates to the data type safety (e.g. you want to avoid a string being pasted in a number column), you could handle that using processRowUpdate https://mui.com/x/react-data-grid/clipboard/#persisting-pasted-data.

Does that fulfill your requirement by any chance?

— Reply to this email directly, view it on GitHub https://github.com/mui/mui-x/issues/13847#issuecomment-2231638560, or unsubscribe https://github.com/notifications/unsubscribe-auth/BEPYNL6KGLMMA7Z4JYALSF3ZMVVHPAVCNFSM6AAAAABK4SXD22VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMZRGYZTQNJWGA . You are receiving this because you were mentioned.Message ID: @.***>

michelengelen commented 2 months ago

I am almost entirely sure that this is not possible atm ... It kind of relates to this issue: #13895

I just tested it in Google Sheets and there at least you can just paste any value in any cell, regardless of pre-existing values.

MBilalShafi commented 2 months ago

Yes, it's not supported at the moment. As you suggested in #13895, we would need to allow the users to pre-process the copied value to add some additional information like the colDef object (basically some stringified JSON) and combine that with the processRowUpdate to parse and not proceed with pasting values of other columns.

But even if we do that, it will come with some limitations, as the clipboard feature uses OS clipboard, you would not be able to paste the values copied from outside the Data Grid correctly.

I'd be in favor of adding waiting for upvotes label to see the demand for it in the community since it might be relevant to a specific set of requirements.

What do you think @michelengelen ?

JahanzaibAbdullah commented 2 months ago

Sure , till then i can add some validations in colDef editcell to avoid this problem if possible, but i will also be looking forward to your response for this issue.

JahanzaibAbdullah commented 2 months ago

Hi @MBilalShafi , did you find any solution for this issue ?

MBilalShafi commented 2 months ago

Hey @JahanzaibAbdullah, as mentioned above, this functionality is not supported at the moment.

This issue is a waiting for 👍 feature request. Feel free to drop an upvote (👍 reaction) on the description of the issue to increase its probability of being picked up by the team as a feature.

JahanzaibAbdullah commented 2 months ago

Hey @MBilalShafi, I was going through the documentation of Datagrid and i have found the Prop in coldef to deal with this issue i was facing but that solution is not so efficient but it is fulfilling my requirements for now.

Here is how i have achieved it, in the following code, pastedValueParser is the prop that helped me in achieving my functionality.

`const validateNumeric = (value: string) => !isNaN(Number(value));

{ field: 'verifiedRADCenter', headerName: 'Verified RAD Center', minWidth: 220, flex: 0.1, display: 'flex', editable: true, pastedValueParser: (value) => { return validateNumeric(value) ? Number(value) : null; }, renderHeader: () => { return ( <> {loading ? ( <Skeleton animation="wave" variant="rounded" width={150} /> ) : ( <Typography variant="subtitle2" fontWeight={600}

Verified RAD Center )} </> ); }, renderCell: (params: any) => <>{loading ? : params.value}</>, }, `

mahammahmood commented 2 months ago

I want the cell color to change to light green when pasting data from one cell to another, similar to how the color changes when editing a cell.

Here is a screenshot showing the cell color change when editing:

Screenshot 2024-08-01 181544

In this screenshot, when I copy the value 52 and paste it into another cell, I want the pasted cell's color to change in the same way as the cell color changes during editing.

Screenshot 2024-08-01 181640

MBilalShafi commented 2 months ago

I want the cell color to change to light green when pasting data from one cell to another, similar to how the color changes when editing a cell.

Here is a screenshot showing the cell color change when editing:

Screenshot 2024-08-01 181544

In this screenshot, when I copy the value 52 and paste it into another cell, I want the pasted cell's color to change in the same way as the cell color changes during editing.

Screenshot 2024-08-01 181640

@mahammahmood This seems like something different from this specific issue. Could you open up a separate issue for it?

You can follow this guide on how to open one in a way for the team to understand your needs best: https://mui.com/x/introduction/support/#github