/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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 {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid";
import Icon from "@mui/material/Icon";
import ListItemIcon from "@mui/material/ListItemIcon";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal";
import React, {useContext, useEffect, useReducer, useState} from "react";
import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom";
import QContext from "QContext";
import BaseLayout from "qqq/components/BaseLayout";
import DashboardWidgets from "qqq/components/DashboardWidgets";
import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons";
import QRecordSidebar from "qqq/components/QRecordSidebar";
import colors from "qqq/components/Temporary/colors";
import MDAlert from "qqq/components/Temporary/MDAlert";
import MDBox from "qqq/components/Temporary/MDBox";
import MDTypography from "qqq/components/Temporary/MDTypography";
import ProcessRun from "qqq/pages/process-run";
import QClient from "qqq/utils/QClient";
import QProcessUtils from "qqq/utils/QProcessUtils";
import QTableUtils from "qqq/utils/QTableUtils";
import QValueUtils from "qqq/utils/QValueUtils";
const qController = QClient.getInstance();
// Declaring props types for ViewForm
interface Props
{
table?: QTableMetaData;
launchProcess?: QProcessMetaData;
}
EntityView.defaultProps =
{
table: null,
launchProcess: null
};
function EntityView({table, launchProcess}: Props): JSX.Element
{
const {id} = useParams();
const location = useLocation();
const navigate = useNavigate();
const pathParts = location.pathname.split("/");
const tableName = table.name;
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map);
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
const [tableMetaData, setTableMetaData] = useState(null);
const [record, setRecord] = useState(null as QRecord);
const [tableSections, setTableSections] = useState([] as QTableSection[]);
const [t1SectionName, setT1SectionName] = useState(null as string);
const [t1SectionElement, setT1SectionElement] = useState(null as JSX.Element);
const [nonT1TableSections, setNonT1TableSections] = useState([] as QTableSection[]);
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
const [actionsMenu, setActionsMenu] = useState(null);
const [tableWidgets, setTableWidgets] = useState([] as QWidgetMetaData[]);
const [notFoundMessage, setNotFoundMessage] = useState(null);
const [searchParams] = useSearchParams();
const {setPageHeader} = useContext(QContext);
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
const closeActionsMenu = () => setActionsMenu(null);
const reload = () =>
{
setAsyncLoadInited(false);
setTableMetaData(null);
setRecord(null);
setT1SectionElement(null);
setNonT1TableSections([]);
setTableProcesses([]);
setTableSections(null);
setTableWidgets(null);
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// monitor location changes - if we've clicked a link from viewing one record to viewing another, //
// we'll stay in this component, but we'll need to reload all data for the new record. //
// if, however, our url looks like a process, then open that process. //
////////////////////////////////////////////////////////////////////////////////////////////////////
useEffect(() =>
{
try
{
/////////////////////////////////////////////////////////////////
// the path for a process looks like: .../table/id/process //
// so if our tableName is in the -3 index, try to open process //
/////////////////////////////////////////////////////////////////
if (pathParts[pathParts.length - 3] === tableName)
{
const processName = pathParts[pathParts.length - 1];
const processList = allTableProcesses.filter(p => p.name.endsWith(processName));
if (processList.length > 0)
{
setActiveModalProcess(processList[0]);
return;
}
else
{
console.log(`Couldn't find process named ${processName}`);
}
}
}
catch (e)
{
console.log(e);
}
/////////////////////////////////////////////////////////////
// if we didn't open a process, assume we need to (re)load //
/////////////////////////////////////////////////////////////
reload();
setActiveModalProcess(null);
}, [location.pathname]);
if (!asyncLoadInited)
{
setAsyncLoadInited(true);
(async () =>
{
/////////////////////////////////////////////////////////////////////
// load the full table meta-data (the one we took in is a partial) //
/////////////////////////////////////////////////////////////////////
const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData);
//////////////////////////////////////////////////////////////////
// load top-level meta-data (e.g., to find processes for table) //
//////////////////////////////////////////////////////////////////
const metaData = await qController.loadMetaData();
QValueUtils.qInstance = metaData;
const processesForTable = QProcessUtils.getProcessesForTable(metaData, tableName);
setTableProcesses(processesForTable);
setAllTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks)
if (launchingProcess)
{
setLaunchingProcess(null);
setActiveModalProcess(launchingProcess);
}
/////////////////////
// load the record //
/////////////////////
let record: QRecord;
try
{
record = await qController.get(tableName, id);
setRecord(record);
}
catch (e)
{
if (e instanceof QException)
{
if ((e as QException).status === "404")
{
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
return;
}
}
}
setPageHeader(record.recordLabel);
///////////////////////////
// load widget meta data //
///////////////////////////
const matchingWidgets: QWidgetMetaData[] = [];
tableMetaData.widgets && tableMetaData.widgets.forEach((widgetName) =>
{
const widget = metaData.widgets.get(widgetName);
matchingWidgets.push(widget);
});
setTableWidgets(matchingWidgets);
/////////////////////////////////////////////////
// define the sections, e.g., for the left-bar //
/////////////////////////////////////////////////
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
setTableSections(tableSections);
////////////////////////////////////////////////////
// make elements with the values for each section //
////////////////////////////////////////////////////
const sectionFieldElements = new Map();
const nonT1TableSections = [];
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
sectionFieldElements.set(
section.name,
{
section.fieldNames.map((fieldName: string) => (
{tableMetaData.fields.get(fieldName).label}:
{QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)}
))
}
,
);
if (section.tier === "T1")
{
setT1SectionElement(sectionFieldElements.get(section.name));
setT1SectionName(section.name);
}
else
{
nonT1TableSections.push(tableSections[i]);
}
}
setSectionFieldElements(sectionFieldElements);
setNonT1TableSections(nonT1TableSections);
forceUpdate();
})();
}
const handleClickDeleteButton = () =>
{
setDeleteConfirmationOpen(true);
};
const handleDeleteConfirmClose = () =>
{
setDeleteConfirmationOpen(false);
};
const handleDelete = (event: { preventDefault: () => void }) =>
{
event.preventDefault();
(async () =>
{
await qController.delete(tableName, id)
.then(() =>
{
const path = `${pathParts.slice(0, -1).join("/")}?deleteSuccess=true`;
navigate(path);
});
})();
};
function processClicked(process: QProcessMetaData)
{
openModalProcess(process);
}
const renderActionsMenu = (
);
const openModalProcess = (process: QProcessMetaData = null) =>
{
navigate(process.name);
closeActionsMenu();
};
const closeModalProcess = (event: object, reason: string) =>
{
if (reason === "backdropClick")
{
return;
}
//////////////////////////////////////////////////////////////////////////
// when closing a modal process, navigate up to the record being viewed //
//////////////////////////////////////////////////////////////////////////
const newPath = location.pathname.split("/");
newPath.pop();
navigate(newPath.join("/"));
};
return (
{
notFoundMessage
?
{notFoundMessage}
:
{
(searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? (
{tableMetaData?.label}
{" "}
successfully
{" "}
{searchParams.get("createSuccess") ? "created" : "updated"}
) : ("")
}
{tableMetaData?.iconName}
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
{renderActionsMenu}
{t1SectionElement ? ({t1SectionElement}) : null}
{tableMetaData && tableMetaData.widgets && record && (
)}
{nonT1TableSections.length > 0 ? nonT1TableSections.map(({
iconName, label, name, fieldNames, tier,
}: any) => (
{label}
{sectionFieldElements.get(name)}
)) : null}
{/* Delete confirmation Dialog */}
{
activeModalProcess &&
closeModalProcess(event, reason)}>