mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Checkpoint; nearing completion of custom filter panel
This commit is contained in:
@ -38,6 +38,7 @@ interface Props
|
||||
tableName?: string;
|
||||
processName?: string;
|
||||
fieldName: string;
|
||||
overrideId?: string;
|
||||
fieldLabel: string;
|
||||
inForm: boolean;
|
||||
initialValue?: any;
|
||||
@ -70,29 +71,34 @@ DynamicSelect.defaultProps = {
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
function DynamicSelect({tableName, processName, fieldName, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues}: Props)
|
||||
function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues}: Props)
|
||||
{
|
||||
const [ open, setOpen ] = useState(false);
|
||||
const [ options, setOptions ] = useState<readonly QPossibleValue[]>([]);
|
||||
const [ searchTerm, setSearchTerm ] = useState(null);
|
||||
const [ firstRender, setFirstRender ] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [options, setOptions] = useState<readonly QPossibleValue[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState(null);
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default value - needs to be an array (from initialValues (array) prop) for multiple mode - //
|
||||
// else non-multiple, assume we took in an initialValue (id) and initialDisplayValue (label), //
|
||||
// and build a little object that looks like a possibleValue out of those //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const [defaultValue, _] = isMultiple ? useState(initialValues ?? undefined)
|
||||
let [defaultValue, _] = isMultiple ? useState(initialValues ?? undefined)
|
||||
: useState(initialValue && initialDisplayValue ? [{id: initialValue, label: initialDisplayValue}] : null);
|
||||
|
||||
if (isMultiple && defaultValue === null)
|
||||
{
|
||||
defaultValue = [];
|
||||
}
|
||||
|
||||
// const loading = open && options.length === 0;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [ switchChecked, setSwitchChecked ] = useState(false);
|
||||
const [ isDisabled, setIsDisabled ] = useState(!isEditable || bulkEditMode);
|
||||
const [switchChecked, setSwitchChecked] = useState(false);
|
||||
const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
|
||||
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
||||
|
||||
let setFieldValueRef: (field: string, value: any, shouldValidate?: boolean) => void = null;
|
||||
if(inForm)
|
||||
if (inForm)
|
||||
{
|
||||
const {setFieldValue} = useFormikContext();
|
||||
setFieldValueRef = setFieldValue;
|
||||
@ -239,9 +245,11 @@ function DynamicSelect({tableName, processName, fieldName, fieldLabel, inForm, i
|
||||
bulkEditSwitchChangeHandler(fieldName, newSwitchValue);
|
||||
};
|
||||
|
||||
// console.log(`default value: ${JSON.stringify(defaultValue)}`);
|
||||
|
||||
const autocomplete = (
|
||||
<Autocomplete
|
||||
id={fieldName}
|
||||
id={overrideId ?? fieldName}
|
||||
sx={{background: isDisabled ? "#f0f2f5!important" : "initial"}}
|
||||
open={open}
|
||||
fullWidth
|
||||
@ -291,6 +299,8 @@ function DynamicSelect({tableName, processName, fieldName, fieldLabel, inForm, i
|
||||
disabled={isDisabled}
|
||||
multiple={isMultiple}
|
||||
disableCloseOnSelect={isMultiple}
|
||||
limitTags={5}
|
||||
slotProps={{popper: {className: "DynamicSelectPopper"}}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
|
@ -19,6 +19,7 @@
|
||||
* 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";
|
||||
@ -28,7 +29,7 @@ import Button from "@mui/material/Button/Button";
|
||||
import Icon from "@mui/material/Icon/Icon";
|
||||
import {GridFilterPanelProps, GridSlotsComponentsProps} from "@mui/x-data-grid-pro";
|
||||
import React, {forwardRef, useReducer} from "react";
|
||||
import {FilterCriteriaRow} from "qqq/components/query/FilterCriteriaRow";
|
||||
import {FilterCriteriaRow, getDefaultCriteriaValue} from "qqq/components/query/FilterCriteriaRow";
|
||||
|
||||
|
||||
declare module "@mui/x-data-grid"
|
||||
@ -39,6 +40,7 @@ declare module "@mui/x-data-grid"
|
||||
interface FilterPanelPropsOverrides
|
||||
{
|
||||
tableMetaData: QTableMetaData;
|
||||
metaData: QInstance;
|
||||
queryFilter: QQueryFilter;
|
||||
updateFilter: (newFilter: QQueryFilter) => void;
|
||||
}
|
||||
@ -66,9 +68,9 @@ export const CustomFilterPanel = forwardRef<any, GridFilterPanelProps>(
|
||||
{
|
||||
setTimeout(() =>
|
||||
{
|
||||
console.log(`Try to focus ${criteriaId - 1}`);
|
||||
try
|
||||
{
|
||||
// console.log(`Try to focus ${criteriaId - 1}`);
|
||||
document.getElementById(`field-${criteriaId - 1}`).focus();
|
||||
}
|
||||
catch (e)
|
||||
@ -80,7 +82,7 @@ export const CustomFilterPanel = forwardRef<any, GridFilterPanelProps>(
|
||||
|
||||
const addCriteria = () =>
|
||||
{
|
||||
const qFilterCriteriaWithId = new QFilterCriteriaWithId(null, QCriteriaOperator.EQUALS, [""]);
|
||||
const qFilterCriteriaWithId = new QFilterCriteriaWithId(null, QCriteriaOperator.EQUALS, getDefaultCriteriaValue());
|
||||
qFilterCriteriaWithId.id = criteriaId++;
|
||||
console.log(`adding criteria id ${qFilterCriteriaWithId.id}`);
|
||||
queryFilter.criteria.push(qFilterCriteriaWithId);
|
||||
@ -98,8 +100,29 @@ export const CustomFilterPanel = forwardRef<any, GridFilterPanelProps>(
|
||||
|
||||
if (queryFilter.criteria.length == 0)
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// make sure there's at least one criteria //
|
||||
/////////////////////////////////////////////
|
||||
addCriteria();
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure all criteria have an id on them (to be used as react component keys) //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
let updatedAny = false;
|
||||
for (let i = 0; i < queryFilter.criteria.length; i++)
|
||||
{
|
||||
if (!queryFilter.criteria[i].id)
|
||||
{
|
||||
queryFilter.criteria[i].id = criteriaId++;
|
||||
}
|
||||
}
|
||||
if (updatedAny)
|
||||
{
|
||||
props.updateFilter(queryFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if(queryFilter.criteria.length == 1 && !queryFilter.criteria[0].fieldName)
|
||||
{
|
||||
@ -149,6 +172,7 @@ export const CustomFilterPanel = forwardRef<any, GridFilterPanelProps>(
|
||||
id={criteria.id}
|
||||
index={index}
|
||||
tableMetaData={props.tableMetaData}
|
||||
metaData={props.metaData}
|
||||
criteria={criteria}
|
||||
booleanOperator={booleanOperator}
|
||||
updateCriteria={(newCriteria, needDebounce) => updateCriteria(newCriteria, index, needDebounce)}
|
||||
|
@ -36,11 +36,12 @@ import ChipTextField from "qqq/components/forms/ChipTextField";
|
||||
interface Props
|
||||
{
|
||||
type: string;
|
||||
onSave: (newValues: any[]) => void;
|
||||
}
|
||||
|
||||
FilterCriteriaPaster.defaultProps = {};
|
||||
|
||||
function FilterCriteriaPaster({type}: Props): JSX.Element
|
||||
function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
||||
{
|
||||
enum Delimiter
|
||||
{
|
||||
@ -86,14 +87,6 @@ function FilterCriteriaPaster({type}: Props): JSX.Element
|
||||
setPasteModalIsOpen(true);
|
||||
};
|
||||
|
||||
const applyValue = (item: GridFilterItem) =>
|
||||
{
|
||||
console.log(`updating grid values: ${JSON.stringify(item.value)}`);
|
||||
// todo!
|
||||
// setGridFilterItem(item);
|
||||
// props.applyValue(item);
|
||||
};
|
||||
|
||||
const clearData = () =>
|
||||
{
|
||||
setDelimiter("");
|
||||
@ -113,34 +106,19 @@ function FilterCriteriaPaster({type}: Props): JSX.Element
|
||||
|
||||
const handleSaveClicked = () =>
|
||||
{
|
||||
//x if (gridFilterItem)
|
||||
/* todo
|
||||
////////////////////////////////////////
|
||||
// if numeric remove any non-numerics //
|
||||
////////////////////////////////////////
|
||||
let saveData = [];
|
||||
for (let i = 0; i < chipData.length; i++)
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// if numeric remove any non-numerics //
|
||||
////////////////////////////////////////
|
||||
let saveData = [];
|
||||
for (let i = 0; i < chipData.length; i++)
|
||||
if (type !== "number" || !Number.isNaN(Number(chipData[i])))
|
||||
{
|
||||
if (type !== "number" || !Number.isNaN(Number(chipData[i])))
|
||||
{
|
||||
saveData.push(chipData[i]);
|
||||
}
|
||||
saveData.push(chipData[i]);
|
||||
}
|
||||
|
||||
if (gridFilterItem.value)
|
||||
{
|
||||
gridFilterItem.value = [...gridFilterItem.value, ...saveData];
|
||||
}
|
||||
else
|
||||
{
|
||||
gridFilterItem.value = saveData;
|
||||
}
|
||||
|
||||
setGridFilterItem(gridFilterItem);
|
||||
props.applyValue(gridFilterItem);
|
||||
}
|
||||
*/
|
||||
|
||||
onSave(saveData);
|
||||
|
||||
clearData();
|
||||
setPasteModalIsOpen(false);
|
||||
@ -299,7 +277,7 @@ function FilterCriteriaPaster({type}: Props): JSX.Element
|
||||
return (
|
||||
<Box>
|
||||
<Tooltip title="Quickly add many values to your filter by pasting them from a spreadsheet or any other data source.">
|
||||
<Icon onClick={handlePasteClick} fontSize="small" color="info" sx={{marginLeft: "10px", cursor: "pointer"}}>paste_content</Icon>
|
||||
<Icon onClick={handlePasteClick} fontSize="small" color="info" sx={{mx: 0.25, cursor: "pointer"}}>paste_content</Icon>
|
||||
</Tooltip>
|
||||
{
|
||||
pasteModalIsOpen &&
|
||||
|
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||
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";
|
||||
@ -57,12 +58,14 @@ export interface OperatorOption
|
||||
valueMode: ValueMode;
|
||||
}
|
||||
|
||||
export const getDefaultCriteriaValue = () => [""];
|
||||
|
||||
interface FilterCriteriaRowProps
|
||||
{
|
||||
id: number;
|
||||
index: number;
|
||||
tableMetaData: QTableMetaData;
|
||||
metaData: QInstance;
|
||||
criteria: QFilterCriteria;
|
||||
booleanOperator: "AND" | "OR" | null;
|
||||
updateCriteria: (newCriteria: QFilterCriteria, needDebounce: boolean) => void;
|
||||
@ -82,11 +85,11 @@ function makeFieldOptionsForTable(tableMetaData: QTableMetaData, fieldOptions: a
|
||||
}
|
||||
}
|
||||
|
||||
export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOperator, updateCriteria, removeCriteria, updateBooleanOperator}: FilterCriteriaRowProps): JSX.Element
|
||||
export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, booleanOperator, updateCriteria, removeCriteria, updateBooleanOperator}: FilterCriteriaRowProps): JSX.Element
|
||||
{
|
||||
// console.log(`FilterCriteriaRow: criteria: ${JSON.stringify(criteria)}`);
|
||||
const [operatorSelectedValue, setOperatorSelectedValue] = useState(null as OperatorOption);
|
||||
const [operatorInputValue, setOperatorInputValue] = useState("")
|
||||
const [operatorInputValue, setOperatorInputValue] = useState("");
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// set up the array of options for the fields Autocomplete //
|
||||
@ -98,12 +101,14 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
|
||||
if (tableMetaData.exposedJoins && tableMetaData.exposedJoins.length > 0)
|
||||
{
|
||||
fieldsGroupBy = (option: any) => `${option.table.label} Fields`;
|
||||
|
||||
for (let i = 0; i < tableMetaData.exposedJoins.length; i++)
|
||||
{
|
||||
const exposedJoin = tableMetaData.exposedJoins[i];
|
||||
makeFieldOptionsForTable(exposedJoin.joinTable, fieldOptions, true);
|
||||
if (metaData.tables.has(exposedJoin.joinTable.name))
|
||||
{
|
||||
fieldsGroupBy = (option: any) => `${option.table.label} fields`;
|
||||
makeFieldOptionsForTable(exposedJoin.joinTable, fieldOptions, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,8 +129,8 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
//////////////////////////////////////////////////////
|
||||
if (field.possibleValueSourceName)
|
||||
{
|
||||
operatorOptions.push({label: "is", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.PVS_SINGLE});
|
||||
operatorOptions.push({label: "is not", value: QCriteriaOperator.NOT_EQUALS, valueMode: ValueMode.PVS_SINGLE});
|
||||
operatorOptions.push({label: "equals", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.PVS_SINGLE});
|
||||
operatorOptions.push({label: "does not equal", value: QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, valueMode: ValueMode.PVS_SINGLE});
|
||||
operatorOptions.push({label: "is empty", value: QCriteriaOperator.IS_BLANK, valueMode: ValueMode.NONE});
|
||||
operatorOptions.push({label: "is not empty", value: QCriteriaOperator.IS_NOT_BLANK, valueMode: ValueMode.NONE});
|
||||
operatorOptions.push({label: "is any of", value: QCriteriaOperator.IN, valueMode: ValueMode.PVS_MULTI});
|
||||
@ -138,7 +143,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
operatorOptions.push({label: "equals", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.SINGLE});
|
||||
operatorOptions.push({label: "not equals", value: QCriteriaOperator.NOT_EQUALS, valueMode: ValueMode.SINGLE});
|
||||
operatorOptions.push({label: "does not equal", value: QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, valueMode: ValueMode.SINGLE});
|
||||
operatorOptions.push({label: "greater than", value: QCriteriaOperator.GREATER_THAN, valueMode: ValueMode.SINGLE});
|
||||
operatorOptions.push({label: "greater than or equals", value: QCriteriaOperator.GREATER_THAN_OR_EQUALS, valueMode: ValueMode.SINGLE});
|
||||
operatorOptions.push({label: "less than", value: QCriteriaOperator.LESS_THAN, valueMode: ValueMode.SINGLE});
|
||||
@ -151,8 +156,8 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
operatorOptions.push({label: "is none of", value: QCriteriaOperator.NOT_IN, valueMode: ValueMode.MULTI});
|
||||
break;
|
||||
case QFieldType.DATE:
|
||||
operatorOptions.push({label: "is", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.SINGLE_DATE});
|
||||
operatorOptions.push({label: "is not", value: QCriteriaOperator.NOT_EQUALS, valueMode: ValueMode.SINGLE_DATE});
|
||||
operatorOptions.push({label: "equals", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.SINGLE_DATE});
|
||||
operatorOptions.push({label: "does not equal", value: QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, valueMode: ValueMode.SINGLE_DATE});
|
||||
operatorOptions.push({label: "is after", value: QCriteriaOperator.GREATER_THAN, valueMode: ValueMode.SINGLE_DATE});
|
||||
operatorOptions.push({label: "is before", value: QCriteriaOperator.LESS_THAN, valueMode: ValueMode.SINGLE_DATE});
|
||||
operatorOptions.push({label: "is empty", value: QCriteriaOperator.IS_BLANK, valueMode: ValueMode.NONE});
|
||||
@ -163,8 +168,8 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
//? operatorOptions.push({label: "is none of", value: QCriteriaOperator.NOT_IN});
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
operatorOptions.push({label: "is", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
operatorOptions.push({label: "is not", value: QCriteriaOperator.NOT_EQUALS, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
operatorOptions.push({label: "equals", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
operatorOptions.push({label: "does not equal", value: QCriteriaOperator.NOT_EQUALS_OR_IS_NULL, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
operatorOptions.push({label: "is after", value: QCriteriaOperator.GREATER_THAN, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
operatorOptions.push({label: "is on or after", value: QCriteriaOperator.GREATER_THAN_OR_EQUALS, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
operatorOptions.push({label: "is before", value: QCriteriaOperator.LESS_THAN, valueMode: ValueMode.SINGLE_DATE_TIME});
|
||||
@ -175,8 +180,8 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
//? operatorOptions.push({label: "is not between", value: QCriteriaOperator.NOT_BETWEEN});
|
||||
break;
|
||||
case QFieldType.BOOLEAN:
|
||||
operatorOptions.push({label: "is yes", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.NONE, implicitValues: [true]});
|
||||
operatorOptions.push({label: "is no", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.NONE, implicitValues: [false]});
|
||||
operatorOptions.push({label: "equals yes", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.NONE, implicitValues: [true]});
|
||||
operatorOptions.push({label: "equals no", value: QCriteriaOperator.EQUALS, valueMode: ValueMode.NONE, implicitValues: [false]});
|
||||
operatorOptions.push({label: "is empty", value: QCriteriaOperator.IS_BLANK, valueMode: ValueMode.NONE});
|
||||
operatorOptions.push({label: "is not empty", value: QCriteriaOperator.IS_NOT_BLANK, valueMode: ValueMode.NONE});
|
||||
/*
|
||||
@ -266,20 +271,39 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
//////////////////////////////////////////
|
||||
const handleFieldChange = (event: any, newValue: any, reason: string) =>
|
||||
{
|
||||
criteria.fieldName = newValue ? newValue.fieldName : null;
|
||||
updateCriteria(criteria, false);
|
||||
const oldFieldName = criteria.fieldName;
|
||||
|
||||
setOperatorOptions(criteria.fieldName)
|
||||
if(operatorOptions.length)
|
||||
criteria.fieldName = newValue ? newValue.fieldName : null;
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// decide if we should clear out the values or not. //
|
||||
//////////////////////////////////////////////////////
|
||||
if (criteria.fieldName == null || isFieldTypeDifferent(oldFieldName, criteria.fieldName))
|
||||
{
|
||||
setOperatorSelectedValue(operatorOptions[0]);
|
||||
setOperatorInputValue(operatorOptions[0].label);
|
||||
criteria.values = getDefaultCriteriaValue();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// update the operator options, and the operator on this criteria //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
setOperatorOptions(criteria.fieldName);
|
||||
if (operatorOptions.length)
|
||||
{
|
||||
if (isFieldTypeDifferent(oldFieldName, criteria.fieldName))
|
||||
{
|
||||
criteria.operator = operatorOptions[0].value;
|
||||
setOperatorSelectedValue(operatorOptions[0]);
|
||||
setOperatorInputValue(operatorOptions[0].label);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
criteria.operator = null;
|
||||
setOperatorSelectedValue(null);
|
||||
setOperatorInputValue("");
|
||||
}
|
||||
|
||||
updateCriteria(criteria, false);
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////
|
||||
@ -314,7 +338,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
const handleValueChange = (event: React.ChangeEvent | SyntheticEvent, valueIndex: number | "all" = 0, newValue?: any) =>
|
||||
{
|
||||
// @ts-ignore
|
||||
const value = newValue ? newValue : event ? event.target.value : null;
|
||||
const value = newValue !== undefined ? newValue : event ? event.target.value : null;
|
||||
|
||||
if(!criteria.values)
|
||||
{
|
||||
@ -323,7 +347,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
|
||||
if(valueIndex == "all")
|
||||
{
|
||||
criteria.values= value;
|
||||
criteria.values = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -333,6 +357,22 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
updateCriteria(criteria, true);
|
||||
};
|
||||
|
||||
const isFieldTypeDifferent = (fieldNameA: string, fieldNameB: string): boolean =>
|
||||
{
|
||||
const [fieldA] = FilterUtils.getField(tableMetaData, fieldNameA);
|
||||
const [fieldB] = FilterUtils.getField(tableMetaData, fieldNameB);
|
||||
if (fieldA?.type !== fieldB.type)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
if (fieldA.possibleValueSourceName !== fieldB.possibleValueSourceName)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
};
|
||||
|
||||
function isFieldOptionEqual(option: any, value: any)
|
||||
{
|
||||
return option.fieldName === value.fieldName;
|
||||
@ -465,6 +505,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
renderOption={(props, option, state) => renderFieldOption(props, option, state)}
|
||||
autoSelect={true}
|
||||
autoHighlight={true}
|
||||
slotProps={{popper: {style: {padding: 0, width: "250px"}}}}
|
||||
/>
|
||||
</Box>
|
||||
<Box display="inline-block" width={200}>
|
||||
@ -481,6 +522,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, criteria, booleanOp
|
||||
getOptionLabel={(option: any) => option.label}
|
||||
autoSelect={true}
|
||||
autoHighlight={true}
|
||||
slotProps={{popper: {style: {padding: 0, maxHeight: "unset", width: "200px"}}}}
|
||||
/*disabled={criteria.fieldName == null}*/
|
||||
/>
|
||||
</Tooltip>
|
||||
|
@ -25,11 +25,16 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Box from "@mui/material/Box";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import InputAdornment from "@mui/material/InputAdornment/InputAdornment";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import React, {SyntheticEvent} from "react";
|
||||
import React, {SyntheticEvent, useReducer} from "react";
|
||||
import DynamicSelect from "qqq/components/forms/DynamicSelect";
|
||||
import {QFilterCriteriaWithId} from "qqq/components/query/CustomFilterPanel";
|
||||
import FilterCriteriaPaster from "qqq/components/query/FilterCriteriaPaster";
|
||||
import {OperatorOption, ValueMode} from "qqq/components/query/FilterCriteriaRow";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -45,31 +50,64 @@ FilterCriteriaRowValues.defaultProps = {
|
||||
|
||||
function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueChangeHandler}: Props): JSX.Element
|
||||
{
|
||||
if(!operatorOption)
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
if (!operatorOption)
|
||||
{
|
||||
return <br />
|
||||
return <br />;
|
||||
}
|
||||
|
||||
const makeTextField = (valueIndex: number = 0, label = "Value", idPrefix="value-") =>
|
||||
const getTypeForTextField = (): string =>
|
||||
{
|
||||
let type = "search"
|
||||
const inputLabelProps: any = {};
|
||||
let type = "search";
|
||||
|
||||
if(field.type == QFieldType.INTEGER)
|
||||
if (field.type == QFieldType.INTEGER)
|
||||
{
|
||||
type = "number";
|
||||
}
|
||||
else if(field.type == QFieldType.DATE)
|
||||
else if (field.type == QFieldType.DATE)
|
||||
{
|
||||
type = "date";
|
||||
inputLabelProps.shrink = true;
|
||||
}
|
||||
else if(field.type == QFieldType.DATE_TIME)
|
||||
else if (field.type == QFieldType.DATE_TIME)
|
||||
{
|
||||
type = "datetime-local";
|
||||
}
|
||||
|
||||
return (type);
|
||||
};
|
||||
|
||||
const makeTextField = (valueIndex: number = 0, label = "Value", idPrefix = "value-") =>
|
||||
{
|
||||
let type = getTypeForTextField();
|
||||
const inputLabelProps: any = {};
|
||||
|
||||
if (field.type == QFieldType.DATE || field.type == QFieldType.DATE_TIME)
|
||||
{
|
||||
inputLabelProps.shrink = true;
|
||||
}
|
||||
|
||||
let value = criteria.values[valueIndex];
|
||||
if (field.type == QFieldType.DATE_TIME && value && String(value).indexOf("Z") > -1)
|
||||
{
|
||||
value = ValueUtils.formatDateTimeValueForForm(value);
|
||||
}
|
||||
|
||||
const clearValue = (event: React.MouseEvent<HTMLAnchorElement> | React.MouseEvent<HTMLButtonElement>, index: number) =>
|
||||
{
|
||||
valueChangeHandler(event, index, "");
|
||||
document.getElementById(`${idPrefix}${criteria.id}`).focus();
|
||||
};
|
||||
|
||||
const inputProps: any = {};
|
||||
inputProps.endAdornment = (
|
||||
<InputAdornment position="end">
|
||||
<IconButton sx={{visibility: value ? "visible" : "hidden"}} onClick={(event) => clearValue(event, valueIndex)}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
return <TextField
|
||||
id={`${idPrefix}${criteria.id}`}
|
||||
label={label}
|
||||
@ -77,17 +115,40 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
||||
autoComplete="off"
|
||||
type={type}
|
||||
onChange={(event) => valueChangeHandler(event, valueIndex)}
|
||||
value={criteria.values[valueIndex]}
|
||||
value={value}
|
||||
InputLabelProps={inputLabelProps}
|
||||
InputProps={inputProps}
|
||||
fullWidth
|
||||
// todo - x to clear value?
|
||||
/>
|
||||
/>;
|
||||
};
|
||||
|
||||
function saveNewPasterValues(newValues: any[])
|
||||
{
|
||||
if (criteria.values)
|
||||
{
|
||||
criteria.values = [...criteria.values, ...newValues];
|
||||
}
|
||||
else
|
||||
{
|
||||
criteria.values = newValues;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we are somehow getting some empty-strings as first-value leaking through. they aren't cool, so, remove them if we find them //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (criteria.values.length > 0 && criteria.values[0] == "")
|
||||
{
|
||||
criteria.values = criteria.values.splice(1);
|
||||
}
|
||||
|
||||
valueChangeHandler(null, "all", criteria.values);
|
||||
forceUpdate();
|
||||
}
|
||||
|
||||
switch (operatorOption.valueMode)
|
||||
{
|
||||
case ValueMode.NONE:
|
||||
return <br />
|
||||
return <br />;
|
||||
case ValueMode.SINGLE:
|
||||
return makeTextField();
|
||||
case ValueMode.SINGLE_DATE:
|
||||
@ -100,30 +161,36 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
||||
{ makeTextField(0, "From", "from-") }
|
||||
</Box>
|
||||
<Box width="50%" display="inline-block">
|
||||
{ makeTextField(1, "To", "to-") }
|
||||
{makeTextField(1, "To", "to-")}
|
||||
</Box>
|
||||
</Box>;
|
||||
case ValueMode.MULTI:
|
||||
let values = criteria.values;
|
||||
if(values && values.length == 1 && values[0] == "")
|
||||
if (values && values.length == 1 && values[0] == "")
|
||||
{
|
||||
values = [];
|
||||
}
|
||||
return <Autocomplete
|
||||
renderInput={(params) => (<TextField {...params} variant="standard" label="Values" />)}
|
||||
options={[]}
|
||||
multiple
|
||||
freeSolo // todo - no debounce after enter?
|
||||
selectOnFocus
|
||||
clearOnBlur
|
||||
limitTags={5}
|
||||
value={values}
|
||||
onChange={(event, value) => valueChangeHandler(event, "all", value)}
|
||||
/>
|
||||
// todo - need the Paste button
|
||||
return <Box display="flex" alignItems="flex-end">
|
||||
<Autocomplete
|
||||
renderInput={(params) => (<TextField {...params} variant="standard" label="Values" />)}
|
||||
options={[]}
|
||||
multiple
|
||||
freeSolo // todo - no debounce after enter?
|
||||
selectOnFocus
|
||||
clearOnBlur
|
||||
fullWidth
|
||||
limitTags={5}
|
||||
value={values}
|
||||
onChange={(event, value) => valueChangeHandler(event, "all", value)}
|
||||
/>
|
||||
<Box>
|
||||
<FilterCriteriaPaster type={getTypeForTextField()} onSave={(newValues: any[]) => saveNewPasterValues(newValues)} />
|
||||
</Box>
|
||||
</Box>;
|
||||
case ValueMode.PVS_SINGLE:
|
||||
console.log("Doing pvs single: " + criteria.values);
|
||||
let selectedPossibleValue = null;
|
||||
if(criteria.values && criteria.values.length > 0)
|
||||
if (criteria.values && criteria.values.length > 0)
|
||||
{
|
||||
selectedPossibleValue = criteria.values[0];
|
||||
}
|
||||
@ -131,22 +198,38 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
||||
<DynamicSelect
|
||||
tableName={table.name}
|
||||
fieldName={field.name}
|
||||
overrideId={field.name + "-single-" + criteria.id}
|
||||
key={field.name + "-single-" + criteria.id}
|
||||
fieldLabel="Value"
|
||||
initialValue={selectedPossibleValue?.id}
|
||||
initialDisplayValue={selectedPossibleValue?.label}
|
||||
inForm={false}
|
||||
onChange={(value: any) => valueChangeHandler(null, 0, value)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>;
|
||||
case ValueMode.PVS_MULTI:
|
||||
// todo - values not sticking when re-opening filter panel
|
||||
console.log("Doing pvs multi: " + criteria.values);
|
||||
let initialValues: any[] = [];
|
||||
if (criteria.values && criteria.values.length > 0)
|
||||
{
|
||||
if (criteria.values.length == 1 && criteria.values[0] == "")
|
||||
{
|
||||
// we never want a tag that's just ""...
|
||||
}
|
||||
else
|
||||
{
|
||||
initialValues = criteria.values;
|
||||
}
|
||||
}
|
||||
return <Box mb={-1.5}>
|
||||
<DynamicSelect
|
||||
tableName={table.name}
|
||||
fieldName={field.name}
|
||||
overrideId={field.name + "-multi-" + criteria.id}
|
||||
key={field.name + "-multi-" + criteria.id}
|
||||
isMultiple
|
||||
fieldLabel="Values"
|
||||
initialValues={criteria.values || []}
|
||||
initialValues={initialValues}
|
||||
inForm={false}
|
||||
onChange={(value: any) => valueChangeHandler(null, "all", value)}
|
||||
/>
|
||||
|
@ -350,7 +350,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const buildQFilter = (tableMetaData: QTableMetaData, filterModel: GridFilterModel, limit?: number) =>
|
||||
{
|
||||
const filter = FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel, limit);
|
||||
let filter = FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel, limit);
|
||||
filter = FilterUtils.convertFilterPossibleValuesToIds(filter);
|
||||
setHasValidFilters(filter.criteria && filter.criteria.length > 0);
|
||||
return (filter);
|
||||
};
|
||||
@ -879,11 +880,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
console.log(columnOrderChangeParams);
|
||||
};
|
||||
|
||||
const handleFilterChange = (filterModel: GridFilterModel, doSetQueryFilter = true) =>
|
||||
const handleFilterChange = (filterModel: GridFilterModel, doSetQueryFilter = true, isChangeFromDataGrid = false) =>
|
||||
{
|
||||
setFilterModel(filterModel);
|
||||
|
||||
if(doSetQueryFilter)
|
||||
if (doSetQueryFilter)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// someone might have already set the query filter, so, only set it if asked to //
|
||||
@ -891,6 +892,18 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setQueryFilter(FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel, rowsPerPage));
|
||||
}
|
||||
|
||||
if (isChangeFromDataGrid)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this function is called by our code several times, but also from dataGridPro when its filter model changes. //
|
||||
// in general, we don't want a "partial" criteria to be part of our query filter object (e.g., w/ no values) //
|
||||
// BUT - for one use-case, when the user adds a "filter" (criteria) from column-header "..." menu, then dataGridPro //
|
||||
// puts a partial item in its filter - so - in that case, we do like to get this partial criteria in our QFilter. //
|
||||
// so far, not seeing any negatives to this being here, and it fixes that user experience, so keep this. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
setQueryFilter(FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel, rowsPerPage, true));
|
||||
}
|
||||
|
||||
if (filterLocalStorageKey)
|
||||
{
|
||||
localStorage.setItem(filterLocalStorageKey, JSON.stringify(filterModel));
|
||||
@ -1700,7 +1713,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
// to avoid both this useEffect and the one below from both doing an "initial query", //
|
||||
// only run this one if at least 1 query has already been ran //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// console.log("calling update table for UE 1");
|
||||
updateTable();
|
||||
}
|
||||
}, [pageNumber, rowsPerPage, columnSortModel, currentSavedFilter]);
|
||||
@ -1712,7 +1724,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
setTotalRecords(null);
|
||||
setDistinctRecords(null);
|
||||
// console.log("calling update table for UE 2");
|
||||
updateTable();
|
||||
}, [columnsModel, tableState]);
|
||||
|
||||
@ -1722,19 +1733,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
currentQFilter.skip = pageNumber * rowsPerPage;
|
||||
const currentQFilterJSON = JSON.stringify(currentQFilter);
|
||||
|
||||
// console.log(`current ${currentQFilterJSON}`);
|
||||
// console.log(`last... ${lastFetchedQFilterJSON}`);
|
||||
if(currentQFilterJSON !== lastFetchedQFilterJSON)
|
||||
{
|
||||
setTotalRecords(null);
|
||||
setDistinctRecords(null);
|
||||
// console.log("calling update table for UE 3");
|
||||
updateTable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// console.log("NOT calling update table for UE 3!!");
|
||||
}
|
||||
|
||||
}, [filterModel]);
|
||||
|
||||
@ -1744,12 +1748,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
}, [pageNumber, rowsPerPage]);
|
||||
|
||||
const updateFilter = (newFilter: QQueryFilter): void =>
|
||||
const updateFilterFromFilterPanel = (newFilter: QQueryFilter): void =>
|
||||
{
|
||||
setQueryFilter(newFilter);
|
||||
const gridFilterModel = FilterUtils.buildGridFilterFromQFilter(tableMetaData, queryFilter);
|
||||
handleFilterChange(gridFilterModel, false);
|
||||
}
|
||||
};
|
||||
|
||||
if (tableMetaData && !tableMetaData.readPermission)
|
||||
{
|
||||
@ -1813,7 +1817,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
}
|
||||
<Box display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
|
||||
<Box display="flex" marginRight="auto">
|
||||
<SavedFilters qController={qController} metaData={metaData} tableMetaData={tableMetaData} currentSavedFilter={currentSavedFilter} filterModel={filterModel} columnSortModel={columnSortModel} filterOnChangeCallback={handleSavedFilterChange} />
|
||||
{
|
||||
metaData && metaData.processes.has("querySavedFilter") &&
|
||||
<SavedFilters qController={qController} metaData={metaData} tableMetaData={tableMetaData} currentSavedFilter={currentSavedFilter} filterModel={filterModel} columnSortModel={columnSortModel} filterOnChangeCallback={handleSavedFilterChange} />
|
||||
}
|
||||
</Box>
|
||||
|
||||
<Box display="flex" width="150px">
|
||||
@ -1840,6 +1847,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
columnsPanel:
|
||||
{
|
||||
tableMetaData: tableMetaData,
|
||||
metaData: metaData,
|
||||
initialOpenedGroups: columnChooserOpenGroups,
|
||||
openGroupsChanger: setColumnChooserOpenGroups,
|
||||
initialFilterText: columnChooserFilterText,
|
||||
@ -1848,8 +1856,9 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
filterPanel:
|
||||
{
|
||||
tableMetaData: tableMetaData,
|
||||
metaData: metaData,
|
||||
queryFilter: queryFilter,
|
||||
updateFilter: updateFilter
|
||||
updateFilter: updateFilterFromFilterPanel
|
||||
}
|
||||
}}
|
||||
localeText={{
|
||||
@ -1880,7 +1889,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
density={density}
|
||||
loading={loading}
|
||||
filterModel={filterModel}
|
||||
onFilterModelChange={(model) => handleFilterChange(model)}
|
||||
onFilterModelChange={(model) => handleFilterChange(model, true, true)}
|
||||
columnVisibilityModel={columnVisibilityModel}
|
||||
onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||
onColumnOrderChange={handleColumnOrderChange}
|
||||
|
@ -421,42 +421,50 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
||||
top: -60px !important;
|
||||
}
|
||||
|
||||
/* tighten the text in the field select dropdown in custom filters */
|
||||
.customFilterPanel .MuiAutocomplete-paper
|
||||
{
|
||||
line-height: 1.375;
|
||||
}
|
||||
|
||||
/* tighten the text in the field select dropdown in custom filters */
|
||||
.customFilterPanel .MuiAutocomplete-groupLabel
|
||||
{
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
/* taller list box */
|
||||
.customFilterPanel .MuiAutocomplete-listbox
|
||||
{
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
/* shrink down-arrows in custom filters panel */
|
||||
.customFilterPanel .booleanOperatorColumn .MuiSelect-iconStandard,
|
||||
.customFilterPanel .MuiSvgIcon-root
|
||||
{
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
/* fix something in AND/OR dropdown in filters */
|
||||
.customFilterPanel .booleanOperatorColumn .MuiSvgIcon-root
|
||||
{
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* adjust bottom of AND/OR dropdown in filters */
|
||||
.customFilterPanel .booleanOperatorColumn .MuiInputBase-formControl
|
||||
{
|
||||
padding-bottom: calc(0.25rem + 1px);
|
||||
}
|
||||
|
||||
/* adjust down-arrow in AND/OR dropdown in filters */
|
||||
.customFilterPanel .booleanOperatorColumn .MuiSelect-iconStandard
|
||||
{
|
||||
top: calc(50% - 0.75rem);
|
||||
}
|
||||
|
||||
/* change tags in any-of value fields to not be black bg with white text */
|
||||
.customFilterPanel .filterValuesColumn .MuiChip-root
|
||||
{
|
||||
background: none;
|
||||
@ -464,13 +472,32 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
/* change 'x' icon in tags in any-of value */
|
||||
.customFilterPanel .filterValuesColumn .MuiChip-root .MuiChip-deleteIcon
|
||||
{
|
||||
color: gray;
|
||||
}
|
||||
|
||||
/* change tags in any-of value fields to not be black bg with white text */
|
||||
.customFilterPanel .filterValuesColumn .MuiAutocomplete-tag
|
||||
{
|
||||
color: #191919;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* default hover color for the 'x' to remove a tag from an 'any-of' value was white, which made it disappear */
|
||||
.customFilterPanel .filterValuesColumn .MuiAutocomplete-tag .MuiSvgIcon-root:hover
|
||||
{
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.DynamicSelectPopper ul
|
||||
{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.DynamicSelectPopper ul li.MuiAutocomplete-option
|
||||
{
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
@ -25,13 +25,42 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {getGridDateOperators, GridColDef, GridRowsProp} from "@mui/x-data-grid-pro";
|
||||
import {GridColDef, GridFilterItem, GridRowsProp} from "@mui/x-data-grid-pro";
|
||||
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {buildQGridPvsOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/records/query/GridFilterOperators";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
|
||||
const emptyApplyFilterFn = (filterItem: GridFilterItem, column: GridColDef): null => null;
|
||||
|
||||
function NullInputComponent()
|
||||
{
|
||||
return (<React.Fragment />);
|
||||
}
|
||||
|
||||
const makeGridFilterOperator = (value: string, label: string, takesValues: boolean = false): GridFilterOperator =>
|
||||
{
|
||||
const rs: GridFilterOperator = {value: value, label: label, getApplyFilterFn: emptyApplyFilterFn};
|
||||
if (takesValues)
|
||||
{
|
||||
rs.InputComponent = NullInputComponent;
|
||||
}
|
||||
return (rs);
|
||||
};
|
||||
|
||||
const QGridDateOperators = [
|
||||
makeGridFilterOperator("equals", "equals", true),
|
||||
makeGridFilterOperator("isNot", "not equals", true),
|
||||
makeGridFilterOperator("after", "is after", true),
|
||||
makeGridFilterOperator("onOrAfter", "is on or after", true),
|
||||
makeGridFilterOperator("before", "is before", true),
|
||||
makeGridFilterOperator("onOrBefore", "is on or before", true),
|
||||
makeGridFilterOperator("isEmpty", "is empty"),
|
||||
makeGridFilterOperator("isNotEmpty", "is not empty"),
|
||||
];
|
||||
|
||||
export default class DataGridUtils
|
||||
{
|
||||
|
||||
@ -40,7 +69,7 @@ export default class DataGridUtils
|
||||
*******************************************************************************/
|
||||
public static makeRows = (results: QRecord[], tableMetaData: QTableMetaData): GridRowsProp[] =>
|
||||
{
|
||||
const fields = [ ...tableMetaData.fields.values() ];
|
||||
const fields = [...tableMetaData.fields.values()];
|
||||
const rows = [] as any[];
|
||||
let rowIndex = 0;
|
||||
results.forEach((record: QRecord) =>
|
||||
@ -188,6 +217,7 @@ export default class DataGridUtils
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -220,12 +250,12 @@ export default class DataGridUtils
|
||||
case QFieldType.DATE:
|
||||
columnType = "date";
|
||||
columnWidth = 100;
|
||||
filterOperators = getGridDateOperators();
|
||||
filterOperators = QGridDateOperators;
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
columnType = "dateTime";
|
||||
columnWidth = 200;
|
||||
filterOperators = getGridDateOperators(true);
|
||||
filterOperators = QGridDateOperators;
|
||||
break;
|
||||
case QFieldType.BOOLEAN:
|
||||
columnType = "string"; // using boolean gives an odd 'no' for nulls.
|
||||
|
@ -264,10 +264,10 @@ class FilterUtils
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return ([null, null]);
|
||||
}
|
||||
return (FilterUtils.prepFilterValuesForBackend(value, fieldMetaData));
|
||||
return (FilterUtils.cleanseCriteriaValueForQQQ(value, fieldMetaData));
|
||||
}
|
||||
|
||||
return (FilterUtils.prepFilterValuesForBackend([value], fieldMetaData));
|
||||
return (FilterUtils.cleanseCriteriaValueForQQQ([value], fieldMetaData));
|
||||
};
|
||||
|
||||
|
||||
@ -278,7 +278,7 @@ class FilterUtils
|
||||
**
|
||||
** Or, if the values are date-times, convert them to UTC.
|
||||
*******************************************************************************/
|
||||
private static prepFilterValuesForBackend = (param: any[], fieldMetaData: QFieldMetaData): number[] | string[] =>
|
||||
private static cleanseCriteriaValueForQQQ = (param: any[], fieldMetaData: QFieldMetaData): number[] | string[] =>
|
||||
{
|
||||
if (param === null || param === undefined)
|
||||
{
|
||||
@ -291,10 +291,15 @@ class FilterUtils
|
||||
console.log(param[i]);
|
||||
if (param[i] && param[i].id && param[i].label)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////
|
||||
// if the param looks like a possible value, return its id //
|
||||
/////////////////////////////////////////////////////////////
|
||||
rs.push(param[i].id);
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the param looks like a possible value, return its id //
|
||||
// during build of new custom filter panel, this ended up causing us //
|
||||
// problems (because we wanted the full PV object in the filter model for the frontend) //
|
||||
// so, we can keep the PV as-is here, and see calls to convertFilterPossibleValuesToIds //
|
||||
// to do what this used to do. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// rs.push(param[i].id);
|
||||
rs.push(param[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -464,7 +469,16 @@ class FilterUtils
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// shift the date/time by the input amount //
|
||||
/////////////////////////////////////////////
|
||||
value.setTime(value.getTime() + 1000 * amount);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// now also shift from local-timezone into UTC //
|
||||
/////////////////////////////////////////////////
|
||||
value.setTime(value.getTime() + 1000 * 60 * value.getTimezoneOffset());
|
||||
|
||||
values = [ValueUtils.formatDateTimeISO8601(value)];
|
||||
}
|
||||
}
|
||||
@ -598,7 +612,7 @@ class FilterUtils
|
||||
/*******************************************************************************
|
||||
** build a qqq filter from a grid and column sort model
|
||||
*******************************************************************************/
|
||||
public static buildQFilterFromGridFilter(tableMetaData: QTableMetaData, filterModel: GridFilterModel, columnSortModel: GridSortItem[], limit?: number): QQueryFilter
|
||||
public static buildQFilterFromGridFilter(tableMetaData: QTableMetaData, filterModel: GridFilterModel, columnSortModel: GridSortItem[], limit?: number, allowIncompleteCriteria = false): QQueryFilter
|
||||
{
|
||||
console.log("Building q filter with model:");
|
||||
console.log(filterModel);
|
||||
@ -638,13 +652,15 @@ class FilterUtils
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// if no value set and not 'empty' or 'not empty' operators, skip this filter //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
if ((!item.value || item.value.length == 0) && item.operatorValue !== "isEmpty" && item.operatorValue !== "isNotEmpty")
|
||||
if ((!item.value || item.value.length == 0 || (item.value.length == 1 && item.value[0] == "")) && item.operatorValue !== "isEmpty" && item.operatorValue !== "isNotEmpty")
|
||||
{
|
||||
return;
|
||||
if (!allowIncompleteCriteria)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var fieldMetadata = tableMetaData?.fields.get(item.columnField);
|
||||
|
||||
const fieldMetadata = tableMetaData?.fields.get(item.columnField);
|
||||
const operator = FilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue);
|
||||
const values = FilterUtils.gridCriteriaValueToQQQ(operator, item.value, item.operatorValue, fieldMetadata);
|
||||
qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values));
|
||||
@ -664,6 +680,33 @@ class FilterUtils
|
||||
return qFilter;
|
||||
};
|
||||
|
||||
|
||||
public static convertFilterPossibleValuesToIds(inputFilter: QQueryFilter): QQueryFilter
|
||||
{
|
||||
const filter = Object.assign({}, inputFilter);
|
||||
|
||||
if (filter.criteria)
|
||||
{
|
||||
for (let i = 0; i < filter.criteria.length; i++)
|
||||
{
|
||||
const criteria = filter.criteria[i];
|
||||
if (criteria.values)
|
||||
{
|
||||
for (let j = 0; j < criteria.values.length; j++)
|
||||
{
|
||||
let value = criteria.values[j];
|
||||
if (value && value.id && value.label)
|
||||
{
|
||||
criteria.values[j] = value.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (filter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FilterUtils;
|
||||
|
Reference in New Issue
Block a user