mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Merge branch 'feature/sprint-10' into feature/QQQ-38-app-home-widgets
This commit is contained in:
@ -78,6 +78,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"jsx-one-expression-per-line": "off",
|
||||||
"max-len": "off",
|
"max-len": "off",
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-constant-condition": "off",
|
"no-constant-condition": "off",
|
||||||
|
Binary file not shown.
@ -344,8 +344,8 @@ function EntityForm({table, id}: Props): JSX.Element
|
|||||||
|
|
||||||
<MDBox component="div" p={3}>
|
<MDBox component="div" p={3}>
|
||||||
<Grid container justifyContent="flex-end" spacing={3}>
|
<Grid container justifyContent="flex-end" spacing={3}>
|
||||||
<QCancelButton onClickHandler={handleCancelClicked} />
|
<QCancelButton onClickHandler={handleCancelClicked} disabled={isSubmitting} />
|
||||||
<QSaveButton />
|
<QSaveButton disabled={isSubmitting} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
|
||||||
|
@ -42,11 +42,16 @@ export function QCreateNewButton(): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QSaveButton(): JSX.Element
|
interface QSaveButtonProps
|
||||||
|
{
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QSaveButton({disabled}: QSaveButtonProps): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<MDBox ml={3} width={standardWidth}>
|
<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
|
Save
|
||||||
</MDButton>
|
</MDButton>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
@ -108,15 +113,48 @@ export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonP
|
|||||||
interface QCancelButtonProps
|
interface QCancelButtonProps
|
||||||
{
|
{
|
||||||
onClickHandler: any;
|
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 (
|
return (
|
||||||
<MDBox ml="auto" width={standardWidth}>
|
<MDBox ml="auto" width={standardWidth}>
|
||||||
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>cancel</Icon>} onClick={onClickHandler}>
|
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} onClick={onClickHandler} disabled={disabled}>
|
||||||
Cancel
|
{label}
|
||||||
</MDButton>
|
</MDButton>
|
||||||
</MDBox>
|
</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",
|
||||||
|
};
|
||||||
|
56
src/qqq/components/Temporary/MDProgress/MDProgressRoot.tsx
Normal file
56
src/qqq/components/Temporary/MDProgress/MDProgressRoot.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 LinearProgress from "@mui/material/LinearProgress";
|
||||||
|
import {styled, Theme} from "@mui/material/styles";
|
||||||
|
|
||||||
|
export default styled(LinearProgress)(
|
||||||
|
({theme, ownerState}: { theme?: Theme | any; ownerState: any }) =>
|
||||||
|
{
|
||||||
|
const {palette, functions} = theme;
|
||||||
|
const {color, value, variant} = ownerState;
|
||||||
|
|
||||||
|
const {text, gradients} = palette;
|
||||||
|
const {linearGradient} = functions;
|
||||||
|
|
||||||
|
// background value
|
||||||
|
let backgroundValue;
|
||||||
|
|
||||||
|
if (variant === "gradient")
|
||||||
|
{
|
||||||
|
backgroundValue = gradients[color]
|
||||||
|
? linearGradient(gradients[color].main, gradients[color].state)
|
||||||
|
: linearGradient(gradients.info.main, gradients.info.state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
backgroundValue = palette[color] ? palette[color].main : palette.info.main;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"& .MuiLinearProgress-bar": {
|
||||||
|
background: backgroundValue,
|
||||||
|
width: `${value}%`,
|
||||||
|
color: text.main,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
60
src/qqq/components/Temporary/MDProgress/index.tsx
Normal file
60
src/qqq/components/Temporary/MDProgress/index.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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 {FC, forwardRef} from "react";
|
||||||
|
import MDProgressRoot from "components/MDProgress/MDProgressRoot";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
|
||||||
|
// Delcare props types for MDProgress
|
||||||
|
interface Props {
|
||||||
|
variant?: "contained" | "gradient";
|
||||||
|
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||||
|
value: number;
|
||||||
|
label?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MDProgress: FC<Props> = forwardRef(({variant, color, value, label, ...rest}, ref) => (
|
||||||
|
<>
|
||||||
|
{label && (
|
||||||
|
<MDTypography variant="button" fontWeight="medium" color="text">
|
||||||
|
{value}%
|
||||||
|
</MDTypography>
|
||||||
|
)}
|
||||||
|
<MDProgressRoot
|
||||||
|
{...rest}
|
||||||
|
ref={ref}
|
||||||
|
variant="determinate"
|
||||||
|
value={value}
|
||||||
|
ownerState={{color, value, variant}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
|
||||||
|
// Declaring default props for MDProgress
|
||||||
|
MDProgress.defaultProps = {
|
||||||
|
variant: "contained",
|
||||||
|
color: "info",
|
||||||
|
value: 0,
|
||||||
|
label: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MDProgress;
|
Binary file not shown.
@ -22,7 +22,6 @@
|
|||||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
|
||||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||||
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
@ -33,9 +32,9 @@ import Icon from "@mui/material/Icon";
|
|||||||
import LinearProgress from "@mui/material/LinearProgress";
|
import LinearProgress from "@mui/material/LinearProgress";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridFilterModel, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, GridExportMenuItemProps, MuiEvent,} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridFilterModel, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, GridExportMenuItemProps, MuiEvent} from "@mui/x-data-grid-pro";
|
||||||
import React, {useCallback, useEffect, useReducer, useRef, useState,} from "react";
|
import React, {useCallback, useEffect, useReducer, useRef, useState} from "react";
|
||||||
import {Link, useNavigate, useParams, useSearchParams,} from "react-router-dom";
|
import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom";
|
||||||
import DashboardLayout from "qqq/components/DashboardLayout";
|
import DashboardLayout from "qqq/components/DashboardLayout";
|
||||||
import Footer from "qqq/components/Footer";
|
import Footer from "qqq/components/Footer";
|
||||||
import Navbar from "qqq/components/Navbar";
|
import Navbar from "qqq/components/Navbar";
|
||||||
@ -43,6 +42,7 @@ import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons";
|
|||||||
import MDAlert from "qqq/components/Temporary/MDAlert";
|
import MDAlert from "qqq/components/Temporary/MDAlert";
|
||||||
import MDBox from "qqq/components/Temporary/MDBox";
|
import MDBox from "qqq/components/Temporary/MDBox";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import QFilterUtils from "qqq/utils/QFilterUtils";
|
||||||
import QProcessUtils from "qqq/utils/QProcessUtils";
|
import QProcessUtils from "qqq/utils/QProcessUtils";
|
||||||
import QValueUtils from "qqq/utils/QValueUtils";
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
|
||||||
@ -55,6 +55,52 @@ interface Props
|
|||||||
table?: QTableMetaData;
|
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
|
function EntityList({table}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const tableNameParam = useParams().tableName;
|
const tableNameParam = useParams().tableName;
|
||||||
@ -70,7 +116,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||||
let defaultSort = [] as GridSortItem[];
|
let defaultSort = [] as GridSortItem[];
|
||||||
let defaultVisibility = {};
|
let defaultVisibility = {};
|
||||||
let _defaultFilter = {items: []} as GridFilterModel;
|
const _defaultFilter = getDefaultFilter(searchParams, filterLocalStorageKey);
|
||||||
|
|
||||||
if (localStorage.getItem(sortLocalStorageKey))
|
if (localStorage.getItem(sortLocalStorageKey))
|
||||||
{
|
{
|
||||||
@ -80,11 +126,6 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey));
|
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 [filterModel, setFilterModel] = useState(_defaultFilter);
|
||||||
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
||||||
@ -124,46 +165,6 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
const closeActionsMenu = () => setActionsMenu(null);
|
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 buildQFilter = () =>
|
||||||
{
|
{
|
||||||
const qFilter = new QQueryFilter();
|
const qFilter = new QQueryFilter();
|
||||||
@ -178,13 +179,9 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
filterModel.items.forEach((item) =>
|
filterModel.items.forEach((item) =>
|
||||||
{
|
{
|
||||||
const operator = translateCriteriaOperator(item.operatorValue);
|
const operator = QFilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue);
|
||||||
let criteria = new QFilterCriteria(item.columnField, operator, [item.value]);
|
const values = QFilterUtils.gridCriteriaValueToQQQ(operator, item.value);
|
||||||
if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK)
|
qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values));
|
||||||
{
|
|
||||||
criteria = new QFilterCriteria(item.columnField, operator, null);
|
|
||||||
}
|
|
||||||
qFilter.addCriteria(criteria);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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 {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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {ListItem} from "@mui/material";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import React from "react";
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine";
|
||||||
|
|
||||||
|
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 sourceTable = qInstance.tables.get(processValues.sourceTable);
|
||||||
|
|
||||||
|
const resultValidationList = (
|
||||||
|
<List sx={{mt: 2}}>
|
||||||
|
{
|
||||||
|
processValues?.recordCount !== undefined && sourceTable && (
|
||||||
|
<ListItem sx={{my: 2}}>
|
||||||
|
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||||
|
{processValues.recordCount.toLocaleString()}
|
||||||
|
{" "}
|
||||||
|
{sourceTable.label}
|
||||||
|
{" "}
|
||||||
|
records were processed.
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<List>
|
||||||
|
{
|
||||||
|
processValues.processResults && processValues.processResults.map((processSummaryLine: ProcessSummaryLine, i: number) => (new ProcessSummaryLine(processSummaryLine).getProcessSummaryListItem(i, sourceTable, 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">
|
||||||
|
{process.iconName && <Icon fontSize="medium" sx={{mr: 1}}>{process.iconName}</Icon>}
|
||||||
|
Process Summary
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
{resultValidationList}
|
||||||
|
</MDBox>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={0} lg={2} />
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QProcessSummaryResults;
|
289
src/qqq/pages/process-run/components/QValidationReview.tsx
Normal file
289
src/qqq/pages/process-run/components/QValidationReview.tsx
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* 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 {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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import {Button, FormControlLabel, ListItem, Radio, RadioGroup, tooltipClasses, TooltipProps} from "@mui/material";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import {styled} from "@mui/material/styles";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine";
|
||||||
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
|
||||||
|
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 sourceTable = qInstance.tables.get(processValues.sourceTable);
|
||||||
|
|
||||||
|
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 buildDoFullValidationRadioListItem = (value: "true" | "false", labelText: string, tooltipHTML: JSX.Element): JSX.Element =>
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// split up the label into words - then we'll display the last word by itself with a non-breaking space, no-wrap-glued to the button. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const labelWords = labelText.split(" ");
|
||||||
|
const lastWord = labelWords[labelWords.length - 1];
|
||||||
|
labelWords.splice(labelWords.length - 1, 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem sx={{pl: 2}}>
|
||||||
|
<FormControlLabel
|
||||||
|
value={value}
|
||||||
|
control={<Radio />}
|
||||||
|
label={(
|
||||||
|
<ListItemText primaryTypographyProps={{fontSize: 16, pt: 0.625}}>
|
||||||
|
{`${labelWords.join(" ")} `}
|
||||||
|
<span style={{whiteSpace: "nowrap"}}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-one-expression-per-line */}
|
||||||
|
{lastWord}. <CustomWidthTooltip title={tooltipHTML}>
|
||||||
|
<IconButton sx={{py: 0}}><Icon fontSize="small">info_outlined</Icon></IconButton>
|
||||||
|
{/* eslint-disable-next-line react/jsx-closing-tag-location */}
|
||||||
|
</CustomWidthTooltip>
|
||||||
|
</span>
|
||||||
|
</ListItemText>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const preValidationList = (
|
||||||
|
<List sx={{mt: 2}}>
|
||||||
|
{
|
||||||
|
processValues?.recordCount !== undefined && sourceTable && (
|
||||||
|
<ListItem sx={{my: 2}}>
|
||||||
|
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||||
|
{`Input: ${processValues.recordCount.toLocaleString()} ${sourceTable?.label} record${processValues.recordCount === 1 ? "" : "s"}.`}
|
||||||
|
</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 className="doFullValidationRadios">
|
||||||
|
<RadioGroup name="doFullValidation" value={formValues.doFullValidation} onChange={doFullValidationRadioChangedHandler}>
|
||||||
|
{buildDoFullValidationRadioListItem(
|
||||||
|
"true",
|
||||||
|
"Perform Validation on all records before processing", (
|
||||||
|
<div>
|
||||||
|
If you choose this option, a Validation step will run on all of the input records.
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
|
||||||
|
{buildDoFullValidationRadioListItem(
|
||||||
|
"false",
|
||||||
|
"Skip Validation. Submit the records for immediate processing", (
|
||||||
|
<div>
|
||||||
|
If you choose this option, the records input records 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>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</RadioGroup>
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
|
||||||
|
const postValidationList = (
|
||||||
|
<List sx={{mt: 2}}>
|
||||||
|
{
|
||||||
|
processValues?.recordCount !== undefined && sourceTable && (
|
||||||
|
<ListItem sx={{my: 2}}>
|
||||||
|
<ListItemText primaryTypographyProps={{fontSize: 16}}>
|
||||||
|
Validation complete on
|
||||||
|
{` ${processValues.recordCount.toLocaleString()} ${sourceTable?.label} `}
|
||||||
|
records.
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<List>
|
||||||
|
{
|
||||||
|
processValues.validationSummary && processValues.validationSummary.map((processSummaryLine: ProcessSummaryLine, i: number) => (new ProcessSummaryLine(processSummaryLine).getProcessSummaryListItem(i, sourceTable, 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">
|
||||||
|
{
|
||||||
|
processValues?.previewMessage && previewRecords && previewRecords.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<i>{processValues?.previewMessage}</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;
|
@ -24,12 +24,14 @@ import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QC
|
|||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent";
|
import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent";
|
||||||
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
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 {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||||
import {QJobRunning} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning";
|
import {QJobRunning} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning";
|
||||||
import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted";
|
import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted";
|
||||||
import {CircularProgress, TablePagination} from "@mui/material";
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import {Button, Icon, CircularProgress, TablePagination} from "@mui/material";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import Step from "@mui/material/Step";
|
import Step from "@mui/material/Step";
|
||||||
@ -38,16 +40,20 @@ import Stepper from "@mui/material/Stepper";
|
|||||||
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import {Form, Formik} from "formik";
|
import {Form, Formik} from "formik";
|
||||||
import React, {useEffect, useState, Fragment} from "react";
|
import React, {Fragment, useEffect, useState} from "react";
|
||||||
import {useLocation, useParams} from "react-router-dom";
|
import {useLocation, useParams, useNavigate} from "react-router-dom";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import BaseLayout from "qqq/components/BaseLayout";
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
|
import {QCancelButton, QSubmitButton} from "qqq/components/QButtons";
|
||||||
import QDynamicForm from "qqq/components/QDynamicForm";
|
import QDynamicForm from "qqq/components/QDynamicForm";
|
||||||
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
|
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
|
||||||
import MDBox from "qqq/components/Temporary/MDBox";
|
import MDBox from "qqq/components/Temporary/MDBox";
|
||||||
import MDButton from "qqq/components/Temporary/MDButton";
|
import MDButton from "qqq/components/Temporary/MDButton";
|
||||||
|
import MDProgress from "qqq/components/Temporary/MDProgress";
|
||||||
import MDTypography from "qqq/components/Temporary/MDTypography";
|
import MDTypography from "qqq/components/Temporary/MDTypography";
|
||||||
|
import QValidationReview from "qqq/pages/process-run/components/QValidationReview";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import QProcessSummaryResults from "./components/QProcessSummaryResults";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
@ -77,12 +83,21 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
||||||
const [needInitialLoad, setNeedInitialLoad] = useState(true);
|
const [needInitialLoad, setNeedInitialLoad] = useState(true);
|
||||||
const [processMetaData, setProcessMetaData] = useState(null);
|
const [processMetaData, setProcessMetaData] = useState(null);
|
||||||
|
const [tableMetaData, setTableMetaData] = useState(null);
|
||||||
|
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||||
const [processValues, setProcessValues] = useState({} as any);
|
const [processValues, setProcessValues] = useState({} as any);
|
||||||
const [processError, setProcessError] = useState(null as string);
|
const [processError, setProcessError] = useState(null as string);
|
||||||
const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false);
|
const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false);
|
||||||
const [lastProcessResponse, setLastProcessResponse] = useState(
|
const [lastProcessResponse, setLastProcessResponse] = useState(
|
||||||
null as QJobStarted | QJobComplete | QJobError | QJobRunning,
|
null as QJobStarted | QJobComplete | QJobError | QJobRunning,
|
||||||
);
|
);
|
||||||
|
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// the validation screen - it can change whether next is actually the final step or not... so, use this state field to track that. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const [overrideOnLastStep, setOverrideOnLastStep] = useState(null as boolean);
|
||||||
|
|
||||||
const onLastStep = activeStepIndex === steps.length - 2;
|
const onLastStep = activeStepIndex === steps.length - 2;
|
||||||
const noMoreSteps = activeStepIndex === steps.length - 1;
|
const noMoreSteps = activeStepIndex === steps.length - 1;
|
||||||
|
|
||||||
@ -103,12 +118,16 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
const [recordConfig, setRecordConfig] = useState({} as any);
|
const [recordConfig, setRecordConfig] = useState({} as any);
|
||||||
const [pageNumber, setPageNumber] = useState(0);
|
const [pageNumber, setPageNumber] = useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||||
|
const [records, setRecords] = useState([] as QRecord[]);
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// state for bulk edit form //
|
// state for bulk edit form //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
const [disabledBulkEditFields, setDisabledBulkEditFields] = useState({} as any);
|
const [disabledBulkEditFields, setDisabledBulkEditFields] = useState({} as any);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const doesStepHaveComponent = (step: QFrontendStepMetaData, type: QComponentType): boolean =>
|
const doesStepHaveComponent = (step: QFrontendStepMetaData, type: QComponentType): boolean =>
|
||||||
{
|
{
|
||||||
if (step.components)
|
if (step.components)
|
||||||
@ -159,7 +178,7 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
if (value === null || value === undefined)
|
if (value === null || value === undefined)
|
||||||
{
|
{
|
||||||
return <span>∅</span>;
|
return <span> </span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "string")
|
if (typeof value === "string")
|
||||||
@ -180,6 +199,11 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
return (<span>{value}</span>);
|
return (<span>{value}</span>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleShowErrorDetail = () =>
|
||||||
|
{
|
||||||
|
setShowErrorDetail(!showErrorDetail);
|
||||||
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
// generate the main form body content for a step //
|
// generate the main form body content for a step //
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
@ -190,60 +214,79 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
processError: string,
|
processError: string,
|
||||||
processValues: any,
|
processValues: any,
|
||||||
recordConfig: any,
|
recordConfig: any,
|
||||||
|
setFieldValue: any,
|
||||||
): JSX.Element =>
|
): JSX.Element =>
|
||||||
{
|
{
|
||||||
if (processError)
|
if (processError)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MDTypography color="error" variant="h5">
|
<MDTypography color="error" variant="h3" component="div">
|
||||||
Error
|
Error
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
<MDTypography color="body" variant="button">
|
<MDTypography color="body" variant="button">
|
||||||
{processError}
|
An error occurred while running the process:
|
||||||
|
{" "}
|
||||||
|
{process.label}
|
||||||
|
<MDBox mt={3} display="flex" justifyContent="center">
|
||||||
|
<MDBox display="flex" flexDirection="column" alignItems="center">
|
||||||
|
<Button onClick={toggleShowErrorDetail} startIcon={<Icon>{showErrorDetail ? "expand_less" : "expand_more"}</Icon>}>
|
||||||
|
{showErrorDetail ? "Hide " : "Show "}
|
||||||
|
detailed error message
|
||||||
|
</Button>
|
||||||
|
<MDBox mt={1} style={{display: showErrorDetail ? "block" : "none"}}>
|
||||||
|
{processError}
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qJobRunning)
|
if (qJobRunning || step === null)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<>
|
<Grid m={3} mt={9} container>
|
||||||
<MDTypography variant="h5">
|
<Grid item xs={0} lg={3} />
|
||||||
{" "}
|
<Grid item xs={12} lg={6}>
|
||||||
Working
|
<Card>
|
||||||
</MDTypography>
|
<MDBox p={3}>
|
||||||
<Grid container>
|
<MDTypography variant="h5" component="div">
|
||||||
<Grid item padding={1}>
|
Working
|
||||||
<CircularProgress color="info" />
|
</MDTypography>
|
||||||
</Grid>
|
<Grid container>
|
||||||
<Grid item>
|
<Grid item padding={2}>
|
||||||
<MDTypography color="body" variant="button">
|
<CircularProgress color="info" />
|
||||||
{qJobRunning?.message}
|
</Grid>
|
||||||
<br />
|
<Grid item padding={1}>
|
||||||
{qJobRunning.current && qJobRunning.total && (
|
<MDTypography color="body" variant="button">
|
||||||
<div>{`${qJobRunning.current.toLocaleString()} of ${qJobRunning.total.toLocaleString()}`}</div>
|
{qJobRunning?.message}
|
||||||
)}
|
<br />
|
||||||
<i>
|
{qJobRunning?.current && qJobRunning?.total && (
|
||||||
{`Updated at ${qJobRunningDate.toLocaleTimeString()}`}
|
<>
|
||||||
</i>
|
<div>{`${qJobRunning.current.toLocaleString()} of ${qJobRunning.total.toLocaleString()}`}</div>
|
||||||
</MDTypography>
|
<MDBox width="20rem">
|
||||||
</Grid>
|
<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>
|
||||||
</>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step === null)
|
|
||||||
{
|
|
||||||
console.log("in getDynamicStepContent. No step yet, so returning 'loading'");
|
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MDTypography variation="h5" fontWeight="bold">{step?.label}</MDTypography>
|
<MDTypography variation="h5" component="div" fontWeight="bold">{step?.label}</MDTypography>
|
||||||
{step.components && (
|
{step.components && (
|
||||||
step.components.map((component: QFrontendComponent, index: number) => (
|
step.components.map((component: QFrontendComponent, index: number) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
@ -255,60 +298,96 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
</MDTypography>
|
</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);
|
||||||
|
|
||||||
|
setOverrideOnLastStep(value !== "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>
|
</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>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -370,6 +449,7 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
setProcessError(`Unknown process step ${newStep}.`);
|
setProcessError(`Unknown process step ${newStep}.`);
|
||||||
}
|
}
|
||||||
setActiveStepIndex(newIndex);
|
setActiveStepIndex(newIndex);
|
||||||
|
setOverrideOnLastStep(null);
|
||||||
|
|
||||||
if (steps)
|
if (steps)
|
||||||
{
|
{
|
||||||
@ -412,6 +492,26 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
setValidationScheme(Yup.object().shape(formValidations));
|
setValidationScheme(Yup.object().shape(formValidations));
|
||||||
setValidationFunction(null);
|
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
|
else
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
@ -490,6 +590,7 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {records} = response;
|
const {records} = response;
|
||||||
|
setRecords(records);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// re-construct the recordConfig object, so the setState call triggers a new rendering //
|
// re-construct the recordConfig object, so the setState call triggers a new rendering //
|
||||||
@ -530,6 +631,12 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
setJobUUID(null);
|
setJobUUID(null);
|
||||||
setNewStep(qJobComplete.nextStep);
|
setNewStep(qJobComplete.nextStep);
|
||||||
setProcessValues(qJobComplete.values);
|
setProcessValues(qJobComplete.values);
|
||||||
|
setQJobRunning(null);
|
||||||
|
|
||||||
|
if (activeStep && activeStep.recordListFields)
|
||||||
|
{
|
||||||
|
setNeedRecords(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (lastProcessResponse instanceof QJobStarted)
|
else if (lastProcessResponse instanceof QJobStarted)
|
||||||
{
|
{
|
||||||
@ -550,6 +657,7 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
console.log(`Got an error from the backend... ${qJobError.error}`);
|
console.log(`Got an error from the backend... ${qJobError.error}`);
|
||||||
setJobUUID(null);
|
setJobUUID(null);
|
||||||
setProcessError(qJobError.error);
|
setProcessError(qJobError.error);
|
||||||
|
setQJobRunning(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [lastProcessResponse]);
|
}, [lastProcessResponse]);
|
||||||
@ -562,13 +670,18 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
if (needToCheckJobStatus)
|
if (needToCheckJobStatus)
|
||||||
{
|
{
|
||||||
setNeedToCheckJobStatus(false);
|
setNeedToCheckJobStatus(false);
|
||||||
|
if (!processUUID || !jobUUID)
|
||||||
|
{
|
||||||
|
console.log(`Missing processUUID[${processUUID}] or jobUUID[${jobUUID}], so returning without checking job status`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
setTimeout(async () =>
|
setTimeout(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
console.log("OK");
|
|
||||||
const processResponse = await QClient.getInstance().processJobStatus(
|
const processResponse = await QClient.getInstance().processJobStatus(
|
||||||
processName,
|
processName,
|
||||||
processUUID,
|
processUUID,
|
||||||
@ -612,8 +725,7 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
setNeedInitialLoad(false);
|
setNeedInitialLoad(false);
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const {search} = useLocation();
|
const urlSearchParams = new URLSearchParams(location.search);
|
||||||
const urlSearchParams = new URLSearchParams(search);
|
|
||||||
let queryStringForInit = null;
|
let queryStringForInit = null;
|
||||||
if (urlSearchParams.get("recordIds"))
|
if (urlSearchParams.get("recordIds"))
|
||||||
{
|
{
|
||||||
@ -632,11 +744,35 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
// queryStringForInit = `recordsParam=filterId&filterId=${urlSearchParams.get("filterId")}`
|
// 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
|
try
|
||||||
{
|
{
|
||||||
const processMetaData = await QClient.getInstance().loadProcessMetaData(processName);
|
const processMetaData = await QClient.getInstance().loadProcessMetaData(processName);
|
||||||
setProcessMetaData(processMetaData);
|
setProcessMetaData(processMetaData);
|
||||||
setSteps(processMetaData.frontendSteps);
|
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)
|
catch (e)
|
||||||
{
|
{
|
||||||
@ -702,6 +838,9 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
|
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setProcessValues({});
|
||||||
|
setRecords([]);
|
||||||
|
setOverrideOnLastStep(null);
|
||||||
setLastProcessResponse(new QJobRunning({message: "Working..."}));
|
setLastProcessResponse(new QJobRunning({message: "Working..."}));
|
||||||
|
|
||||||
setTimeout(async () =>
|
setTimeout(async () =>
|
||||||
@ -717,15 +856,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 (!processError && (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 (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<MDBox py={3} mb={20}>
|
<MDBox py={3} mb={20}>
|
||||||
<Grid
|
<Grid container justifyContent="center" alignItems="center" sx={{height: "100%", mt: 8}}>
|
||||||
container
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
sx={{height: "100%", mt: 8}}
|
|
||||||
>
|
|
||||||
<Grid item xs={12} lg={8}>
|
<Grid item xs={12} lg={8}>
|
||||||
<Formik
|
<Formik
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
@ -735,10 +901,10 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{({
|
{({
|
||||||
values, errors, touched, isSubmitting,
|
values, errors, touched, isSubmitting, setFieldValue,
|
||||||
}) => (
|
}) => (
|
||||||
<Form id={formId} autoComplete="off">
|
<Form id={formId} autoComplete="off">
|
||||||
<Card sx={{minHeight: "calc(100vh - 400px)"}}>
|
<Card sx={mainCardStyles}>
|
||||||
<MDBox mx={2} mt={-3}>
|
<MDBox mx={2} mt={-3}>
|
||||||
<Stepper activeStep={activeStepIndex} alternativeLabel>
|
<Stepper activeStep={activeStepIndex} alternativeLabel>
|
||||||
{steps.map((step) => (
|
{steps.map((step) => (
|
||||||
@ -748,6 +914,7 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
))}
|
))}
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
|
||||||
<MDBox p={3}>
|
<MDBox p={3}>
|
||||||
<MDBox>
|
<MDBox>
|
||||||
{/***************************************************************************
|
{/***************************************************************************
|
||||||
@ -765,28 +932,18 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
processError,
|
processError,
|
||||||
processValues,
|
processValues,
|
||||||
recordConfig,
|
recordConfig,
|
||||||
|
setFieldValue,
|
||||||
)}
|
)}
|
||||||
{/********************************
|
{/********************************
|
||||||
** back &| next/submit buttons **
|
** back &| next/submit buttons **
|
||||||
********************************/}
|
********************************/}
|
||||||
<MDBox
|
<MDBox mt={6} width="100%" display="flex" justifyContent="space-between">
|
||||||
mt={2}
|
|
||||||
width="100%"
|
|
||||||
display="flex"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
{true || activeStepIndex === 0 ? (
|
{true || activeStepIndex === 0 ? (
|
||||||
<MDBox />
|
<MDBox />
|
||||||
) : (
|
) : (
|
||||||
<MDButton
|
<MDButton variant="gradient" color="light" onClick={handleBack}>back</MDButton>
|
||||||
variant="gradient"
|
|
||||||
color="light"
|
|
||||||
onClick={handleBack}
|
|
||||||
>
|
|
||||||
back
|
|
||||||
</MDButton>
|
|
||||||
)}
|
)}
|
||||||
{noMoreSteps || processError || qJobRunning ? (
|
{processError || qJobRunning || !activeStep ? (
|
||||||
<MDBox />
|
<MDBox />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -795,14 +952,19 @@ function ProcessRun({process}: Props): JSX.Element
|
|||||||
{formError}
|
{formError}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
)}
|
)}
|
||||||
<MDButton
|
{
|
||||||
disabled={isSubmitting}
|
noMoreSteps && <QCancelButton onClickHandler={handleCancelClicked} label="Return" iconName="arrow_back" disabled={isSubmitting} />
|
||||||
type="submit"
|
}
|
||||||
variant="gradient"
|
{
|
||||||
color="dark"
|
!noMoreSteps && (
|
||||||
>
|
<MDBox component="div" py={3}>
|
||||||
{onLastStep ? "submit" : "next"}
|
<Grid container justifyContent="flex-end" spacing={3}>
|
||||||
</MDButton>
|
<QCancelButton onClickHandler={handleCancelClicked} disabled={isSubmitting} />
|
||||||
|
<QSubmitButton label={nextButtonLabel} iconName={nextButtonIcon} disabled={isSubmitting} />
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
)
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
148
src/qqq/pages/process-run/model/ProcessSummaryLine.tsx
Normal file
148
src/qqq/pages/process-run/model/ProcessSummaryLine.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* 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 {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||||
|
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||||
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
|
import {ListItem} from "@mui/material";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
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
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// split up the message into words - then we'll display the last word by itself with a non-breaking space, no-wrap-glued to the button. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const messageWords = this.message ? this.message.split(" ") : [];
|
||||||
|
const lastWord = messageWords.length > 1 ? messageWords[messageWords.length - 1] : "";
|
||||||
|
if (messageWords.length > 1)
|
||||||
|
{
|
||||||
|
messageWords.splice(messageWords.length - 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}}>
|
||||||
|
{/* work hard to prevent the icon from falling down to the next line by itself... */}
|
||||||
|
{`${this.count.toLocaleString()} ${messageWords.join(" ")} `}
|
||||||
|
{
|
||||||
|
(table && this.primaryKeys) ? (
|
||||||
|
<span style={{whiteSpace: "nowrap"}}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-one-expression-per-line */}
|
||||||
|
{lastWord} <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>
|
||||||
|
{/* eslint-disable-next-line react/jsx-closing-tag-location */}
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
) : <span>{lastWord}</span>
|
||||||
|
}
|
||||||
|
</ListItemText>
|
||||||
|
</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)]);
|
||||||
|
return (`${tablePath}?filter=${JSON.stringify(filter)}`);
|
||||||
|
}
|
||||||
|
}
|
@ -110,3 +110,10 @@
|
|||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Help make the radio, text, and icon wrap in a good way */
|
||||||
|
.doFullValidationRadios label
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
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 {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
|
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** 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;
|
@ -55,6 +55,11 @@ class QValueUtils
|
|||||||
return (displayValue);
|
return (displayValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (displayValue === undefined && rawValue !== undefined)
|
||||||
|
{
|
||||||
|
return (rawValue);
|
||||||
|
}
|
||||||
|
|
||||||
return (displayValue);
|
return (displayValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user