Merge branch 'dev' into integration/sprint-25

This commit is contained in:
Tim Chamberlain
2023-05-03 11:16:56 -05:00
7 changed files with 1940 additions and 2663 deletions

4448
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,9 +47,9 @@
},
"scripts": {
"build": "react-scripts build",
"clean": "rm -rf node_modules package-lock.json",
"clean": "rm -rf node_modules package-lock.json lib",
"eject": "react-scripts eject",
"geff-ham": "rm -rf node_modules/ && rm -rf package-lock.json && rm -rf lib/ && npm install",
"clean-and-install": "rm -rf node_modules/ && rm -rf package-lock.json && rm -rf lib/ && npm install",
"npm-install": "npm install",
"prepublishOnly": "tsc -p ./ --outDir lib/",
"start": "BROWSER=none react-scripts --max-http-header-size=65535 start",

View File

@ -26,9 +26,8 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Alert} from "@mui/material";
import {Alert, Box} from "@mui/material";
import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid";
import Icon from "@mui/material/Icon";
@ -80,6 +79,7 @@ function EntityForm(props: Props): JSX.Element
const [nonT1Sections, setNonT1Sections] = useState([] as QTableSection[]);
const [alertContent, setAlertContent] = useState("");
const [warningContent, setWarningContent] = useState("");
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [formValues, setFormValues] = useState({} as { [key: string]: string });
@ -424,7 +424,16 @@ function EntityForm(props: Props): JSX.Element
{
console.log("Caught:");
console.log(error);
setAlertContent(error.message);
if(error.message.toLowerCase().startsWith("warning"))
{
const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true&warning=${encodeURIComponent(error.message)}`;
navigate(path);
}
else
{
setAlertContent(error.message);
}
});
}
else
@ -445,7 +454,15 @@ function EntityForm(props: Props): JSX.Element
})
.catch((error) =>
{
setAlertContent(error.message);
if(error.message.toLowerCase().startsWith("warning"))
{
const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true&warning=${encodeURIComponent(error.message)}`;
navigate(path);
}
else
{
setAlertContent(error.message);
}
});
}
})();
@ -485,6 +502,11 @@ function EntityForm(props: Props): JSX.Element
<Alert severity="error">{alertContent}</Alert>
</Box>
) : ("")}
{warningContent ? (
<Box mb={3}>
<Alert severity="warning">{warningContent}</Alert>
</Box>
) : ("")}
</Grid>
</Grid>
<Grid container spacing={3}>

View File

@ -29,8 +29,7 @@ import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin";
import {Alert, Collapse, TablePagination} from "@mui/material";
import Box from "@mui/material/Box";
import {Alert, Box, Collapse, TablePagination} from "@mui/material";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import Dialog from "@mui/material/Dialog";
@ -53,7 +52,6 @@ import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnMenuContainer, G
import {GridColumnsPanelProps} from "@mui/x-data-grid/components/panel/GridColumnsPanel";
import {gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector} from "@mui/x-data-grid/hooks/features/columns/gridColumnsSelector";
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
import {finance} from "faker";
import FormData from "form-data";
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";

View File

@ -26,9 +26,8 @@ import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Alert, Typography} from "@mui/material";
import {Alert, Box, Typography} from "@mui/material";
import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import Dialog from "@mui/material/Dialog";
@ -43,7 +42,7 @@ import ListItemIcon from "@mui/material/ListItemIcon";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal";
import React, {useContext, useEffect, useReducer, useState} from "react";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom";
import QContext from "QContext";
import AuditBody from "qqq/components/audits/AuditBody";
@ -86,6 +85,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map<string, boolean>);
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
const [tableMetaData, setTableMetaData] = useState(null);
const [metaData, setMetaData] = useState(null as QInstance);
@ -99,10 +99,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const [actionsMenu, setActionsMenu] = useState(null);
const [notFoundMessage, setNotFoundMessage] = useState(null);
const [successMessage, setSuccessMessage] = useState(null as string);
const [warningMessage, setWarningMessage] = useState(null as string);
const [searchParams] = useSearchParams();
const {setPageHeader} = useContext(QContext);
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [reloadCounter, setReloadCounter] = useState(0);
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
@ -368,8 +369,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
<Box key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
{
section.fieldNames.map((fieldName: string) => (
<Box key={fieldName} flexDirection="row" pr={2}>
<Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)">
<Box display="flex" key={fieldName} flexDirection="row" pb={2} pr={2}>
<Typography sx={{display: "flex"}} variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)">
{tableMetaData.fields.get(fieldName).label}:
<div style={{display: "inline-block", width: 0}}>&nbsp;</div>
</Typography>
@ -426,8 +427,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{
setSuccessMessage(`${tableMetaData.label} successfully ${searchParams.get("createSuccess") ? "created" : "updated"}`);
}
forceUpdate();
if (searchParams.get("warning"))
{
setWarningMessage(searchParams.get("warning"));
}
})();
}
@ -455,6 +458,13 @@ function RecordView({table, launchProcess}: Props): JSX.Element
})();
};
function handleRevealIconClick(fieldName: string)
{
adornmentFieldsMap.set(fieldName, !adornmentFieldsMap.get(fieldName));
setAdornmentFieldsMap(adornmentFieldsMap);
setReloadCounter(reloadCounter + 1);
}
function processClicked(process: QProcessMetaData)
{
openModalProcess(process);
@ -646,6 +656,16 @@ function RecordView({table, launchProcess}: Props): JSX.Element
</Alert>
: ("")
}
{
warningMessage ?
<Alert color="warning" sx={{mb: 3}} onClose={() =>
{
setWarningMessage(null);
}}>
{warningMessage}
</Alert>
: ("")
}
<Grid container spacing={3}>
<Grid item xs={12} lg={3}>

View File

@ -384,4 +384,10 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
.dashboardDropdownMenu #timeframe-form label
{
font-size: 0.875rem;
}
}
.MuiGrid-root > .MuiBox-root > .material-icons-round,
.MuiBox-root > .MuiBox-root > .material-icons-round
{
font-size: 2rem !important;
}

View File

@ -25,10 +25,11 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import "datejs"; // https://github.com/datejs/Datejs
import {Box, Chip, Icon} from "@mui/material";
import {Box, Chip, ClickAwayListener, Icon} from "@mui/material";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import parse from "html-react-parser";
import React, {Fragment, useState} from "react";
import React, {Fragment, useReducer, useState} from "react";
import AceEditor from "react-ace";
import {Link} from "react-router-dom";
import Client from "qqq/utils/qqq/Client";
@ -79,6 +80,7 @@ class ValueUtils
return ValueUtils.getValueForDisplay(field, rawValue, displayValue, usage);
}
/*******************************************************************************
** When you have a field and a value (either just a raw value, or a raw and
** display value), call this method to get a string Element to display.
@ -132,6 +134,11 @@ class ValueUtils
}
}
if (field.hasAdornment(AdornmentType.REVEAL))
{
return (<RevealComponent fieldName={field.name} value={displayValue} usage={usage} />);
}
if (field.hasAdornment(AdornmentType.RENDER_HTML))
{
return (rawValue ? parse(rawValue) : "");
@ -470,6 +477,68 @@ function CodeViewer({name, mode, code}: {name: string; mode: string; code: strin
</>);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// little private component here, for rendering an AceEditor with some buttons/controls/state //
////////////////////////////////////////////////////////////////////////////////////////////////
function RevealComponent({fieldName, value, usage}: {fieldName: string, value: string, usage: string;}): JSX.Element
{
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map<string, boolean>);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [tooltipOpen, setToolTipOpen] = useState(false);
const [displayValue, setDisplayValue] = useState(value.replace(/./g, "*"));
const handleTooltipClose = () =>
{
setToolTipOpen(false);
};
const handleTooltipOpen = () =>
{
setToolTipOpen(true);
};
const handleRevealIconClick = (fieldName: string) =>
{
const displayValue = (adornmentFieldsMap.get(fieldName)) ? value.replace(/./g, "*") : value;
setDisplayValue(displayValue);
adornmentFieldsMap.set(fieldName, !adornmentFieldsMap.get(fieldName));
setAdornmentFieldsMap(adornmentFieldsMap);
forceUpdate();
};
const copyToClipboard = (value: string) =>
{
navigator.clipboard.writeText(value);
setToolTipOpen(true);
};
return (
usage === "view" && adornmentFieldsMap.get(fieldName) === true ? (
<Box>
<Icon onClick={() => handleRevealIconClick(fieldName)} sx={{cursor: "pointer", fontSize: "15px !important", position: "relative", top: "3px", marginRight: "5px"}}>visibility_on</Icon>
{displayValue}
<ClickAwayListener onClickAway={handleTooltipClose}>
<Tooltip
PopperProps={{
disablePortal: true,
}}
onClose={handleTooltipClose}
open={tooltipOpen}
disableFocusListener
disableHoverListener
disableTouchListener
title="Copied To Clipboard"
>
<Icon onClick={() => copyToClipboard(value)} sx={{cursor: "pointer", fontSize: "15px !important", position: "relative", top: "3px", marginLeft: "5px"}}>copy</Icon>
</Tooltip>
</ClickAwayListener>
</Box>
):(
<Box><Icon onClick={() => handleRevealIconClick(fieldName)} sx={{cursor: "pointer", fontSize: "15px !important", position: "relative", top: "3px", marginRight: "5px"}}>visibility_off</Icon>{displayValue}</Box>
)
);
}
export default ValueUtils;