mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
added branding as metadata, fixed problem where query string filter wasn't seeding properly,
This commit is contained in:
34
src/App.tsx
34
src/App.tsx
@ -22,6 +22,7 @@
|
||||
import {useAuth0} from "@auth0/auth0-react";
|
||||
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
||||
import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode";
|
||||
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Icon from "@mui/material/Icon";
|
||||
@ -38,8 +39,7 @@ import Sidenav from "qqq/components/Sidenav";
|
||||
import Configurator from "qqq/components/Temporary/Configurator";
|
||||
import MDAvatar from "qqq/components/Temporary/MDAvatar";
|
||||
import MDBox from "qqq/components/Temporary/MDBox";
|
||||
import theme from "qqq/components/Temporary/Theme"
|
||||
import Logo from "qqq/images/logo-blue.png";
|
||||
import theme from "qqq/components/Temporary/Theme";
|
||||
import AppHome from "qqq/pages/app-home";
|
||||
import CarrierPerformance from "qqq/pages/dashboards/CarrierPerformance";
|
||||
import Overview from "qqq/pages/dashboards/Overview";
|
||||
@ -50,6 +50,8 @@ import EntityView from "qqq/pages/entity-view";
|
||||
import ProcessRun from "qqq/pages/process-run";
|
||||
import QClient from "qqq/utils/QClient";
|
||||
import QProcessUtils from "qqq/utils/QProcessUtils";
|
||||
import BlueIcon from "../public/nf-icon-blue.png";
|
||||
import BlueLogo from "../public/nf-logo-blue.png";
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// define the parts of the nav that are static - before the qqq tables etc get dynamic added //
|
||||
@ -87,6 +89,7 @@ LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
|
||||
|
||||
export default function App()
|
||||
{
|
||||
let logo, icon;
|
||||
const [, setCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
|
||||
const {
|
||||
user, getAccessTokenSilently, getIdTokenClaims, logout, loginWithRedirect,
|
||||
@ -94,6 +97,7 @@ export default function App()
|
||||
const [loadingToken, setLoadingToken] = useState(false);
|
||||
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
||||
const [profileRoutes, setProfileRoutes] = useState({});
|
||||
const [branding, setBranding] = useState({} as QBrandingMetaData);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -122,13 +126,7 @@ export default function App()
|
||||
}, [loadingToken]);
|
||||
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
miniSidenav,
|
||||
direction,
|
||||
layout,
|
||||
openConfigurator,
|
||||
sidenavColor,
|
||||
} = controller;
|
||||
const {miniSidenav, direction, layout, openConfigurator, sidenavColor} = controller;
|
||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||
const {pathname} = useLocation();
|
||||
|
||||
@ -284,9 +282,21 @@ export default function App()
|
||||
try
|
||||
{
|
||||
const metaData = await QClient.getInstance().loadMetaData();
|
||||
setBranding(metaData.branding);
|
||||
|
||||
const favicon = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
|
||||
if(favicon)
|
||||
{
|
||||
favicon.href = metaData.branding.icon;
|
||||
}
|
||||
const appleIcon = document.querySelector("link[rel~='apple-touch-icon']") as HTMLLinkElement;
|
||||
if(appleIcon)
|
||||
{
|
||||
appleIcon.href = metaData.branding.icon
|
||||
}
|
||||
|
||||
let profileRoutes = {};
|
||||
const gravatarBase = "http://www.gravatar.com/avatar/";
|
||||
const gravatarBase = "https://www.gravatar.com/avatar/";
|
||||
const hash = Md5.hashStr(user.email);
|
||||
const profilePicture = `${gravatarBase}${hash}`;
|
||||
profileRoutes = {
|
||||
@ -434,7 +444,9 @@ export default function App()
|
||||
<>
|
||||
<Sidenav
|
||||
color={sidenavColor}
|
||||
brand={Logo}
|
||||
icon={branding.icon}
|
||||
logo={branding.logo}
|
||||
companyName={branding.companyName}
|
||||
routes={sideNavRoutes}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
|
@ -35,32 +35,35 @@ import SidenavItem from "qqq/components/Sidenav/SidenavItem";
|
||||
import SidenavList from "qqq/components/Sidenav/SidenavList";
|
||||
import SidenavRoot from "qqq/components/Sidenav/SidenavRoot";
|
||||
|
||||
interface Props {
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark";
|
||||
brand?: string;
|
||||
brandName?: string;
|
||||
routes: {
|
||||
[key: string]:
|
||||
| ReactNode
|
||||
| string
|
||||
| {
|
||||
interface Props
|
||||
{
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark";
|
||||
icon?: string;
|
||||
logo?: string;
|
||||
companyName?: string;
|
||||
routes: {
|
||||
[key: string]:
|
||||
| ReactNode
|
||||
| string
|
||||
| {
|
||||
[key: string]:
|
||||
| ReactNode
|
||||
| string
|
||||
| {
|
||||
[key: string]: ReactNode | string;
|
||||
}[];
|
||||
[key: string]:
|
||||
| ReactNode
|
||||
| string
|
||||
| {
|
||||
[key: string]:
|
||||
| ReactNode
|
||||
| string
|
||||
| {
|
||||
[key: string]: ReactNode | string;
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
[key: string]: any;
|
||||
}[];
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
function Sidenav({color, icon, logo, companyName, routes, ...rest}: Props): JSX.Element
|
||||
{
|
||||
const [openCollapse, setOpenCollapse] = useState<boolean | string>(false);
|
||||
const [openNestedCollapse, setOpenNestedCollapse] = useState<boolean | string>(false);
|
||||
@ -75,17 +78,17 @@ function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
let textColor:
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "error"
|
||||
| "dark"
|
||||
| "white"
|
||||
| "inherit"
|
||||
| "text"
|
||||
| "light" = "white";
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "error"
|
||||
| "dark"
|
||||
| "white"
|
||||
| "inherit"
|
||||
| "text"
|
||||
| "light" = "white";
|
||||
|
||||
if (transparentSidenav || (whiteSidenav && !darkMode))
|
||||
{
|
||||
@ -115,8 +118,8 @@ function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
}
|
||||
|
||||
/**
|
||||
The event listener that's calling the handleMiniSidenav function when resizing the window.
|
||||
*/
|
||||
The event listener that's calling the handleMiniSidenav function when resizing the window.
|
||||
*/
|
||||
window.addEventListener("resize", handleMiniSidenav);
|
||||
window.onload = () =>
|
||||
{
|
||||
@ -283,7 +286,7 @@ function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
key={key}
|
||||
light={
|
||||
(!darkMode && !whiteSidenav && !transparentSidenav) ||
|
||||
(darkMode && !transparentSidenav && whiteSidenav)
|
||||
(darkMode && !transparentSidenav && whiteSidenav)
|
||||
}
|
||||
/>
|
||||
);
|
||||
@ -314,10 +317,11 @@ function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
<MDBox component={NavLink} to="/" display="flex" alignItems="center">
|
||||
{brand && <MDBox component="img" src={brand} alt="Brand" width="100%" />}
|
||||
{brandName && <MDBox width={!brandName && "100%"} sx={(theme: any) => sidenavLogoLabel(theme, {miniSidenav})}>
|
||||
{!miniSidenav && logo && <MDBox component="img" src={logo} alt="Logo" width="100%" />}
|
||||
{miniSidenav && icon && <MDBox component="img" src={icon} alt="Icon" width="160%" />}
|
||||
{!miniSidenav && companyName && <MDBox width={!companyName && "100%"} sx={(theme: any) => sidenavLogoLabel(theme, {miniSidenav})}>
|
||||
<MDTypography component="h6" variant="button" fontWeight="medium" color={textColor}>
|
||||
{brandName}
|
||||
{companyName}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
}
|
||||
@ -326,7 +330,7 @@ function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
<Divider
|
||||
light={
|
||||
(!darkMode && !whiteSidenav && !transparentSidenav) ||
|
||||
(darkMode && !transparentSidenav && whiteSidenav)
|
||||
(darkMode && !transparentSidenav && whiteSidenav)
|
||||
}
|
||||
/>
|
||||
<List>{renderRoutes}</List>
|
||||
@ -338,8 +342,9 @@ function Sidenav({color, brand, brandName, routes, ...rest}: Props): JSX.Element
|
||||
// Declaring default props for Sidenav
|
||||
Sidenav.defaultProps = {
|
||||
color: "info",
|
||||
brand: "",
|
||||
brandName: "",
|
||||
icon: "",
|
||||
logo: "",
|
||||
companyName: "",
|
||||
};
|
||||
|
||||
export default Sidenav;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.0 KiB |
@ -59,43 +59,46 @@ interface Props
|
||||
** Get the default filter to use on the page - either from query string, or
|
||||
** local storage, or a default (empty).
|
||||
*******************************************************************************/
|
||||
function getDefaultFilter(searchParams: URLSearchParams, filterLocalStorageKey: string): GridFilterModel
|
||||
function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URLSearchParams, filterLocalStorageKey: string): GridFilterModel
|
||||
{
|
||||
if (searchParams.has("filter"))
|
||||
if (tableMetaData.fields !== undefined)
|
||||
{
|
||||
try
|
||||
if (searchParams.has("filter"))
|
||||
{
|
||||
const qQueryFilter = JSON.parse(searchParams.get("filter")) as QQueryFilter;
|
||||
console.log(`Got a filter from the query string: ${JSON.stringify(qQueryFilter)}`);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// translate from a qqq-style filter to one that the grid wants //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
const defaultFilter = {items: []} as GridFilterModel;
|
||||
let id = 1;
|
||||
qQueryFilter.criteria.forEach((criteria) =>
|
||||
try
|
||||
{
|
||||
defaultFilter.items.push({
|
||||
columnField: criteria.fieldName,
|
||||
operatorValue: QFilterUtils.qqqCriteriaOperatorToGrid(criteria.operator),
|
||||
value: QFilterUtils.qqqCriteriaValuesToGrid(criteria.operator, criteria.values),
|
||||
id: id++, // not sure what this id is!!
|
||||
});
|
||||
});
|
||||
const qQueryFilter = JSON.parse(searchParams.get("filter")) as QQueryFilter;
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// translate from a qqq-style filter to one that the grid wants //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
const defaultFilter = {items: []} as GridFilterModel;
|
||||
let id = 1;
|
||||
qQueryFilter.criteria.forEach((criteria) =>
|
||||
{
|
||||
const fieldType = tableMetaData.fields.get(criteria.fieldName).type;
|
||||
defaultFilter.items.push({
|
||||
columnField: criteria.fieldName,
|
||||
operatorValue: QFilterUtils.qqqCriteriaOperatorToGrid(criteria.operator, fieldType),
|
||||
value: QFilterUtils.qqqCriteriaValuesToGrid(criteria.operator, criteria.values),
|
||||
id: id++, // not sure what this id is!!
|
||||
});
|
||||
});
|
||||
|
||||
return (defaultFilter);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.warn("Error parsing filter from query string", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem(filterLocalStorageKey))
|
||||
{
|
||||
const defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey));
|
||||
console.log(`Got default from LS: ${JSON.stringify(defaultFilter)}`);
|
||||
return (defaultFilter);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.warn("Error parsing filter from query string", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem(filterLocalStorageKey))
|
||||
{
|
||||
const defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey));
|
||||
console.log(`Got default from LS: ${JSON.stringify(defaultFilter)}`);
|
||||
return (defaultFilter);
|
||||
}
|
||||
|
||||
return ({items: []});
|
||||
@ -116,7 +119,6 @@ function EntityList({table}: Props): JSX.Element
|
||||
const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
let defaultSort = [] as GridSortItem[];
|
||||
let defaultVisibility = {};
|
||||
const _defaultFilter = getDefaultFilter(searchParams, filterLocalStorageKey);
|
||||
|
||||
if (localStorage.getItem(sortLocalStorageKey))
|
||||
{
|
||||
@ -127,7 +129,7 @@ function EntityList({table}: Props): JSX.Element
|
||||
defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey));
|
||||
}
|
||||
|
||||
const [filterModel, setFilterModel] = useState(_defaultFilter);
|
||||
const [filterModel, setFilterModel] = useState({items: []} as GridFilterModel);
|
||||
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
||||
const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility);
|
||||
|
||||
@ -137,11 +139,11 @@ function EntityList({table}: Props): JSX.Element
|
||||
// when that happens put the default back - it needs to be in state //
|
||||
// const [defaultFilter1] = useState(defaultFilter); //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const [defaultFilter] = useState(_defaultFilter);
|
||||
const [filterChangeHasOccurred, setFilterChangeHasOccurred] = useState(false);
|
||||
const [defaultFilter] = useState({items: []} as GridFilterModel);
|
||||
|
||||
const [tableState, setTableState] = useState("");
|
||||
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
||||
const [defaultFilterLoaded, setDefaultFilterLoaded] = useState(false);
|
||||
const [, setFiltersMenu] = useState(null);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
@ -194,6 +196,19 @@ function EntityList({table}: Props): JSX.Element
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we need the table meta data to look up the default filter (if it comes from query string), //
|
||||
// because we need to know field types to translate qqq filter to material filter //
|
||||
// because we need to know field types to translate qqq filter to material filter //
|
||||
// return here ane wait for the next 'turn' to allow doing the actual query //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (!defaultFilterLoaded)
|
||||
{
|
||||
setDefaultFilterLoaded(true);
|
||||
setFilterModel(getDefaultFilter(tableMetaData, searchParams, filterLocalStorageKey));
|
||||
return;
|
||||
}
|
||||
setTableMetaData(tableMetaData);
|
||||
if (columnSortModel.length === 0)
|
||||
{
|
||||
@ -416,21 +431,13 @@ function EntityList({table}: Props): JSX.Element
|
||||
|
||||
const handleFilterChange = (filterModel: GridFilterModel) =>
|
||||
{
|
||||
if (!filterChangeHasOccurred)
|
||||
setFilterModel(filterModel);
|
||||
if (filterLocalStorageKey)
|
||||
{
|
||||
setFilterModel(defaultFilter);
|
||||
setFilterChangeHasOccurred(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFilterModel(filterModel);
|
||||
if (filterLocalStorageKey)
|
||||
{
|
||||
localStorage.setItem(
|
||||
filterLocalStorageKey,
|
||||
JSON.stringify(filterModel),
|
||||
);
|
||||
}
|
||||
localStorage.setItem(
|
||||
filterLocalStorageKey,
|
||||
JSON.stringify(filterModel),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -64,7 +64,7 @@ class QFilterUtils
|
||||
return QCriteriaOperator.IS_BLANK;
|
||||
case "isNotEmpty":
|
||||
return QCriteriaOperator.IS_NOT_BLANK;
|
||||
case "isAny":
|
||||
case "isAnyOf":
|
||||
return QCriteriaOperator.IN;
|
||||
case "isNone": // todo - verify - not seen in UI
|
||||
return QCriteriaOperator.NOT_IN;
|
||||
@ -118,7 +118,7 @@ class QFilterUtils
|
||||
return ("isNot");
|
||||
}
|
||||
case QCriteriaOperator.IN:
|
||||
return ("isAny");
|
||||
return ("isAnyOf");
|
||||
case QCriteriaOperator.NOT_IN:
|
||||
return ("isNone"); // todo verify - not seen in UI
|
||||
case QCriteriaOperator.STARTS_WITH:
|
||||
|
Reference in New Issue
Block a user