diff --git a/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx b/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx index c94919f..085d01c 100644 --- a/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx +++ b/src/qqq/components/query/AdvancedDateTimeFilterValues.tsx @@ -43,9 +43,12 @@ interface Props type: QFieldType expression: any; onSave: (expression: any) => void; + forcedOpen: boolean; } -AdvancedDateTimeFilterValues.defaultProps = {}; +AdvancedDateTimeFilterValues.defaultProps = { + forcedOpen: false +}; const extractExpressionType = (expression: any) => expression?.type ?? "NowWithOffset"; const extractNowWithOffsetAmount = (expression: any) => expression?.type == "NowWithOffset" ? (expression?.amount ?? 1) : 1; @@ -54,7 +57,7 @@ const extractNowWithOffsetOperator = (expression: any) => expression?.type == "N 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 +function AdvancedDateTimeFilterValues({type, expression, onSave, forcedOpen}: Props): JSX.Element { const [originalExpression, setOriginalExpression] = useState(JSON.stringify(expression)); @@ -69,6 +72,11 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El const [isOpen, setIsOpen] = useState(false) + if(!isOpen && forcedOpen) + { + setIsOpen(true); + } + const setStateToExpression = (activeExpression: any) => { setExpressionType(extractExpressionType(activeExpression)) @@ -196,8 +204,8 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El return ( - - settings + + settings { isOpen && @@ -209,7 +217,7 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El - Advanced Date Filter Condition + Custom Date Filter Condition Select the type of expression you want for your condition.
Then enter values to express your condition. @@ -220,11 +228,11 @@ function AdvancedDateTimeFilterValues({type, expression, onSave}: Props): JSX.El - } label="Now" /> + } label={type == QFieldType.DATE_TIME ? "Now" : "Today"} /> - } label="Relative Expression" /> + } label="Relative Expression" /> - } label={`${type == QFieldType.DATE_TIME ? "Start of " : ""}This or Last...`} /> + } 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 index c6b7c00..7040c5d 100644 --- a/src/qqq/components/query/CriteriaDateField.tsx +++ b/src/qqq/components/query/CriteriaDateField.tsx @@ -25,18 +25,25 @@ import {NowExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/NowExp 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 Divider from "@mui/material/Divider"; 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 {styled} from "@mui/material/styles"; import TextField from "@mui/material/TextField"; -import Tooltip from "@mui/material/Tooltip"; -import React, {SyntheticEvent, useReducer, useState} from "react"; +import Tooltip, {tooltipClasses, TooltipProps} from "@mui/material/Tooltip"; +import React, {SyntheticEvent, useEffect, useReducer, useState} from "react"; import AdvancedDateTimeFilterValues from "qqq/components/query/AdvancedDateTimeFilterValues"; import {QFilterCriteriaWithId} from "qqq/components/query/CustomFilterPanel"; +import {EvaluatedExpression} from "qqq/components/query/EvaluatedExpression"; import {makeTextField} from "qqq/components/query/FilterCriteriaRowValues"; + +export type Expression = NowWithOffsetExpression | ThisOrLastPeriodExpression | NowExpression; + + interface CriteriaDateFieldProps { valueIndex: number; @@ -56,6 +63,7 @@ CriteriaDateField.defaultProps = { export default function CriteriaDateField({valueIndex, label, idPrefix, field, criteria, valueChangeHandler}: CriteriaDateFieldProps): JSX.Element { const [relativeDateTimeMenuAnchorElement, setRelativeDateTimeMenuAnchorElement] = useState(null); + const [forceAdvancedDateTimeDialogOpen, setForceAdvancedDateTimeDialogOpen] = useState(false) const [, forceUpdate] = useReducer((x) => x + 1, 0); const openRelativeDateTimeMenu = (event: React.MouseEvent) => @@ -68,34 +76,9 @@ export default function CriteriaDateField({valueIndex, label, idPrefix, field, c setRelativeDateTimeMenuAnchorElement(null); }; - const setExpressionNow = (valueIndex: number) => + const setExpression = (valueIndex: number, expression: Expression) => { - 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(); }; @@ -110,7 +93,7 @@ export default function CriteriaDateField({valueIndex, label, idPrefix, field, c const clearValue = (event: React.MouseEvent | React.MouseEvent, index: number) => { valueChangeHandler(event, index, ""); - forceUpdate() + forceUpdate(); document.getElementById(`${idPrefix}${criteria.id}`).focus(); }; @@ -126,13 +109,20 @@ export default function CriteriaDateField({valueIndex, label, idPrefix, field, c let displayValue = expression.toString(); if (expression?.type == "ThisOrLastPeriod") { - if(field.type == QFieldType.DATE_TIME || (field.type == QFieldType.DATE && expression.timeUnit != "DAYS")) + if (field.type == QFieldType.DATE_TIME || (field.type == QFieldType.DATE && expression.timeUnit != "DAYS")) { displayValue = "start of " + displayValue; } } + if (expression?.type == "Now") + { + if (field.type == QFieldType.DATE) + { + displayValue = "today"; + } + } - return } placement="bottom" enterDelay={1000} sx={{marginLeft: "-75px !important", marginTop: "-8px !important"}}>; - } + />; + }; const isExpression = criteria.values && criteria.values[valueIndex] && criteria.values[valueIndex].type; const currentExpression = isExpression ? criteria.values[valueIndex] : null; + const NoWrapTooltip = styled(({className, children, ...props}: TooltipProps) => ( + {children} + ))({ + [`& .${tooltipClasses.tooltip}`]: { + whiteSpace: "nowrap" + }, + }); + + const tooltipMenuItemFromExpression = (valueIndex: number, tooltipPlacement: "left" | "right", expression: Expression) => + { + let startOfPrefix = ""; + if(expression.type == "ThisOrLastPeriod") + { + if(field.type == QFieldType.DATE_TIME || expression.timeUnit != "DAYS") + { + startOfPrefix = "start of "; + } + } + + return } placement={tooltipPlacement}> + setExpression(valueIndex, expression)}>{startOfPrefix}{expression.toString()} + ; + }; + + const newNowExpression = (): NowExpression => + { + const expression = new NowExpression(); + return (expression); + }; + + const newNowWithOffsetExpression = (operator: NowWithOffsetOperator, amount: number, timeUnit: NowWithOffsetUnit): NowWithOffsetExpression => + { + const expression = new NowWithOffsetExpression(); + expression.operator = operator; + expression.amount = amount; + expression.timeUnit = timeUnit; + return (expression); + }; + + const newThisOrLastPeriodExpression = (operator: ThisOrLastPeriodOperator, timeUnit: ThisOrLastPeriodUnit): ThisOrLastPeriodExpression => + { + const expression = new ThisOrLastPeriodExpression(); + expression.operator = operator; + expression.timeUnit = timeUnit; + return (expression); + }; + + function doForceAdvancedDateTimeDialogOpen() + { + setForceAdvancedDateTimeDialogOpen(true); + closeRelativeDateTimeMenu(); + setTimeout(() => setForceAdvancedDateTimeDialogOpen(false), 100); + } + return { isExpression ? makeDateTimeExpressionTextField(criteria.values[valueIndex], valueIndex, label, idPrefix) : makeTextField(field, criteria, valueChangeHandler, valueIndex, label, idPrefix) } - - date_range + + date_range - 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 + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 7, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 14, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 30, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 90, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 180, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 1, "YEARS"))} + + + Custom + - 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 + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "WEEKS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "WEEKS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "MONTHS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "MONTHS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "YEARS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "YEARS"))} : - 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 + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 1, "HOURS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 12, "HOURS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 24, "HOURS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 7, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 14, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 30, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 90, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 180, "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "left", newNowWithOffsetExpression("MINUS", 1, "YEARS"))} + + + Custom + - 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 + {tooltipMenuItemFromExpression(valueIndex, "right", newNowExpression())} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "HOURS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "HOURS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "DAYS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "WEEKS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "WEEKS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "MONTHS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "MONTHS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("THIS", "YEARS"))} + {tooltipMenuItemFromExpression(valueIndex, "right", newThisOrLastPeriodExpression("LAST", "YEARS"))} } - saveNewDateTimeExpression(valueIndex, expression)} /> + saveNewDateTimeExpression(valueIndex, expression)} forcedOpen={forceAdvancedDateTimeDialogOpen} /> ; } + diff --git a/src/qqq/components/query/EvaluatedExpression.tsx b/src/qqq/components/query/EvaluatedExpression.tsx new file mode 100644 index 0000000..9ab4f53 --- /dev/null +++ b/src/qqq/components/query/EvaluatedExpression.tsx @@ -0,0 +1,189 @@ +/* + * 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 React, {useEffect, useState} from "react"; +import {Expression} from "qqq/components/query/CriteriaDateField"; +import ValueUtils from "qqq/utils/qqq/ValueUtils"; + +/******************************************************************************* + ** Helper component to show value inside tooltips that ticks up every second. + ** Without this, changing state on the higher-level component caused the tooltip to flicker. + *******************************************************************************/ +interface EvaluatedExpressionProps +{ + field: QFieldMetaData; + expression: any; +} + + +export function EvaluatedExpression({field, expression}: EvaluatedExpressionProps) +{ + const [timeForEvaluations, setTimeForEvaluations] = useState(new Date()); + + useEffect(() => + { + const interval = setInterval(() => + { + setTimeForEvaluations(new Date()); + }, 1000); + + return () => clearInterval(interval); + }, []); + + return <>{`${evaluateExpression(timeForEvaluations, field, expression)}`}; +} + +const HOUR_MS = 60 * 60 * 1000; +const DAY_MS = 24 * 60 * 60 * 1000; +const evaluateExpression = (time: Date, field: QFieldMetaData, expression: Expression): string => +{ + let rs: Date = null; + if (expression.type == "NowWithOffset") + { + rs = time; + let amount = Number(expression.amount); + switch (expression.timeUnit) + { + case "MINUTES": + { + amount = amount * 60 * 1000; + break; + } + case "HOURS": + { + amount = amount * HOUR_MS; + break; + } + case "DAYS": + { + amount = amount * DAY_MS; + break; + } + case "YEARS": + { + amount = amount * 365 * DAY_MS; + break; + } + default: + { + console.log("Unrecognized time unit: " + expression.timeUnit); + } + } + + if (expression.operator == "MINUS") + { + amount = -amount; + } + + rs.setTime(rs.getTime() + amount); + + if (expression.timeUnit == "YEARS") + { + ////////////////////// + // handle leap year // + ////////////////////// + const today = time; + while (today.getDate() != rs.getDate()) + { + rs.setTime(rs.getTime() - DAY_MS); + } + } + } + else if (expression.type == "Now") + { + rs = time; + } + else if (expression.type == "ThisOrLastPeriod") + { + rs = time; + rs.setSeconds(0); + rs.setMinutes(0); + if (expression.timeUnit == "HOURS") + { + if (expression.operator == "LAST") + { + rs.setTime(rs.getTime() - HOUR_MS); + } + } + else + { + rs.setHours(0); + if (expression.timeUnit == "DAYS") + { + if (expression.operator == "LAST") + { + rs.setTime(rs.getTime() - DAY_MS); + } + } + else if (expression.timeUnit == "WEEKS") + { + while (rs.getDay() != 0) + { + rs.setTime(rs.getTime() - DAY_MS); + } + + if (expression.operator == "LAST") + { + rs.setTime(rs.getTime() - 7 * DAY_MS); + } + } + else if (expression.timeUnit == "MONTHS") + { + rs.setDate(1); + + if (expression.operator == "LAST") + { + rs.setTime(rs.getTime() - DAY_MS); + rs.setDate(1); + } + } + else if (expression.timeUnit == "YEARS") + { + rs.setDate(1); + rs.setMonth(0); + + if (expression.operator == "LAST") + { + rs.setTime(rs.getTime() - 365 * DAY_MS); + } + } + } + } + + if (rs) + { + if (field.type == QFieldType.DATE) + { + return (ValueUtils.formatDate(rs)); + } + else + { + return (ValueUtils.formatDateTime(rs)); + } + } + + return null; +}; + + + diff --git a/src/qqq/utils/DataGridUtils.tsx b/src/qqq/utils/DataGridUtils.tsx index 6a04389..0aa5626 100644 --- a/src/qqq/utils/DataGridUtils.tsx +++ b/src/qqq/utils/DataGridUtils.tsx @@ -50,15 +50,33 @@ const makeGridFilterOperator = (value: string, label: string, takesValues: boole return (rs); }; +//////////////////////////////////////////////////////////////////////////////////////// +// at this point, these may only be used to drive the toolitp on the FILTER button... // +//////////////////////////////////////////////////////////////////////////////////////// const QGridDateOperators = [ makeGridFilterOperator("equals", "equals", true), - makeGridFilterOperator("isNot", "not equals", true), + makeGridFilterOperator("isNot", "does not equal", 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"), + makeGridFilterOperator("between", "is between", true), + makeGridFilterOperator("notBetween", "is not between", true), +]; + +const QGridDateTimeOperators = [ + makeGridFilterOperator("equals", "equals", true), + makeGridFilterOperator("isNot", "does not equal", true), + makeGridFilterOperator("after", "is after", true), + makeGridFilterOperator("onOrAfter", "is at or after", true), + makeGridFilterOperator("before", "is before", true), + makeGridFilterOperator("onOrBefore", "is at or before", true), + makeGridFilterOperator("isEmpty", "is empty"), + makeGridFilterOperator("isNotEmpty", "is not empty"), + makeGridFilterOperator("between", "is between", true), + makeGridFilterOperator("notBetween", "is not between", true), ]; export default class DataGridUtils @@ -272,7 +290,7 @@ export default class DataGridUtils case QFieldType.DATE_TIME: columnType = "dateTime"; columnWidth = 200; - filterOperators = QGridDateOperators; + filterOperators = QGridDateTimeOperators; break; case QFieldType.BOOLEAN: columnType = "string"; // using boolean gives an odd 'no' for nulls. diff --git a/src/qqq/utils/qqq/FilterUtils.ts b/src/qqq/utils/qqq/FilterUtils.ts index d3c4372..8ce5706 100644 --- a/src/qqq/utils/qqq/FilterUtils.ts +++ b/src/qqq/utils/qqq/FilterUtils.ts @@ -446,107 +446,15 @@ class FilterUtils } } - // todo - use expressions here!! - if (field && field.type == "DATE_TIME" && !values) + ////////////////////////////////////////////////////////////////////////// + // replace objects that look like expressions with expression instances // + ////////////////////////////////////////////////////////////////////////// + for(let i = 0; i < values.length; i++) { - try + const expression = this.gridCriteriaValueToExpression(values[i]) + if(expression) { - const criteria = filterJSON.criteria[i]; - if (criteria && criteria.expression) - { - let value = new Date(); - let amount = Number(criteria.expression.amount); - switch (criteria.expression.timeUnit) - { - case "MINUTES": - { - amount = amount * 60; - break; - } - case "HOURS": - { - amount = amount * 60 * 60; - break; - } - case "DAYS": - { - amount = amount * 60 * 60 * 24; - break; - } - default: - { - console.log("Unrecognized time unit: " + criteria.expression.timeUnit); - } - } - - if (criteria.expression.operator == "MINUS") - { - 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)]; - } - } - catch (e) - { - console.log(e); - } - } - - if (field && field.type == "DATE" && !values) - { - try - { - const criteria = filterJSON.criteria[i]; - if (criteria && criteria.expression) - { - let value = new Date(); - let amount = Number(criteria.expression.amount); - switch (criteria.expression.timeUnit) - { - case "MINUTES": - { - amount = amount * 60; - break; - } - case "HOURS": - { - amount = amount * 60 * 60; - break; - } - case "DAYS": - { - amount = amount * 60 * 60 * 24; - break; - } - default: - { - console.log("Unrecognized time unit: " + criteria.expression.timeUnit); - } - } - - if (criteria.expression.operator == "MINUS") - { - amount = -amount; - } - - value.setTime(value.getTime() + 1000 * amount); - values = [ValueUtils.formatDateISO8601(value)]; - } - } - catch (e) - { - console.log(e); + values[i] = expression; } } @@ -554,7 +462,7 @@ class FilterUtils columnField: criteria.fieldName, operatorValue: FilterUtils.qqqCriteriaOperatorToGrid(criteria.operator, field, values), value: FilterUtils.qqqCriteriaValuesToGrid(criteria.operator, values, field), - id: id++, // not sure what this id is!! + id: id++ }); } @@ -564,9 +472,9 @@ class FilterUtils defaultFilter.linkOperator = GridLinkOperator.Or; } - ////////////////////////////////////////////////////////////////// - // translate from a qqq-style filter to one that the grid wants // - ////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////// + // translate from qqq-style orderBy to one that the grid wants // + ///////////////////////////////////////////////////////////////// if (qQueryFilter.orderBys && qQueryFilter.orderBys.length > 0) { for (let i = 0; i < qQueryFilter.orderBys.length; i++) @@ -610,14 +518,32 @@ class FilterUtils } } + ///////////////////////////////////////////////////////////////////////////////// + // if any values in the items are objects, but should be expression instances, // + // then convert & replace them. // + ///////////////////////////////////////////////////////////////////////////////// if(defaultFilter && defaultFilter.items && defaultFilter.items.length) { defaultFilter.items.forEach((item) => { - const expression = this.gridCriteriaValueToExpression(item.value) - if(expression) + if(item.value && item.value.length) { - item.value = expression; + for (let i = 0; i < item.value.length; i++) + { + const expression = this.gridCriteriaValueToExpression(item.value[i]) + if(expression) + { + item.value[i] = expression; + } + } + } + else + { + const expression = this.gridCriteriaValueToExpression(item.value) + if(expression) + { + item.value = expression; + } } }); } @@ -726,14 +652,24 @@ class FilterUtils //////////////////////////////////////////////////////////////////////////////// // if no value set and not 'empty' or 'not empty' operators, skip this filter // //////////////////////////////////////////////////////////////////////////////// - if ((!item.value || item.value.length == 0 || (item.value.length == 1 && (item.value[0] === "" || item.value[0] === undefined))) && item.operatorValue !== "isEmpty" && item.operatorValue !== "isNotEmpty") + let incomplete = false; + if (item.operatorValue === "between" || item.operatorValue === "notBetween") { - if (!allowIncompleteCriteria) + if(!item.value || !item.value.length || item.value.length < 2 || this.isUnset(item.value[0]) || this.isUnset(item.value[1])) { - console.log(`Discarding incomplete filter criteria: ${JSON.stringify(item)}`); - return; + incomplete = true; } } + else if ((!item.value || item.value.length == 0 || (item.value.length == 1 && this.isUnset(item.value[0]))) && item.operatorValue !== "isEmpty" && item.operatorValue !== "isNotEmpty") + { + incomplete = true; + } + + if (incomplete && !allowIncompleteCriteria) + { + console.log(`Discarding incomplete filter criteria: ${JSON.stringify(item)}`); + return; + } const fieldMetadata = tableMetaData?.fields.get(item.columnField); const operator = FilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue); @@ -757,27 +693,38 @@ class FilterUtils }; + /******************************************************************************* + ** + *******************************************************************************/ + private static isUnset(value: any) + { + return value === "" || value === undefined; + } + /******************************************************************************* ** *******************************************************************************/ private static gridCriteriaValueToExpression(value: any) { - if (value.length) + if (value && value.length) { value = value[0]; } - if (value.type && value.type == "NowWithOffset") + if (value && value.type) { - return (new NowWithOffsetExpression(value)); - } - else if (value.type && value.type == "Now") - { - return (new NowExpression(value)); - } - else if (value.type && value.type == "ThisOrLastPeriod") - { - return (new ThisOrLastPeriodExpression(value)); + if (value.type == "NowWithOffset") + { + return (new NowWithOffsetExpression(value)); + } + else if (value.type == "Now") + { + return (new NowExpression(value)); + } + else if (value.type == "ThisOrLastPeriod") + { + return (new ThisOrLastPeriodExpression(value)); + } } return (null); diff --git a/src/qqq/utils/qqq/ValueUtils.tsx b/src/qqq/utils/qqq/ValueUtils.tsx index ad9695c..909ec04 100644 --- a/src/qqq/utils/qqq/ValueUtils.tsx +++ b/src/qqq/utils/qqq/ValueUtils.tsx @@ -254,6 +254,16 @@ class ValueUtils return (returnValue); } + public static formatDate(date: Date) + { + if (!(date instanceof Date)) + { + date = new Date(date); + } + // @ts-ignore + return (`${date.toString("yyyy-MM-dd")}`); + } + public static formatDateTime(date: Date) { if (!(date instanceof Date))