diff --git a/src/qqq/components/misc/GotoRecordDialog.tsx b/src/qqq/components/misc/GotoRecordDialog.tsx new file mode 100644 index 0000000..b0ea04d --- /dev/null +++ b/src/qqq/components/misc/GotoRecordDialog.tsx @@ -0,0 +1,276 @@ +/* + * 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 {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; +import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; +import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Grid from "@mui/material/Grid"; +import Icon from "@mui/material/Icon"; +import TextField from "@mui/material/TextField"; +import React, {useState} from "react"; +import {useNavigate} from "react-router-dom"; +import {QCancelButton} from "qqq/components/buttons/DefaultButtons"; +import MDButton from "qqq/components/legacy/MDButton"; +import Client from "qqq/utils/qqq/Client"; + +interface Props +{ + isOpen: boolean; + metaData: QInstance; + tableMetaData: QTableMetaData; + closeHandler: () => void; + mayClose: boolean; +} + +GotoRecordDialog.defaultProps = { + mayClose: true +}; + +const qController = Client.getInstance(); + +function hasGotoFieldNames(tableMetaData: QTableMetaData): boolean +{ + const mdbMetaData = tableMetaData?.supplementalTableMetaData?.get("materialDashboard"); + if(mdbMetaData && mdbMetaData.gotoFieldNames) + { + return (true); + } + + return (false); +} + +function GotoRecordDialog(props: Props): JSX.Element +{ + const fields: QFieldMetaData[] = [] + + let pkey = props?.tableMetaData?.fields.get(props?.tableMetaData?.primaryKeyField); + let addedPkey = false; + const mdbMetaData = props?.tableMetaData?.supplementalTableMetaData?.get("materialDashboard"); + if(mdbMetaData) + { + if(mdbMetaData.gotoFieldNames) + { + for(let i = 0; i + { + const rs = {} as {[field: string]: string}; + fields.forEach((field) => rs[field.name] = ""); + return (rs); + } + + const [error, setError] = useState(""); + const [values, setValues] = useState(makeInitialValues()); + const navigate = useNavigate(); + + const handleChange = (fieldName: string, newValue: string) => + { + values[fieldName] = newValue; + setValues(JSON.parse(JSON.stringify(values))); + } + + const close = () => + { + setError(""); + setValues(makeInitialValues()); + props.closeHandler(); + } + + const keyPressed = (e: React.KeyboardEvent) => + { + // @ts-ignore + const targetId: string = e.target?.id; + + if(e.key == "Esc") + { + if(props.mayClose) + { + close(); + } + } + else if(e.key == "Enter" && targetId?.startsWith("gotoInput-")) + { + const index = targetId?.replaceAll("gotoInput-", ""); + document.getElementById("gotoButton-" + index).click(); + } + } + + const closeRequested = () => + { + if(props.mayClose) + { + close(); + } + } + + const goClicked = async (fieldName: string) => + { + setError(""); + const filter = new QQueryFilter([new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [values[fieldName]])], null, "AND", null, 10); + const queryResult = await qController.query(props.tableMetaData.name, filter) + if(queryResult.length == 0) + { + setError("Record not found."); + setTimeout(() => setError(""), 3000); + } + else if(queryResult.length == 1) + { + navigate(`${props.metaData.getTablePathByName(props.tableMetaData.name)}/${queryResult[0].values.get(props.tableMetaData.primaryKeyField)}`); + close(); + } + else + { + setError("More than 1 record found..."); + setTimeout(() => setError(""), 3000); + } + } + + if(props.tableMetaData) + { + if (fields.length == 0 && !error) + { + setError("This table is not configured for this feature.") + } + } + + return ( + closeRequested} onKeyPress={(e) => keyPressed(e)} fullWidth maxWidth={"sm"}> + Go To... + + { + fields.map((field, index) => + ( + + + {field.label} + + + handleChange(field.name, e.target.value)} + value={values[field.name]} + sx={{width: "100%"}} + onFocus={event => event.target.select()} + /> + + + goClicked(field.name)} fullWidth startIcon={double_arrow} disabled={`${values[field.name]}`.length == 0}> + Go + + + + )) + } + { + error && + + {error} + + } + + { + //////////////////////////////////////////////////////////////////////////////////////// + // show the cancel button if allowed - else we need a little spacing, so an empty box // + //////////////////////////////////////////////////////////////////////////////////////// + props.mayClose ? + + + + :   + } + + ) +} + +interface GotoRecordButtonProps +{ + metaData: QInstance; + tableMetaData: QTableMetaData; + autoOpen?: boolean; + buttonVisible?: boolean; + mayClose?: boolean; +} + +GotoRecordButton.defaultProps = { + autoOpen: false, + buttonVisible: true, + mayClose: true +}; + +export function GotoRecordButton(props: GotoRecordButtonProps): JSX.Element +{ + const [gotoIsOpen, setGotoIsOpen] = useState(props.autoOpen) + + function openGoto() + { + setGotoIsOpen(true); + } + + function closeGoto() + { + setGotoIsOpen(false); + } + + + return ( + + { + props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && + } + + + ); +} + +export default GotoRecordDialog; diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index aad82a4..354cc69 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -57,6 +57,7 @@ import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; import QContext from "QContext"; import {QActionsMenuButton, QCancelButton, QCreateNewButton, QSaveButton} from "qqq/components/buttons/DefaultButtons"; import MenuButton from "qqq/components/buttons/MenuButton"; +import {GotoRecordButton} from "qqq/components/misc/GotoRecordDialog"; import SavedFilters from "qqq/components/misc/SavedFilters"; import {CustomColumnsPanel} from "qqq/components/query/CustomColumnsPanel"; import {CustomFilterPanel} from "qqq/components/query/CustomFilterPanel"; @@ -644,6 +645,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element }); } + if(!tableMetaData.capabilities.has(Capability.TABLE_QUERY)) + { + console.log("Cannot update table - it does not have QUERY capability."); + return; + } + setLastFetchedQFilterJSON(JSON.stringify(qFilter)); qController.query(tableName, qFilter, queryJoins).then((results) => { @@ -1803,6 +1810,19 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element ); } + ///////////////////////////////////////////////////////////////////////////////////////////// + // if the table doesn't allow QUERY, but does allow GET, don't render a data grid - // + // instead, try to just render a Goto Record button, in auto-open, and may-not-close modes // + ///////////////////////////////////////////////////////////////////////////////////////////// + if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_QUERY) && tableMetaData.capabilities.has(Capability.TABLE_GET)) + { + return ( + + + + ); + } + return (
@@ -1857,6 +1877,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element } + {renderActionsMenu} diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index 9861535..a978507 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -48,6 +48,7 @@ import QContext from "QContext"; import AuditBody from "qqq/components/audits/AuditBody"; import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton} from "qqq/components/buttons/DefaultButtons"; import EntityForm from "qqq/components/forms/EntityForm"; +import {GotoRecordButton} from "qqq/components/misc/GotoRecordDialog"; import QRecordSidebar from "qqq/components/misc/RecordSidebar"; import DashboardWidgets from "qqq/components/widgets/DashboardWidgets"; import BaseLayout from "qqq/layouts/BaseLayout"; @@ -739,7 +740,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel || ""}` : ""} - + + + + {renderActionsMenu}