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}
+ }
+
+
+
+
+ );
+ }
+}