update audit to include child details

This commit is contained in:
2023-02-08 20:11:42 -06:00
parent 6fa1bd1f85
commit 54e3b82ad5
5 changed files with 69 additions and 23 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ yalc.lock
# production # production
/build /build
/lib /lib
/target
# misc # misc
.DS_Store .DS_Store

View File

@ -6,7 +6,7 @@
"@auth0/auth0-react": "1.10.2", "@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1", "@emotion/react": "11.7.1",
"@emotion/styled": "11.6.0", "@emotion/styled": "11.6.0",
"@kingsrook/qqq-frontend-core": "1.0.49", "@kingsrook/qqq-frontend-core": "1.0.50",
"@mui/icons-material": "5.4.1", "@mui/icons-material": "5.4.1",
"@mui/material": "5.11.1", "@mui/material": "5.11.1",
"@mui/styles": "5.11.1", "@mui/styles": "5.11.1",

View File

@ -26,6 +26,7 @@ import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QC
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy"; import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Icon from "@mui/material/Icon/Icon"; import Icon from "@mui/material/Icon/Icon";
@ -52,12 +53,12 @@ const qController = Client.getInstance();
function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element
{ {
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
const [audits, setAudits] = useState([] as QRecord[]); const [audits, setAudits] = useState([] as QRecord[]);
const [total, setTotal] = useState(null as number); const [total, setTotal] = useState(null as number);
const [limit, setLimit] = useState(1000); const [limit, setLimit] = useState(1000);
const [statusString, setStatusString] = useState("Loading audits..."); const [statusString, setStatusString] = useState("Loading audits...");
const [auditsByDate, setAuditsByDate] = useState([] as QRecord[][]); const [auditsByDate, setAuditsByDate] = useState([] as QRecord[][]);
const [auditDetailMap, setAuditDetailMap] = useState(null as Map<number, string[]>)
const [sortDirection, setSortDirection] = useState(localStorage.getItem("audit.sortDirection") === "true"); const [sortDirection, setSortDirection] = useState(localStorage.getItem("audit.sortDirection") === "true");
useEffect(() => useEffect(() =>
@ -72,7 +73,8 @@ function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element
new QFilterCriteria("recordId", QCriteriaOperator.EQUALS, [recordId]), new QFilterCriteria("recordId", QCriteriaOperator.EQUALS, [recordId]),
], [ ], [
new QFilterOrderBy("timestamp", sortDirection), new QFilterOrderBy("timestamp", sortDirection),
new QFilterOrderBy("id", sortDirection) new QFilterOrderBy("id", sortDirection),
new QFilterOrderBy("auditDetail.id", true)
]); ]);
/////////////////////////////// ///////////////////////////////
@ -81,7 +83,7 @@ function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element
let audits = [] as QRecord[] let audits = [] as QRecord[]
try try
{ {
audits = await qController.query("audit", filter, limit, 0); audits = await qController.query("audit", filter, limit, 0, [new QueryJoin("auditDetail", true, "LEFT")]);
setAudits(audits); setAudits(audits);
} }
catch(e) catch(e)
@ -105,8 +107,37 @@ function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element
setTotal(count); setTotal(count);
} }
setInitialLoadComplete(true); //////////////////////////////////////////////////////////////////////////////////////////////////////////
// group the audits by auditId (e.g., this is a list that joined audit & auditDetail, so un-flatten it) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
const unflattenedAudits: QRecord[] = []
const detailMap: Map<number, string[]> = new Map();
for (let i = 0; i < audits.length; i++)
{
let id = audits[i].values.get("id");
if(i == 0 || unflattenedAudits[unflattenedAudits.length-1].values.get("id") != id)
{
unflattenedAudits.push(audits[i]);
}
let auditDetail = audits[i].values.get("auditDetail.message");
if(auditDetail)
{
if(!detailMap.has(id))
{
detailMap.set(id, []);
}
detailMap.get(id).push(auditDetail)
}
}
audits = unflattenedAudits;
setAuditDetailMap(detailMap);
console.log(detailMap);
//////////////////////////////
// group the audits by date //
//////////////////////////////
const auditsByDate = []; const auditsByDate = [];
let thisDatesAudits = null as QRecord[]; let thisDatesAudits = null as QRecord[];
let lastDate = null; let lastDate = null;
@ -240,6 +271,16 @@ function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element
<Box fontSize="1rem"> <Box fontSize="1rem">
{audit.values.get("message")} {audit.values.get("message")}
</Box> </Box>
<Box fontSize="0.875rem">
<ul style={{"marginLeft": "1rem"}}>
{
auditDetailMap.get(audit.values.get("id"))?.map((detail, key) =>
{
return (<li key={key}>{detail}</li>);
})
}
</ul>
</Box>
</Box> </Box>
</Box> </Box>
); );

View File

@ -83,6 +83,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{ {
const tableName = table.name; const tableName = table.name;
const [ searchParams ] = useSearchParams(); const [ searchParams ] = useSearchParams();
const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(searchParams.has("deleteSuccess"));
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
@ -1154,9 +1156,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
"" ""
)} )}
{ {
(tableLabel && searchParams.get("deleteSuccess")) ? ( (tableLabel && showSuccessfullyDeletedAlert) ? (
<Alert color="success" onClose={() => <Alert color="success" sx={{mb: 3}} onClose={() =>
{}}> {
setShowSuccessfullyDeletedAlert(false);
}}>
{`${tableLabel} successfully deleted`} {`${tableLabel} successfully deleted`}
</Alert> </Alert>
) : null ) : null

View File

@ -47,7 +47,7 @@ import React, {useContext, useEffect, useReducer, useState} from "react";
import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom"; import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import AuditBody from "qqq/components/audits/AuditBody"; import AuditBody from "qqq/components/audits/AuditBody";
import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton, QSaveButton} from "qqq/components/buttons/DefaultButtons"; import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton} from "qqq/components/buttons/DefaultButtons";
import EntityForm from "qqq/components/forms/EntityForm"; import EntityForm from "qqq/components/forms/EntityForm";
import colors from "qqq/components/legacy/colors"; import colors from "qqq/components/legacy/colors";
import QRecordSidebar from "qqq/components/misc/RecordSidebar"; import QRecordSidebar from "qqq/components/misc/RecordSidebar";
@ -164,12 +164,12 @@ function RecordView({table, launchProcess}: Props): JSX.Element
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
for (let i = 0; i < hashParts.length; i++) for (let i = 0; i < hashParts.length; i++)
{ {
const parts = hashParts[i].split("=") const parts = hashParts[i].split("=");
if (parts.length > 1 && parts[0] == "launchProcess") if (parts.length > 1 && parts[0] == "launchProcess")
{ {
(async () => (async () =>
{ {
const processMetaData = await qController.loadProcessMetaData(parts[1]) const processMetaData = await qController.loadProcessMetaData(parts[1]);
setActiveModalProcess(processMetaData); setActiveModalProcess(processMetaData);
})(); })();
return; return;
@ -184,7 +184,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{ {
(async () => (async () =>
{ {
const childTable = await qController.loadTableMetaData(pathParts[pathParts.length - 1]) const childTable = await qController.loadTableMetaData(pathParts[pathParts.length - 1]);
const childId: any = null; // todo - for editing a child, not just creating one. const childId: any = null; // todo - for editing a child, not just creating one.
openEditChildForm(childTable, childId, null, null); // todo - defaults & disableds openEditChildForm(childTable, childId, null, null); // todo - defaults & disableds
})(); })();
@ -198,12 +198,12 @@ function RecordView({table, launchProcess}: Props): JSX.Element
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
for (let i = 0; i < hashParts.length; i++) for (let i = 0; i < hashParts.length; i++)
{ {
const parts = hashParts[i].split("=") const parts = hashParts[i].split("=");
if (parts.length > 1 && parts[0] == "createChild") if (parts.length > 1 && parts[0] == "createChild")
{ {
(async () => (async () =>
{ {
const childTable = await qController.loadTableMetaData(parts[1]) const childTable = await qController.loadTableMetaData(parts[1]);
const childId: any = null; // todo - for editing a child, not just creating one. const childId: any = null; // todo - for editing a child, not just creating one.
openEditChildForm(childTable, childId, null, null); // todo - defaults & disableds openEditChildForm(childTable, childId, null, null); // todo - defaults & disableds
})(); })();
@ -292,20 +292,20 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{ {
console.error("Error pushing history: " + e); console.error("Error pushing history: " + e);
} }
} };
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "404") if ((e as QException).status === "404")
{ {
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`); setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
historyPurge(location.pathname) historyPurge(location.pathname);
return; return;
} }
else if ((e as QException).status === "403") else if ((e as QException).status === "403")
{ {
setNotFoundMessage(`You do not have permission to view ${tableMetaData.label} records`); setNotFoundMessage(`You do not have permission to view ${tableMetaData.label} records`);
historyPurge(location.pathname) historyPurge(location.pathname);
return; return;
} }
} }
@ -315,7 +315,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
try try
{ {
HistoryUtils.push({label: `${tableMetaData?.label}: ${record.recordLabel}`, path: location.pathname, iconName: table.iconName}) HistoryUtils.push({label: `${tableMetaData?.label}: ${record.recordLabel}`, path: location.pathname, iconName: table.iconName});
} }
catch(e) catch(e)
{ {
@ -441,7 +441,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const handleDelete = (event: { preventDefault: () => void }) => const handleDelete = (event: { preventDefault: () => void }) =>
{ {
event.preventDefault(); event?.preventDefault();
(async () => (async () =>
{ {
await qController.delete(tableName, id) await qController.delete(tableName, id)
@ -509,7 +509,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
<MenuItem onClick={() => <MenuItem onClick={() =>
{ {
setActionsMenu(null); setActionsMenu(null);
navigate("#audit") navigate("#audit");
}}> }}>
<ListItemIcon><Icon>checklist</Icon></ListItemIcon> <ListItemIcon><Icon>checklist</Icon></ListItemIcon>
Audit Audit
@ -585,7 +585,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const closeAudit = (event: object, reason: string) => const closeAudit = (event: object, reason: string) =>
{ {
if (reason === "backdropClick" || reason === "escapeKeyDown") if (reason === "backdropClick") // allowing esc here, as it's a non-destructive close || reason === "escapeKeyDown")
{ {
return; return;
} }
@ -623,7 +623,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
successMessage ? successMessage ?
<Alert color="success" sx={{mb: 3}} onClose={() => <Alert color="success" sx={{mb: 3}} onClose={() =>
{ {
setSuccessMessage(null) setSuccessMessage(null);
}}> }}>
{successMessage} {successMessage}
</Alert> </Alert>
@ -638,7 +638,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12} mb={3}> <Grid item xs={12} mb={3}>
<Card id={t1SectionName} sx={{scrollMarginTop: "100px"}}> <Card id={t1SectionName} sx={{scrollMarginTop: "100px", minHeight: "88px"}}>
<Box display="flex" p={3} pb={1}> <Box display="flex" p={3} pb={1}>
<Box mr={1.5}> <Box mr={1.5}>
<Avatar sx={{bgcolor: colors.info.main}}> <Avatar sx={{bgcolor: colors.info.main}}>