mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-22 07:08:44 +00:00
Compare commits
16 Commits
snapshot-f
...
wip/bugfix
Author | SHA1 | Date | |
---|---|---|---|
b1eba925fa | |||
e7b5821fbd | |||
aed1c9d4d0 | |||
88a4c17bbc | |||
2900cd8593 | |||
8ab0f5f549 | |||
8cffbbcac4 | |||
37eb280d79 | |||
948aee70fd | |||
f0c1af18d0 | |||
fa65d6c0ad | |||
d6c9bf79b1 | |||
677b93a09f | |||
314bf0fd67 | |||
76642f13e9 | |||
0eaf171523 |
@ -2,12 +2,12 @@ version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@5.1.0
|
||||
browser-tools: circleci/browser-tools@1.4.6
|
||||
browser-tools: circleci/browser-tools@1.4.7
|
||||
|
||||
executors:
|
||||
java17:
|
||||
docker:
|
||||
- image: 'cimg/openjdk:17.0'
|
||||
- image: 'cimg/openjdk:17.0.9'
|
||||
|
||||
commands:
|
||||
install_java17:
|
||||
|
@ -33,6 +33,7 @@
|
||||
"html-react-parser": "1.4.8",
|
||||
"html-to-text": "^9.0.5",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"idb": "8.0.0",
|
||||
"jwt-decode": "3.1.2",
|
||||
"rapidoc": "9.3.4",
|
||||
"react": "18.0.0",
|
||||
|
@ -354,8 +354,7 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
<Grid container columnSpacing={5} rowSpacing={1}>
|
||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>n</span>Create a New Record</Grid>
|
||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>r</span>Refresh the Query</Grid>
|
||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>c</span>Open the Columns Panel</Grid>
|
||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>f</span>Open the Filter Panel</Grid>
|
||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>f</span>Open the Filter Builder (Advanced mode only)</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography variant="h6" pt={3}>Record View</Typography>
|
||||
|
@ -45,7 +45,7 @@ export default function ExportMenuItem(props: QExportMenuItemProps)
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
disabled={!totalRecords}
|
||||
disabled={totalRecords === 0}
|
||||
onClick={() =>
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -94,6 +94,24 @@ export const makeTextField = (field: QFieldMetaData, criteria: QFilterCriteriaWi
|
||||
document.getElementById(`${idPrefix}${criteria.id}`).focus();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Event handler for key-down events - specifically added here, to stop pressing
|
||||
** 'tab' in a date or date-time from closing the quick-filter...
|
||||
*******************************************************************************/
|
||||
const handleKeyDown = (e: any) =>
|
||||
{
|
||||
if (field.type == QFieldType.DATE || field.type == QFieldType.DATE_TIME)
|
||||
{
|
||||
if(e.code == "Tab")
|
||||
{
|
||||
console.log("Tab on date or date-time - don't close me, just move to the next sub-field!...");
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inputProps: any = {};
|
||||
inputProps.endAdornment = (
|
||||
<InputAdornment position="end">
|
||||
@ -110,6 +128,7 @@ export const makeTextField = (field: QFieldMetaData, criteria: QFilterCriteriaWi
|
||||
autoComplete="off"
|
||||
type={type}
|
||||
onChange={(event) => valueChangeHandler(event, valueIndex)}
|
||||
onKeyDown={handleKeyDown}
|
||||
value={value}
|
||||
InputLabelProps={inputLabelProps}
|
||||
InputProps={inputProps}
|
||||
|
@ -504,7 +504,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
||||
//////////////////////////////
|
||||
// return the button & menu //
|
||||
//////////////////////////////
|
||||
const widthAndMaxWidth = 250
|
||||
const widthAndMaxWidth = fieldMetaData?.type == QFieldType.DATE_TIME ? 275 : 250
|
||||
return (
|
||||
<>
|
||||
{button}
|
||||
|
@ -39,65 +39,79 @@ ChartJS.register(
|
||||
Legend
|
||||
);
|
||||
|
||||
export const options = {
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
elements: {
|
||||
bar: {
|
||||
borderRadius: 4
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
// todo - some configs around this
|
||||
callbacks: {
|
||||
title: function(context: any)
|
||||
{
|
||||
return ("");
|
||||
},
|
||||
label: function(context: any)
|
||||
{
|
||||
if(context.dataset.label.startsWith(context.label))
|
||||
{
|
||||
return `${context.label}: ${context.formattedValue}`;
|
||||
}
|
||||
else
|
||||
export const makeOptions = (data: DefaultChartData) =>
|
||||
{
|
||||
return({
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
elements: {
|
||||
bar: {
|
||||
borderRadius: 4
|
||||
}
|
||||
},
|
||||
onHover: function (event: any, elements: any[], chart: any)
|
||||
{
|
||||
if(event.type == "mousemove" && elements.length > 0 && data.urls && data.urls.length > elements[0].index && data.urls[elements[0].index])
|
||||
{
|
||||
chart.canvas.style.cursor = "pointer";
|
||||
}
|
||||
else
|
||||
{
|
||||
chart.canvas.style.cursor = "default";
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
// todo - some configs around this
|
||||
callbacks: {
|
||||
title: function(context: any)
|
||||
{
|
||||
return ("");
|
||||
},
|
||||
label: function(context: any)
|
||||
{
|
||||
if(context.dataset.label.startsWith(context.label))
|
||||
{
|
||||
return `${context.label}: ${context.formattedValue}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: "bottom",
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: "circle",
|
||||
boxHeight: 6,
|
||||
boxWidth: 6,
|
||||
padding: 12,
|
||||
font: {
|
||||
size: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: "bottom",
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: "circle",
|
||||
boxHeight: 6,
|
||||
boxWidth: 6,
|
||||
padding: 12,
|
||||
font: {
|
||||
size: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {display: false},
|
||||
ticks: {autoSkip: false, maxRotation: 90}
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {display: false},
|
||||
ticks: {autoSkip: false, maxRotation: 90}
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
position: "right",
|
||||
ticks: {precision: 0}
|
||||
},
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
position: "right",
|
||||
ticks: {precision: 0}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -151,7 +165,7 @@ function StackedBarChart({data, chartSubheaderData}: Props): JSX.Element
|
||||
<Box>
|
||||
{chartSubheaderData && (<ChartSubheaderWithData chartSubheaderData={chartSubheaderData} />)}
|
||||
<Box width="100%" height="300px">
|
||||
<Bar data={data} options={options} getElementsAtEvent={handleClick} />
|
||||
<Bar data={data} options={makeOptions(data)} getElementsAtEvent={handleClick} />
|
||||
</Box>
|
||||
</Box>
|
||||
) : <Skeleton sx={{marginLeft: "20px", marginRight: "20px", height: "200px"}} />;
|
||||
|
@ -70,7 +70,7 @@ function PieChart({description, chartData, chartSubheaderData}: Props): JSX.Elem
|
||||
chartData.dataset.backgroundColors = chartColors;
|
||||
}
|
||||
}
|
||||
const {data, options} = configs(chartData?.labels || [], chartData?.dataset || {});
|
||||
const {data, options} = configs(chartData?.labels || [], chartData?.dataset || {}, chartData?.dataset?.urls);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ import colors from "qqq/assets/theme/base/colors";
|
||||
|
||||
const {gradients, dark} = colors;
|
||||
|
||||
function configs(labels: any, datasets: any)
|
||||
function configs(labels: any, datasets: any, urls: string[] | undefined)
|
||||
{
|
||||
const backgroundColors = [];
|
||||
|
||||
@ -66,6 +66,17 @@ function configs(labels: any, datasets: any)
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
onHover: function (event: any, elements: any[], chart: any)
|
||||
{
|
||||
if(event.type == "mousemove" && elements.length > 0 && urls && urls.length > elements[0].index && urls[elements[0].index])
|
||||
{
|
||||
chart.canvas.style.cursor = "pointer";
|
||||
}
|
||||
else
|
||||
{
|
||||
chart.canvas.style.cursor = "default";
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
|
@ -1089,19 +1089,17 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const formData = new FormData();
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
let queryStringPairsForInit = [];
|
||||
if (urlSearchParams.get("recordIds"))
|
||||
{
|
||||
const recordIdsFromQueryString = urlSearchParams.get("recordIds").split(",");
|
||||
const encodedRecordIds = recordIdsFromQueryString.map(r => encodeURIComponent(r)).join(",");
|
||||
queryStringPairsForInit.push("recordsParam=recordIds");
|
||||
queryStringPairsForInit.push(`recordIds=${encodedRecordIds}`);
|
||||
formData.append("recordsParam", "recordIds")
|
||||
formData.append("recordIds", urlSearchParams.get("recordIds"))
|
||||
}
|
||||
else if (urlSearchParams.get("filterJSON"))
|
||||
{
|
||||
queryStringPairsForInit.push("recordsParam=filterJSON");
|
||||
queryStringPairsForInit.push(`filterJSON=${encodeURIComponent(urlSearchParams.get("filterJSON"))}`);
|
||||
formData.append("recordsParam", "filterJSON")
|
||||
formData.append("filterJSON", urlSearchParams.get("filterJSON"));
|
||||
}
|
||||
// todo once saved filters exist
|
||||
//else if(urlSearchParams.get("filterId")) {
|
||||
@ -1110,23 +1108,23 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
// }
|
||||
else if (recordIds)
|
||||
{
|
||||
if (recordIds instanceof QQueryFilter)
|
||||
// @ts-ignore - we're checking to see if recordIds is a QQueryFilter-looking object here.
|
||||
if (recordIds instanceof QQueryFilter || (typeof recordIds === "object" && recordIds.criteria))
|
||||
{
|
||||
queryStringPairsForInit.push("recordsParam=filterJSON");
|
||||
queryStringPairsForInit.push(`filterJSON=${encodeURIComponent(JSON.stringify(recordIds))}`);
|
||||
formData.append("recordsParam", "filterJSON")
|
||||
formData.append("filterJSON", JSON.stringify(recordIds));
|
||||
}
|
||||
else if (typeof recordIds === "object" && recordIds.length)
|
||||
{
|
||||
const encodedRecordIds = recordIds.map(r => encodeURIComponent(r)).join(",");
|
||||
queryStringPairsForInit.push("recordsParam=recordIds");
|
||||
queryStringPairsForInit.push(`recordIds=${encodedRecordIds}`);
|
||||
formData.append("recordsParam", "recordIds")
|
||||
formData.append("recordIds", recordIds.join(","))
|
||||
}
|
||||
}
|
||||
|
||||
if (tableVariantLocalStorageKey && localStorage.getItem(tableVariantLocalStorageKey))
|
||||
{
|
||||
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
|
||||
queryStringPairsForInit.push(`tableVariant=${encodeURIComponent(JSON.stringify(tableVariant))}`);
|
||||
formData.append("tableVariant", JSON.stringify(tableVariant));
|
||||
}
|
||||
|
||||
try
|
||||
@ -1170,18 +1168,18 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
{
|
||||
for (let key in defaultProcessValues)
|
||||
{
|
||||
queryStringPairsForInit.push(`${key}=${encodeURIComponent(defaultProcessValues[key])}`);
|
||||
formData.append(key, defaultProcessValues[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (tableMetaData)
|
||||
{
|
||||
queryStringPairsForInit.push(`tableName=${encodeURIComponent(tableMetaData.name)}`);
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const processResponse = await Client.getInstance().processInit(processName, queryStringPairsForInit.join("&"));
|
||||
const processResponse = await Client.getInstance().processInit(processName, formData);
|
||||
setProcessUUID(processResponse.processUUID);
|
||||
setLastProcessResponse(processResponse);
|
||||
}
|
||||
|
@ -44,9 +44,10 @@ import LinearProgress from "@mui/material/LinearProgress";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridFilterMenuItem, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
||||
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarContainer, GridToolbarDensitySelector, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
||||
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
||||
import FormData from "form-data";
|
||||
import {IDBPDatabase, openDB} from "idb";
|
||||
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
@ -101,6 +102,13 @@ RecordQuery.defaultProps = {
|
||||
///////////////////////////////////////////////////////
|
||||
type PageState = "initial" | "loadingMetaData" | "loadedMetaData" | "loadingView" | "loadedView" | "preparingGrid" | "ready";
|
||||
|
||||
//////////////////////////////////////////
|
||||
// define IndexedDB store & field names //
|
||||
//////////////////////////////////////////
|
||||
const recordIdsForProcessesDBName = "qqq.recordIdsForProcesses";
|
||||
const recordIdsForProcessesStoreName = "recordIdsForProcesses";
|
||||
const timestampIndexAndFieldName = "timestamp";
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
/*******************************************************************************
|
||||
@ -114,7 +122,6 @@ const getLoadingScreen = () =>
|
||||
</BaseLayout>);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ Record Query Screen component.
|
||||
**
|
||||
@ -168,7 +175,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
// define some default values (e.g., to be used if nothing in local storage or no active view) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
let defaultSort = [] as GridSortItem[];
|
||||
let defaultRowsPerPage = 10;
|
||||
let defaultRowsPerPage = 50;
|
||||
let defaultDensity = "standard" as GridDensity;
|
||||
let defaultTableVariant: QTableVariant = null;
|
||||
let defaultMode = "basic";
|
||||
@ -610,11 +617,14 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
e.preventDefault()
|
||||
updateTable("'r' keyboard event");
|
||||
}
|
||||
/*
|
||||
// disable until we add a ... ref down to let us programmatically open Columns button
|
||||
else if (! e.metaKey && e.key === "c")
|
||||
{
|
||||
e.preventDefault()
|
||||
gridApiRef.current.showPreferences(GridPreferencePanelsValue.columns)
|
||||
}
|
||||
*/
|
||||
else if (! e.metaKey && e.key === "f")
|
||||
{
|
||||
e.preventDefault()
|
||||
@ -1380,7 +1390,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
if (selectFullFilterState === "filter")
|
||||
{
|
||||
return `?recordsParam=filterJSON&filterJSON=${encodeURIComponent(JSON.stringify(prepQueryFilterForBackend(queryFilter)))}`;
|
||||
const filterForBackend = prepQueryFilterForBackend(queryFilter);
|
||||
filterForBackend.skip = 0;
|
||||
filterForBackend.limit = null;
|
||||
return `?recordsParam=filterJSON&filterJSON=${encodeURIComponent(JSON.stringify(filterForBackend))}`;
|
||||
}
|
||||
|
||||
if (selectFullFilterState === "filterSubset")
|
||||
@ -1401,14 +1414,114 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** launch/open a modal process. Ends up navigating to the process's path w/
|
||||
** records selected via query string.
|
||||
** For the various functions that work with the recordIdsForProcess indexedDB,
|
||||
** open that database (creating if needed).
|
||||
*******************************************************************************/
|
||||
const openModalProcess = (process: QProcessMetaData = null) =>
|
||||
const openRecordIdsForProcessIndexedDB = async () =>
|
||||
{
|
||||
return await openDB(recordIdsForProcessesDBName, 1, {
|
||||
upgrade(db)
|
||||
{
|
||||
const store = db.createObjectStore(recordIdsForProcessesStoreName, {
|
||||
keyPath: "uuid"
|
||||
});
|
||||
store.createIndex(timestampIndexAndFieldName, timestampIndexAndFieldName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** clean up old indexedDB records that were created to launch processes in the past.
|
||||
*******************************************************************************/
|
||||
const manageRecordIdsForProcessIndexedDB = async (db: IDBPDatabase) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const now = new Date().getTime();
|
||||
const limit = now - (1000 * 60 * 60 * 24 * 7); // now minus 1 week
|
||||
|
||||
const expiredKeys = await db.getAllKeysFromIndex(recordIdsForProcessesStoreName, timestampIndexAndFieldName, IDBKeyRange.upperBound(limit, true));
|
||||
for (let expiredKey of expiredKeys)
|
||||
{
|
||||
db.delete(recordIdsForProcessesStoreName, expiredKey);
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log("Error managing recordIdsForProcess in indexeddb: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** we used to pass recordIds (that is, either an array of ids (from checkboxes)
|
||||
** or a json-query-filter) on the query string... but that gets too big, so,
|
||||
** instead, write it ... not to local storage (5MB limit, and we want to keep
|
||||
** them around for ... some period of time, for reloading processes), so,
|
||||
** write it to indexedDB instead.
|
||||
*******************************************************************************/
|
||||
const storeRecordIdsForProcessInIndexedDB = async (recordIds: string[] | QQueryFilter): Promise<string> =>
|
||||
{
|
||||
const uuid = crypto.randomUUID()
|
||||
|
||||
let db = await openRecordIdsForProcessIndexedDB();
|
||||
await db.add(recordIdsForProcessesStoreName, {
|
||||
uuid: uuid,
|
||||
json: JSON.stringify(recordIds),
|
||||
timestamp: new Date().getTime()
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we shouldn't need to await this function - it's just good to run the cleanup "sometimes" //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
manageRecordIdsForProcessIndexedDB(db);
|
||||
|
||||
return (uuid);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** when launching a process, if we're to use recordIds(/filter) from indexedDB,
|
||||
** then do that read (async), and open the modal process after it completes.
|
||||
*******************************************************************************/
|
||||
const launchModalProcessUsingRecordIdsFromIndexedDB = async (uuid: string, processMetaData: QProcessMetaData): Promise<void> =>
|
||||
{
|
||||
let db = await openRecordIdsForProcessIndexedDB();
|
||||
const recordIds = await db.get(recordIdsForProcessesStoreName, uuid)
|
||||
|
||||
if(recordIds)
|
||||
{
|
||||
const recordIdsObject = JSON.parse(recordIds.json);
|
||||
setRecordIdsForProcess(recordIdsObject);
|
||||
setActiveModalProcess(processMetaData);
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// closing the process will do a navigate - so we can't just set an alert, we need it to be passed in the navigation call as state - //
|
||||
// so pass it as a param to closeModalProcess, which will set it in the navigation state. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
closeModalProcess({}, "failed to start", "Could not find query filter to start this process. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** launch/open a modal process. Writes the records (ids or filter) to indexedDB,
|
||||
** identified by a UUID. Then navigates to the process's path w/
|
||||
** that UUID in the query string.
|
||||
*******************************************************************************/
|
||||
const openModalProcess = async (process: QProcessMetaData = null) =>
|
||||
{
|
||||
let uuid = "";
|
||||
if (selectFullFilterState === "filter")
|
||||
{
|
||||
setRecordIdsForProcess(prepQueryFilterForBackend(queryFilter));
|
||||
const filterForBackend = prepQueryFilterForBackend(queryFilter);
|
||||
filterForBackend.skip = 0;
|
||||
filterForBackend.limit = null;
|
||||
setRecordIdsForProcess(filterForBackend);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB(filterForBackend);
|
||||
}
|
||||
else if (selectFullFilterState === "filterSubset")
|
||||
{
|
||||
@ -1416,24 +1529,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
filterForBackend.skip = 0;
|
||||
filterForBackend.limit = selectionSubsetSize;
|
||||
setRecordIdsForProcess(filterForBackend);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB(filterForBackend);
|
||||
}
|
||||
else if (selectedIds.length > 0)
|
||||
{
|
||||
setRecordIdsForProcess(selectedIds);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB(selectedIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
setRecordIdsForProcess([]);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB([]);
|
||||
}
|
||||
|
||||
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}${getRecordsQueryString()}`);
|
||||
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}?recordsParam=recordsKey&recordsKey=${uuid}`);
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** close callback for modal processes
|
||||
*******************************************************************************/
|
||||
const closeModalProcess = (event: object, reason: string) =>
|
||||
const closeModalProcess = (event: object, reason: string, warning?: string) =>
|
||||
{
|
||||
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
||||
{
|
||||
@ -1445,7 +1561,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
const newPath = location.pathname.split("/");
|
||||
newPath.pop();
|
||||
navigate(newPath.join("/"));
|
||||
navigate(newPath.join("/"), warning ? {state: {warning: warning}} : undefined);
|
||||
|
||||
updateTable("close modal process");
|
||||
};
|
||||
@ -2103,20 +2219,32 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
if(selectedIndex == 0)
|
||||
{
|
||||
///////////////
|
||||
// this page //
|
||||
///////////////
|
||||
programmaticallySelectSomeOrAllRows();
|
||||
setSelectFullFilterState("checked")
|
||||
}
|
||||
else if(selectedIndex == 1)
|
||||
{
|
||||
///////////////////////
|
||||
// full query result //
|
||||
///////////////////////
|
||||
programmaticallySelectSomeOrAllRows();
|
||||
setSelectFullFilterState("filter")
|
||||
}
|
||||
else if(selectedIndex == 2)
|
||||
{
|
||||
////////////////////////////
|
||||
// subset of query result //
|
||||
////////////////////////////
|
||||
setSelectionSubsetSizePromptOpen(true);
|
||||
}
|
||||
else if(selectedIndex == 3)
|
||||
{
|
||||
/////////////////////
|
||||
// clear selection //
|
||||
/////////////////////
|
||||
setSelectFullFilterState("n/a")
|
||||
setRowSelectionModel([]);
|
||||
setSelectedIds([]);
|
||||
@ -2312,22 +2440,38 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if (pathParts[pathParts.length - 2] === tableName)
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// try to find a process by this name //
|
||||
////////////////////////////////////////
|
||||
const processName = pathParts[pathParts.length - 1];
|
||||
const processList = allTableProcesses.filter(p => p.name == processName);
|
||||
if (processList.length > 0)
|
||||
let process = processList.length > 0 ? processList[0] : null;
|
||||
if(!process)
|
||||
{
|
||||
setActiveModalProcess(processList[0]);
|
||||
process = metaData?.processes.get(processName)
|
||||
}
|
||||
else if (metaData?.processes.has(processName))
|
||||
|
||||
if(process)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// check for generic processes - should this be a specific attribute on the process? //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
setActiveModalProcess(metaData?.processes.get(processName));
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// check if a recordsKey UUID (e.g., from indexedDB) is in the query string //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
const uuid = urlSearchParams.get("recordsKey")
|
||||
|
||||
if(uuid)
|
||||
{
|
||||
await launchModalProcessUsingRecordIdsFromIndexedDB(uuid, processList[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
setActiveModalProcess(processList[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`Couldn't find process named ${processName}`);
|
||||
setWarningAlert(`Couldn't find process named ${processName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,8 +114,11 @@ class FilterUtils
|
||||
// e.g., ...values=[1]... //
|
||||
// but we need them to be possibleValue objects (w/ id & label) so the label //
|
||||
// can be shown in the filter dropdown. So, make backend call to look them up. //
|
||||
// also, there are cases where we can get a null or "" as the only value in the //
|
||||
// values array - avoid sending that to the backend, as it comes back w/ all //
|
||||
// possible values, and a general "bad time" //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
if (values && values.length > 0)
|
||||
if (values && values.length > 0 && values[0] !== null && values[0] !== undefined && values[0] !== "")
|
||||
{
|
||||
values = await qController.possibleValues(fieldTable.name, null, field.name, "", values);
|
||||
}
|
||||
|
Reference in New Issue
Block a user