diff --git a/package.json b/package.json index 3d122a7..17d12a6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@auth0/auth0-react": "1.10.2", "@emotion/react": "11.7.1", "@emotion/styled": "11.6.0", - "@kingsrook/qqq-frontend-core": "1.0.57", + "@kingsrook/qqq-frontend-core": "1.0.61", "@mui/icons-material": "5.4.1", "@mui/material": "5.11.1", "@mui/styles": "5.11.1", diff --git a/src/qqq/components/forms/EntityForm.tsx b/src/qqq/components/forms/EntityForm.tsx index b27a9f3..471735c 100644 --- a/src/qqq/components/forms/EntityForm.tsx +++ b/src/qqq/components/forms/EntityForm.tsx @@ -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 {alertContent} ) : ("")} + {warningContent ? ( + + {warningContent} + + ) : ("")} diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index 78ff247..09d23cd 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -27,8 +27,7 @@ import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJo import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; -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"; diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index 7652795..90c1b0c 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -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); + const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map); 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 { section.fieldNames.map((fieldName: string) => ( - - + + {tableMetaData.fields.get(fieldName).label}:
 
@@ -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 : ("") } + { + warningMessage ? + + { + setWarningMessage(null); + }}> + {warningMessage} + + : ("") + } diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index 13393ff..3f8024f 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -385,4 +385,10 @@ input[type="search"]::-webkit-search-results-decoration { display: none; } .dashboardDropdownMenu #timeframe-form label { font-size: 0.875rem; -} \ No newline at end of file +} + +.MuiGrid-root > .MuiBox-root > .material-icons-round, +.MuiBox-root > .MuiBox-root > .material-icons-round +{ + font-size: 2rem !important; +} diff --git a/src/qqq/utils/qqq/ValueUtils.tsx b/src/qqq/utils/qqq/ValueUtils.tsx index f7fa71d..6300ae9 100644 --- a/src/qqq/utils/qqq/ValueUtils.tsx +++ b/src/qqq/utils/qqq/ValueUtils.tsx @@ -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"; @@ -77,6 +78,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. @@ -130,6 +132,11 @@ class ValueUtils } } + if (field.hasAdornment(AdornmentType.REVEAL)) + { + return (); + } + if (field.hasAdornment(AdornmentType.RENDER_HTML)) { return (rawValue ? parse(rawValue) : ""); @@ -468,6 +475,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); + const [, forceUpdate] = useReducer((x) => x + 1, 0); + const [tooltipOpen, setToolTipOpen] = React.useState(false); + const [displayValue, setDisplayValue] = React.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 ? ( + + handleRevealIconClick(fieldName)} sx={{cursor: "pointer", fontSize: "15px !important", position: "relative", top: "3px", marginRight: "5px"}}>visibility_on + {displayValue} + + + copyToClipboard(value)} sx={{cursor: "pointer", fontSize: "15px !important", position: "relative", top: "3px", marginLeft: "5px"}}>copy + + + + ):( + handleRevealIconClick(fieldName)} sx={{cursor: "pointer", fontSize: "15px !important", position: "relative", top: "3px", marginRight: "5px"}}>visibility_off{displayValue} + ) + ); +} + export default ValueUtils;