mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
Checkpoint - new validation & summary screens
This commit is contained in:
@ -57,6 +57,7 @@
|
||||
}
|
||||
],
|
||||
"import/order": "off",
|
||||
"jsx-one-expression-per-line": "off",
|
||||
"max-len": "off",
|
||||
"no-console": "off",
|
||||
"no-constant-condition": "off",
|
||||
|
@ -334,8 +334,8 @@ function EntityForm({table, id}: Props): JSX.Element
|
||||
|
||||
<MDBox component="div" p={3}>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
<QCancelButton onClickHandler={handleCancelClicked} />
|
||||
<QSaveButton />
|
||||
<QCancelButton onClickHandler={handleCancelClicked} disabled={isSubmitting} />
|
||||
<QSaveButton disabled={isSubmitting} />
|
||||
</Grid>
|
||||
</MDBox>
|
||||
|
||||
|
@ -24,6 +24,7 @@ import {Link} from "react-router-dom";
|
||||
import MDButton from "components/MDButton";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import React from "react";
|
||||
import EntityForm from "qqq/components/EntityForm";
|
||||
|
||||
// eslint-disable import/prefer-default-export
|
||||
|
||||
@ -42,11 +43,16 @@ export function QCreateNewButton(): JSX.Element
|
||||
);
|
||||
}
|
||||
|
||||
export function QSaveButton(): JSX.Element
|
||||
interface QSaveButtonProps
|
||||
{
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export function QSaveButton({disabled}: QSaveButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDBox ml={3} width={standardWidth}>
|
||||
<MDButton type="submit" variant="gradient" color="info" size="small" fullWidth startIcon={<Icon>save</Icon>}>
|
||||
<MDButton type="submit" variant="gradient" color="info" size="small" fullWidth startIcon={<Icon>save</Icon>} disabled={disabled}>
|
||||
Save
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
@ -108,15 +114,48 @@ export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonP
|
||||
interface QCancelButtonProps
|
||||
{
|
||||
onClickHandler: any;
|
||||
disabled: boolean;
|
||||
label?: string;
|
||||
iconName?: string
|
||||
}
|
||||
|
||||
export function QCancelButton({onClickHandler}: QCancelButtonProps): JSX.Element
|
||||
export function QCancelButton({
|
||||
onClickHandler, disabled, label, iconName,
|
||||
}: QCancelButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDBox ml="auto" width={standardWidth}>
|
||||
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>cancel</Icon>} onClick={onClickHandler}>
|
||||
Cancel
|
||||
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} onClick={onClickHandler} disabled={disabled}>
|
||||
{label}
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
|
||||
QCancelButton.defaultProps = {
|
||||
label: "cancel",
|
||||
iconName: "cancel",
|
||||
};
|
||||
|
||||
interface QSubmitButtonProps
|
||||
{
|
||||
label?: string
|
||||
iconName?: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export function QSubmitButton({label, iconName, disabled}: QSubmitButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDBox ml={3} width={standardWidth}>
|
||||
<MDButton type="submit" variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} disabled={disabled}>
|
||||
{label}
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
|
||||
QSubmitButton.defaultProps = {
|
||||
label: "Submit",
|
||||
iconName: "check",
|
||||
};
|
||||
|
@ -20,9 +20,7 @@
|
||||
*/
|
||||
|
||||
import React, {
|
||||
SyntheticEvent,
|
||||
useCallback,
|
||||
useEffect, useReducer, useRef, useState,
|
||||
useCallback, useEffect, useReducer, useRef, useState,
|
||||
} from "react";
|
||||
import {
|
||||
Link, useNavigate, useParams, useSearchParams,
|
||||
@ -33,13 +31,14 @@ import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import {Alert, Pagination, TablePagination} from "@mui/material";
|
||||
import {Alert, TablePagination} from "@mui/material";
|
||||
import {
|
||||
DataGridPro,
|
||||
GridCallbackDetails,
|
||||
GridColDef,
|
||||
GridColumnOrderChangeParams,
|
||||
GridColumnVisibilityModel,
|
||||
GridExportMenuItemProps,
|
||||
GridFilterModel,
|
||||
GridRowId,
|
||||
GridRowParams,
|
||||
@ -52,15 +51,12 @@ import {
|
||||
GridToolbarDensitySelector,
|
||||
GridToolbarExportContainer,
|
||||
GridToolbarFilterButton,
|
||||
GridExportMenuItemProps,
|
||||
MuiEvent,
|
||||
} from "@mui/x-data-grid-pro";
|
||||
|
||||
// Material Dashboard 2 PRO React TS components
|
||||
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
||||
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
|
||||
import MDBox from "components/MDBox";
|
||||
import MDButton from "components/MDButton";
|
||||
import MDAlert from "components/MDAlert";
|
||||
|
||||
// QQQ
|
||||
@ -80,6 +76,7 @@ import QProcessUtils from "../../utils/QProcessUtils";
|
||||
import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons";
|
||||
import QValueUtils from "qqq/utils/QValueUtils";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import QFilterUtils from "qqq/utils/QFilterUtils";
|
||||
|
||||
const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility";
|
||||
const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort";
|
||||
@ -90,6 +87,52 @@ interface Props
|
||||
table?: QTableMetaData;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the default filter to use on the page - either from query string, or
|
||||
** local storage, or a default (empty).
|
||||
*******************************************************************************/
|
||||
function getDefaultFilter(searchParams: URLSearchParams, filterLocalStorageKey: string): GridFilterModel
|
||||
{
|
||||
if (searchParams.has("filter"))
|
||||
{
|
||||
try
|
||||
{
|
||||
const qQueryFilter = JSON.parse(searchParams.get("filter")) as QQueryFilter;
|
||||
console.log(`Got a filter from the query string: ${JSON.stringify(qQueryFilter)}`);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// translate from a qqq-style filter to one that the grid wants //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
const defaultFilter = {items: []} as GridFilterModel;
|
||||
let id = 1;
|
||||
qQueryFilter.criteria.forEach((criteria) =>
|
||||
{
|
||||
defaultFilter.items.push({
|
||||
columnField: criteria.fieldName,
|
||||
operatorValue: QFilterUtils.qqqCriteriaOperatorToGrid(criteria.operator),
|
||||
value: QFilterUtils.qqqCriteriaValuesToGrid(criteria.operator, criteria.values),
|
||||
id: id++, // not sure what this id is!!
|
||||
});
|
||||
});
|
||||
|
||||
return (defaultFilter);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.warn("Error parsing filter from query string", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem(filterLocalStorageKey))
|
||||
{
|
||||
const defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey));
|
||||
console.log(`Got default from LS: ${JSON.stringify(defaultFilter)}`);
|
||||
return (defaultFilter);
|
||||
}
|
||||
|
||||
return ({items: []});
|
||||
}
|
||||
|
||||
function EntityList({table}: Props): JSX.Element
|
||||
{
|
||||
const tableNameParam = useParams().tableName;
|
||||
@ -105,7 +148,7 @@ function EntityList({table}: Props): JSX.Element
|
||||
const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
let defaultSort = [] as GridSortItem[];
|
||||
let defaultVisibility = {};
|
||||
let _defaultFilter = {items: []} as GridFilterModel;
|
||||
const _defaultFilter = getDefaultFilter(searchParams, filterLocalStorageKey);
|
||||
|
||||
if (localStorage.getItem(sortLocalStorageKey))
|
||||
{
|
||||
@ -115,11 +158,6 @@ function EntityList({table}: Props): JSX.Element
|
||||
{
|
||||
defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey));
|
||||
}
|
||||
if (localStorage.getItem(filterLocalStorageKey))
|
||||
{
|
||||
_defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey));
|
||||
console.log(`Got default from LS: ${JSON.stringify(_defaultFilter)}`);
|
||||
}
|
||||
|
||||
const [filterModel, setFilterModel] = useState(_defaultFilter);
|
||||
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
||||
@ -159,46 +197,6 @@ function EntityList({table}: Props): JSX.Element
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
|
||||
const translateCriteriaOperator = (operator: string) =>
|
||||
{
|
||||
switch (operator)
|
||||
{
|
||||
case "contains":
|
||||
return QCriteriaOperator.CONTAINS;
|
||||
case "startsWith":
|
||||
return QCriteriaOperator.STARTS_WITH;
|
||||
case "endsWith":
|
||||
return QCriteriaOperator.ENDS_WITH;
|
||||
case "is":
|
||||
case "equals":
|
||||
case "=":
|
||||
return QCriteriaOperator.EQUALS;
|
||||
case "isNot":
|
||||
case "!=":
|
||||
return QCriteriaOperator.NOT_EQUALS;
|
||||
case "after":
|
||||
case ">":
|
||||
return QCriteriaOperator.GREATER_THAN;
|
||||
case "onOrAfter":
|
||||
case ">=":
|
||||
return QCriteriaOperator.GREATER_THAN_OR_EQUALS;
|
||||
case "before":
|
||||
case "<":
|
||||
return QCriteriaOperator.LESS_THAN;
|
||||
case "onOrBefore":
|
||||
case "<=":
|
||||
return QCriteriaOperator.LESS_THAN_OR_EQUALS;
|
||||
case "isEmpty":
|
||||
return QCriteriaOperator.IS_BLANK;
|
||||
case "isNotEmpty":
|
||||
return QCriteriaOperator.IS_NOT_BLANK;
|
||||
// case "is any of":
|
||||
// TODO: handle this case
|
||||
default:
|
||||
return QCriteriaOperator.EQUALS;
|
||||
}
|
||||
};
|
||||
|
||||
const buildQFilter = () =>
|
||||
{
|
||||
const qFilter = new QQueryFilter();
|
||||
@ -213,13 +211,9 @@ function EntityList({table}: Props): JSX.Element
|
||||
{
|
||||
filterModel.items.forEach((item) =>
|
||||
{
|
||||
const operator = translateCriteriaOperator(item.operatorValue);
|
||||
let criteria = new QFilterCriteria(item.columnField, operator, [item.value]);
|
||||
if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK)
|
||||
{
|
||||
criteria = new QFilterCriteria(item.columnField, operator, null);
|
||||
}
|
||||
qFilter.addCriteria(criteria);
|
||||
const operator = QFilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue);
|
||||
const values = QFilterUtils.gridCriteriaValueToQQQ(operator, item.value);
|
||||
qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
||||
import List from "@mui/material/List";
|
||||
import {ListItem} from "@mui/material";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine";
|
||||
import MDBox from "components/MDBox";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
|
||||
interface Props
|
||||
{
|
||||
qInstance: QInstance;
|
||||
process: QProcessMetaData;
|
||||
table: QTableMetaData;
|
||||
processValues: any;
|
||||
step: QFrontendStepMetaData;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** This is the process summary result component.
|
||||
*******************************************************************************/
|
||||
function QProcessSummaryResults({
|
||||
qInstance, process, table = null, processValues, step,
|
||||
}: Props): JSX.Element
|
||||
{
|
||||
const resultValidationList = (
|
||||
<List sx={{mt: 2}}>
|
||||
{
|
||||
processValues?.recordCount && table && (
|
||||
<ListItem sx={{my: 2}}>
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||
{processValues.recordCount.toLocaleString()}
|
||||
{" "}
|
||||
{table.label}
|
||||
{" "}
|
||||
records were processed.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
<List>
|
||||
{
|
||||
processValues.processResults && processValues.processResults.map((processSummaryLine: ProcessSummaryLine, i: number) => (new ProcessSummaryLine(processSummaryLine).getProcessSummaryListItem(i, table, qInstance, true)))
|
||||
}
|
||||
</List>
|
||||
</List>
|
||||
);
|
||||
|
||||
return (
|
||||
<MDBox m={3} mt={6}>
|
||||
<Grid container>
|
||||
<Grid item xs={0} lg={2} />
|
||||
<Grid item xs={12} lg={8}>
|
||||
<MDBox border="1px solid rgb(70%, 70%, 70%)" borderRadius="lg" p={2} mt={2}>
|
||||
<MDBox mt={-5} p={1} sx={{width: "fit-content"}} bgColor="success" borderRadius=".25em" width="initial" color="white">
|
||||
<MDBox display="flex" alignItems="center" color="white">
|
||||
<Icon fontSize="medium" sx={{mr: 1}}>{process.iconName}</Icon>
|
||||
{`${process.label} : ${step.label}`}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
{resultValidationList}
|
||||
</MDBox>
|
||||
</Grid>
|
||||
<Grid item xs={0} lg={2} />
|
||||
</Grid>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default QProcessSummaryResults;
|
286
src/qqq/pages/process-run/components/QValidationReview.tsx
Normal file
286
src/qqq/pages/process-run/components/QValidationReview.tsx
Normal file
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import List from "@mui/material/List";
|
||||
import {
|
||||
Button, FormControlLabel, ListItem, Radio, RadioGroup, tooltipClasses, TooltipProps,
|
||||
} from "@mui/material";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import MDBox from "components/MDBox";
|
||||
import MDTypography from "components/MDTypography";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import React, {useState} from "react";
|
||||
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import QValueUtils from "qqq/utils/QValueUtils";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine";
|
||||
import {Field} from "formik";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import {styled} from "@mui/material/styles";
|
||||
import QTableUtils from "qqq/utils/QTableUtils";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
|
||||
interface Props
|
||||
{
|
||||
qInstance: QInstance;
|
||||
process: QProcessMetaData;
|
||||
table: QTableMetaData;
|
||||
processValues: any;
|
||||
step: QFrontendStepMetaData;
|
||||
previewRecords: QRecord[];
|
||||
formValues: any;
|
||||
doFullValidationRadioChangedHandler: any
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** This is the process validation/review component - where the user may be prompted
|
||||
** to do a full validation or skip it. It's the same screen that shows validation
|
||||
** results when they are available.
|
||||
*******************************************************************************/
|
||||
function QValidationReview({
|
||||
qInstance, process, table = null, processValues, step, previewRecords = [], formValues, doFullValidationRadioChangedHandler,
|
||||
}: Props): JSX.Element
|
||||
{
|
||||
const [previewRecordIndex, setPreviewRecordIndex] = useState(0);
|
||||
|
||||
const updatePreviewRecordIndex = (offset: number) =>
|
||||
{
|
||||
let newIndex = previewRecordIndex + offset;
|
||||
if (newIndex < 0)
|
||||
{
|
||||
newIndex = 0;
|
||||
}
|
||||
if (newIndex >= previewRecords.length - 1)
|
||||
{
|
||||
newIndex = previewRecords.length - 1;
|
||||
}
|
||||
|
||||
setPreviewRecordIndex(newIndex);
|
||||
};
|
||||
|
||||
const CustomWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{popper: className}} />
|
||||
))({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
maxWidth: 500,
|
||||
textAlign: "left",
|
||||
},
|
||||
});
|
||||
|
||||
const preValidationList = (
|
||||
<List sx={{mt: 2}}>
|
||||
{
|
||||
processValues?.recordCount && table && (
|
||||
<ListItem sx={{my: 2}}>
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||
You selected
|
||||
{` ${processValues.recordCount.toLocaleString()} ${table?.label} `}
|
||||
records.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
{
|
||||
processValues?.supportsFullValidation && formValues && formValues.doFullValidation !== undefined && (
|
||||
<>
|
||||
<ListItem sx={{mb: 1, mt: 6}}>
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>How would you like to proceed?</ListItemText>
|
||||
</ListItem>
|
||||
<List>
|
||||
<RadioGroup name="doFullValidation" value={formValues.doFullValidation} onChange={doFullValidationRadioChangedHandler}>
|
||||
<ListItem sx={{pl: 2}}>
|
||||
<FormControlLabel
|
||||
value="true"
|
||||
control={<Radio />}
|
||||
label={(
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||
Perform Validation on all records before processing.
|
||||
<CustomWidthTooltip
|
||||
title={(
|
||||
<div>
|
||||
If you choose this option, a Validation step will run on all of the records that you selected.
|
||||
You will then be told how many can process successfully, and how many have issues.
|
||||
<br />
|
||||
<br />
|
||||
Running this validation may take several minutes, depending on the complexity of the work, and the number of records.
|
||||
<br />
|
||||
<br />
|
||||
Choose this option if you want more information about what will happen, and you are willing to wait for that information.
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<IconButton sx={{py: 0}}><Icon fontSize="small">info_outlined</Icon></IconButton>
|
||||
</CustomWidthTooltip>
|
||||
</ListItemText>
|
||||
)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem sx={{pl: 2}}>
|
||||
<FormControlLabel
|
||||
value="false"
|
||||
control={<Radio />}
|
||||
label={(
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||
Skip Validation. Submit the records for immediate processing.
|
||||
<CustomWidthTooltip
|
||||
title={(
|
||||
<div>
|
||||
If you choose this option, the records you selected will immediately be processed.
|
||||
You will be told how many records were successfully processed, and which ones had issues after the processing is completed.
|
||||
<br />
|
||||
<br />
|
||||
Choose this option if you feel that you do not need this information, or are not willing to wait for it.
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<IconButton sx={{py: 0}}><Icon fontSize="small">info_outlined</Icon></IconButton>
|
||||
</CustomWidthTooltip>
|
||||
</ListItemText>
|
||||
)}
|
||||
/>
|
||||
</ListItem>
|
||||
</RadioGroup>
|
||||
</List>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</List>
|
||||
);
|
||||
|
||||
const postValidationList = (
|
||||
<List sx={{mt: 2}}>
|
||||
{
|
||||
processValues?.recordCount && table && (
|
||||
<ListItem sx={{my: 2}}>
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||
Validation complete on
|
||||
{` ${processValues.recordCount.toLocaleString()} ${table?.label} `}
|
||||
records.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
<List>
|
||||
{
|
||||
processValues.validationSummary && processValues.validationSummary.map((processSummaryLine: ProcessSummaryLine, i: number) => (new ProcessSummaryLine(processSummaryLine).getProcessSummaryListItem(i, table, qInstance)))
|
||||
}
|
||||
</List>
|
||||
</List>
|
||||
);
|
||||
|
||||
const recordPreviewWidget = step.recordListFields && (
|
||||
<MDBox border="1px solid rgb(70%, 70%, 70%)" borderRadius="lg" p={2} mt={2}>
|
||||
<MDBox mx={2} mt={-5} p={1} sx={{width: "fit-content", borderWidth: "2px", borderStyle: "solid"}} bgColor="white" borderColor="orange" borderRadius=".25em" width="initial" color="white">
|
||||
<MDTypography sx={{color: "warning"}}>Preview</MDTypography>
|
||||
</MDBox>
|
||||
<MDBox p={3} pb={0}>
|
||||
<MDTypography color="body" variant="body2" component="div" mb={2}>
|
||||
<MDBox display="flex">
|
||||
{
|
||||
previewRecords && previewRecords.length > 0 ? (
|
||||
<>
|
||||
<i>This is a preview of the records that will be created.</i>
|
||||
<CustomWidthTooltip
|
||||
title={(
|
||||
<div>
|
||||
Note that the number of preview records available may be fewer than the total number of records being processed.
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<IconButton sx={{py: 0}}><Icon fontSize="small">info_outlined</Icon></IconButton>
|
||||
</CustomWidthTooltip>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i>No record previews are available at this time.</i>
|
||||
<CustomWidthTooltip
|
||||
title={(
|
||||
<div>
|
||||
{
|
||||
processValues.validationSummary ? (
|
||||
<>
|
||||
It appears as though this process does not contain any valid records.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
If you choose to Perform Validation, and there are any valid records, then you will see a preview here.
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<IconButton sx={{py: 0}}><Icon fontSize="small">info_outlined</Icon></IconButton>
|
||||
</CustomWidthTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</MDBox>
|
||||
</MDTypography>
|
||||
<MDTypography color="body" variant="body2" component="div">
|
||||
{
|
||||
previewRecords && previewRecords[previewRecordIndex] && step.recordListFields.map((field) => (
|
||||
<MDBox key={field.name} style={{marginBottom: "12px"}}>
|
||||
<b>{`${field.label}:`}</b>
|
||||
{" "}
|
||||
|
||||
{" "}
|
||||
{QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex])}
|
||||
</MDBox>
|
||||
))
|
||||
}
|
||||
{
|
||||
previewRecords && previewRecords.length > 0 && (
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Button startIcon={<Icon>navigate_before</Icon>} onClick={() => updatePreviewRecordIndex(-1)} disabled={previewRecordIndex <= 0}>Previous</Button>
|
||||
<span>
|
||||
{`Preview ${previewRecordIndex + 1} of ${previewRecords.length}`}
|
||||
</span>
|
||||
<Button endIcon={<Icon>navigate_next</Icon>} onClick={() => updatePreviewRecordIndex(1)} disabled={previewRecordIndex >= previewRecords.length - 1}>Next</Button>
|
||||
</MDBox>
|
||||
)
|
||||
}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
);
|
||||
|
||||
return (
|
||||
<MDBox m={3}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} lg={6}>
|
||||
<MDTypography color="body" variant="button">
|
||||
{processValues.validationSummary ? postValidationList : preValidationList}
|
||||
</MDTypography>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6} mt={3}>
|
||||
{recordPreviewWidget}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default QValidationReview;
|
@ -19,47 +19,46 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState, Fragment} from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
import {CircularProgress, TablePagination} from "@mui/material";
|
||||
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
|
||||
// formik components
|
||||
import {Form, Formik} from "formik";
|
||||
import React, {Fragment, useEffect, useState} from "react";
|
||||
import {useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
|
||||
// @mui material components
|
||||
import Grid from "@mui/material/Grid";
|
||||
import BaseLayout from "qqq/components/BaseLayout";
|
||||
import Card from "@mui/material/Card";
|
||||
import Stepper from "@mui/material/Stepper";
|
||||
import Step from "@mui/material/Step";
|
||||
import StepLabel from "@mui/material/StepLabel";
|
||||
|
||||
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
|
||||
import FormData from "form-data";
|
||||
import Grid from "@mui/material/Grid";
|
||||
// Material Dashboard 2 PRO React TS components
|
||||
import MDBox from "components/MDBox";
|
||||
import MDButton from "components/MDButton";
|
||||
|
||||
// Material Dashboard 2 PRO React TS examples components
|
||||
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
||||
|
||||
import * as Yup from "yup";
|
||||
import MDProgress from "components/MDProgress";
|
||||
import MDTypography from "../../../components/MDTypography";
|
||||
import QClient from "qqq/utils/QClient";
|
||||
import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QComponentType";
|
||||
import QDynamicForm from "../../components/QDynamicForm";
|
||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||
import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent";
|
||||
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
||||
import {useLocation, useParams} from "react-router-dom";
|
||||
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
|
||||
import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted";
|
||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import {QJobRunning} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning";
|
||||
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
|
||||
import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent";
|
||||
import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QComponentType";
|
||||
import FormData from "form-data";
|
||||
import QClient from "qqq/utils/QClient";
|
||||
import {CircularProgress, TablePagination} from "@mui/material";
|
||||
import QDynamicForm from "../../components/QDynamicForm";
|
||||
import MDTypography from "../../../components/MDTypography";
|
||||
import Footer from "examples/Footer";
|
||||
import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import Navbar from "qqq/components/Navbar";
|
||||
import BaseLayout from "qqq/components/BaseLayout";
|
||||
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||
import Step from "@mui/material/Step";
|
||||
import StepLabel from "@mui/material/StepLabel";
|
||||
import Stepper from "@mui/material/Stepper";
|
||||
import QValidationReview from "qqq/pages/process-run/components/QValidationReview";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import QProcessSummaryResults from "./components/QProcessSummaryResults";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {QCancelButton, QSubmitButton} from "qqq/components/QButtons";
|
||||
import {formatDate} from "@fullcalendar/react";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -89,12 +88,16 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
||||
const [needInitialLoad, setNeedInitialLoad] = useState(true);
|
||||
const [processMetaData, setProcessMetaData] = useState(null);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||
const [processValues, setProcessValues] = useState({} as any);
|
||||
const [processError, setProcessError] = useState(null as string);
|
||||
const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false);
|
||||
const [lastProcessResponse, setLastProcessResponse] = useState(
|
||||
null as QJobStarted | QJobComplete | QJobError | QJobRunning,
|
||||
);
|
||||
|
||||
const [overrideOnLastStep, setOverrideOnLastStep] = useState(null as boolean);
|
||||
const onLastStep = activeStepIndex === steps.length - 2;
|
||||
const noMoreSteps = activeStepIndex === steps.length - 1;
|
||||
|
||||
@ -115,12 +118,16 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
const [recordConfig, setRecordConfig] = useState({} as any);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [records, setRecords] = useState([] as QRecord[]);
|
||||
|
||||
//////////////////////////////
|
||||
// state for bulk edit form //
|
||||
//////////////////////////////
|
||||
const [disabledBulkEditFields, setDisabledBulkEditFields] = useState({} as any);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const doesStepHaveComponent = (step: QFrontendStepMetaData, type: QComponentType): boolean =>
|
||||
{
|
||||
if (step.components)
|
||||
@ -171,7 +178,7 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
{
|
||||
if (value === null || value === undefined)
|
||||
{
|
||||
return <span>∅</span>;
|
||||
return <span> </span>;
|
||||
}
|
||||
|
||||
if (typeof value === "string")
|
||||
@ -202,13 +209,14 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
processError: string,
|
||||
processValues: any,
|
||||
recordConfig: any,
|
||||
setFieldValue: any,
|
||||
): JSX.Element =>
|
||||
{
|
||||
if (processError)
|
||||
{
|
||||
return (
|
||||
<>
|
||||
<MDTypography color="error" variant="h5">
|
||||
<MDTypography color="error" variant="h5" component="div">
|
||||
Error
|
||||
</MDTypography>
|
||||
<MDTypography color="body" variant="button">
|
||||
@ -218,44 +226,49 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
);
|
||||
}
|
||||
|
||||
if (qJobRunning)
|
||||
if (qJobRunning || step === null)
|
||||
{
|
||||
return (
|
||||
<>
|
||||
<MDTypography variant="h5">
|
||||
{" "}
|
||||
Working
|
||||
</MDTypography>
|
||||
<Grid container>
|
||||
<Grid item padding={1}>
|
||||
<CircularProgress color="info" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<MDTypography color="body" variant="button">
|
||||
{qJobRunning?.message}
|
||||
<br />
|
||||
{qJobRunning.current && qJobRunning.total && (
|
||||
<div>{`${qJobRunning.current.toLocaleString()} of ${qJobRunning.total.toLocaleString()}`}</div>
|
||||
)}
|
||||
<i>
|
||||
{`Updated at ${qJobRunningDate.toLocaleTimeString()}`}
|
||||
</i>
|
||||
</MDTypography>
|
||||
</Grid>
|
||||
<Grid m={3} mt={9} container>
|
||||
<Grid item xs={0} lg={3} />
|
||||
<Grid item xs={12} lg={6}>
|
||||
<Card>
|
||||
<MDBox p={3}>
|
||||
<MDTypography variant="h5" component="div">
|
||||
Working
|
||||
</MDTypography>
|
||||
<Grid container>
|
||||
<Grid item padding={2}>
|
||||
<CircularProgress color="info" />
|
||||
</Grid>
|
||||
<Grid item padding={1}>
|
||||
<MDTypography color="body" variant="button">
|
||||
{qJobRunning?.message}
|
||||
<br />
|
||||
{qJobRunning?.current && qJobRunning?.total && (
|
||||
<>
|
||||
<div>{`${qJobRunning.current.toLocaleString()} of ${qJobRunning.total.toLocaleString()}`}</div>
|
||||
<MDBox width="20rem">
|
||||
<MDProgress variant="gradient" value={100 * (qJobRunning.current / qJobRunning.total)} color="success" />
|
||||
</MDBox>
|
||||
</>
|
||||
)}
|
||||
{
|
||||
qJobRunningDate && (<i>{`Updated at ${qJobRunningDate?.toLocaleTimeString()}`}</i>)
|
||||
}
|
||||
</MDTypography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === null)
|
||||
{
|
||||
console.log("in getDynamicStepContent. No step yet, so returning 'loading'");
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<MDTypography variation="h5" fontWeight="bold">{step?.label}</MDTypography>
|
||||
<MDTypography variation="h5" component="div" fontWeight="bold">{step?.label}</MDTypography>
|
||||
{step.components && (
|
||||
step.components.map((component: QFrontendComponent, index: number) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
@ -267,60 +280,97 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
</MDTypography>
|
||||
)
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.BULK_EDIT_FORM && (
|
||||
<QDynamicForm formData={formData} bulkEditMode bulkEditSwitchChangeHandler={bulkEditSwitchChanged} />
|
||||
)
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.EDIT_FORM && (
|
||||
<QDynamicForm formData={formData} />
|
||||
)
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.VIEW_FORM && step.viewFields && (
|
||||
<div>
|
||||
{step.viewFields.map((field: QFieldMetaData) => (
|
||||
<MDBox key={field.name} display="flex" py={1} pr={2}>
|
||||
<MDTypography variant="button" fontWeight="bold">
|
||||
{field.label}
|
||||
:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{formatViewValue(processValues[field.name])}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.VALIDATION_REVIEW_SCREEN && (
|
||||
<QValidationReview
|
||||
qInstance={qInstance}
|
||||
process={processMetaData}
|
||||
table={tableMetaData}
|
||||
processValues={processValues}
|
||||
step={step}
|
||||
previewRecords={records}
|
||||
formValues={formData.values}
|
||||
doFullValidationRadioChangedHandler={(event: any) =>
|
||||
{
|
||||
const {value} = event.currentTarget;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// call the formik function to set the value in this field. //
|
||||
//////////////////////////////////////////////////////////////
|
||||
setFieldValue("doFullValidation", value);
|
||||
|
||||
// eslint-disable-next-line no-unneeded-ternary
|
||||
setOverrideOnLastStep(value === "true" ? false : true);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.PROCESS_SUMMARY_RESULTS && (
|
||||
<QProcessSummaryResults qInstance={qInstance} process={processMetaData} table={tableMetaData} processValues={processValues} step={step} />
|
||||
)
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.RECORD_LIST && step.recordListFields && recordConfig.columns && (
|
||||
<div>
|
||||
<MDTypography variant="button" fontWeight="bold">Records</MDTypography>
|
||||
{" "}
|
||||
<br />
|
||||
<MDBox height="100%">
|
||||
<DataGridPro
|
||||
components={{Pagination: CustomPagination}}
|
||||
page={recordConfig.pageNo}
|
||||
disableSelectionOnClick
|
||||
autoHeight
|
||||
rows={recordConfig.rows}
|
||||
columns={recordConfig.columns}
|
||||
rowBuffer={10}
|
||||
rowCount={recordConfig.totalRecords}
|
||||
pageSize={recordConfig.rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
onPageSizeChange={recordConfig.handleRowsPerPageChange}
|
||||
onPageChange={recordConfig.handlePageChange}
|
||||
onRowClick={recordConfig.handleRowClick}
|
||||
getRowId={(row) => row.__idForDataGridPro__}
|
||||
paginationMode="server"
|
||||
pagination
|
||||
density="compact"
|
||||
loading={recordConfig.loading}
|
||||
disableColumnFilter
|
||||
/>
|
||||
</MDBox>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)))}
|
||||
{step.formFields && (
|
||||
<QDynamicForm
|
||||
formData={formData}
|
||||
bulkEditMode={doesStepHaveComponent(activeStep, QComponentType.BULK_EDIT_FORM)}
|
||||
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
|
||||
/>
|
||||
)}
|
||||
{step.viewFields && (
|
||||
<div>
|
||||
{step.viewFields.map((field: QFieldMetaData) => (
|
||||
<MDBox key={field.name} display="flex" py={1} pr={2}>
|
||||
<MDTypography variant="button" fontWeight="bold">
|
||||
{field.label}
|
||||
:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{formatViewValue(processValues[field.name])}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{(step.recordListFields && recordConfig.columns) && (
|
||||
<div>
|
||||
<MDTypography variant="button" fontWeight="bold">Records</MDTypography>
|
||||
{" "}
|
||||
<br />
|
||||
<MDBox height="100%">
|
||||
<DataGridPro
|
||||
components={{Pagination: CustomPagination}}
|
||||
page={recordConfig.pageNo}
|
||||
disableSelectionOnClick
|
||||
autoHeight
|
||||
rows={recordConfig.rows}
|
||||
columns={recordConfig.columns}
|
||||
rowBuffer={10}
|
||||
rowCount={recordConfig.totalRecords}
|
||||
pageSize={recordConfig.rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
onPageSizeChange={recordConfig.handleRowsPerPageChange}
|
||||
onPageChange={recordConfig.handlePageChange}
|
||||
onRowClick={recordConfig.handleRowClick}
|
||||
getRowId={(row) => row.__idForDataGridPro__}
|
||||
paginationMode="server"
|
||||
pagination
|
||||
density="compact"
|
||||
loading={recordConfig.loading}
|
||||
disableColumnFilter
|
||||
/>
|
||||
</MDBox>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -382,6 +432,7 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
setProcessError(`Unknown process step ${newStep}.`);
|
||||
}
|
||||
setActiveStepIndex(newIndex);
|
||||
setOverrideOnLastStep(null);
|
||||
|
||||
if (steps)
|
||||
{
|
||||
@ -424,6 +475,26 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
setValidationScheme(Yup.object().shape(formValidations));
|
||||
setValidationFunction(null);
|
||||
}
|
||||
else if (doesStepHaveComponent(activeStep, QComponentType.VALIDATION_REVIEW_SCREEN))
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// this component requires this field //
|
||||
////////////////////////////////////////
|
||||
const dynamicFormFields: any = {};
|
||||
dynamicFormFields.doFullValidation = {type: "radio"};
|
||||
|
||||
const initialValues: any = {};
|
||||
initialValues.doFullValidation = "true";
|
||||
setOverrideOnLastStep(false);
|
||||
|
||||
const formValidations: any = {};
|
||||
formValidations.doFullValidation = null;
|
||||
|
||||
setFormFields(dynamicFormFields);
|
||||
setInitialValues(initialValues);
|
||||
setValidationScheme(Yup.object().shape(formValidations));
|
||||
setValidationFunction(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
@ -502,6 +573,7 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
);
|
||||
|
||||
const {records} = response;
|
||||
setRecords(records);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// re-construct the recordConfig object, so the setState call triggers a new rendering //
|
||||
@ -542,6 +614,12 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
setJobUUID(null);
|
||||
setNewStep(qJobComplete.nextStep);
|
||||
setProcessValues(qJobComplete.values);
|
||||
setQJobRunning(null);
|
||||
|
||||
if (activeStep && activeStep.recordListFields)
|
||||
{
|
||||
setNeedRecords(true);
|
||||
}
|
||||
}
|
||||
else if (lastProcessResponse instanceof QJobStarted)
|
||||
{
|
||||
@ -562,6 +640,7 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
console.log(`Got an error from the backend... ${qJobError.error}`);
|
||||
setJobUUID(null);
|
||||
setProcessError(qJobError.error);
|
||||
setQJobRunning(null);
|
||||
}
|
||||
}
|
||||
}, [lastProcessResponse]);
|
||||
@ -574,13 +653,18 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
if (needToCheckJobStatus)
|
||||
{
|
||||
setNeedToCheckJobStatus(false);
|
||||
if (!processUUID || !jobUUID)
|
||||
{
|
||||
console.log(`Missing processUUID[${processUUID}] or jobUUID[${jobUUID}], so returning without checking job status`);
|
||||
return;
|
||||
}
|
||||
|
||||
(async () =>
|
||||
{
|
||||
setTimeout(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
console.log("OK");
|
||||
const processResponse = await QClient.getInstance().processJobStatus(
|
||||
processName,
|
||||
processUUID,
|
||||
@ -624,8 +708,7 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
setNeedInitialLoad(false);
|
||||
(async () =>
|
||||
{
|
||||
const {search} = useLocation();
|
||||
const urlSearchParams = new URLSearchParams(search);
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
let queryStringForInit = null;
|
||||
if (urlSearchParams.get("recordIds"))
|
||||
{
|
||||
@ -644,11 +727,35 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
// queryStringForInit = `recordsParam=filterId&filterId=${urlSearchParams.get("filterId")}`
|
||||
// }
|
||||
|
||||
try
|
||||
{
|
||||
const qInstance = await QClient.getInstance().loadMetaData();
|
||||
setQInstance(qInstance);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
setProcessError("Error loading process definition.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const processMetaData = await QClient.getInstance().loadProcessMetaData(processName);
|
||||
setProcessMetaData(processMetaData);
|
||||
setSteps(processMetaData.frontendSteps);
|
||||
if (processMetaData.tableName)
|
||||
{
|
||||
try
|
||||
{
|
||||
const tableMetaData = await QClient.getInstance().loadTableMetaData(processMetaData.tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
setProcessError("Error loading process's table definition.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
@ -714,6 +821,8 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
|
||||
};
|
||||
|
||||
setProcessValues({});
|
||||
setRecords([]);
|
||||
setLastProcessResponse(new QJobRunning({message: "Working..."}));
|
||||
|
||||
setTimeout(async () =>
|
||||
@ -729,15 +838,42 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelClicked = () =>
|
||||
{
|
||||
const pathParts = location.pathname.split(/\//);
|
||||
pathParts.pop();
|
||||
const path = pathParts.join("/");
|
||||
navigate(path, {replace: true});
|
||||
};
|
||||
|
||||
const mainCardStyles: any = {};
|
||||
mainCardStyles.minHeight = "calc(100vh - 400px)";
|
||||
if (qJobRunning || activeStep === null)
|
||||
{
|
||||
mainCardStyles.background = "none";
|
||||
mainCardStyles.boxShadow = "none";
|
||||
}
|
||||
|
||||
let nextButtonLabel = "Next";
|
||||
let nextButtonIcon = "arrow_forward";
|
||||
if (overrideOnLastStep !== null)
|
||||
{
|
||||
if (overrideOnLastStep)
|
||||
{
|
||||
nextButtonLabel = "Submit";
|
||||
nextButtonIcon = "check";
|
||||
}
|
||||
}
|
||||
else if (onLastStep)
|
||||
{
|
||||
nextButtonLabel = "Submit";
|
||||
nextButtonIcon = "check";
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseLayout>
|
||||
<MDBox py={3} mb={20}>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{height: "100%", mt: 8}}
|
||||
>
|
||||
<Grid container justifyContent="center" alignItems="center" sx={{height: "100%", mt: 8}}>
|
||||
<Grid item xs={12} lg={8}>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
@ -747,10 +883,10 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({
|
||||
values, errors, touched, isSubmitting,
|
||||
values, errors, touched, isSubmitting, setFieldValue,
|
||||
}) => (
|
||||
<Form id={formId} autoComplete="off">
|
||||
<Card sx={{minHeight: "calc(100vh - 400px)"}}>
|
||||
<Card sx={mainCardStyles}>
|
||||
<MDBox mx={2} mt={-3}>
|
||||
<Stepper activeStep={activeStepIndex} alternativeLabel>
|
||||
{steps.map((step) => (
|
||||
@ -760,6 +896,7 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
))}
|
||||
</Stepper>
|
||||
</MDBox>
|
||||
|
||||
<MDBox p={3}>
|
||||
<MDBox>
|
||||
{/***************************************************************************
|
||||
@ -777,28 +914,18 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
processError,
|
||||
processValues,
|
||||
recordConfig,
|
||||
setFieldValue,
|
||||
)}
|
||||
{/********************************
|
||||
** back &| next/submit buttons **
|
||||
********************************/}
|
||||
<MDBox
|
||||
mt={2}
|
||||
width="100%"
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<MDBox mt={6} width="100%" display="flex" justifyContent="space-between">
|
||||
{true || activeStepIndex === 0 ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<MDButton
|
||||
variant="gradient"
|
||||
color="light"
|
||||
onClick={handleBack}
|
||||
>
|
||||
back
|
||||
</MDButton>
|
||||
<MDButton variant="gradient" color="light" onClick={handleBack}>back</MDButton>
|
||||
)}
|
||||
{noMoreSteps || processError || qJobRunning ? (
|
||||
{processError || qJobRunning || !activeStep ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<>
|
||||
@ -807,14 +934,19 @@ function ProcessRun({process}: Props): JSX.Element
|
||||
{formError}
|
||||
</MDTypography>
|
||||
)}
|
||||
<MDButton
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
variant="gradient"
|
||||
color="dark"
|
||||
>
|
||||
{onLastStep ? "submit" : "next"}
|
||||
</MDButton>
|
||||
{
|
||||
noMoreSteps && <QCancelButton onClickHandler={handleCancelClicked} label="Return" iconName="arrow_back" disabled={isSubmitting} />
|
||||
}
|
||||
{
|
||||
!noMoreSteps && (
|
||||
<MDBox component="div" py={3}>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
<QCancelButton onClickHandler={handleCancelClicked} disabled={isSubmitting} />
|
||||
<QSubmitButton label={nextButtonLabel} iconName={nextButtonIcon} disabled={isSubmitting} />
|
||||
</Grid>
|
||||
</MDBox>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</MDBox>
|
||||
|
138
src/qqq/pages/process-run/model/ProcessSummaryLine.tsx
Normal file
138
src/qqq/pages/process-run/model/ProcessSummaryLine.tsx
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||
import {ListItem} from "@mui/material";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {Link} from "react-router-dom";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import React from "react";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import QTableUtils from "qqq/utils/QTableUtils";
|
||||
import MDBox from "components/MDBox";
|
||||
|
||||
/*******************************************************************************
|
||||
** Entity that corresponds to qqq backend's ProcessSummaryLine - with methods
|
||||
** to help display properly in a process review screen.
|
||||
*******************************************************************************/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export class ProcessSummaryLine
|
||||
{
|
||||
status: "OK" | "INFO" | "WARNING" | "ERROR";
|
||||
|
||||
count: number;
|
||||
|
||||
message: string;
|
||||
|
||||
primaryKeys: any[];
|
||||
|
||||
constructor(processSummaryLine: any)
|
||||
{
|
||||
this.status = processSummaryLine.status;
|
||||
this.count = processSummaryLine.count;
|
||||
this.message = processSummaryLine.message;
|
||||
this.primaryKeys = processSummaryLine.primaryKeys;
|
||||
}
|
||||
|
||||
getProcessSummaryListItem(i: number, table: QTableMetaData, qInstance: QInstance, isResultScreen: boolean = false): JSX.Element
|
||||
{
|
||||
return (
|
||||
<ListItem key={i} sx={{pl: 4, my: 2}}>
|
||||
<MDBox display="flex" alignItems="top">
|
||||
<Icon fontSize="medium" sx={{mr: 1}} color={this.getColor()}>{this.getIcon(isResultScreen)}</Icon>
|
||||
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||
{this.count.toLocaleString()}
|
||||
{" "}
|
||||
{this.message}
|
||||
</ListItemText>
|
||||
{
|
||||
table && this.primaryKeys && (
|
||||
<Link target="_blank" to={this.getLinkToRecords(table, qInstance)}>
|
||||
<Tooltip title="See these records in a new tab" sx={{py: 0}}>
|
||||
<IconButton sx={{py: 0}}><Icon fontSize="small">open_in_new</Icon></IconButton>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</MDBox>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
private getColor(): "success" | "info" | "warning" | "error" | "secondary"
|
||||
{
|
||||
if (this.status === "OK")
|
||||
{
|
||||
return "success";
|
||||
}
|
||||
else if (this.status === "INFO")
|
||||
{
|
||||
return "info";
|
||||
}
|
||||
else if (this.status === "WARNING")
|
||||
{
|
||||
return "warning";
|
||||
}
|
||||
else if (this.status === "ERROR")
|
||||
{
|
||||
return "error";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "secondary";
|
||||
}
|
||||
}
|
||||
|
||||
private getIcon(isResultScreen: boolean): string
|
||||
{
|
||||
if (this.status === "OK")
|
||||
{
|
||||
return isResultScreen ? "check" : "arrow_forward";
|
||||
}
|
||||
else if (this.status === "INFO")
|
||||
{
|
||||
return "info";
|
||||
}
|
||||
else if (this.status === "WARNING")
|
||||
{
|
||||
return "warning_amber";
|
||||
}
|
||||
else if (this.status === "ERROR")
|
||||
{
|
||||
return "report";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private getLinkToRecords(table: QTableMetaData, qInstance: QInstance): string
|
||||
{
|
||||
const tablePath = qInstance.getTablePath(table);
|
||||
const filter = new QQueryFilter([new QFilterCriteria(table.primaryKeyField, QCriteriaOperator.IN, this.primaryKeys)]);
|
||||
console.log("Link to records:");
|
||||
console.log(filter);
|
||||
return (`${tablePath}?filter=${JSON.stringify(filter)}`);
|
||||
}
|
||||
}
|
229
src/qqq/utils/QFilterUtils.ts
Normal file
229
src/qqq/utils/QFilterUtils.ts
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for working with QQQ Filters
|
||||
**
|
||||
*******************************************************************************/
|
||||
class QFilterUtils
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Convert a grid operator to a QQQ Criteria Operator.
|
||||
*******************************************************************************/
|
||||
public static gridCriteriaOperatorToQQQ = (operator: string): QCriteriaOperator =>
|
||||
{
|
||||
switch (operator)
|
||||
{
|
||||
case "contains":
|
||||
return QCriteriaOperator.CONTAINS;
|
||||
case "startsWith":
|
||||
return QCriteriaOperator.STARTS_WITH;
|
||||
case "endsWith":
|
||||
return QCriteriaOperator.ENDS_WITH;
|
||||
case "is":
|
||||
case "equals":
|
||||
case "=":
|
||||
return QCriteriaOperator.EQUALS;
|
||||
case "isNot":
|
||||
case "!=":
|
||||
return QCriteriaOperator.NOT_EQUALS;
|
||||
case "after":
|
||||
case ">":
|
||||
return QCriteriaOperator.GREATER_THAN;
|
||||
case "onOrAfter":
|
||||
case ">=":
|
||||
return QCriteriaOperator.GREATER_THAN_OR_EQUALS;
|
||||
case "before":
|
||||
case "<":
|
||||
return QCriteriaOperator.LESS_THAN;
|
||||
case "onOrBefore":
|
||||
case "<=":
|
||||
return QCriteriaOperator.LESS_THAN_OR_EQUALS;
|
||||
case "isEmpty":
|
||||
return QCriteriaOperator.IS_BLANK;
|
||||
case "isNotEmpty":
|
||||
return QCriteriaOperator.IS_NOT_BLANK;
|
||||
case "isAny":
|
||||
return QCriteriaOperator.IN;
|
||||
case "isNone": // todo - verify - not seen in UI
|
||||
return QCriteriaOperator.NOT_IN;
|
||||
default:
|
||||
return QCriteriaOperator.EQUALS;
|
||||
}
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert a qqq criteria operator to one expected by the grid.
|
||||
*******************************************************************************/
|
||||
public static qqqCriteriaOperatorToGrid = (operator: QCriteriaOperator, fieldType: QFieldType = QFieldType.STRING): string =>
|
||||
{
|
||||
switch (operator)
|
||||
{
|
||||
case QCriteriaOperator.EQUALS:
|
||||
switch (fieldType)
|
||||
{
|
||||
case QFieldType.INTEGER:
|
||||
case QFieldType.DECIMAL:
|
||||
return ("=");
|
||||
case QFieldType.DATE:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE_TIME:
|
||||
return ("equals");
|
||||
case QFieldType.BOOLEAN:
|
||||
case QFieldType.STRING:
|
||||
case QFieldType.TEXT:
|
||||
case QFieldType.HTML:
|
||||
case QFieldType.PASSWORD:
|
||||
case QFieldType.BLOB:
|
||||
default:
|
||||
return ("is");
|
||||
}
|
||||
case QCriteriaOperator.NOT_EQUALS:
|
||||
switch (fieldType)
|
||||
{
|
||||
case QFieldType.INTEGER:
|
||||
case QFieldType.DECIMAL:
|
||||
return ("!=");
|
||||
case QFieldType.DATE:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE_TIME:
|
||||
case QFieldType.BOOLEAN:
|
||||
case QFieldType.STRING:
|
||||
case QFieldType.TEXT:
|
||||
case QFieldType.HTML:
|
||||
case QFieldType.PASSWORD:
|
||||
case QFieldType.BLOB:
|
||||
default:
|
||||
return ("isNot");
|
||||
}
|
||||
case QCriteriaOperator.IN:
|
||||
return ("isAny");
|
||||
case QCriteriaOperator.NOT_IN:
|
||||
return ("isNone"); // todo verify - not seen in UI
|
||||
case QCriteriaOperator.STARTS_WITH:
|
||||
return ("startsWith");
|
||||
case QCriteriaOperator.ENDS_WITH:
|
||||
return ("endsWith");
|
||||
case QCriteriaOperator.CONTAINS:
|
||||
return ("contains");
|
||||
case QCriteriaOperator.NOT_STARTS_WITH:
|
||||
return (""); // todo - not supported in grid?
|
||||
case QCriteriaOperator.NOT_ENDS_WITH:
|
||||
return (""); // todo - not supported in grid?
|
||||
case QCriteriaOperator.NOT_CONTAINS:
|
||||
return (""); // todo - not supported in grid?
|
||||
case QCriteriaOperator.LESS_THAN:
|
||||
switch (fieldType)
|
||||
{
|
||||
case QFieldType.DATE:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE_TIME:
|
||||
return ("before");
|
||||
default:
|
||||
return ("<");
|
||||
}
|
||||
case QCriteriaOperator.LESS_THAN_OR_EQUALS:
|
||||
switch (fieldType)
|
||||
{
|
||||
case QFieldType.DATE:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE_TIME:
|
||||
return ("onOrBefore");
|
||||
default:
|
||||
return ("<=");
|
||||
}
|
||||
case QCriteriaOperator.GREATER_THAN:
|
||||
switch (fieldType)
|
||||
{
|
||||
case QFieldType.DATE:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE_TIME:
|
||||
return ("after");
|
||||
default:
|
||||
return (">");
|
||||
}
|
||||
case QCriteriaOperator.GREATER_THAN_OR_EQUALS:
|
||||
switch (fieldType)
|
||||
{
|
||||
case QFieldType.DATE:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE_TIME:
|
||||
return ("onOrAfter");
|
||||
default:
|
||||
return (">=");
|
||||
}
|
||||
case QCriteriaOperator.IS_BLANK:
|
||||
return ("isEmpty");
|
||||
case QCriteriaOperator.IS_NOT_BLANK:
|
||||
return ("isNotEmpty");
|
||||
case QCriteriaOperator.BETWEEN:
|
||||
return (""); // todo - not supported in grid?
|
||||
case QCriteriaOperator.NOT_BETWEEN:
|
||||
return (""); // todo - not supported in grid?
|
||||
default:
|
||||
console.warn(`Unhandled criteria operator: ${operator}`);
|
||||
return ("=");
|
||||
}
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
** the values object needs handled differently based on cardinality of the operator.
|
||||
** that is - qqq always wants a list, but the grid provides it differently per-operator.
|
||||
** for single-values (the default), we must wrap it in an array.
|
||||
** for non-values (e.g., blank), set it to null.
|
||||
** for list-values, it's already in an array, so don't wrap it.
|
||||
*******************************************************************************/
|
||||
public static gridCriteriaValueToQQQ = (operator: QCriteriaOperator, value: any): any[] =>
|
||||
{
|
||||
if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else if (operator === QCriteriaOperator.IN || operator === QCriteriaOperator.NOT_IN)
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
|
||||
return ([value]);
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static qqqCriteriaValuesToGrid = (operator: QCriteriaOperator, values: any[]): any | any[] =>
|
||||
{
|
||||
if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK)
|
||||
{
|
||||
return (null); // todo - verify
|
||||
}
|
||||
else if (operator === QCriteriaOperator.IN || operator === QCriteriaOperator.NOT_IN)
|
||||
{
|
||||
return (values);
|
||||
}
|
||||
|
||||
return (values[0]);
|
||||
};
|
||||
}
|
||||
|
||||
export default QFilterUtils;
|
@ -56,6 +56,11 @@ class QValueUtils
|
||||
return (displayValue);
|
||||
}
|
||||
|
||||
if (displayValue === undefined && rawValue !== undefined)
|
||||
{
|
||||
return (rawValue);
|
||||
}
|
||||
|
||||
return (displayValue);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user