Compare commits

...
7 changed files with 283 additions and 83 deletions

No files matched your search

+4 -1
View File
@@ -58,7 +58,7 @@ import GoogleAnalyticsUtils, {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUti
import {detectBasePath, resolveAssetUrl} from "qqq/utils/PathUtils";
import Client from "qqq/utils/qqq/Client";
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
import React, {JSXElementConstructor, Key, ReactElement, ReactNode, useEffect, useState,} from "react";
import {useCookies} from "react-cookie";
import {Navigate, Route, Routes, useLocation, useParams, useSearchParams,} from "react-router-dom";
import {Md5} from "ts-md5/dist/md5";
@@ -650,6 +650,7 @@ export default function App({authenticationMetaData}: Props)
);
const [pageHeader, setPageHeader] = useState("" as string | JSX.Element);
const [pageHeaderRightContent, setPageHeaderRightContent] = useState(null as ReactNode);
const [accentColor, setAccentColor] = useState("#0062FF");
const [accentColorLight, setAccentColorLight] = useState("#C0D6F7");
const [tableMetaData, setTableMetaData] = useState(null);
@@ -760,6 +761,7 @@ export default function App({authenticationMetaData}: Props)
appRoutes && (
<QContext.Provider value={{
pageHeader: pageHeader,
pageHeaderRightContent: pageHeaderRightContent,
accentColor: accentColor,
accentColorLight: accentColorLight,
tableMetaData: tableMetaData,
@@ -770,6 +772,7 @@ export default function App({authenticationMetaData}: Props)
helpHelpActive: helpHelpActive,
userId: userId,
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
setPageHeaderRightContent: (content: ReactNode) => setPageHeaderRightContent(content),
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
setAccentColorLight: (accentColorLight: string) => setAccentColorLight(accentColorLight),
setTableMetaData: (tableMetaData: QTableMetaData) => setTableMetaData(tableMetaData),
+4 -1
View File
@@ -23,12 +23,14 @@ import {QBrandingMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QB
import {QProcessMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
import {createContext} from "react";
import {createContext, ReactNode} from "react";
interface QContext
{
pageHeader: string | JSX.Element;
setPageHeader?: (header: string | JSX.Element) => void;
pageHeaderRightContent?: ReactNode;
setPageHeaderRightContent?: (content: ReactNode) => void;
accentColor: string;
setAccentColor?: (color: string) => void;
@@ -69,6 +71,7 @@ interface QContext
const defaultState = {
pageHeader: "",
pageHeaderRightContent: null as ReactNode,
accentColor: "#0062FF",
accentColorLight: "#C0D6F7",
dotMenuOpen: false,
@@ -39,6 +39,8 @@ public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceM
private Integer queryScreenCopyFullQueryColumnValuesLimit = 100_000;
private MaterialDashboardTableMetaData.RecordViewActionsPlacement recordViewActionsPlacement = MaterialDashboardTableMetaData.RecordViewActionsPlacement.IN_IDENTITY_SECTION;
/*******************************************************************************
@@ -155,4 +157,35 @@ public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceM
}
/*******************************************************************************
** Getter for recordViewActionsPlacement
*******************************************************************************/
public MaterialDashboardTableMetaData.RecordViewActionsPlacement getRecordViewActionsPlacement()
{
return (this.recordViewActionsPlacement);
}
/*******************************************************************************
** Setter for recordViewActionsPlacement
*******************************************************************************/
public void setRecordViewActionsPlacement(MaterialDashboardTableMetaData.RecordViewActionsPlacement recordViewActionsPlacement)
{
this.recordViewActionsPlacement = recordViewActionsPlacement;
}
/*******************************************************************************
** Fluent setter for recordViewActionsPlacement
*******************************************************************************/
public MaterialDashboardInstanceMetaData withRecordViewActionsPlacement(MaterialDashboardTableMetaData.RecordViewActionsPlacement recordViewActionsPlacement)
{
this.recordViewActionsPlacement = recordViewActionsPlacement;
return (this);
}
}
@@ -45,6 +45,12 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
*******************************************************************************/
public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
{
public enum RecordViewActionsPlacement
{
IN_IDENTITY_SECTION,
INLINE_WITH_PAGE_TITLE
}
private static final QLogger LOG = QLogger.getLogger(MaterialDashboardTableMetaData.class);
public static final String TYPE = "materialDashboard";
@@ -52,6 +58,8 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
private List<List<String>> gotoFieldNames;
private List<String> defaultQuickFilterFieldNames;
private List<FieldRule> fieldRules;
private Boolean showRecordSidebar = true;
private RecordViewActionsPlacement recordViewActionsPlacement = RecordViewActionsPlacement.IN_IDENTITY_SECTION;
private QCodeReference onLoadFormAdjuster = null;
@@ -262,6 +270,68 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
/*******************************************************************************
** Getter for showRecordSidebar
*******************************************************************************/
public Boolean getShowRecordSidebar()
{
return (this.showRecordSidebar);
}
/*******************************************************************************
** Setter for showRecordSidebar
*******************************************************************************/
public void setShowRecordSidebar(Boolean showRecordSidebar)
{
this.showRecordSidebar = showRecordSidebar;
}
/*******************************************************************************
** Fluent setter for showRecordSidebar
*******************************************************************************/
public MaterialDashboardTableMetaData withShowRecordSidebar(Boolean showRecordSidebar)
{
this.showRecordSidebar = showRecordSidebar;
return (this);
}
/*******************************************************************************
** Getter for recordViewActionsPlacement
*******************************************************************************/
public RecordViewActionsPlacement getRecordViewActionsPlacement()
{
return (this.recordViewActionsPlacement);
}
/*******************************************************************************
** Setter for recordViewActionsPlacement
*******************************************************************************/
public void setRecordViewActionsPlacement(RecordViewActionsPlacement recordViewActionsPlacement)
{
this.recordViewActionsPlacement = recordViewActionsPlacement;
}
/*******************************************************************************
** Fluent setter for recordViewActionsPlacement
*******************************************************************************/
public MaterialDashboardTableMetaData withRecordViewActionsPlacement(RecordViewActionsPlacement recordViewActionsPlacement)
{
this.recordViewActionsPlacement = recordViewActionsPlacement;
return (this);
}
/*******************************************************************************
** Getter for fieldRules
*******************************************************************************/
@@ -384,6 +454,9 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
clone.onLoadFormAdjuster = onLoadFormAdjuster.clone();
}
clone.showRecordSidebar = showRecordSidebar;
clone.recordViewActionsPlacement = recordViewActionsPlacement;
return abstractClone;
}
+3 -2
View File
@@ -65,7 +65,7 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
const route = useLocation().pathname.split("/").slice(1);
const navigate = useNavigate();
const {pageHeader, setDotMenuOpen} = useContext(QContext);
const {pageHeader, pageHeaderRightContent, setDotMenuOpen} = useContext(QContext);
useEffect(() =>
{
@@ -269,10 +269,11 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
</Toolbar>
{
pageHeader &&
<Box display="flex" justifyContent="space-between">
<Box display="flex" justifyContent="space-between" alignItems="center" flexWrap={{xs: "wrap", md: "nowrap"}} columnGap={2} rowGap={1}>
<MDTypography pb="0.5rem" variant="h3" color={light ? "white" : "dark"}>
{pageHeader}
</MDTypography>
{pageHeaderRightContent && <Box display="flex">{pageHeaderRightContent}</Box>}
</Box>
}
</AppBar>
+112 -76
View File
@@ -70,7 +70,7 @@ import Client from "qqq/utils/qqq/Client";
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
import React, {useContext, useEffect, useMemo, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
const qController = Client.getInstance();
@@ -121,13 +121,13 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
{
return <Grid container lg={12} key={key} display="flex" py={1} pr={2}>
{
fieldNames.map((fieldName: string) =>
fieldNames.map((fieldName: string) =>
{
let [field, tableForField] = tableMetaData ? TableUtils.getFieldAndTable(tableMetaData, fieldName) : fieldMap ? [fieldMap[fieldName], null] : [null, null];
if(field == null)
if (field == null)
{
if(tableMetaData?.virtualFields?.has(fieldName))
if (tableMetaData?.virtualFields?.has(fieldName))
{
field = tableMetaData.virtualFields.get(fieldName);
}
@@ -144,7 +144,7 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default", ...(styleOverrides?.label ?? {})}}>{label}:</Typography>;
if(field.hasAdornment(AdornmentType.WIDGET))
if (field.hasAdornment(AdornmentType.WIDGET))
{
return (<Grid item key={fieldName} lg={gridColumns} flexDirection="column" pr={2}>
<FieldValueAsWidget field={field} record={record} />
@@ -186,7 +186,7 @@ export function getVisibleJoinTables(tableMetaData: QTableMetaData): Set<string>
continue;
}
section.fieldNames.forEach((fieldName) =>
section.fieldNames.forEach((fieldName) =>
{
const [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
if (tableForField && tableForField.name != tableMetaData.name)
@@ -248,7 +248,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
const openActionsMenu = (event: any) => setActionsMenuAnchorElement(event.currentTarget);
const closeActionsMenu = () => setActionsMenuAnchorElement(null);
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen, modalStack, helpHelpActive, recordAnalytics, userId: currentUserId} = useContext(QContext);
const {accentColor, setPageHeader, setPageHeaderRightContent, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen, modalStack, helpHelpActive, recordAnalytics, userId: currentUserId} = useContext(QContext);
const CREATE_CHILD_KEY = "createChild";
@@ -257,7 +257,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
}
const reload = () =>
const reload = () =>
{
setSuccessMessage(null);
setNotFoundMessage(null);
@@ -275,18 +275,18 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
///////////////////////
// Keyboard handling //
///////////////////////
useEffect(() =>
useEffect(() =>
{
if (tableMetaData == null)
{
(async () =>
(async () =>
{
const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData);
})();
}
const down = (e: KeyboardEvent) =>
const down = (e: KeyboardEvent) =>
{
const type = (e.target as any).type;
const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search");
@@ -322,19 +322,19 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
};
document.addEventListener("keydown", down);
return () =>
return () =>
{
document.removeEventListener("keydown", down);
};
}, [dotMenuOpen, keyboardHelpOpen, modalStack, showEditChildForm, showAudit, metaData, location]);
const gotoCreate = () =>
const gotoCreate = () =>
{
const path = `${pathParts.slice(0, -1).join("/")}/create`;
navigate(path);
};
const gotoEdit = () =>
const gotoEdit = () =>
{
const path = `${pathParts.slice(0, -1).join("/")}/${record.values.get(table.primaryKeyField)}/edit`;
navigate(path);
@@ -345,7 +345,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
// 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(() =>
useEffect(() =>
{
try
{
@@ -390,7 +390,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
const parts = hashParts[i].split("=");
if (parts.length > 1 && parts[0] == "launchProcess")
{
(async () =>
(async () =>
{
const processMetaData = await qController.loadProcessMetaData(parts[1]);
setActiveModalProcess(processMetaData);
@@ -405,7 +405,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (pathParts[pathParts.length - 4] === tableName && pathParts[pathParts.length - 2] == CREATE_CHILD_KEY)
{
(async () =>
(async () =>
{
const childTable = await qController.loadTableMetaData(pathParts[pathParts.length - 1]);
const childId: any = null; // todo - for editing a child, not just creating one.
@@ -424,7 +424,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
const parts = hashParts[i].split("=");
if (parts.length > 1 && parts[0] == CREATE_CHILD_KEY)
{
(async () =>
(async () =>
{
const childTable = await qController.loadTableMetaData(parts[1]);
const childId: any = null; // todo - for editing a child, not just creating one.
@@ -512,7 +512,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
/////////////////////////////////////////////////////////////////////////////////////////////
// when the tableMetaData changes, grab the action menu out of it (or build a default one) //
/////////////////////////////////////////////////////////////////////////////////////////////
useEffect(() =>
useEffect(() =>
{
let actionMenu: QMenu = null;
if (metaData && tableMetaData)
@@ -531,7 +531,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
/*******************************************************************************
** get an element (or empty) to use as help content for a section
*******************************************************************************/
const getSectionHelp = (section: QTableSection) =>
const getSectionHelp = (section: QTableSection) =>
{
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
const formattedHelpContent = <HelpContent helpContents={section.helpContents} roles={helpRoles} helpContentKey={`table:${tableName};section:${section.name}`} />;
@@ -591,7 +591,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
{
setAsyncLoadInited(true);
(async () =>
(async () =>
{
/////////////////////////////////////////////////////////////////////
// load the full table meta-data (the one we took in is a partial) //
@@ -667,7 +667,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
}
catch (e)
{
const historyPurge = (path: string) =>
const historyPurge = (path: string) =>
{
try
{
@@ -726,7 +726,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
{
let section = tableSections[i];
if(section.alternatives?.has("RECORD_VIEW"))
if (section.alternatives?.has("RECORD_VIEW"))
{
section = section.alternatives.get("RECORD_VIEW")
}
@@ -739,13 +739,13 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if a section has collapsible metaData that says it IS collapsible, then figure out what the initial open state should be //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(section.collapsible && section.collapsible.isCollapsible)
if (section.collapsible && section.collapsible.isCollapsible)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// try to read a previous value from local storage. if it doesn't exist, use the value in the meta-data //
///////////////////////////////////////////////////////////////////////////////////////////////////////////
const lsValue = localStorage.getItem(makeCollapsibleSectionOpenStateLocalStorageKey(tableMetaData.name, section.name));
if(lsValue != undefined)
if (lsValue != undefined)
{
initialCollapsibleOpenStates[section.name] = (lsValue == "true");
}
@@ -829,33 +829,33 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
})();
}
const handleClickDeleteButton = () =>
const handleClickDeleteButton = () =>
{
setDeleteConfirmationOpen(true);
setIsDeleteSubmitting(false);
};
const handleDeleteConfirmClose = () =>
const handleDeleteConfirmClose = () =>
{
setDeleteConfirmationOpen(false);
};
const handleDelete = (event: { preventDefault: () => void }) =>
const handleDelete = (event: { preventDefault: () => void }) =>
{
setIsDeleteSubmitting(true);
event?.preventDefault();
(async () =>
(async () =>
{
recordAnalytics({category: "tableEvents", action: "delete", label: tableMetaData?.label + " / " + record?.recordLabel});
await qController.delete(tableName, id)
.then(() =>
.then(() =>
{
setIsDeleteSubmitting(false);
const path = pathParts.slice(0, -1).join("/");
navigate(path, {state: {deleteSuccess: true}});
})
.catch((error) =>
.catch((error) =>
{
setIsDeleteSubmitting(false);
setDeleteConfirmationOpen(false);
@@ -890,18 +890,18 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
new: () => gotoCreate(),
copy: () => navigate("copy"),
edit: () => navigate("edit"),
delete: (closeMenu?: () => void) =>
delete: (closeMenu?: () => void) =>
{
closeMenu?.();
handleClickDeleteButton();
},
developerMode: () => navigate("dev"),
audit: (closeMenu?: () => void) =>
audit: (closeMenu?: () => void) =>
{
closeMenu?.();
navigate("#audit");
},
runProcess: (processName: string) =>
runProcess: (processName: string) =>
{
const process = metaData?.processes?.get(processName);
if (process)
@@ -913,7 +913,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
console.log("No process found for name: [" + processName + "]");
}
},
downloadFileFromField: (fieldName: string, closeMenu?: () => void) =>
downloadFileFromField: (fieldName: string, closeMenu?: () => void) =>
{
////////////////////////////////////////////////////////////
// todo can or should this share more with BlobComponent? //
@@ -937,15 +937,15 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
closeMenu?.();
},
getMetaData: () =>
getMetaData: () =>
{
return metaData;
},
getTableProcesses: () =>
getTableProcesses: () =>
{
return tableProcesses;
},
getGenericProcesses: () =>
getGenericProcesses: () =>
{
return getGenericProcesses(metaData);
}
@@ -961,7 +961,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
/*******************************************************************************
** function to open the sharing modal
*******************************************************************************/
const openShareModal = () =>
const openShareModal = () =>
{
setShowShareModal(true);
};
@@ -970,7 +970,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
/*******************************************************************************
** function to close the sharing modal
*******************************************************************************/
const closeShareModal = () =>
const closeShareModal = () =>
{
setShowShareModal(false);
};
@@ -979,7 +979,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
/*******************************************************************************
** render the share button (if allowed for table)
*******************************************************************************/
const renderShareButton = () =>
const renderShareButton = () =>
{
if (tableMetaData && tableMetaData.shareableTableMetaData)
{
@@ -1019,13 +1019,13 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
};
const openModalProcess = (process: QProcessMetaData = null) =>
const openModalProcess = (process: QProcessMetaData = null) =>
{
navigate(process.name);
closeActionsMenu();
};
const closeModalProcess = (event: object, reason: string) =>
const closeModalProcess = (event: object, reason: string) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
@@ -1059,7 +1059,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
setShowEditChildForm(showEditChildForm);
}
const closeEditChildForm = (event: object, reason: string) =>
const closeEditChildForm = (event: object, reason: string) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
@@ -1084,7 +1084,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
setShowEditChildForm(null);
};
const closeAudit = (event: object, reason: string) =>
const closeAudit = (event: object, reason: string) =>
{
if (reason === "backdropClick") // allowing esc here, as it's a non-destructive close || reason === "escapeKeyDown")
{
@@ -1120,6 +1120,58 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
localStorage.setItem(makeCollapsibleSectionOpenStateLocalStorageKey(tableMetaData.name, sectionName), newValue.toString());
}
const materialDashboardTableMetaData = tableMetaData?.supplementalTableMetaData?.get("materialDashboard");
const materialDashboardInstanceMetaData = metaData?.supplementalInstanceMetaData?.get("materialDashboard");
const showRecordSidebar = materialDashboardTableMetaData?.showRecordSidebar !== false;
const recordViewActionsPlacement = materialDashboardInstanceMetaData?.recordViewActionsPlacement ?? materialDashboardTableMetaData?.recordViewActionsPlacement ?? "IN_IDENTITY_SECTION";
const showRecordViewActionsInlineWithPageTitle = recordViewActionsPlacement === "INLINE_WITH_PAGE_TITLE";
const renderRecordViewActions = (boxProps: any = {}) =>
{
return (
<>
<Box display="flex" {...boxProps}>
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
{renderShareButton()}
{metaData && tableMetaData && <RecordViewAdditionalMenus tableMetaData={tableMetaData} record={record} actions={recordViewMenuActions} />}
{metaData && tableMetaData && <QActionsMenuButton isOpen={actionsMenuAnchorElement} onClickHandler={openActionsMenu} />}
</Box>
<Menu
anchorEl={actionsMenuAnchorElement}
anchorOrigin={{vertical: "bottom", horizontal: "right",}}
transformOrigin={{vertical: "top", horizontal: "right",}}
open={Boolean(actionsMenuAnchorElement)}
onClose={closeActionsMenu}
keepMounted
data-qqq-id="record-view-actions-menu"
>
{actionMenu?.items.map((item, index) => (<RecordViewMenuItem key={index} tableMetaData={tableMetaData} menuItem={item} record={record} actions={recordViewMenuActions} closeMenu={closeActionsMenu} itemsShown={itemsShownInActionsMenu} />))}
</Menu>
</>
);
};
const pageHeaderRecordViewActions = useMemo(() =>
{
if (!showRecordViewActionsInlineWithPageTitle)
{
return (null);
}
return (
<Box display="flex" pb="1rem" pr="0.5rem">
{renderRecordViewActions()}
</Box>
);
}, [showRecordViewActionsInlineWithPageTitle, metaData, tableMetaData, record, actionsMenuAnchorElement, actionMenu, itemsShownInActionsMenu]);
useEffect(() =>
{
setPageHeaderRightContent?.(pageHeaderRecordViewActions);
return () => setPageHeaderRightContent?.(null);
}, [setPageHeaderRightContent, pageHeaderRecordViewActions]);
return (
<BaseLayout>
<Box className="recordView">
@@ -1134,7 +1186,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
<Box pb={3}>
{
successMessage ?
<Alert color="success" sx={{mb: 3}} onClose={() =>
<Alert color="success" sx={{mb: 3}} onClose={() =>
{
setSuccessMessage(null);
}}>
@@ -1144,7 +1196,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
}
{
warningMessage ?
<Alert color="warning" sx={{mb: 3}} icon={<Icon>warning</Icon>} onClose={() =>
<Alert color="warning" sx={{mb: 3}} icon={<Icon>warning</Icon>} onClose={() =>
{
setWarningMessage(null);
}}>
@@ -1154,7 +1206,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
}
{
errorMessage ?
<Alert color="error" sx={{mb: 3}} onClose={() =>
<Alert color="error" sx={{mb: 3}} onClose={() =>
{
setErrorMessage(null);
}}>
@@ -1164,10 +1216,10 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
}
<Grid container spacing={3} flexWrap={{md: "nowrap"}}>
<Grid item xs={12} lg={3} className="recordSidebar">
{showRecordSidebar && <Grid item xs={12} lg={3} className="recordSidebar">
<QRecordSidebar tableSections={tableSections} />
</Grid>
<Grid item xs={12} lg={9} className="recordWithSidebar">
</Grid>}
<Grid item xs={12} lg={showRecordSidebar ? 9 : 12} className={showRecordSidebar ? "recordWithSidebar" : ""}>
<Grid container spacing={3}>
<Grid item xs={12} mb={3}>
@@ -1184,23 +1236,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
<Typography variant="h5" mb="0.5rem">
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel || ""}` : ""}
</Typography>
<Box display="flex" ml="auto">
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
{renderShareButton()}
{metaData && tableMetaData && <RecordViewAdditionalMenus tableMetaData={tableMetaData} record={record} actions={recordViewMenuActions} />}
{metaData && tableMetaData && <QActionsMenuButton isOpen={actionsMenuAnchorElement} onClickHandler={openActionsMenu} />}
</Box>
<Menu
anchorEl={actionsMenuAnchorElement}
anchorOrigin={{vertical: "bottom", horizontal: "right",}}
transformOrigin={{vertical: "top", horizontal: "right",}}
open={Boolean(actionsMenuAnchorElement)}
onClose={closeActionsMenu}
keepMounted
data-qqq-id="record-view-actions-menu"
>
{actionMenu?.items.map((item, index) => (<RecordViewMenuItem key={index} tableMetaData={tableMetaData} menuItem={item} record={record} actions={recordViewMenuActions} closeMenu={closeActionsMenu} itemsShown={itemsShownInActionsMenu} />))}
</Menu>
{!showRecordViewActionsInlineWithPageTitle && renderRecordViewActions({ml: "auto"})}
</Box>
</Box>
{t1Section && getSectionHelp(t1Section)}
@@ -1209,7 +1245,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
</Grid>
</Grid>
<Grid container spacing={3} pb={4}>
{nonT1TableSections.length > 0 ? nonT1TableSections.map((section: QTableSection) =>
{nonT1TableSections.length > 0 ? nonT1TableSections.map((section: QTableSection) =>
{
///////////////////////////////////////////////
// render all sections after the T1 section. //
@@ -1222,7 +1258,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
if (section.widgetName)
{
const widgetMetaData = metaData.widgets.get(section.widgetName);
if(section.collapsible)
if (section.collapsible)
{
///////////////////////////////////////////////////////////////////////////////////
// if the section has collapsible meta-data, then put that meta-data in the //
@@ -1265,14 +1301,14 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
</Typography>
{
section.collapsible?.isCollapsible &&
<Box p="0.75rem 0.25rem 0">
<IconButton onClick={() => toggleCollapsibleSectionOpenState(section.name)}>
<Icon fontSize="large">{open ? "expand_less" : "expand_more"}</Icon>
</IconButton>
</Box>
<Box p="0.75rem 0.25rem 0">
<IconButton onClick={() => toggleCollapsibleSectionOpenState(section.name)}>
<Icon fontSize="large">{open ? "expand_less" : "expand_more"}</Icon>
</IconButton>
</Box>
}
</Box>
<Box style={{display: open ? "block" : "none"}}>
<Box style={{display: open ? "block" : "none"}}>
{getSectionHelp(section)}
<Box p={3} pt={0} flexDirection="column">
{sectionFieldElements.get(section.name)}
@@ -40,10 +40,62 @@ import static org.junit.jupiter.api.Assertions.fail;
/*******************************************************************************
** Unit test for MaterialDashboardTableMetaData
** Unit test for MaterialDashboardTableMetaData
*******************************************************************************/
class MaterialDashboardTableMetaDataTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testShowRecordSidebarDefaultsTrue()
{
MaterialDashboardTableMetaData tableMetaData = new MaterialDashboardTableMetaData();
assertThat(tableMetaData.getShowRecordSidebar()).isTrue();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testShowRecordSidebarCloned()
{
MaterialDashboardTableMetaData tableMetaData = new MaterialDashboardTableMetaData().withShowRecordSidebar(false);
MaterialDashboardTableMetaData clone = (MaterialDashboardTableMetaData) tableMetaData.clone();
assertThat(clone.getShowRecordSidebar()).isFalse();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRecordViewActionsPlacementDefaultsToInIdentitySection()
{
MaterialDashboardTableMetaData tableMetaData = new MaterialDashboardTableMetaData();
assertThat(tableMetaData.getRecordViewActionsPlacement()).isEqualTo(MaterialDashboardTableMetaData.RecordViewActionsPlacement.IN_IDENTITY_SECTION);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRecordViewActionsPlacementCloned()
{
MaterialDashboardTableMetaData tableMetaData = new MaterialDashboardTableMetaData()
.withRecordViewActionsPlacement(MaterialDashboardTableMetaData.RecordViewActionsPlacement.INLINE_WITH_PAGE_TITLE);
MaterialDashboardTableMetaData clone = (MaterialDashboardTableMetaData) tableMetaData.clone();
assertThat(clone.getRecordViewActionsPlacement()).isEqualTo(MaterialDashboardTableMetaData.RecordViewActionsPlacement.INLINE_WITH_PAGE_TITLE);
}
/*******************************************************************************
**
@@ -119,7 +171,6 @@ class MaterialDashboardTableMetaDataTest extends BaseTest
//////////////////////////////////////////////////////////////////////////
/*******************************************************************************
** Run a little setup code on a qInstance; then validate it, and assert that it
** failed validation with reasons that match the supplied vararg-reasons (but allow
@@ -209,4 +260,4 @@ class MaterialDashboardTableMetaDataTest extends BaseTest
/////////////////////////////////////////////////////////////////
// todo - end of methods copied from QInstanceValidatorTest... //
/////////////////////////////////////////////////////////////////
}
}