diff --git a/src/App.tsx b/src/App.tsx index 7d2501d..fd4c6b5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -49,6 +49,7 @@ import EntityEdit from "qqq/pages/records/edit/RecordEdit"; import RecordQuery from "qqq/pages/records/query/RecordQuery"; import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView"; import RecordView from "qqq/pages/records/view/RecordView"; +import RecordViewByUniqueKey from "qqq/pages/records/view/RecordViewByUniqueKey"; import GoogleAnalyticsUtils, {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils"; import Client from "qqq/utils/qqq/Client"; import ProcessUtils from "qqq/utils/qqq/ProcessUtils"; @@ -392,6 +393,13 @@ export default function App() component: , }); + routeList.push({ + name: `${app.label} View`, + key: `${app.name}.view`, + route: `${path}/key`, + component: , + }); + routeList.push({ name: `${app.label}`, key: `${app.name}.edit`, diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index e34bc28..dd300c0 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -74,12 +74,14 @@ const qController = Client.getInstance(); interface Props { table?: QTableMetaData; + record?: QRecord; launchProcess?: QProcessMetaData; } RecordView.defaultProps = { table: null, + record: null, launchProcess: null, }; @@ -130,7 +132,7 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe /******************************************************************************* ** Record View Screen component. *******************************************************************************/ -function RecordView({table, launchProcess}: Props): JSX.Element +function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.Element { const {id} = useParams(); @@ -147,7 +149,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map); const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false); const [metaData, setMetaData] = useState(null as QInstance); - const [record, setRecord] = useState(null as QRecord); + const [record, setRecord] = useState(overrideRecord ?? null as QRecord); const [tableSections, setTableSections] = useState([] as QTableSection[]); const [t1Section, setT1Section] = useState(null as QTableSection); const [t1SectionName, setT1SectionName] = useState(null as string); @@ -481,7 +483,18 @@ function RecordView({table, launchProcess}: Props): JSX.Element let record: QRecord; try { - record = await qController.get(tableName, id, tableVariant, null, queryJoins); + //////////////////////////////////////////////////////////////////////////// + // if the component took in a record object, then we don't need to GET it // + //////////////////////////////////////////////////////////////////////////// + if(overrideRecord) + { + record = overrideRecord; + } + else + { + record = await qController.get(tableName, id, tableVariant, null, queryJoins); + } + setRecord(record); recordAnalytics({category: "tableEvents", action: "view", label: tableMetaData?.label + " / " + record?.recordLabel}); } diff --git a/src/qqq/pages/records/view/RecordViewByUniqueKey.tsx b/src/qqq/pages/records/view/RecordViewByUniqueKey.tsx new file mode 100644 index 0000000..ecfe7ff --- /dev/null +++ b/src/qqq/pages/records/view/RecordViewByUniqueKey.tsx @@ -0,0 +1,154 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +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 {Alert, Box} from "@mui/material"; +import Grid from "@mui/material/Grid"; +import BaseLayout from "qqq/layouts/BaseLayout"; +import RecordView from "qqq/pages/records/view/RecordView"; +import Client from "qqq/utils/qqq/Client"; +import React, {useEffect, useState} from "react"; +import {useSearchParams} from "react-router-dom"; + +interface RecordViewByUniqueKeyProps +{ + table: QTableMetaData; +} + +RecordViewByUniqueKey.defaultProps = {}; + +const qController = Client.getInstance(); + +/*************************************************************************** + ** Wrapper around RecordView, that reads a unique key from the query string, + ** looks for a record matching that key, and shows that record. + ***************************************************************************/ +export default function RecordViewByUniqueKey({table}: RecordViewByUniqueKeyProps): JSX.Element +{ + const tableName = table.name; + + const [asyncLoadInited, setAsyncLoadInited] = useState(false); + const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); + const [doneLoading, setDoneLoading] = useState(false); + const [record, setRecord] = useState(null as QRecord); + const [errorMessage, setErrorMessage] = useState(null as string); + + const [queryParams] = useSearchParams(); + + if (!asyncLoadInited) + { + setAsyncLoadInited(true); + + (async () => + { + const tableMetaData = await qController.loadTableMetaData(tableName); + setTableMetaData(tableMetaData); + + const criteria: QFilterCriteria[] = []; + for (let [name, value] of queryParams.entries()) + { + criteria.push(new QFilterCriteria(name, QCriteriaOperator.EQUALS, [value])); + if(!tableMetaData.fields.has(name)) + { + setErrorMessage(`Query-string parameter [${name}] is not a defined field on the ${tableMetaData.label} table.`); + setDoneLoading(true); + return; + } + } + + const filter = new QQueryFilter(criteria, null, null, "AND", 0, 2); + qController.query(tableName, filter) + .then((queryResult) => + { + setDoneLoading(true); + if (queryResult.length == 1) + { + setRecord(queryResult[0]); + } + else if (queryResult.length == 0) + { + setErrorMessage(`No ${tableMetaData.label} record was found matching the given values.`); + } + else if (queryResult.length > 1) + { + setErrorMessage(`More than one ${tableMetaData.label} record was found matching the given values.`); + } + }) + .catch((error) => + { + setDoneLoading(true); + console.log(error); + if (error && error.message) + { + setErrorMessage(error.message); + } + else if (error && error.response && error.response.data && error.response.data.error) + { + setErrorMessage(error.response.data.error); + } + else + { + setErrorMessage("Unexpected error running query"); + } + }); + })(); + } + + useEffect(() => + { + if (asyncLoadInited) + { + setAsyncLoadInited(false); + setDoneLoading(false); + setRecord(null); + } + }, [queryParams]); + + if (!doneLoading) + { + return (
Loading...
); + } + else if (record) + { + return (); + } + else if (errorMessage) + { + return ( + + + + + { + {errorMessage} + } + + + + + ); + } +}