mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
CE-1180 Add new table/key?queryString endpoint, to look up a record by a (unique) key and then view it
This commit is contained in:
@ -49,6 +49,7 @@ import EntityEdit from "qqq/pages/records/edit/RecordEdit";
|
|||||||
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||||
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
||||||
import RecordView from "qqq/pages/records/view/RecordView";
|
import RecordView from "qqq/pages/records/view/RecordView";
|
||||||
|
import RecordViewByUniqueKey from "qqq/pages/records/view/RecordViewByUniqueKey";
|
||||||
import GoogleAnalyticsUtils, {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
import GoogleAnalyticsUtils, {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
||||||
@ -392,6 +393,13 @@ export default function App()
|
|||||||
component: <RecordView table={table} />,
|
component: <RecordView table={table} />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label} View`,
|
||||||
|
key: `${app.name}.view`,
|
||||||
|
route: `${path}/key`,
|
||||||
|
component: <RecordViewByUniqueKey table={table} />,
|
||||||
|
});
|
||||||
|
|
||||||
routeList.push({
|
routeList.push({
|
||||||
name: `${app.label}`,
|
name: `${app.label}`,
|
||||||
key: `${app.name}.edit`,
|
key: `${app.name}.edit`,
|
||||||
|
@ -74,12 +74,14 @@ const qController = Client.getInstance();
|
|||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
table?: QTableMetaData;
|
table?: QTableMetaData;
|
||||||
|
record?: QRecord;
|
||||||
launchProcess?: QProcessMetaData;
|
launchProcess?: QProcessMetaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordView.defaultProps =
|
RecordView.defaultProps =
|
||||||
{
|
{
|
||||||
table: null,
|
table: null,
|
||||||
|
record: null,
|
||||||
launchProcess: null,
|
launchProcess: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -130,7 +132,7 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Record View Screen component.
|
** Record View Screen component.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
function RecordView({table, launchProcess}: Props): JSX.Element
|
function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
|
|
||||||
@ -147,7 +149,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map<string, boolean>);
|
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map<string, boolean>);
|
||||||
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
|
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
|
||||||
const [metaData, setMetaData] = useState(null as QInstance);
|
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 [tableSections, setTableSections] = useState([] as QTableSection[]);
|
||||||
const [t1Section, setT1Section] = useState(null as QTableSection);
|
const [t1Section, setT1Section] = useState(null as QTableSection);
|
||||||
const [t1SectionName, setT1SectionName] = useState(null as string);
|
const [t1SectionName, setT1SectionName] = useState(null as string);
|
||||||
@ -481,7 +483,18 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
let record: QRecord;
|
let record: QRecord;
|
||||||
try
|
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);
|
setRecord(record);
|
||||||
recordAnalytics({category: "tableEvents", action: "view", label: tableMetaData?.label + " / " + record?.recordLabel});
|
recordAnalytics({category: "tableEvents", action: "view", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||||
}
|
}
|
||||||
|
154
src/qqq/pages/records/view/RecordViewByUniqueKey.tsx
Normal file
154
src/qqq/pages/records/view/RecordViewByUniqueKey.tsx
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
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 (<div>Loading...</div>);
|
||||||
|
}
|
||||||
|
else if (record)
|
||||||
|
{
|
||||||
|
return (<RecordView table={table} record={record} />);
|
||||||
|
}
|
||||||
|
else if (errorMessage)
|
||||||
|
{
|
||||||
|
return (<BaseLayout>
|
||||||
|
<Box className="recordView">
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box mb={3}>
|
||||||
|
{
|
||||||
|
<Alert color="error" sx={{mb: 3}}>{errorMessage}</Alert>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</BaseLayout>);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user