mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 13:20:43 +00:00
Add Copy Values to grid column menu
This commit is contained in:
@ -27,7 +27,7 @@ import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJo
|
|||||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
import {Alert, TablePagination} from "@mui/material";
|
import {Alert, Collapse, TablePagination} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
@ -44,9 +44,9 @@ import Menu from "@mui/material/Menu";
|
|||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnMenu, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterMenuItem, GridFilterModel, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import React, {useContext, useEffect, useReducer, useRef, useState} from "react";
|
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||||
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import {QActionsMenuButton, QCreateNewButton} from "qqq/components/buttons/DefaultButtons";
|
import {QActionsMenuButton, QCreateNewButton} from "qqq/components/buttons/DefaultButtons";
|
||||||
@ -57,6 +57,7 @@ import DataGridUtils from "qqq/utils/DataGridUtils";
|
|||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||||
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
||||||
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
|
|
||||||
const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId";
|
const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId";
|
||||||
const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility";
|
const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility";
|
||||||
@ -83,8 +84,9 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
const tableName = table.name;
|
const tableName = table.name;
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
|
|
||||||
const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(searchParams.has("deleteSuccess"));
|
const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(searchParams.has("deleteSuccess"));
|
||||||
|
const [successAlert, setSuccessAlert] = useState(null as string)
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -175,6 +177,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
const [ countResults, setCountResults ] = useState({} as any);
|
const [ countResults, setCountResults ] = useState({} as any);
|
||||||
const [ receivedCountTimestamp, setReceivedCountTimestamp ] = useState(new Date());
|
const [ receivedCountTimestamp, setReceivedCountTimestamp ] = useState(new Date());
|
||||||
const [ queryResults, setQueryResults ] = useState({} as any);
|
const [ queryResults, setQueryResults ] = useState({} as any);
|
||||||
|
const [ latestQueryResults, setLatestQueryResults ] = useState(null as QRecord[]);
|
||||||
const [ receivedQueryTimestamp, setReceivedQueryTimestamp ] = useState(new Date());
|
const [ receivedQueryTimestamp, setReceivedQueryTimestamp ] = useState(new Date());
|
||||||
const [ queryErrors, setQueryErrors ] = useState({} as any);
|
const [ queryErrors, setQueryErrors ] = useState({} as any);
|
||||||
const [ receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp ] = useState(new Date());
|
const [ receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp ] = useState(new Date());
|
||||||
@ -425,6 +428,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
console.log(`Outputting results for query ${latestQueryId}...`);
|
console.log(`Outputting results for query ${latestQueryId}...`);
|
||||||
const results = queryResults[latestQueryId];
|
const results = queryResults[latestQueryId];
|
||||||
delete queryResults[latestQueryId];
|
delete queryResults[latestQueryId];
|
||||||
|
setLatestQueryResults(results);
|
||||||
|
|
||||||
const {rows, columnsToRender} = DataGridUtils.makeRows(results, tableMetaData);
|
const {rows, columnsToRender} = DataGridUtils.makeRows(results, tableMetaData);
|
||||||
|
|
||||||
@ -874,7 +878,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
if(selectedSavedFilterId != null)
|
if(selectedSavedFilterId != null)
|
||||||
{
|
{
|
||||||
const qRecord = await fetchSavedFilter(selectedSavedFilterId);
|
const qRecord = await fetchSavedFilter(selectedSavedFilterId);
|
||||||
const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null,null);
|
const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null);
|
||||||
handleFilterChange(models.filter);
|
handleFilterChange(models.filter);
|
||||||
handleSortChange(models.sort);
|
handleSortChange(models.sort);
|
||||||
localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString());
|
localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString());
|
||||||
@ -917,6 +921,71 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
return(qRecord);
|
return(qRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copyColumnValues = async (column: GridColDef) =>
|
||||||
|
{
|
||||||
|
let data = "";
|
||||||
|
if(latestQueryResults && latestQueryResults.length)
|
||||||
|
{
|
||||||
|
let qFieldMetaData = tableMetaData.fields.get(column.field);
|
||||||
|
for(let i = 0; i < latestQueryResults.length; i++)
|
||||||
|
{
|
||||||
|
let record = latestQueryResults[i] as QRecord;
|
||||||
|
const value = ValueUtils.getUnadornedValueForDisplay(qFieldMetaData, record.values.get(qFieldMetaData.name), record.displayValues.get(qFieldMetaData.name));
|
||||||
|
data += value + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(data)
|
||||||
|
setSuccessAlert("Copied " + latestQueryResults.length + " " + qFieldMetaData.label + " values.");
|
||||||
|
setTimeout(() => setSuccessAlert(null), 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomColumnMenu = forwardRef<HTMLUListElement, GridColumnMenuProps>(
|
||||||
|
function GridColumnMenu(props: GridColumnMenuProps, ref)
|
||||||
|
{
|
||||||
|
const {hideMenu, currentColumn} = props;
|
||||||
|
|
||||||
|
/*
|
||||||
|
const [copyMoreMenu, setCopyMoreMenu] = useState(null)
|
||||||
|
const openCopyMoreMenu = (event: any) =>
|
||||||
|
{
|
||||||
|
setCopyMoreMenu(event.currentTarget);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
const closeCopyMoreMenu = () => setCopyMoreMenu(null);
|
||||||
|
*/
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridColumnMenuContainer ref={ref} {...props}>
|
||||||
|
<SortGridMenuItems onClick={hideMenu} column={currentColumn!} />
|
||||||
|
<GridFilterMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||||
|
<HideGridColMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||||
|
<GridColumnsMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<GridColumnPinningMenuItems onClick={hideMenu} column={currentColumn!} />
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<MenuItem sx={{justifyContent: "space-between"}} onClick={(e) =>
|
||||||
|
{
|
||||||
|
hideMenu(e);
|
||||||
|
copyColumnValues(currentColumn)
|
||||||
|
}}>
|
||||||
|
Copy values
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<Button sx={{minHeight: "auto", minWidth: "auto", padding: 0}} onClick={(e) => openCopyMoreMenu(e)}>...</Button>
|
||||||
|
<Menu anchorEl={copyMoreMenu} anchorOrigin={{vertical: "top", horizontal: "right"}} transformOrigin={{vertical: "top", horizontal: "left"}} open={Boolean(copyMoreMenu)} onClose={closeCopyMoreMenu} keepMounted>
|
||||||
|
<MenuItem>Oh</MenuItem>
|
||||||
|
<MenuItem>My</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
*/}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
</GridColumnMenuContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
function CustomToolbar()
|
function CustomToolbar()
|
||||||
{
|
{
|
||||||
const handleMouseDown: GridEventListener<"cellMouseDown"> = (
|
const handleMouseDown: GridEventListener<"cellMouseDown"> = (
|
||||||
@ -1164,6 +1233,18 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
</Alert>
|
</Alert>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
(successAlert) ? (
|
||||||
|
<Collapse in={Boolean(successAlert)}>
|
||||||
|
<Alert color="success" sx={{mb: 3}} onClose={() =>
|
||||||
|
{
|
||||||
|
setSuccessAlert(null);
|
||||||
|
}}>
|
||||||
|
{successAlert}
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
<Box display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
|
<Box display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
|
||||||
<Box display="flex" marginRight="auto">
|
<Box display="flex" marginRight="auto">
|
||||||
<SavedFilters qController={qController} metaData={metaData} tableMetaData={tableMetaData} currentSavedFilter={currentSavedFilter} filterModel={filterModel} columnSortModel={columnSortModel} filterOnChangeCallback={handleSavedFilterChange}/>
|
<SavedFilters qController={qController} metaData={metaData} tableMetaData={tableMetaData} currentSavedFilter={currentSavedFilter} filterModel={filterModel} columnSortModel={columnSortModel} filterOnChangeCallback={handleSavedFilterChange}/>
|
||||||
@ -1182,7 +1263,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
<Card>
|
<Card>
|
||||||
<Box height="100%">
|
<Box height="100%">
|
||||||
<DataGridPro
|
<DataGridPro
|
||||||
components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
|
components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading, ColumnMenu: CustomColumnMenu}}
|
||||||
pinnedColumns={pinnedColumns}
|
pinnedColumns={pinnedColumns}
|
||||||
onPinnedColumnsChange={handlePinnedColumnsChange}
|
onPinnedColumnsChange={handlePinnedColumnsChange}
|
||||||
pagination
|
pagination
|
||||||
|
@ -199,7 +199,7 @@ class ValueUtils
|
|||||||
** After we know there's no element to be returned (e.g., because no adornment),
|
** After we know there's no element to be returned (e.g., because no adornment),
|
||||||
** this method does the string formatting.
|
** this method does the string formatting.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static getUnadornedValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any): string | JSX.Element
|
public static getUnadornedValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any): string | JSX.Element
|
||||||
{
|
{
|
||||||
if(! displayValue && field.defaultValue)
|
if(! displayValue && field.defaultValue)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user