diff --git a/package.json b/package.json index a258814..7b9f81a 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.61", + "@kingsrook/qqq-frontend-core": "1.0.62", "@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 471735c..6e1a748 100644 --- a/src/qqq/components/forms/EntityForm.tsx +++ b/src/qqq/components/forms/EntityForm.tsx @@ -41,6 +41,7 @@ import QDynamicForm from "qqq/components/forms/DynamicForm"; import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils"; import MDTypography from "qqq/components/legacy/MDTypography"; import QRecordSidebar from "qqq/components/misc/RecordSidebar"; +import HtmlUtils from "qqq/utils/HtmlUtils"; import Client from "qqq/utils/qqq/Client"; import TableUtils from "qqq/utils/qqq/TableUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; @@ -82,7 +83,6 @@ function EntityForm(props: Props): JSX.Element const [warningContent, setWarningContent] = useState(""); const [asyncLoadInited, setAsyncLoadInited] = useState(false); - const [formValues, setFormValues] = useState({} as { [key: string]: string }); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [record, setRecord] = useState(null as QRecord); const [tableSections, setTableSections] = useState(null as QTableSection[]); @@ -185,8 +185,6 @@ function EntityForm(props: Props): JSX.Element initialValues[key] = record.values.get(key); }); - //? safe to delete? setFormValues(formValues); - if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE)) { setNotAllowedError("Records may not be edited in this table"); @@ -416,8 +414,8 @@ function EntityForm(props: Props): JSX.Element } else { - const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true`; - navigate(path); + const path = location.pathname.replace(/\/edit$/, ""); + navigate(path, {state: {updateSuccess: true}}); } }) .catch((error) => @@ -427,12 +425,13 @@ function EntityForm(props: Props): JSX.Element if(error.message.toLowerCase().startsWith("warning")) { - const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true&warning=${encodeURIComponent(error.message)}`; - navigate(path); + const path = location.pathname.replace(/\/edit$/, ""); + navigate(path, {state: {updateSuccess: true, warning: error.message}}); } else { setAlertContent(error.message); + HtmlUtils.autoScroll(0); } }); } @@ -448,20 +447,22 @@ function EntityForm(props: Props): JSX.Element } else { - const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true`; - navigate(path); + const path = location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField)); + navigate(path, {state: {createSuccess: true}}); } }) .catch((error) => { if(error.message.toLowerCase().startsWith("warning")) { - const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true&warning=${encodeURIComponent(error.message)}`; + const path = location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField)); navigate(path); + navigate(path, {state: {createSuccess: true, warning: error.message}}); } else { setAlertContent(error.message); + HtmlUtils.autoScroll(0); } }); } @@ -499,12 +500,12 @@ function EntityForm(props: Props): JSX.Element {alertContent ? ( - {alertContent} + setAlertContent(null)}>{alertContent} ) : ("")} {warningContent ? ( - {warningContent} + setWarningContent(null)}>{warningContent} ) : ("")} diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index a28910b..4af7be4 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -97,12 +97,31 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element const tableName = table.name; const [searchParams] = useSearchParams(); - const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(searchParams.has("deleteSuccess")); + const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(false); + const [warningAlert, setWarningAlert] = useState(null as string); const [successAlert, setSuccessAlert] = useState(null as string); const location = useLocation(); const navigate = useNavigate(); + if(location.state) + { + let state: any = location.state; + if(state["deleteSuccess"]) + { + setShowSuccessfullyDeletedAlert(true); + delete state["deleteSuccess"]; + } + + if(state["warning"]) + { + setWarningAlert(state["warning"]); + delete state["warning"]; + } + + window.history.replaceState(state, ""); + } + const pathParts = location.pathname.replace(/\/+$/, "").split("/"); //////////////////////////////////////////// @@ -1815,23 +1834,20 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element )} { (tableLabel && showSuccessfullyDeletedAlert) ? ( - - { - setShowSuccessfullyDeletedAlert(false); - }}> - {`${tableLabel} successfully deleted`} - + setShowSuccessfullyDeletedAlert(false)}>{`${tableLabel} successfully deleted`} ) : null } { (successAlert) ? ( - - { - setSuccessAlert(null); - }}> - {successAlert} - + setSuccessAlert(null)}>{successAlert} + + ) : null + } + { + (warningAlert) ? ( + + setWarningAlert(null)}>{warningAlert} ) : null } diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index aac96eb..546648b 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -43,7 +43,7 @@ import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Modal from "@mui/material/Modal"; import React, {useContext, useEffect, useState} from "react"; -import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom"; +import {useLocation, useNavigate, useParams} from "react-router-dom"; import QContext from "QContext"; import AuditBody from "qqq/components/audits/AuditBody"; import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton} from "qqq/components/buttons/DefaultButtons"; @@ -53,6 +53,7 @@ import DashboardWidgets from "qqq/components/widgets/DashboardWidgets"; import BaseLayout from "qqq/layouts/BaseLayout"; import ProcessRun from "qqq/pages/processes/ProcessRun"; import HistoryUtils from "qqq/utils/HistoryUtils"; +import HtmlUtils from "qqq/utils/HtmlUtils"; import Client from "qqq/utils/qqq/Client"; import ProcessUtils from "qqq/utils/qqq/ProcessUtils"; import TableUtils from "qqq/utils/qqq/TableUtils"; @@ -97,10 +98,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]); const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]); const [actionsMenu, setActionsMenu] = useState(null); - const [notFoundMessage, setNotFoundMessage] = useState(null); + const [notFoundMessage, setNotFoundMessage] = useState(null as string); + const [errorMessage, setErrorMessage] = useState(null as string) 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 [reloadCounter, setReloadCounter] = useState(0); @@ -116,6 +117,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element { setSuccessMessage(null); setNotFoundMessage(null); + setErrorMessage(null); setAsyncLoadInited(false); setTableMetaData(null); setRecord(null); @@ -423,14 +425,26 @@ function RecordView({table, launchProcess}: Props): JSX.Element setSectionFieldElements(sectionFieldElements); setNonT1TableSections(nonT1TableSections); - if (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) + if(location.state) { - setSuccessMessage(`${tableMetaData.label} successfully ${searchParams.get("createSuccess") ? "created" : "updated"}`); - } - if (searchParams.get("warning")) - { - setWarningMessage(searchParams.get("warning")); + let state: any = location.state; + if (state["createSuccess"] || state["updateSuccess"]) + { + setSuccessMessage(`${tableMetaData.label} successfully ${state["createSuccess"] ? "created" : "updated"}`); + } + + if (state["warning"]) + { + setWarningMessage(state["warning"]); + } + + delete state["createSuccess"] + delete state["updateSuccess"] + delete state["warning"] + + window.history.replaceState(state, ""); } + })(); } @@ -452,8 +466,25 @@ function RecordView({table, launchProcess}: Props): JSX.Element await qController.delete(tableName, id) .then(() => { - const path = `${pathParts.slice(0, -1).join("/")}?deleteSuccess=true`; - navigate(path); + const path = pathParts.slice(0, -1).join("/"); + navigate(path, {state: {deleteSuccess: true}}); + }) + .catch((error) => + { + setDeleteConfirmationOpen(false); + console.log("Caught:"); + console.log(error); + + if(error.message.toLowerCase().startsWith("warning")) + { + const path = pathParts.slice(0, -1).join("/"); + navigate(path, {state: {deleteSuccess: true, warning: error.message}}); + } + else + { + setErrorMessage(error.message); + HtmlUtils.autoScroll(0); + } }); })(); }; @@ -648,7 +679,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element { successMessage ? - + { setSuccessMessage(null); }}> @@ -666,6 +697,16 @@ function RecordView({table, launchProcess}: Props): JSX.Element : ("") } + { + errorMessage ? + + { + setErrorMessage(null); + }}> + {errorMessage} + + : ("") + } diff --git a/src/qqq/utils/HtmlUtils.ts b/src/qqq/utils/HtmlUtils.ts new file mode 100644 index 0000000..8b4887e --- /dev/null +++ b/src/qqq/utils/HtmlUtils.ts @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +export default class HtmlUtils +{ + + /******************************************************************************* + ** Since our pages are set (w/ style on the HTML element) to smooth scroll, + ** if you ever want to do an "auto" scroll (e.g., instant, not smooth), you can + ** call this method, which will remove that style, and then put it back. + *******************************************************************************/ + static autoScroll = (top: number, left: number = 0) => + { + let htmlElement = document.querySelector("html"); + const initialScrollBehavior = htmlElement.style.scrollBehavior; + htmlElement.style.scrollBehavior = "auto"; + setTimeout(() => + { + window.scrollTo({top: top, left: left, behavior: "auto"}); + htmlElement.style.scrollBehavior = initialScrollBehavior; + }); + }; + +} \ No newline at end of file