Compare commits

...
7 changed files with 243 additions and 42 deletions

No files matched your search

+4 -1
View File
@@ -60,7 +60,7 @@ import {injectIslandVariables} from "qqq/utils/injectIslandVariables";
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, useMemo, useState,} from "react";
import React, {JSXElementConstructor, Key, ReactElement, ReactNode, useEffect, useMemo, 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";
@@ -691,6 +691,7 @@ export default function App({authenticationMetaData}: Props)
}, [themeMetaData]);
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);
@@ -801,6 +802,7 @@ export default function App({authenticationMetaData}: Props)
appRoutes && (
<QContext.Provider value={{
pageHeader: pageHeader,
pageHeaderRightContent: pageHeaderRightContent,
accentColor: accentColor,
accentColorLight: accentColorLight,
tableMetaData: tableMetaData,
@@ -811,6 +813,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>
+72 -35
View File
@@ -72,7 +72,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();
@@ -127,9 +127,9 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
{
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);
}
@@ -146,7 +146,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} />
@@ -250,7 +250,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";
@@ -516,6 +516,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
/////////////////////////////////////////////////////////////////////////////////////////////
useEffect(() =>
{
console.log("@dk the set action mennu memo is running");
let actionMenu: QMenu = null;
if (metaData && tableMetaData)
{
@@ -728,7 +729,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")
}
@@ -741,13 +742,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");
}
@@ -1124,6 +1125,58 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
const tableNameForId = tableMetaData ? sanitizeId(tableMetaData.name) : "";
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]);
useEffect(() =>
{
setPageHeaderRightContent?.(pageHeaderRecordViewActions);
return () => setPageHeaderRightContent?.(null);
}, [setPageHeaderRightContent, pageHeaderRecordViewActions]);
return (
<BaseLayout>
<Box className="recordView" data-qqq-id={`record-view-${tableNameForId}`}>
@@ -1168,10 +1221,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}>
@@ -1188,23 +1241,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
<Typography variant="h5" mb="0.5rem" data-qqq-id={`record-view-title-${tableNameForId}`}>
{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)}
@@ -1226,7 +1263,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 //
@@ -1269,14 +1306,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... //
/////////////////////////////////////////////////////////////////
}
}