From 28c48cc2ef5ee873642eb6bf09f6f123f5d5b460 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 14 Jul 2023 09:36:33 -0500 Subject: [PATCH] Checkpoint - functional between, expressions working as values, etc --- .../query/AdvancedDateTimeFilterValues.tsx | 161 +++++++-- .../components/query/CriteriaDateField.tsx | 221 ++++++++++++ .../components/query/FilterCriteriaRow.tsx | 30 +- .../query/FilterCriteriaRowValues.tsx | 337 +++++------------- src/qqq/utils/qqq/FilterUtils.ts | 31 +- 5 files changed, 453 insertions(+), 327 deletions(-) create mode 100644 src/qqq/components/query/CriteriaDateField.tsx diff --git a/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx b/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx index fe08c19..c94919f 100644 --- a/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx +++ b/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx @@ -19,7 +19,10 @@ * along with this program. If not, see . */ +import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import {NowExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/NowExpression"; import {NowWithOffsetExpression, NowWithOffsetOperator, NowWithOffsetUnit} from "@kingsrook/qqq-frontend-core/lib/model/query/NowWithOffsetExpression"; +import {ThisOrLastPeriodExpression, ThisOrLastPeriodOperator, ThisOrLastPeriodUnit} from "@kingsrook/qqq-frontend-core/lib/model/query/ThisOrLastPeriodExpression"; import {FormControl, FormControlLabel, Radio, RadioGroup, Select} from "@mui/material"; import Box from "@mui/material/Box"; import Card from "@mui/material/Card"; @@ -37,33 +40,57 @@ import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons" interface Props { - type: "date" | "datetime"; + type: QFieldType expression: any; onSave: (expression: any) => void; } AdvancedDateTimeFilterValues.defaultProps = {}; +const extractExpressionType = (expression: any) => expression?.type ?? "NowWithOffset"; +const extractNowWithOffsetAmount = (expression: any) => expression?.type == "NowWithOffset" ? (expression?.amount ?? 1) : 1; +const extractNowWithOffsetTimeUnit = (expression: any) => expression?.type == "NowWithOffset" ? (expression?.timeUnit ?? "DAYS") : "DAYS" as NowWithOffsetUnit; +const extractNowWithOffsetOperator = (expression: any) => expression?.type == "NowWithOffset" ? (expression?.operator ?? "MINUS") : "MINUS" as NowWithOffsetOperator; +const extractThisOrLastPeriodTimeUnit = (expression: any) => expression?.type == "ThisOrLastPeriod" ? (expression?.timeUnit ?? "DAYS") : "DAYS" as ThisOrLastPeriodUnit; +const extractThisOrLastPeriodOperator = (expression: any) => expression?.type == "ThisOrLastPeriod" ? (expression?.operator ?? "THIS") : "THIS" as ThisOrLastPeriodOperator; + function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.Element { const [originalExpression, setOriginalExpression] = useState(JSON.stringify(expression)); - const [expressionType, setExpressionType] = useState(expression?.type ?? "NowWithOffset") + const [expressionType, setExpressionType] = useState(extractExpressionType(expression)) + + const [nowWithOffsetAmount, setNowWithOffsetAmount] = useState(extractNowWithOffsetAmount(expression)); + const [nowWithOffsetTimeUnit, setNowWithOffsetTimeUnit] = useState(extractNowWithOffsetTimeUnit(expression)); + const [nowWithOffsetOperator, setNowWithOffsetOperator] = useState(extractNowWithOffsetOperator(expression)); + + const [thisOrLastPeriodTimeUnit, setThisOrLastPeriodTimeUnit] = useState(extractThisOrLastPeriodTimeUnit(expression)); + const [thisOrLastPeriodOperator, setThisOrLastPeriodOperator] = useState(extractThisOrLastPeriodOperator(expression)); - const [amount, setAmount] = useState(expression?.amount ?? 1) - const [timeUnit, setTimeUnit] = useState(expression?.timeUnit ?? "DAYS" as NowWithOffsetUnit); - const [operator, setOperator] = useState(expression?.operator ?? "MINUS" as NowWithOffsetOperator); const [isOpen, setIsOpen] = useState(false) + const setStateToExpression = (activeExpression: any) => + { + setExpressionType(extractExpressionType(activeExpression)) + + setNowWithOffsetAmount(extractNowWithOffsetAmount(activeExpression)) + setNowWithOffsetTimeUnit(extractNowWithOffsetTimeUnit(activeExpression)) + setNowWithOffsetOperator(extractNowWithOffsetOperator(activeExpression)) + + setThisOrLastPeriodTimeUnit(extractThisOrLastPeriodTimeUnit(activeExpression)) + setThisOrLastPeriodOperator(extractThisOrLastPeriodOperator(activeExpression)) + } + ////////////////////////////////////////////////////////////////////////////////// // if the expression (prop) has changed, re-set the state variables based on it // ////////////////////////////////////////////////////////////////////////////////// if(JSON.stringify(expression) !== originalExpression) { - setExpressionType(expression?.type ?? "NowWithOffset") - setAmount(expression?.amount ?? 1) - setTimeUnit(expression?.timeUnit ?? "DAYS") - setOperator(expression?.operator ?? "MINUS") + /////////////////////////////////////////////////////////// + // update all state vars based on the current expression // + /////////////////////////////////////////////////////////// + setStateToExpression(expression); + setOriginalExpression(JSON.stringify(expression)) } @@ -72,17 +99,47 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El setIsOpen(true); } + const handleCancelClicked = () => + { + /////////////////////////////////////////////////////////// + // update all state vars back to the original expression // + /////////////////////////////////////////////////////////// + const restoreExpression = JSON.parse(originalExpression) + setStateToExpression(restoreExpression); + + close(); + } + const handleSaveClicked = () => { switch(expressionType) { + case "Now": + { + const expression = new NowExpression(); + onSave(expression); + break; + } case "NowWithOffset": { const expression = new NowWithOffsetExpression() - expression.operator = operator; - expression.amount = amount; - expression.timeUnit = timeUnit; + expression.operator = nowWithOffsetOperator; + expression.amount = nowWithOffsetAmount; + expression.timeUnit = nowWithOffsetTimeUnit; onSave(expression); + break; + } + case "ThisOrLastPeriod": + { + const expression = new ThisOrLastPeriodExpression() + expression.operator = thisOrLastPeriodOperator; + expression.timeUnit = thisOrLastPeriodTimeUnit; + onSave(expression); + break; + } + default: + { + console.log(`Unmapped expression type in handleSAveClicked: ${expressionType}`); } } @@ -99,35 +156,47 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El setExpressionType(e.target.value); } - function handleAmountChange(event: React.ChangeEvent) + function handleNowWithOffsetAmountChange(event: React.ChangeEvent) { - setAmount(parseInt(event.target.value)); + setNowWithOffsetAmount(parseInt(event.target.value)); } - function handleTimeUnitChange(event: SelectChangeEvent, child: ReactNode) + function handleNowWithOffsetTimeUnitChange(event: SelectChangeEvent, child: ReactNode) { // @ts-ignore - setTimeUnit(event.target.value) + setNowWithOffsetTimeUnit(event.target.value) } - function handleOperatorChange(event: SelectChangeEvent, child: ReactNode) + function handleNowWithOffsetOperatorChange(event: SelectChangeEvent, child: ReactNode) { // @ts-ignore - setOperator(event.target.value) + setNowWithOffsetOperator(event.target.value) } + function handleThisOrLastPeriodTimeUnitChange(event: SelectChangeEvent, child: ReactNode) + { + // @ts-ignore + setThisOrLastPeriodTimeUnit(event.target.value) + } + + function handleThisOrLastPeriodOperatorChange(event: SelectChangeEvent, child: ReactNode) + { + // @ts-ignore + setThisOrLastPeriodOperator(event.target.value) + } const mainCardStyles: any = {}; mainCardStyles.width = "600px"; ///////////////////////////////////////////////////////////////////////// // for the time units, have them end in an 's' if the amount is plural // + // name here means "time unit 's'" // ///////////////////////////////////////////////////////////////////////// - const tuS = (amount == 1 ? "" : "s"); + const nwoTUs = (nowWithOffsetAmount == 1 ? "" : "s"); return ( - + settings { @@ -150,6 +219,10 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El + + } label="Now" /> + + } label="Relative Expression" /> @@ -159,26 +232,26 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El type="number" inputProps={{min: 0}} autoComplete="off" - value={amount} - onChange={(event) => handleAmountChange(event)} + value={nowWithOffsetAmount} + onChange={(event) => handleNowWithOffsetAmountChange(event)} fullWidth /> - + {type == QFieldType.DATE_TIME && Second{nwoTUs}} + {type == QFieldType.DATE_TIME && Minute{nwoTUs}} + {type == QFieldType.DATE_TIME && Hour{nwoTUs}} + Day{nwoTUs} + Week{nwoTUs} + Month{nwoTUs} + Year{nwoTUs} - Ago (in the past) From now (in the future) @@ -186,10 +259,34 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El + + } label={`${type == QFieldType.DATE_TIME ? "Start of " : ""}This or Last...`} /> + + + + + + + + + + + + + - + diff --git a/src/qqq/components/query/CriteriaDateField.tsx b/src/qqq/components/query/CriteriaDateField.tsx new file mode 100644 index 0000000..c6b7c00 --- /dev/null +++ b/src/qqq/components/query/CriteriaDateField.tsx @@ -0,0 +1,221 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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 . + */ + +import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; +import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import {NowExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/NowExpression"; +import {NowWithOffsetExpression, NowWithOffsetOperator, NowWithOffsetUnit} from "@kingsrook/qqq-frontend-core/lib/model/query/NowWithOffsetExpression"; +import {ThisOrLastPeriodExpression, ThisOrLastPeriodOperator, ThisOrLastPeriodUnit} from "@kingsrook/qqq-frontend-core/lib/model/query/ThisOrLastPeriodExpression"; +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 Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import React, {SyntheticEvent, useReducer, useState} from "react"; +import AdvancedDateTimeFilterValues from "qqq/components/query/AdvancedDateTimeFilterValues"; +import {QFilterCriteriaWithId} from "qqq/components/query/CustomFilterPanel"; +import {makeTextField} from "qqq/components/query/FilterCriteriaRowValues"; + +interface CriteriaDateFieldProps +{ + valueIndex: number; + label: string; + idPrefix: string; + field: QFieldMetaData; + criteria: QFilterCriteriaWithId; + valueChangeHandler: (event: React.ChangeEvent | SyntheticEvent, valueIndex?: number | "all", newValue?: any) => void; +} + +CriteriaDateField.defaultProps = { + valueIndex: 0, + label: "Value", + idPrefix: "value-" +}; + +export default function CriteriaDateField({valueIndex, label, idPrefix, field, criteria, valueChangeHandler}: CriteriaDateFieldProps): JSX.Element +{ + const [relativeDateTimeMenuAnchorElement, setRelativeDateTimeMenuAnchorElement] = useState(null); + const [, forceUpdate] = useReducer((x) => x + 1, 0); + + const openRelativeDateTimeMenu = (event: React.MouseEvent) => + { + setRelativeDateTimeMenuAnchorElement(event.currentTarget); + }; + + const closeRelativeDateTimeMenu = () => + { + setRelativeDateTimeMenuAnchorElement(null); + }; + + const setExpressionNow = (valueIndex: number) => + { + const expression = new NowExpression() + saveNewDateTimeExpression(valueIndex, expression); + + closeRelativeDateTimeMenu(); + }; + + const setExpressionNowWithOffset = (valueIndex: number, operator: NowWithOffsetOperator, amount: number, timeUnit: NowWithOffsetUnit) => + { + const expression = new NowWithOffsetExpression() + expression.operator = operator; + expression.amount = amount; + expression.timeUnit = timeUnit; + + saveNewDateTimeExpression(valueIndex, expression); + + closeRelativeDateTimeMenu(); + }; + + const setExpressionThisOrLastPeriod = (valueIndex: number, operator: ThisOrLastPeriodOperator, timeUnit: ThisOrLastPeriodUnit) => + { + const expression = new ThisOrLastPeriodExpression() + expression.operator = operator; + expression.timeUnit = timeUnit; + + saveNewDateTimeExpression(valueIndex, expression); + + closeRelativeDateTimeMenu(); + }; + + function saveNewDateTimeExpression(valueIndex: number, expression: any) + { + valueChangeHandler(null, valueIndex, expression); + forceUpdate(); + } + + const makeDateTimeExpressionTextField = (expression: any, valueIndex: number = 0, label = "Value", idPrefix = "value-") => + { + const clearValue = (event: React.MouseEvent | React.MouseEvent, index: number) => + { + valueChangeHandler(event, index, ""); + forceUpdate() + document.getElementById(`${idPrefix}${criteria.id}`).focus(); + }; + + const inputProps: any = {}; + inputProps.endAdornment = ( + + clearValue(event, valueIndex)}> + close + + + ); + + let displayValue = expression.toString(); + if (expression?.type == "ThisOrLastPeriod") + { + if(field.type == QFieldType.DATE_TIME || (field.type == QFieldType.DATE && expression.timeUnit != "DAYS")) + { + displayValue = "start of " + displayValue; + } + } + + return ; + } + + const isExpression = criteria.values && criteria.values[valueIndex] && criteria.values[valueIndex].type; + const currentExpression = isExpression ? criteria.values[valueIndex] : null; + + return + { + isExpression ? makeDateTimeExpressionTextField(criteria.values[valueIndex], valueIndex, label, idPrefix) + : makeTextField(field, criteria, valueChangeHandler, valueIndex, label, idPrefix) + } + + + date_range + + + { + field.type == QFieldType.DATE ? + + + setExpressionNowWithOffset(valueIndex, "MINUS", 7, "DAYS")}>7 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 14, "DAYS")}>14 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 30, "DAYS")}>30 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 90, "DAYS")}>90 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 180, "DAYS")}>180 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 1, "YEARS")}>1 year ago + + + setExpressionThisOrLastPeriod(valueIndex, "THIS", "DAYS")}>today + setExpressionThisOrLastPeriod(valueIndex, "LAST", "DAYS")}>yesterday + setExpressionThisOrLastPeriod(valueIndex, "THIS", "WEEKS")}>start of this week + setExpressionThisOrLastPeriod(valueIndex, "LAST", "WEEKS")}>start of last week + setExpressionThisOrLastPeriod(valueIndex, "THIS", "MONTHS")}>start of this month + setExpressionThisOrLastPeriod(valueIndex, "LAST", "MONTHS")}>start of last month + setExpressionThisOrLastPeriod(valueIndex, "THIS", "YEARS")}>start of this year + setExpressionThisOrLastPeriod(valueIndex, "LAST", "YEARS")}>start of last year + + + : + + + setExpressionNowWithOffset(valueIndex, "MINUS", 1, "HOURS")}>1 hour ago + setExpressionNowWithOffset(valueIndex, "MINUS", 12, "HOURS")}>12 hours ago + setExpressionNowWithOffset(valueIndex, "MINUS", 24, "HOURS")}>24 hours ago + setExpressionNowWithOffset(valueIndex, "MINUS", 7, "DAYS")}>7 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 14, "DAYS")}>14 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 30, "DAYS")}>30 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 90, "DAYS")}>90 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 180, "DAYS")}>180 days ago + setExpressionNowWithOffset(valueIndex, "MINUS", 1, "YEARS")}>1 year ago + + + setExpressionNow(valueIndex)}>now + setExpressionThisOrLastPeriod(valueIndex, "THIS", "HOURS")}>start of this hour + setExpressionThisOrLastPeriod(valueIndex, "LAST", "HOURS")}>start of last hour + setExpressionThisOrLastPeriod(valueIndex, "THIS", "DAYS")}>start of today + setExpressionThisOrLastPeriod(valueIndex, "LAST", "DAYS")}>start of yesterday + setExpressionThisOrLastPeriod(valueIndex, "THIS", "WEEKS")}>start of this week + setExpressionThisOrLastPeriod(valueIndex, "LAST", "WEEKS")}>start of last week + setExpressionThisOrLastPeriod(valueIndex, "THIS", "MONTHS")}>start of this month + setExpressionThisOrLastPeriod(valueIndex, "LAST", "MONTHS")}>start of last month + setExpressionThisOrLastPeriod(valueIndex, "THIS", "YEARS")}>start of this year + setExpressionThisOrLastPeriod(valueIndex, "LAST", "YEARS")}>start of last year + + + } + + + + saveNewDateTimeExpression(valueIndex, expression)} /> + + ; +} diff --git a/src/qqq/components/query/FilterCriteriaRow.tsx b/src/qqq/components/query/FilterCriteriaRow.tsx index 1f4049c..8114c63 100644 --- a/src/qqq/components/query/FilterCriteriaRow.tsx +++ b/src/qqq/components/query/FilterCriteriaRow.tsx @@ -343,24 +343,8 @@ export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, ////////////////////////////////////////////////// // event handler for value field (of all types) // ////////////////////////////////////////////////// - const handleValueChange = (event: React.ChangeEvent | SyntheticEvent, valueIndex: number | "all" = 0, newValue?: any, newExpression?: any) => + const handleValueChange = (event: React.ChangeEvent | SyntheticEvent, valueIndex: number | "all" = 0, newValue?: any) => { - /////////////////////////////////////////////////////////////////////////////////////////////////////// - // if an expression was passed in - put it on the criteria, removing the values. // - // else - if no expression - make sure criteria.expression is null, and do the various values logics // - /////////////////////////////////////////////////////////////////////////////////////////////////////// - if(newExpression) - { - criteria.expression = newExpression; - criteria.values = null; - updateCriteria(criteria, true); - return; - } - else - { - criteria.expression = null; - } - // @ts-ignore const value = newValue !== undefined ? newValue : event ? event.target.value : null; @@ -471,15 +455,9 @@ export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, // don't need to look at values // ////////////////////////////////// } - else if (criteria.expression) + else if(operatorSelectedValue.valueMode == ValueMode.DOUBLE || operatorSelectedValue.valueMode == ValueMode.DOUBLE_DATE || operatorSelectedValue.valueMode == ValueMode.DOUBLE_DATE_TIME) { - //////////////////////////////////////////////////////// - // if there's an expression - let's assume it's valid // - //////////////////////////////////////////////////////// - } - else if(operatorSelectedValue.valueMode == ValueMode.DOUBLE) - { - if(criteria.values.length < 2) + if(criteria.values.length < 2 || isNotSet(criteria.values[0]) || isNotSet(criteria.values[1])) { criteriaIsValid = false; criteriaStatusTooltip = "You must enter two values to complete the definition of this condition."; @@ -563,7 +541,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, criteria={{id: id, ...criteria}} field={field} table={fieldTable} - valueChangeHandler={(event, valueIndex, newValue, newExpression) => handleValueChange(event, valueIndex, newValue, newExpression)} + valueChangeHandler={(event, valueIndex, newValue) => handleValueChange(event, valueIndex, newValue)} /> diff --git a/src/qqq/components/query/FilterCriteriaRowValues.tsx b/src/qqq/components/query/FilterCriteriaRowValues.tsx index 8be4182..d1eb339 100644 --- a/src/qqq/components/query/FilterCriteriaRowValues.tsx +++ b/src/qqq/components/query/FilterCriteriaRowValues.tsx @@ -23,21 +23,15 @@ import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import {NowWithOffsetExpression, NowWithOffsetOperator, NowWithOffsetUnit} from "@kingsrook/qqq-frontend-core/lib/model/query/NowWithOffsetExpression"; -import {ThisOrLastPeriodExpression, ThisOrLastPeriodOperator, ThisOrLastPeriodUnit} from "@kingsrook/qqq-frontend-core/lib/model/query/ThisOrLastPeriodExpression"; 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 Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; -import Select, {SelectChangeEvent} from "@mui/material/Select/Select"; import TextField from "@mui/material/TextField"; -import Tooltip from "@mui/material/Tooltip"; -import React, {ReactNode, SyntheticEvent, useReducer, useState} from "react"; +import React, {SyntheticEvent, useReducer} from "react"; import DynamicSelect from "qqq/components/forms/DynamicSelect"; -import AdvancedDateTimeFilterValues from "qqq/components/query/AdvancedDateTimeFilterValues"; +import CriteriaDateField from "qqq/components/query/CriteriaDateField"; import {QFilterCriteriaWithId} from "qqq/components/query/CustomFilterPanel"; import FilterCriteriaPaster from "qqq/components/query/FilterCriteriaPaster"; import {OperatorOption, ValueMode} from "qqq/components/query/FilterCriteriaRow"; @@ -49,16 +43,78 @@ interface Props criteria: QFilterCriteriaWithId; field: QFieldMetaData; table: QTableMetaData; - valueChangeHandler: (event: React.ChangeEvent | SyntheticEvent, valueIndex?: number | "all", newValue?: any, newExpression?: any) => void; + valueChangeHandler: (event: React.ChangeEvent | SyntheticEvent, valueIndex?: number | "all", newValue?: any) => void; } -FilterCriteriaRowValues.defaultProps = { +FilterCriteriaRowValues.defaultProps = {}; + +export const getTypeForTextField = (field: QFieldMetaData): string => +{ + let type = "search"; + + if (field.type == QFieldType.INTEGER) + { + type = "number"; + } + else if (field.type == QFieldType.DATE) + { + type = "date"; + } + else if (field.type == QFieldType.DATE_TIME) + { + type = "datetime-local"; + } + + return (type); +}; + +export const makeTextField = (field: QFieldMetaData, criteria: QFilterCriteriaWithId, valueChangeHandler?: (event: (React.ChangeEvent | React.SyntheticEvent), valueIndex?: (number | "all"), newValue?: any) => void, valueIndex: number = 0, label = "Value", idPrefix = "value-") => +{ + let type = getTypeForTextField(field); + 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 | React.MouseEvent, index: number) => + { + valueChangeHandler(event, index, ""); + document.getElementById(`${idPrefix}${criteria.id}`).focus(); + }; + + const inputProps: any = {}; + inputProps.endAdornment = ( + + clearValue(event, valueIndex)}> + close + + + ); + + return valueChangeHandler(event, valueIndex)} + value={value} + InputLabelProps={inputLabelProps} + InputProps={inputProps} + fullWidth + />; }; function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueChangeHandler}: Props): JSX.Element { - const [relativeDateTimeMenuAnchorElement, setRelativeDateTimeMenuAnchorElement] = useState(null); - const [, forceUpdate] = useReducer((x) => x + 1, 0); if (!operatorOption) @@ -66,197 +122,6 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC return
; } - const getTypeForTextField = (): string => - { - let type = "search"; - - if (field.type == QFieldType.INTEGER) - { - type = "number"; - } - else if (field.type == QFieldType.DATE) - { - type = "date"; - } - 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 | React.MouseEvent, index: number) => - { - valueChangeHandler(event, index, ""); - document.getElementById(`${idPrefix}${criteria.id}`).focus(); - }; - - const inputProps: any = {}; - inputProps.endAdornment = ( - - clearValue(event, valueIndex)}> - close - - - ); - - return valueChangeHandler(event, valueIndex)} - value={value} - InputLabelProps={inputLabelProps} - InputProps={inputProps} - fullWidth - />; - }; - - const makeDateTimeExpressionTextField = (expression: any, valueIndex: number = 0, label = "Value", idPrefix = "value-") => - { - const clearValue = (event: React.MouseEvent | React.MouseEvent, index: number) => - { - valueChangeHandler(event, index, ""); - forceUpdate() - document.getElementById(`${idPrefix}${criteria.id}`).focus(); - }; - - const inputProps: any = {}; - inputProps.endAdornment = ( - - clearValue(event, valueIndex)}> - close - - - ); - - let displayValue = expression.toString(); - if (expression?.type == "ThisOrLastPeriod") - { - if(field.type == QFieldType.DATE_TIME || (field.type == QFieldType.DATE && expression.timeUnit != "DAYS")) - { - displayValue = "start of " + displayValue; - } - } - - return ; - } - - const makeDateField = (valueIndex: number = 0, label = "Value", idPrefix = "value-") => - { - return - { - criteria.expression == null && makeTextField(valueIndex, label, idPrefix) - } - { - criteria.expression != null && makeDateTimeExpressionTextField(criteria.expression, valueIndex, label, idPrefix) - } - - - date_range - - - setExpressionNowWithOffset(valueIndex, "MINUS", 1, "DAYS")}>1 day ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "DAYS")}>today - setExpressionThisOrLastPeriod(valueIndex, "LAST", "DAYS")}>yesterday - setExpressionNowWithOffset(valueIndex, "MINUS", 7, "DAYS")}>7 days ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "WEEKS")}>start of this week - setExpressionThisOrLastPeriod(valueIndex, "LAST", "WEEKS")}>start of last week - setExpressionNowWithOffset(valueIndex, "MINUS", 14, "DAYS")}>14 days ago - setExpressionNowWithOffset(valueIndex, "MINUS", 30, "DAYS")}>30 days ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "MONTHS")}>start of this month - setExpressionThisOrLastPeriod(valueIndex, "LAST", "MONTHS")}>start of last month - setExpressionNowWithOffset(valueIndex, "MINUS", 90, "DAYS")}>90 days ago - setExpressionNowWithOffset(valueIndex, "MINUS", 180, "DAYS")}>180 days ago - setExpressionNowWithOffset(valueIndex, "MINUS", 1, "YEARS")}>1 year ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "YEARS")}>start of this year - setExpressionThisOrLastPeriod(valueIndex, "LAST", "YEARS")}>start of last year - - - - saveNewDateTimeExpression(valueIndex, expression)} /> - - ; - } - - const makeDateTimeField = (valueIndex: number = 0, label = "Value", idPrefix = "value-") => - { - return - { - criteria.expression == null && makeTextField(valueIndex, label, idPrefix) - } - { - criteria.expression != null && makeDateTimeExpressionTextField(criteria.expression, valueIndex, label, idPrefix) - } - - - date_range - - - setExpressionNowWithOffset(valueIndex, "MINUS", 1, "HOURS")}>1 hour ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "HOURS")}>start of this hour - setExpressionThisOrLastPeriod(valueIndex, "LAST", "HOURS")}>start of last hour - setExpressionNowWithOffset(valueIndex, "MINUS", 12, "HOURS")}>12 hours ago - setExpressionNowWithOffset(valueIndex, "MINUS", 24, "HOURS")}>24 hours ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "DAYS")}>start of today - setExpressionThisOrLastPeriod(valueIndex, "LAST", "DAYS")}>start of yesterday - setExpressionNowWithOffset(valueIndex, "MINUS", 7, "DAYS")}>7 days ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "WEEKS")}>start of this week - setExpressionThisOrLastPeriod(valueIndex, "LAST", "WEEKS")}>start of last week - setExpressionNowWithOffset(valueIndex, "MINUS", 14, "DAYS")}>14 days ago - setExpressionNowWithOffset(valueIndex, "MINUS", 30, "DAYS")}>30 days ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "MONTHS")}>start of this month - setExpressionThisOrLastPeriod(valueIndex, "LAST", "MONTHS")}>start of last month - setExpressionNowWithOffset(valueIndex, "MINUS", 90, "DAYS")}>90 days ago - setExpressionNowWithOffset(valueIndex, "MINUS", 180, "DAYS")}>180 days ago - setExpressionNowWithOffset(valueIndex, "MINUS", 1, "YEARS")}>1 year ago - setExpressionThisOrLastPeriod(valueIndex, "THIS", "YEARS")}>start of this year - setExpressionThisOrLastPeriod(valueIndex, "LAST", "YEARS")}>start of last year - - - - saveNewDateTimeExpression(valueIndex, expression)} /> - - ; - } - function saveNewPasterValues(newValues: any[]) { if (criteria.values) @@ -280,69 +145,33 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC forceUpdate(); } - const openRelativeDateTimeMenu = (event: React.MouseEvent) => - { - setRelativeDateTimeMenuAnchorElement(event.currentTarget); - }; - - const closeRelativeDateTimeMenu = () => - { - setRelativeDateTimeMenuAnchorElement(null); - }; - - const setExpressionNowWithOffset = (valueIndex: number, operator: NowWithOffsetOperator, amount: number, timeUnit: NowWithOffsetUnit) => - { - const expression = new NowWithOffsetExpression() - expression.operator = operator; - expression.amount = amount; - expression.timeUnit = timeUnit; - - saveNewDateTimeExpression(valueIndex, expression); - - closeRelativeDateTimeMenu(); - }; - - const setExpressionThisOrLastPeriod = (valueIndex: number, operator: ThisOrLastPeriodOperator, timeUnit: ThisOrLastPeriodUnit) => - { - const expression = new ThisOrLastPeriodExpression() - expression.operator = operator; - expression.timeUnit = timeUnit; - - saveNewDateTimeExpression(valueIndex, expression); - - closeRelativeDateTimeMenu(); - }; - - function saveNewDateTimeExpression(valueIndex: number, expression: any) - { - criteria.expression = expression; - criteria.values = null; - valueChangeHandler(null, valueIndex, null, expression); - forceUpdate(); - } - switch (operatorOption.valueMode) { case ValueMode.NONE: return
; case ValueMode.SINGLE: - return makeTextField(); + return makeTextField(field, criteria, valueChangeHandler); case ValueMode.SINGLE_DATE: - return makeDateField(); + return ; case ValueMode.DOUBLE_DATE: return - {makeDateField(0, "From", "from-")} - {makeDateField(1, "To", "to-")} - + + +
; case ValueMode.SINGLE_DATE_TIME: - return makeDateTimeField(); + return ; + case ValueMode.DOUBLE_DATE_TIME: + return + + + ; case ValueMode.DOUBLE: return - { makeTextField(0, "From", "from-") } + {makeTextField(field, criteria, valueChangeHandler, 0, "From", "from-")} - {makeTextField(1, "To", "to-")} + {makeTextField(field, criteria, valueChangeHandler, 1, "To", "to-")} ; case ValueMode.MULTI: @@ -365,7 +194,7 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC onChange={(event, value) => valueChangeHandler(event, "all", value)} /> - saveNewPasterValues(newValues)} /> + saveNewPasterValues(newValues)} /> ; case ValueMode.PVS_SINGLE: @@ -414,7 +243,7 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC inForm={false} onChange={(value: any) => valueChangeHandler(null, "all", value)} /> - + ; } return (
); diff --git a/src/qqq/utils/qqq/FilterUtils.ts b/src/qqq/utils/qqq/FilterUtils.ts index 2c5b0dd..d3c4372 100644 --- a/src/qqq/utils/qqq/FilterUtils.ts +++ b/src/qqq/utils/qqq/FilterUtils.ts @@ -23,6 +23,7 @@ import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QControl import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {NowExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/NowExpression"; import {NowWithOffsetExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/NowWithOffsetExpression"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; @@ -289,7 +290,7 @@ class FilterUtils if (FilterUtils.gridCriteriaValueToExpression(param)) { - return (null); + return (param); } let rs = []; @@ -337,13 +338,8 @@ class FilterUtils ** Convert a filter field's value from the style that qqq uses, to the style that ** the grid uses. *******************************************************************************/ - public static qqqCriteriaValuesToGrid = (operator: QCriteriaOperator, values: any[], expression: any, field: QFieldMetaData): any | any[] => + public static qqqCriteriaValuesToGrid = (operator: QCriteriaOperator, values: any[], field: QFieldMetaData): any | any[] => { - if(expression) - { - return (expression); - } - const fieldType = field.type; if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK) { @@ -361,7 +357,13 @@ class FilterUtils //////////////////////////////////////////////////////////////////////////////////////////////// if (fieldType === QFieldType.DATE_TIME) { - values[0] = ValueUtils.formatDateTimeValueForForm(values[0]); + for(let i = 0; i