diff --git a/src/App.tsx b/src/App.tsx index ad58422..7eb9507 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -377,23 +377,57 @@ export default function App({authenticationMetaData}: Props) }); }); - const runRecordScriptProcess = metaData.processes.get("runRecordScript"); - if (runRecordScriptProcess) + const materialDashboardInstanceMetaData = metaData.supplementalInstanceMetaData?.get("materialDashboard"); + if (materialDashboardInstanceMetaData) { - const process = runRecordScriptProcess; - routeList.push({ - name: process.label, - key: process.name, - route: `${path}/${process.name}`, - component: , - }); + const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens; + if (processNamesToAddToAllQueryAndViewScreens) + { + for (let processName of processNamesToAddToAllQueryAndViewScreens) + { + const process = metaData.processes.get(processName); + if (process) + { + routeList.push({ + name: process.label, + key: process.name, + route: `${path}/${process.name}`, + component: , + }); - routeList.push({ - name: process.label, - key: `${app.name}/${process.name}`, - route: `${path}/:id/${process.name}`, - component: , - }); + routeList.push({ + name: process.label, + key: `${app.name}/${process.name}`, + route: `${path}/:id/${process.name}`, + component: , + }); + } + } + } + } + else + { + //////////////// + // deprecated // + //////////////// + const runRecordScriptProcess = metaData.processes.get("runRecordScript"); + if (runRecordScriptProcess) + { + const process = runRecordScriptProcess; + routeList.push({ + name: process.label, + key: process.name, + route: `${path}/${process.name}`, + component: , + }); + + routeList.push({ + name: process.label, + key: `${app.name}/${process.name}`, + route: `${path}/:id/${process.name}`, + component: , + }); + } } const reportsForTable = ProcessUtils.getReportsForTable(metaData, table.name, true); diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java new file mode 100644 index 0000000..8c4906d --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java @@ -0,0 +1,113 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.model.metadata; + + +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData; + + +/******************************************************************************* + ** table-level meta-data for this module (handled as QSupplementalTableMetaData) + *******************************************************************************/ +public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceMetaData +{ + public static final String TYPE = "materialDashboard"; + + private List processNamesToAddToAllQueryAndViewScreens = new ArrayList<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String getName() + { + return (TYPE); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static MaterialDashboardInstanceMetaData ofOrWithNew(QInstance qInstance) + { + MaterialDashboardInstanceMetaData supplementalMetaData = (MaterialDashboardInstanceMetaData) qInstance.getSupplementalMetaData(TYPE); + if(supplementalMetaData == null) + { + supplementalMetaData = new MaterialDashboardInstanceMetaData(); + qInstance.withSupplementalMetaData(supplementalMetaData); + } + + return (supplementalMetaData); + } + + + + /******************************************************************************* + ** Getter for processNamesToAddToAllQueryAndViewScreens + *******************************************************************************/ + public List getProcessNamesToAddToAllQueryAndViewScreens() + { + return (this.processNamesToAddToAllQueryAndViewScreens); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addProcessNameToAddToAllQueryAndViewScreens(String processNamesToAddToAllQueryAndViewScreens) + { + if(this.processNamesToAddToAllQueryAndViewScreens == null) + { + this.processNamesToAddToAllQueryAndViewScreens = new ArrayList<>(); + } + this.processNamesToAddToAllQueryAndViewScreens.add(processNamesToAddToAllQueryAndViewScreens); + } + + + + /******************************************************************************* + ** Setter for processNamesToAddToAllQueryAndViewScreens + *******************************************************************************/ + public void setProcessNamesToAddToAllQueryAndViewScreens(List processNamesToAddToAllQueryAndViewScreens) + { + this.processNamesToAddToAllQueryAndViewScreens = processNamesToAddToAllQueryAndViewScreens; + } + + + + /******************************************************************************* + ** Fluent setter for processNamesToAddToAllQueryAndViewScreens + *******************************************************************************/ + public MaterialDashboardInstanceMetaData withProcessNamesToAddToAllQueryAndViewScreens(List processNamesToAddToAllQueryAndViewScreens) + { + this.processNamesToAddToAllQueryAndViewScreens = processNamesToAddToAllQueryAndViewScreens; + return (this); + } + +} diff --git a/src/qqq/components/query/QueryScreenActionMenu.tsx b/src/qqq/components/query/QueryScreenActionMenu.tsx index 73147a4..af1e5b7 100644 --- a/src/qqq/components/query/QueryScreenActionMenu.tsx +++ b/src/qqq/components/query/QueryScreenActionMenu.tsx @@ -29,9 +29,9 @@ 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 {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons"; import React, {useState} from "react"; import {useNavigate} from "react-router-dom"; -import {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons"; interface QueryScreenActionMenuProps { @@ -44,40 +44,35 @@ interface QueryScreenActionMenuProps processClicked: (process: QProcessMetaData) => void; } -QueryScreenActionMenu.defaultProps = { -}; +QueryScreenActionMenu.defaultProps = {}; export default function QueryScreenActionMenu({metaData, tableMetaData, tableProcesses, bulkLoadClicked, bulkEditClicked, bulkDeleteClicked, processClicked}: QueryScreenActionMenuProps): JSX.Element { - const [anchorElement, setAnchorElement] = useState(null) + const [anchorElement, setAnchorElement] = useState(null); const navigate = useNavigate(); const openActionsMenu = (event: any) => { setAnchorElement(event.currentTarget); - } + }; const closeActionsMenu = () => { setAnchorElement(null); - } - - const pushDividerIfNeeded = (menuItems: JSX.Element[]) => - { - if (menuItems.length > 0) - { - menuItems.push(); - } }; const runSomething = (handler: () => void) => { closeActionsMenu(); handler(); - } + }; const menuItems: JSX.Element[] = []; + + ////////////////////////////////////////////////////// + // start with bulk actions, if user has permissions // + ////////////////////////////////////////////////////// if (tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission) { menuItems.push( runSomething(bulkLoadClicked)}>library_addBulk Load); @@ -91,19 +86,7 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro menuItems.push( runSomething(bulkDeleteClicked)}>deleteBulk Delete); } - const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); - if (runRecordScriptProcess) - { - const process = runRecordScriptProcess; - menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); - } - - menuItems.push( navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}>codeDeveloper Mode); - - if (tableProcesses && tableProcesses.length) - { - pushDividerIfNeeded(menuItems); - } + menuItems.push(); tableProcesses.sort((a, b) => a.label.localeCompare(b.label)); tableProcesses.map((process) => @@ -111,11 +94,62 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); }); + menuItems.push(); + + //////////////////////////////////////////// + // add processes that apply to all tables // + //////////////////////////////////////////// + const materialDashboardInstanceMetaData = metaData.supplementalInstanceMetaData?.get("materialDashboard"); + if (materialDashboardInstanceMetaData) + { + const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens; + if (processNamesToAddToAllQueryAndViewScreens) + { + for (let processName of processNamesToAddToAllQueryAndViewScreens) + { + const process = metaData?.processes.get(processName); + if (process) + { + menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); + } + } + } + } + else + { + ////////////////////////////////////// + // deprecated in favor of the above // + ////////////////////////////////////// + const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); + if (runRecordScriptProcess) + { + const process = runRecordScriptProcess; + menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); + } + } + + //////////////////////////////////////// + // todo - any conditions around this? // + //////////////////////////////////////// + menuItems.push( navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}>codeDeveloper Mode); + if (menuItems.length === 0) { menuItems.push(blockNo actions available); } + //////////////////////////////////////////////////////////////////////////////// + // remove any duplicated dividers, and any dividers in the first or last slot // + //////////////////////////////////////////////////////////////////////////////// + for (let i = 0; i < menuItems.length; i++) + { + if (menuItems[i].type == Divider && (i == 0 || (i > 0 && menuItems[i - 1].type == Divider) || i == menuItems.length - 1)) + { + menuItems.splice(i, 1); + i--; + } + } + return ( <> @@ -130,5 +164,5 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro {menuItems} - ) + ); } diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index 3aeec37..f52e9de 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -440,6 +440,34 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. }; + /*************************************************************************** + ** + ***************************************************************************/ + function getGenericProcesses(metaData: QInstance) + { + const genericProcesses: QProcessMetaData[] = []; + const materialDashboardInstanceMetaData = metaData?.supplementalInstanceMetaData?.get("materialDashboard"); + if (materialDashboardInstanceMetaData) + { + const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens; + if (processNamesToAddToAllQueryAndViewScreens) + { + for (let processName of processNamesToAddToAllQueryAndViewScreens) + { + genericProcesses.push(metaData?.processes?.get(processName)); + } + } + } + else + { + //////////////// + // deprecated // + //////////////// + genericProcesses.push(metaData?.processes.get("runRecordScript")); + } + return genericProcesses; + } + if (!asyncLoadInited) { setAsyncLoadInited(true); @@ -472,11 +500,16 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. // load processes that the routing needs to respect // ////////////////////////////////////////////////////// const allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true); // these include hidden ones (e.g., to find the bulks) - const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); - if (runRecordScriptProcess) + const genericProcesses = getGenericProcesses(metaData); + + for (let genericProcess of genericProcesses) { - allTableProcesses.unshift(runRecordScriptProcess); + if (genericProcess) + { + allTableProcesses.unshift(genericProcess); + } } + setAllTableProcesses(allTableProcesses); if (launchingProcess) @@ -726,7 +759,6 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. let hasEditOrDelete = (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission); - const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); const renderActionsMenu = ( 0 || hasEditOrDelete) && } { - runRecordScriptProcess && - processClicked(runRecordScriptProcess)}> - {runRecordScriptProcess.iconName ?? "arrow_forward"} - {runRecordScriptProcess.label} - + getGenericProcesses(metaData).map((process) => + ( + process && + processClicked(process)}> + {process.iconName ?? "arrow_forward"} + {process.label} + + )) } navigate("dev")}> code @@ -969,7 +1004,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. { notFoundMessage ? - {notFoundMessage} + warning}>{notFoundMessage} : {