mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-19 13:50:43 +00:00
added ability to search for possible value data using the PVS labels, rather than just the ids, updated the values paster widget thingy to use this change to make pvs requests in a paginated manner
This commit is contained in:
23616
package-lock.json
generated
23616
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.119",
|
"@kingsrook/qqq-frontend-core": "1.0.122",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
@ -35,9 +35,9 @@
|
|||||||
"html-react-parser": "1.4.8",
|
"html-react-parser": "1.4.8",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.6",
|
||||||
|
"lodash": "4.17.21",
|
||||||
"jwt-decode": "3.1.2",
|
"jwt-decode": "3.1.2",
|
||||||
"oidc-client-ts": "2.4.1",
|
"oidc-client-ts": "2.4.1",
|
||||||
"react-oidc-context": "2.3.1",
|
|
||||||
"rapidoc": "9.3.4",
|
"rapidoc": "9.3.4",
|
||||||
"react": "18.0.0",
|
"react": "18.0.0",
|
||||||
"react-ace": "10.1.0",
|
"react-ace": "10.1.0",
|
||||||
@ -51,6 +51,7 @@
|
|||||||
"react-github-btn": "1.2.1",
|
"react-github-btn": "1.2.1",
|
||||||
"react-google-drive-picker": "^1.2.0",
|
"react-google-drive-picker": "^1.2.0",
|
||||||
"react-markdown": "9.0.1",
|
"react-markdown": "9.0.1",
|
||||||
|
"react-oidc-context": "2.3.1",
|
||||||
"react-router-dom": "6.2.1",
|
"react-router-dom": "6.2.1",
|
||||||
"react-router-hash-link": "2.4.3",
|
"react-router-hash-link": "2.4.3",
|
||||||
"react-table": "7.7.0",
|
"react-table": "7.7.0",
|
||||||
|
@ -23,8 +23,10 @@ import {Chip} from "@mui/material";
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {makeStyles} from "@mui/styles";
|
import {makeStyles} from "@mui/styles";
|
||||||
import Downshift from "downshift";
|
import Downshift from "downshift";
|
||||||
|
import {debounce} from "lodash";
|
||||||
import {arrayOf, func, string} from "prop-types";
|
import {arrayOf, func, string} from "prop-types";
|
||||||
import React, {useEffect, useState} from "react";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: any) => ({
|
const useStyles = makeStyles((theme: any) => ({
|
||||||
chip: {
|
chip: {
|
||||||
@ -34,21 +36,99 @@ const useStyles = makeStyles((theme: any) => ({
|
|||||||
|
|
||||||
function ChipTextField({...props})
|
function ChipTextField({...props})
|
||||||
{
|
{
|
||||||
|
const qController = Client.getInstance();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const {handleChipChange, label, chipType, disabled, placeholder, chipData, multiline, rows, ...other} = props;
|
const {table, field, handleChipChange, label, chipType, disabled, placeholder, chipData, multiline, rows, ...other} = props;
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [chips, setChips] = useState([]);
|
const [chips, setChips] = useState([]);
|
||||||
|
const [chipColors, setChipColors] = useState([]);
|
||||||
|
const [chipValidity, setChipValidity] = useState([] as boolean[]);
|
||||||
|
const [chipPVSIds, setChipPVSIds] = useState([] as any[]);
|
||||||
|
const [isMakingRequest, setIsMakingRequest] = useState(false);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// these refs are used for the async api call for possible values //
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
const chipsRef = useRef<string[]>([]);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// use debounce library to not flood server as user types, wait a second before requesting //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
async function fetchPVSLabelsAndColorChips()
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// make a request for the possible value labels (chips) //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
setIsMakingRequest(true);
|
||||||
|
const currentChips = chipsRef.current;
|
||||||
|
setChipColors([]);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Determine chip colors based on whether each chip value appears in results //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
const newChipColors = [] as string[];
|
||||||
|
const chipValidity = [] as boolean[];
|
||||||
|
const chipPVSIds = [] as any[];
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make the request for all 'chips' with pagination to handle large sizes //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
const BATCH_SIZE = 250;
|
||||||
|
for (let i = 0; i < currentChips.length; i += BATCH_SIZE)
|
||||||
|
{
|
||||||
|
const batch = currentChips.slice(i, i + BATCH_SIZE);
|
||||||
|
const page = await qController.possibleValues(
|
||||||
|
table.name,
|
||||||
|
null,
|
||||||
|
field.name,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
batch
|
||||||
|
);
|
||||||
|
for (let j = 0; j < batch.length; j++)
|
||||||
|
{
|
||||||
|
let found = false;
|
||||||
|
for (let k = 0; k < page.length; k++)
|
||||||
|
{
|
||||||
|
const result = page[k];
|
||||||
|
if (result.label === batch[j])
|
||||||
|
{
|
||||||
|
chipPVSIds.push(result.id);
|
||||||
|
newChipColors.push("info");
|
||||||
|
chipValidity.push(true);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
chipPVSIds.push(null);
|
||||||
|
chipValidity.push(false);
|
||||||
|
newChipColors.push("error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setChipPVSIds(chipPVSIds);
|
||||||
|
setChipColors(newChipColors);
|
||||||
|
setChipValidity(chipValidity);
|
||||||
|
setIsMakingRequest(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedApiCall = useRef(debounce(fetchPVSLabelsAndColorChips, 500)).current;
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
setChips(chipData);
|
setChips(chipData);
|
||||||
}, [chipData]);
|
chipsRef.current = chipData;
|
||||||
|
determineChipColors();
|
||||||
|
}, [JSON.stringify(chipData)]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
handleChipChange(chips);
|
handleChipChange(isMakingRequest, chipValidity, chipPVSIds);
|
||||||
}, [chips, handleChipChange]);
|
}, [chipValidity, chipPVSIds, isMakingRequest]);
|
||||||
|
|
||||||
function handleKeyDown(event: any)
|
function handleKeyDown(event: any)
|
||||||
{
|
{
|
||||||
@ -64,11 +144,14 @@ function ChipTextField({...props})
|
|||||||
setInputValue("");
|
setInputValue("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!event.target.value.replace(/\s/g, "").length) return;
|
if (!event.target.value.replace(/\s/g, "").length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInputValue("");
|
||||||
newChipList.push(event.target.value.trim());
|
newChipList.push(event.target.value.trim());
|
||||||
setChips(newChipList);
|
setChips(newChipList);
|
||||||
setInputValue("");
|
|
||||||
}
|
}
|
||||||
else if (chips.length && !inputValue.length && event.key === "Backspace")
|
else if (chips.length && !inputValue.length && event.key === "Backspace")
|
||||||
{
|
{
|
||||||
@ -87,18 +170,26 @@ function ChipTextField({...props})
|
|||||||
setChips(newChipList);
|
setChips(newChipList);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = (item: any) => () =>
|
|
||||||
{
|
|
||||||
const newChipList = [...chips];
|
|
||||||
newChipList.splice(newChipList.indexOf(item), 1);
|
|
||||||
setChips(newChipList);
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleInputChange(event: { target: { value: React.SetStateAction<string>; }; })
|
function handleInputChange(event: { target: { value: React.SetStateAction<string>; }; })
|
||||||
{
|
{
|
||||||
setInputValue(event.target.value);
|
setInputValue(event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function determineChipColors(): any
|
||||||
|
{
|
||||||
|
if (chipType === "pvs")
|
||||||
|
{
|
||||||
|
debouncedApiCall();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const newChipColors = chips.map((chip, i) =>
|
||||||
|
(chipType !== "number" || !Number.isNaN(Number(chips[i]))) ? "info" : "error"
|
||||||
|
);
|
||||||
|
setChipColors(newChipColors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -125,16 +216,16 @@ function ChipTextField({...props})
|
|||||||
startAdornment:
|
startAdornment:
|
||||||
<div style={{overflowY: "auto", overflowX: "hidden", margin: "-10px", width: "calc(100% + 20px)", padding: "10px", marginBottom: "-20px", height: "calc(100% + 10px)"}}>
|
<div style={{overflowY: "auto", overflowX: "hidden", margin: "-10px", width: "calc(100% + 20px)", padding: "10px", marginBottom: "-20px", height: "calc(100% + 10px)"}}>
|
||||||
{
|
{
|
||||||
chips.map((item, i) => (
|
chips.map((item, index) => (
|
||||||
<Chip
|
<Chip
|
||||||
color={(chipType !== "number" || ! Number.isNaN(Number(item))) ? "info" : "error"}
|
onChange={determineChipColors}
|
||||||
key={`${item}-${i}`}
|
color={chipColors[index]}
|
||||||
|
key={`${item}-${index}`}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
label={item}
|
label={item}
|
||||||
className={classes.chip}
|
className={classes.chip}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>,
|
</div>,
|
||||||
@ -158,6 +249,7 @@ function ChipTextField({...props})
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChipTextField.defaultProps = {
|
ChipTextField.defaultProps = {
|
||||||
chipData: []
|
chipData: []
|
||||||
};
|
};
|
||||||
@ -166,4 +258,4 @@ ChipTextField.propTypes = {
|
|||||||
chipData: arrayOf(string)
|
chipData: arrayOf(string)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChipTextField
|
export default ChipTextField;
|
||||||
|
@ -20,17 +20,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
||||||
import {Box, InputAdornment, InputLabel} from "@mui/material";
|
import {InputAdornment, InputLabel} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import {ErrorMessage, Field, useFormikContext} from "formik";
|
import {ErrorMessage, Field, useFormikContext} from "formik";
|
||||||
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
|
||||||
import DynamicSelect from "qqq/components/forms/DynamicSelect";
|
|
||||||
import React, {useMemo, useState} from "react";
|
|
||||||
import AceEditor from "react-ace";
|
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import BooleanFieldSwitch from "qqq/components/forms/BooleanFieldSwitch";
|
import BooleanFieldSwitch from "qqq/components/forms/BooleanFieldSwitch";
|
||||||
|
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
||||||
|
import DynamicSelect from "qqq/components/forms/DynamicSelect";
|
||||||
import MDInput from "qqq/components/legacy/MDInput";
|
import MDInput from "qqq/components/legacy/MDInput";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
|
import React, {useMemo, useState} from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
import {flushSync} from "react-dom";
|
import {flushSync} from "react-dom";
|
||||||
|
|
||||||
// Declaring props types for FormField
|
// Declaring props types for FormField
|
||||||
@ -83,7 +84,7 @@ function QDynamicFormField({
|
|||||||
|
|
||||||
if (placeholder)
|
if (placeholder)
|
||||||
{
|
{
|
||||||
inputProps.placeholder = placeholder
|
inputProps.placeholder = placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backgroundColor)
|
if (backgroundColor)
|
||||||
@ -167,7 +168,7 @@ function QDynamicFormField({
|
|||||||
{
|
{
|
||||||
if (onChangeCallback)
|
if (onChangeCallback)
|
||||||
{
|
{
|
||||||
onChangeCallback(newValue == null ? null : newValue.id)
|
onChangeCallback(newValue == null ? null : newValue.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +187,7 @@ function QDynamicFormField({
|
|||||||
onChange={dynamicSelectOnChange}
|
onChange={dynamicSelectOnChange}
|
||||||
// otherValues={otherValuesMap}
|
// otherValues={otherValuesMap}
|
||||||
useCase="form"
|
useCase="form"
|
||||||
/>)
|
/>);
|
||||||
}
|
}
|
||||||
else if (type === "checkbox")
|
else if (type === "checkbox")
|
||||||
{
|
{
|
||||||
|
@ -174,7 +174,7 @@ function DynamicSelect({fieldPossibleValueProps, overrideId, name, fieldLabel, i
|
|||||||
const filterInlinePossibleValues = (searchTerm: string, possibleValues: QPossibleValue[]): QPossibleValue[] =>
|
const filterInlinePossibleValues = (searchTerm: string, possibleValues: QPossibleValue[]): QPossibleValue[] =>
|
||||||
{
|
{
|
||||||
return possibleValues.filter(pv => pv.label?.toLowerCase().startsWith(searchTerm));
|
return possibleValues.filter(pv => pv.label?.toLowerCase().startsWith(searchTerm));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
@ -184,13 +184,13 @@ function DynamicSelect({fieldPossibleValueProps, overrideId, name, fieldLabel, i
|
|||||||
{
|
{
|
||||||
if (possibleValues)
|
if (possibleValues)
|
||||||
{
|
{
|
||||||
return filterInlinePossibleValues(searchTerm, possibleValues)
|
return filterInlinePossibleValues(searchTerm, possibleValues);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return await qController.possibleValues(tableName, processName, possibleValueSourceName ?? fieldName, searchTerm ?? "", null, otherValues, useCase);
|
return await qController.possibleValues(tableName, processName, possibleValueSourceName ?? fieldName, searchTerm ?? "", null, null, otherValues, useCase);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
@ -284,14 +284,14 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// this function internally memoizes, so, we could potentially avoid an await here, but, seems ok... //
|
// this function internally memoizes, so, we could potentially avoid an await here, but, seems ok... //
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
const childTableMetaData = await qController.loadTableMetaData(childTableName)
|
const childTableMetaData = await qController.loadTableMetaData(childTableName);
|
||||||
for (let key in values)
|
for (let key in values)
|
||||||
{
|
{
|
||||||
const value = values[key];
|
const value = values[key];
|
||||||
const field = childTableMetaData.fields.get(key);
|
const field = childTableMetaData.fields.get(key);
|
||||||
if (field.possibleValueSourceName)
|
if (field.possibleValueSourceName)
|
||||||
{
|
{
|
||||||
const possibleValues = await qController.possibleValues(childTableName, null, field.name, null, [value], objectToMap(values), "form")
|
const possibleValues = await qController.possibleValues(childTableName, null, field.name, null, [value], null, objectToMap(values), "form");
|
||||||
if (possibleValues && possibleValues.length > 0)
|
if (possibleValues && possibleValues.length > 0)
|
||||||
{
|
{
|
||||||
displayValues[key] = possibleValues[0].label;
|
displayValues[key] = possibleValues[0].label;
|
||||||
@ -516,7 +516,6 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
@ -532,7 +531,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
rs.set(key, object[key]);
|
rs.set(key, object[key]);
|
||||||
}
|
}
|
||||||
return rs
|
return rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -667,7 +666,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
|
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
|
||||||
if (defaultValue && fieldMetaData.possibleValueSourceName)
|
if (defaultValue && fieldMetaData.possibleValueSourceName)
|
||||||
{
|
{
|
||||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]], objectToMap(initialValues), "form");
|
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]], null, objectToMap(initialValues), "form");
|
||||||
if (results && results.length > 0)
|
if (results && results.length > 0)
|
||||||
{
|
{
|
||||||
defaultDisplayValues.set(fieldName, results[0].label);
|
defaultDisplayValues.set(fieldName, results[0].label);
|
||||||
|
@ -26,7 +26,6 @@ import Autocomplete from "@mui/material/Autocomplete";
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import RadioGroup from "@mui/material/RadioGroup";
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {useFormikContext} from "formik";
|
import {useFormikContext} from "formik";
|
||||||
@ -104,7 +103,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const possibleValues = await qController.possibleValues(bulkLoadField.tableStructure.tableName, null, fieldMetaData.name, null, [bulkLoadField.defaultValue], undefined, "filter");
|
const possibleValues = await qController.possibleValues(bulkLoadField.tableStructure.tableName, null, fieldMetaData.name, null, [bulkLoadField.defaultValue], undefined, null, "filter");
|
||||||
if (possibleValues && possibleValues.length > 0)
|
if (possibleValues && possibleValues.length > 0)
|
||||||
{
|
{
|
||||||
setPossibleValueInitialDisplayValue(possibleValues[0].label);
|
setPossibleValueInitialDisplayValue(possibleValues[0].label);
|
||||||
@ -116,7 +115,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
|||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
console.log(`Error loading possible value: ${e}`)
|
console.log(`Error loading possible value: ${e}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
actuallyDoingInitialLoadOfPossibleValue = false;
|
actuallyDoingInitialLoadOfPossibleValue = false;
|
||||||
@ -150,7 +149,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
if (bulkLoadField.columnIndex != null && bulkLoadField.columnIndex != undefined && selectedColumn.label && columnNames[bulkLoadField.columnIndex] != selectedColumn.label)
|
if (bulkLoadField.columnIndex != null && bulkLoadField.columnIndex != undefined && selectedColumn.label && columnNames[bulkLoadField.columnIndex] != selectedColumn.label)
|
||||||
{
|
{
|
||||||
setSelectedColumn({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex})
|
setSelectedColumn({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex});
|
||||||
setSelectedColumnInputValue(columnNames[bulkLoadField.columnIndex]);
|
setSelectedColumnInputValue(columnNames[bulkLoadField.columnIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
||||||
import {FormControl, InputLabel, Select, SelectChangeEvent} from "@mui/material";
|
import {FormControl, InputLabel, Select, SelectChangeEvent} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
@ -28,20 +31,23 @@ import Modal from "@mui/material/Modal";
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {GridFilterItem} from "@mui/x-data-grid-pro";
|
|
||||||
import React, {useEffect, useState} from "react";
|
|
||||||
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
|
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
|
||||||
|
|
||||||
import ChipTextField from "qqq/components/forms/ChipTextField";
|
import ChipTextField from "qqq/components/forms/ChipTextField";
|
||||||
|
import {LoadingState} from "qqq/models/LoadingState";
|
||||||
|
import React, {useEffect, useReducer, useState} from "react";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
type: string;
|
type: string;
|
||||||
onSave: (newValues: any[]) => void;
|
onSave: (newValues: any[]) => void;
|
||||||
|
table?: QTableMetaData;
|
||||||
|
field?: QFieldMetaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterCriteriaPaster.defaultProps = {};
|
FilterCriteriaPaster.defaultProps = {};
|
||||||
|
|
||||||
function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
function FilterCriteriaPaster({table, field, type, onSave}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
enum Delimiter
|
enum Delimiter
|
||||||
{
|
{
|
||||||
@ -68,6 +74,12 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
mainCardStyles.width = "60%";
|
mainCardStyles.width = "60%";
|
||||||
mainCardStyles.minWidth = "500px";
|
mainCardStyles.minWidth = "500px";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// add a LoadingState object, in case the initial loads (of meta data and view) are slow //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
const [pageLoadingState, _] = useState(new LoadingState(forceUpdate));
|
||||||
|
|
||||||
//x const [gridFilterItem, setGridFilterItem] = useState(props.item);
|
//x const [gridFilterItem, setGridFilterItem] = useState(props.item);
|
||||||
const [pasteModalIsOpen, setPasteModalIsOpen] = useState(false);
|
const [pasteModalIsOpen, setPasteModalIsOpen] = useState(false);
|
||||||
const [inputText, setInputText] = useState("");
|
const [inputText, setInputText] = useState("");
|
||||||
@ -75,8 +87,11 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
const [delimiterCharacter, setDelimiterCharacter] = useState("");
|
const [delimiterCharacter, setDelimiterCharacter] = useState("");
|
||||||
const [customDelimiterValue, setCustomDelimiterValue] = useState("");
|
const [customDelimiterValue, setCustomDelimiterValue] = useState("");
|
||||||
const [chipData, setChipData] = useState(undefined);
|
const [chipData, setChipData] = useState(undefined);
|
||||||
|
const [chipValidity, setChipValidity] = useState([] as boolean[]);
|
||||||
|
const [chipPVSIds, setChipPVSIds] = useState([] as any[]);
|
||||||
const [detectedText, setDetectedText] = useState("");
|
const [detectedText, setDetectedText] = useState("");
|
||||||
const [errorText, setErrorText] = useState("");
|
const [errorText, setErrorText] = useState("");
|
||||||
|
const [saveDisabled, setSaveDisabled] = useState(true);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
// handler for when paste icon is clicked in 'any' operator //
|
// handler for when paste icon is clicked in 'any' operator //
|
||||||
@ -92,6 +107,7 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
setDelimiter("");
|
setDelimiter("");
|
||||||
setDelimiterCharacter("");
|
setDelimiterCharacter("");
|
||||||
setChipData([]);
|
setChipData([]);
|
||||||
|
setChipValidity([]);
|
||||||
setInputText("");
|
setInputText("");
|
||||||
setDetectedText("");
|
setDetectedText("");
|
||||||
setCustomDelimiterValue("");
|
setCustomDelimiterValue("");
|
||||||
@ -106,17 +122,42 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
|
|
||||||
const handleSaveClicked = () =>
|
const handleSaveClicked = () =>
|
||||||
{
|
{
|
||||||
////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
// if numeric remove any non-numerics //
|
// if numeric remove any non-numerics, or invalid pvs values //
|
||||||
////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
let saveData = [];
|
let saveData = [];
|
||||||
|
let usedLabels = new Map<any, boolean>();
|
||||||
for (let i = 0; i < chipData.length; i++)
|
for (let i = 0; i < chipData.length; i++)
|
||||||
{
|
{
|
||||||
if (type !== "number" || !Number.isNaN(Number(chipData[i])))
|
if (chipValidity[i] === true)
|
||||||
|
{
|
||||||
|
if (type === "pvs")
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// if already used this PVS label, skip it //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
if (usedLabels.get(chipData[i]) != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveData.push(new QPossibleValue({id: chipPVSIds[i], label: chipData[i]}));
|
||||||
|
usedLabels.set(chipData[i], true);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
saveData.push(chipData[i]);
|
saveData.push(chipData[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// for pvs, sort by label before saving //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
if (type === "pvs")
|
||||||
|
{
|
||||||
|
saveData.sort((a: QPossibleValue, b: QPossibleValue) => b.label.localeCompare(a.label));
|
||||||
|
}
|
||||||
|
|
||||||
onSave(saveData);
|
onSave(saveData);
|
||||||
|
|
||||||
@ -259,20 +300,27 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
chipData.push(part);
|
chipData.push(part);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// if numeric, check that first before pushing as a chip //
|
// if numeric or pvs, check validity first before pushing as a chip //
|
||||||
///////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
if (chipValidity[i] !== true)
|
||||||
|
{
|
||||||
if (type === "number" && Number.isNaN(Number(part)))
|
if (type === "number" && Number.isNaN(Number(part)))
|
||||||
{
|
{
|
||||||
setErrorText("Some values are not numbers");
|
setErrorText("Some values are not numbers");
|
||||||
}
|
}
|
||||||
|
else if (type === "pvs")
|
||||||
|
{
|
||||||
|
setErrorText("Some values are not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setChipData(chipData);
|
setChipData(chipData);
|
||||||
|
|
||||||
}, [inputText, delimiterCharacter, customDelimiterValue, detectedText]);
|
}, [inputText, delimiterCharacter, customDelimiterValue, detectedText, chipValidity]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -283,6 +331,7 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
pasteModalIsOpen &&
|
pasteModalIsOpen &&
|
||||||
(
|
(
|
||||||
<Modal open={pasteModalIsOpen}>
|
<Modal open={pasteModalIsOpen}>
|
||||||
|
<Box>
|
||||||
<Box sx={{position: "absolute", overflowY: "auto", width: "100%"}}>
|
<Box sx={{position: "absolute", overflowY: "auto", width: "100%"}}>
|
||||||
<Box py={3} justifyContent="center" sx={{display: "flex", mt: 8}}>
|
<Box py={3} justifyContent="center" sx={{display: "flex", mt: 8}}>
|
||||||
<Card sx={mainCardStyles}>
|
<Card sx={mainCardStyles}>
|
||||||
@ -314,10 +363,25 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
<Grid item xs={6} lg={6} sx={{display: "flex", flexGrow: 1}}>
|
<Grid item xs={6} lg={6} sx={{display: "flex", flexGrow: 1}}>
|
||||||
<FormControl sx={{m: 1, width: "100%"}}>
|
<FormControl sx={{m: 1, width: "100%"}}>
|
||||||
<ChipTextField
|
<ChipTextField
|
||||||
handleChipChange={() =>
|
handleChipChange={(isMakingRequest: boolean, chipValidity: boolean[], chipPVSIds: any[]) =>
|
||||||
{
|
{
|
||||||
|
setErrorText("");
|
||||||
|
if (isMakingRequest)
|
||||||
|
{
|
||||||
|
pageLoadingState.setLoading();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageLoadingState.setNotLoading();
|
||||||
|
}
|
||||||
|
setSaveDisabled(isMakingRequest);
|
||||||
|
setChipPVSIds(chipPVSIds);
|
||||||
|
setChipValidity(chipValidity);
|
||||||
}}
|
}}
|
||||||
|
table={table}
|
||||||
|
field={field}
|
||||||
chipData={chipData}
|
chipData={chipData}
|
||||||
|
chipValidity={chipValidity}
|
||||||
chipType={type}
|
chipType={type}
|
||||||
multiline
|
multiline
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -386,6 +450,14 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
pageLoadingState.isLoadingSlow() && (
|
||||||
|
<Box sx={{display: "flex", justifyContent: "flex-start", alignItems: "flex-start"}}>
|
||||||
|
<Icon color="warning">warning</Icon>
|
||||||
|
<Typography sx={{paddingLeft: "4px", textTransform: "revert"}} variant="button">Loading...</Typography>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid sx={{display: "flex", justifyContent: "flex-end", alignItems: "flex-start"}} item pr={1} xs={3} lg={3}>
|
<Grid sx={{display: "flex", justifyContent: "flex-end", alignItems: "flex-start"}} item pr={1} xs={3} lg={3}>
|
||||||
{
|
{
|
||||||
@ -401,12 +473,13 @@ function FilterCriteriaPaster({type, onSave}: Props): JSX.Element
|
|||||||
onClickHandler={handleCancelClicked}
|
onClickHandler={handleCancelClicked}
|
||||||
iconName="cancel"
|
iconName="cancel"
|
||||||
disabled={false} />
|
disabled={false} />
|
||||||
<QSaveButton onClickHandler={handleSaveClicked} label="Add Filters" disabled={false} />
|
<QSaveButton onClickHandler={handleSaveClicked} label="Add Filters" disabled={saveDisabled} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -398,11 +398,12 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
|||||||
initialValues = criteria.values;
|
initialValues = criteria.values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <Box>
|
return <Box display="flex" alignItems="flex-end" className="multiValue">
|
||||||
|
<Box width={"100%"}>
|
||||||
<DynamicSelect
|
<DynamicSelect
|
||||||
fieldPossibleValueProps={{tableName: table.name, fieldName: field.name, initialDisplayValue: null}}
|
fieldPossibleValueProps={{tableName: table.name, fieldName: field.name, initialDisplayValue: null}}
|
||||||
overrideId={field.name + "-multi-" + criteria.id}
|
overrideId={field.name + "-multi-" + criteria.id}
|
||||||
key={field.name + "-multi-" + criteria.id}
|
key={field.name + "-multi-" + criteria.id + "-" + criteria.values.length}
|
||||||
isMultiple
|
isMultiple
|
||||||
fieldLabel="Values"
|
fieldLabel="Values"
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
@ -412,6 +413,10 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
|||||||
variant="standard"
|
variant="standard"
|
||||||
useCase="filter"
|
useCase="filter"
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<FilterCriteriaPaster table={table} field={field} type="pvs" onSave={(newValues: any[]) => saveNewPasterValues(newValues)} />
|
||||||
|
</Box>
|
||||||
</Box>;
|
</Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ import XIcon from "qqq/components/query/XIcon";
|
|||||||
import {QueryScreenUsage} from "qqq/pages/records/query/RecordQuery";
|
import {QueryScreenUsage} from "qqq/pages/records/query/RecordQuery";
|
||||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||||
import TableUtils from "qqq/utils/qqq/TableUtils";
|
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||||
import React, {SyntheticEvent, useContext, useReducer, useState} from "react";
|
import React, {SyntheticEvent, useContext, useEffect, useReducer, useState} from "react";
|
||||||
|
|
||||||
export type CriteriaParamType = QFilterCriteriaWithId | null | "tooComplex";
|
export type CriteriaParamType = QFilterCriteriaWithId | null | "tooComplex";
|
||||||
|
|
||||||
@ -186,6 +186,13 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
//////////////////////
|
//////////////////////
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// was not seeing criteria changes take place until watching it stringified //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
setCriteria(criteria);
|
||||||
|
}, [JSON.stringify(criteria)]);
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
|
@ -133,7 +133,7 @@ class FilterUtils
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
values = await qController.possibleValues(fieldTable.name, null, field.name, "", values, undefined, "filter");
|
values = await qController.possibleValues(fieldTable.name, null, field.name, "", values, undefined, undefined, "filter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user