Booleans on query; getProcessSummaryListItemForTableRecordLink; better location-change on entity-view; better singular/plural words in processes

This commit is contained in:
2022-10-04 10:30:54 -05:00
parent 017111d183
commit 2ba9085883
7 changed files with 121 additions and 13 deletions

View File

@ -35,12 +35,13 @@ 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 { import {
DataGridPro, DataGridPro, getGridDateOperators, getGridNumericOperators, getGridStringOperators,
GridCallbackDetails, GridCallbackDetails,
GridColDef, GridColDef,
GridColumnOrderChangeParams, GridColumnOrderChangeParams,
GridColumnVisibilityModel, GridColumnVisibilityModel,
GridExportMenuItemProps, GridExportMenuItemProps,
GridFilterItem,
GridFilterModel, GridFilterModel,
GridRowId, GridRowId,
GridRowParams, GridRowParams,
@ -55,6 +56,7 @@ import {
GridToolbarFilterButton, GridToolbarFilterButton,
MuiEvent MuiEvent
} from "@mui/x-data-grid-pro"; } from "@mui/x-data-grid-pro";
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
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";
@ -216,7 +218,7 @@ function EntityList({table}: Props): JSX.Element
filterModel.items.forEach((item) => filterModel.items.forEach((item) =>
{ {
const operator = QFilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue); const operator = QFilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue);
const values = QFilterUtils.gridCriteriaValueToQQQ(operator, item.value); const values = QFilterUtils.gridCriteriaValueToQQQ(operator, item.value, item.operatorValue);
qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values)); qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values));
}); });
} }
@ -324,6 +326,35 @@ function EntityList({table}: Props): JSX.Element
delete countResults[latestQueryId]; delete countResults[latestQueryId];
}, [receivedCountTimestamp]); }, [receivedCountTimestamp]);
const booleanTrueOperator: GridFilterOperator = {
label: "is yes",
value: "isTrue",
getApplyFilterFn: (filterItem: GridFilterItem, column: GridColDef) => null
};
const booleanFalseOperator: GridFilterOperator = {
label: "is no",
value: "isFalse",
getApplyFilterFn: (filterItem: GridFilterItem, column: GridColDef) => null
};
const booleanEmptyOperator: GridFilterOperator = {
label: "is empty",
value: "isEmpty",
getApplyFilterFn: (filterItem: GridFilterItem, column: GridColDef) => null
};
const booleanNotEmptyOperator: GridFilterOperator = {
label: "is not empty",
value: "isNotEmpty",
getApplyFilterFn: (filterItem: GridFilterItem, column: GridColDef) => null
};
const getCustomGridBooleanOperators = (): GridFilterOperator[] =>
{
return [booleanTrueOperator, booleanFalseOperator, booleanEmptyOperator, booleanNotEmptyOperator];
}
/////////////////////////// ///////////////////////////
// display query results // // display query results //
/////////////////////////// ///////////////////////////
@ -380,8 +411,13 @@ function EntityList({table}: Props): JSX.Element
let columnType = "string"; let columnType = "string";
let columnWidth = 200; let columnWidth = 200;
let filterOperators: GridFilterOperator<any>[] = getGridStringOperators();
if (!field.possibleValueSourceName) if (field.possibleValueSourceName)
{
filterOperators = getGridNumericOperators();
}
else
{ {
switch (field.type) switch (field.type)
{ {
@ -395,18 +431,23 @@ function EntityList({table}: Props): JSX.Element
columnWidth = 75; columnWidth = 75;
} }
// @ts-ignore
filterOperators = getGridNumericOperators();
break; break;
case QFieldType.DATE: case QFieldType.DATE:
columnType = "date"; columnType = "date";
columnWidth = 100; columnWidth = 100;
filterOperators = getGridDateOperators();
break; break;
case QFieldType.DATE_TIME: case QFieldType.DATE_TIME:
columnType = "dateTime"; columnType = "dateTime";
columnWidth = 200; columnWidth = 200;
filterOperators = getGridDateOperators(true);
break; break;
case QFieldType.BOOLEAN: case QFieldType.BOOLEAN:
columnType = "boolean"; columnType = "string"; // using boolean gives an odd 'no' for nulls.
columnWidth = 75; columnWidth = 75;
filterOperators = getCustomGridBooleanOperators();
break; break;
default: default:
// noop - leave as string // noop - leave as string
@ -452,6 +493,7 @@ function EntityList({table}: Props): JSX.Element
headerName: field.label, headerName: field.label,
width: columnWidth, width: columnWidth,
renderCell: null as any, renderCell: null as any,
filterOperators: filterOperators,
}; };
if(columnsToRender[field.name]) if(columnsToRender[field.name])
@ -835,7 +877,7 @@ function EntityList({table}: Props): JSX.Element
records on this page are selected. records on this page are selected.
<Button onClick={() => setSelectFullFilterState("filter")}> <Button onClick={() => setSelectFullFilterState("filter")}>
Select all Select all
{` ${totalRecords.toLocaleString()} `} {` ${totalRecords ? totalRecords.toLocaleString() : ""} `}
records matching this query records matching this query
</Button> </Button>
</div> </div>
@ -845,7 +887,7 @@ function EntityList({table}: Props): JSX.Element
selectFullFilterState === "filter" && ( selectFullFilterState === "filter" && (
<div className="selectionTool"> <div className="selectionTool">
All All
<strong>{` ${totalRecords.toLocaleString()} `}</strong> <strong>{` ${totalRecords ? totalRecords.toLocaleString() : "All"} `}</strong>
records matching this query are selected. records matching this query are selected.
<Button onClick={() => setSelectFullFilterState("checked")}> <Button onClick={() => setSelectFullFilterState("checked")}>
Select the Select the

View File

@ -88,6 +88,13 @@ function ViewContents({id, table}: Props): JSX.Element
useEffect(() => useEffect(() =>
{ {
setAsyncLoadInited(false); setAsyncLoadInited(false);
setTableMetaData(null)
setRecord(null)
setT1SectionElement(null)
setNonT1TableSections([])
setTableProcesses([])
setTableSections(null)
setWidgets(null)
}, [location]); }, [location]);
if (!asyncLoadInited) if (!asyncLoadInited)

View File

@ -70,8 +70,7 @@ function QProcessSummaryResults({
{QValueUtils.getFormattedNumber(processValues.recordCount)} {QValueUtils.getFormattedNumber(processValues.recordCount)}
{" "} {" "}
{sourceTableMetaData.label} {sourceTableMetaData.label}
{" "} {processValues.recordCount === 1 ? " record was" : " records were"} processed.
records were processed.
</ListItemText> </ListItemText>
</ListItem> </ListItem>
) )

View File

@ -189,8 +189,7 @@ function QValidationReview({
<ListItem sx={{my: 2}}> <ListItem sx={{my: 2}}>
<ListItemText primaryTypographyProps={{fontSize: 16}}> <ListItemText primaryTypographyProps={{fontSize: 16}}>
Validation complete on Validation complete on
{` ${QValueUtils.getFormattedNumber(processValues.recordCount)} ${sourceTableMetaData?.label} `} {` ${QValueUtils.getFormattedNumber(processValues.recordCount)} ${sourceTableMetaData?.label} ${processValues.recordCount === 1 ? "record." : "records."}`}
records.
</ListItemText> </ListItemText>
</ListItem> </ListItem>
) )

View File

@ -44,20 +44,58 @@ export class ProcessSummaryLine
status: "OK" | "INFO" | "WARNING" | "ERROR"; status: "OK" | "INFO" | "WARNING" | "ERROR";
count: number; count: number;
message: string; message: string;
primaryKeys: any[]; primaryKeys: any[];
tableName: string;
recordId: any;
linkPreText: string;
linkText: string;
linkPostText: string;
constructor(processSummaryLine: any) constructor(processSummaryLine: any)
{ {
this.status = processSummaryLine.status; this.status = processSummaryLine.status;
this.count = processSummaryLine.count; this.count = processSummaryLine.count;
this.message = processSummaryLine.message; this.message = processSummaryLine.message;
this.primaryKeys = processSummaryLine.primaryKeys; this.primaryKeys = processSummaryLine.primaryKeys;
this.tableName = processSummaryLine.tableName;
this.recordId = processSummaryLine.recordId;
this.linkPreText = processSummaryLine.linkPreText;
this.linkText = processSummaryLine.linkText;
this.linkPostText = processSummaryLine.linkPostText;
} }
getProcessSummaryListItem(i: number, table: QTableMetaData, qInstance: QInstance, isResultScreen: boolean = false): JSX.Element getProcessSummaryListItem(i: number, table: QTableMetaData, qInstance: QInstance, isResultScreen: boolean = false): JSX.Element
{
if (this.tableName != undefined && this.recordId != undefined)
{
return (this.getProcessSummaryListItemForTableRecordLink(i, table, qInstance, isResultScreen));
}
return (this.getProcessSummaryListItemForCountAndMessage(i, table, qInstance, isResultScreen));
}
private getProcessSummaryListItemForTableRecordLink(i: number, table: QTableMetaData, qInstance: QInstance, isResultScreen: boolean = false): JSX.Element
{
const tablePath = qInstance.getTablePathByName(this.tableName);
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.linkPreText ?? ""}
<Link to={`${tablePath}/${this.recordId}`}>{this.linkText}</Link>
{this.linkPostText ?? ""}
</ListItemText>
</MDBox>
</ListItem>
);
}
private getProcessSummaryListItemForCountAndMessage(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. // // 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. //

View File

@ -117,3 +117,15 @@
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
} }
/* let long field names in filter dropdown wrap instead of get cut off */
.MuiDataGrid-filterForm .MuiDataGrid-filterFormColumnInput .MuiNativeSelect-select.MuiNativeSelect-standard
{
white-space: normal;
height: auto;
}
.MuiDataGrid-filterForm
{
align-items: flex-end;
}

View File

@ -44,6 +44,8 @@ class QFilterUtils
case "is": case "is":
case "equals": case "equals":
case "=": case "=":
case "isTrue":
case "isFalse":
return QCriteriaOperator.EQUALS; return QCriteriaOperator.EQUALS;
case "isNot": case "isNot":
case "!=": case "!=":
@ -194,8 +196,17 @@ class QFilterUtils
** for non-values (e.g., blank), set it to null. ** for non-values (e.g., blank), set it to null.
** for list-values, it's already in an array, so don't wrap it. ** for list-values, it's already in an array, so don't wrap it.
*******************************************************************************/ *******************************************************************************/
public static gridCriteriaValueToQQQ = (operator: QCriteriaOperator, value: any): any[] => public static gridCriteriaValueToQQQ = (operator: QCriteriaOperator, value: any, gridOperatorValue: string): any[] =>
{ {
if(gridOperatorValue === "isTrue")
{
return [true];
}
else if(gridOperatorValue === "isFalse")
{
return [false];
}
if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK) if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK)
{ {
return (null); return (null);