Merge branch 'dev_environment' of gitlab.com:brandx/bxt-app into dev_environment

This commit is contained in:
csawatzky 2025-04-24 12:55:01 -06:00
commit 7c5332662d
7 changed files with 90 additions and 34 deletions

View file

@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"start-local": "VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=http://localhost:50052/v1 VITE_APP_WS_URL=ws://localhost:50052/v1/live VITE_APP_BILLING_URL=http://localhost:50053/v1 VITE_APP_RECLUSE_URL=http://localhost:50054/v1 VITE_APP_GITLAB_URL=http://localhost:50055/v1 vite", "start-local": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=http://localhost:50052/v1 VITE_APP_WS_URL=ws://localhost:50052/v1/live VITE_APP_BILLING_URL=http://localhost:50053/v1 VITE_APP_RECLUSE_URL=http://localhost:50054/v1 VITE_APP_GITLAB_URL=http://localhost:50055/v1 vite",
"start-dev": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 vite", "start-dev": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 vite",
"start-staging": "VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca vite", "start-staging": "VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",

View file

@ -11,7 +11,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import { Replay } from "@mui/icons-material"; import { Replay } from "@mui/icons-material";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { ReactNode } from "react"; import { ReactNode, useEffect, useRef } from "react";
import { useLocation } from "react-router"; import { useLocation } from "react-router";
import { Link as RouterLink } from "react-router-dom"; import { Link as RouterLink } from "react-router-dom";
import { or } from "utils/types"; import { or } from "utils/types";
@ -47,6 +47,7 @@ interface Props {
componentName?: string; componentName?: string;
groupName?: string; groupName?: string;
binName?: string; binName?: string;
teamName?: string;
reportTarget?: string; reportTarget?: string;
loading?: boolean; loading?: boolean;
reload?: () => void; reload?: () => void;
@ -114,6 +115,11 @@ export default function SmartBreadcrumb(props: Props) {
return or(regexMatch[1], "0").toString(); return or(regexMatch[1], "0").toString();
}; };
const teamKey = (): string => {
const regexMatch = or(location.pathname.match(/\/teams\/([^/]+)/), [])
return or(regexMatch[1], "0").toString();
};
const reportIDShort = (): string => { const reportIDShort = (): string => {
return reportID().split("-")[0]; return reportID().split("-")[0];
}; };
@ -220,6 +226,10 @@ export default function SmartBreadcrumb(props: Props) {
return "/teams"; return "/teams";
} }
const teamPath = (): string => {
return "/teams" + "/" + teamKey();
}
const getBreadcrumbMap = (): { [key: string]: string } => { const getBreadcrumbMap = (): { [key: string]: string } => {
const { groupName, deviceName, componentName, reportTarget, binName } = props; const { groupName, deviceName, componentName, reportTarget, binName } = props;
const deviceLabel: string = deviceName ? deviceName : "Device " + deviceID(); const deviceLabel: string = deviceName ? deviceName : "Device " + deviceID();
@ -227,6 +237,13 @@ export default function SmartBreadcrumb(props: Props) {
const binLabel: string = binName ?? "Bin " + binID(); const binLabel: string = binName ?? "Bin " + binID();
const mineLabel: string = "Mine Site"; const mineLabel: string = "Mine Site";
const jobsiteLabel: string = "Job Site"; const jobsiteLabel: string = "Job Site";
let lastTeamName = props.teamName;
if (props.teamName) {
localStorage.setItem('lastTeamName-'+teamKey(), props.teamName);
} else {
lastTeamName = or(localStorage.getItem('lastTeamName-'+teamKey()), undefined);
}
return { return {
[dashboardPath()]: "Dashboard", [dashboardPath()]: "Dashboard",
@ -250,7 +267,8 @@ export default function SmartBreadcrumb(props: Props) {
[usersPath()]: "Users", [usersPath()]: "Users",
[binsPath()]: "Bins", [binsPath()]: "Bins",
[binPath()]: binLabel, [binPath()]: binLabel,
[teamsPath()]: "Teams" [teamsPath()]: "Teams",
[teamPath()]: lastTeamName ? lastTeamName : "Team " + teamKey(),
}; };
}; };

View file

@ -97,33 +97,37 @@ export default function BottomNavigator(props: Props) {
} }
}; };
const getType = () => {
return theme.palette.mode === "light" ? "dark" : "light"
}
const authenticatedNavigation = () => { const authenticatedNavigation = () => {
return ( return (
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}> <BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
{isAdaptive && ( {isAdaptive && (
<BottomNavigationAction label="Farm" icon={<FieldsIcon type="light" />} value="fields" /> <BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="fields" />
)} )}
{isAdaptive && ( {isAdaptive && (
<BottomNavigationAction label="Bins" icon={<BinsIcon type="light" />} value="bins" /> <BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
)} )}
{isAdCon && ( {isAdCon && (
<BottomNavigationAction <BottomNavigationAction
label="Site Map" label="Site Map"
icon={<FieldsIcon type="light" />} icon={<FieldsIcon type={getType()} />}
value="constructionsiteMap" value="constructionsiteMap"
/> />
)} )}
{isOmni && ( {isOmni && (
<BottomNavigationAction <BottomNavigationAction
label="Map" label="Map"
icon={<AirportMapIcon type="light" />} icon={<AirportMapIcon type={getType()} />}
value="aviationMap" value="aviationMap"
/> />
)} )}
{isOmni && ( {isOmni && (
<BottomNavigationAction <BottomNavigationAction
label="Terminals" label="Terminals"
icon={<PlaneIcon type="light" />} icon={<PlaneIcon type={getType()} />}
value="terminals" value="terminals"
/> />
)} )}
@ -132,11 +136,11 @@ export default function BottomNavigator(props: Props) {
label="Devices" label="Devices"
icon={ icon={
isAdaptive ? ( isAdaptive ? (
<BindaptIcon type="light" /> <BindaptIcon type={getType()} />
) : isAdCon ? ( ) : isAdCon ? (
<NexusSTIcon type="light" /> <NexusSTIcon type={getType()} />
) : isOmni ? ( ) : isOmni ? (
<OmniAirDeviceIcon type="light" /> <OmniAirDeviceIcon type={getType()} />
) : ( ) : (
<DevicesIcon /> <DevicesIcon />
) )
@ -146,7 +150,7 @@ export default function BottomNavigator(props: Props) {
{isAdCon && ( {isAdCon && (
<BottomNavigationAction <BottomNavigationAction
label="Sites" label="Sites"
icon={<JobsiteIcon type="light" />} icon={<JobsiteIcon type={getType()} />}
value="jobsites" value="jobsites"
/> />
)} )}
@ -183,6 +187,7 @@ export default function BottomNavigator(props: Props) {
bottom={0} bottom={0}
width="100vw" width="100vw"
zIndex={theme.zIndex.appBar} zIndex={theme.zIndex.appBar}
sx={{ background: theme.palette.background.paper }}
style={{ style={{
overflow: overflow:
window.location.origin.includes("staging") || window.location.origin.includes("localhost") window.location.origin.includes("staging") || window.location.origin.includes("localhost")

View file

@ -120,10 +120,16 @@ export default function Devices() {
setGroups(newGroups); setGroups(newGroups);
}; };
const getGroup = () => {
let group = groups[parseInt(tab)]
return group
}
useEffect(() => { useEffect(() => {
setGroupPermissions([]) setGroupPermissions([])
if (tab === "all") return if (tab === "all") return
groupAPI.getGroupPermissions(parseInt(tab)).then(resp => { groupAPI.getGroupPermissions(getGroup().id()).then(resp => {
console.log(resp)
setGroupPermissions(resp.data.permissions) setGroupPermissions(resp.data.permissions)
}) })
}, [tab]) }, [tab])
@ -375,11 +381,6 @@ export default function Devices() {
) )
} }
const getGroup = () => {
let group = groups[parseInt(tab)]
return group
}
const mobile = (row: Device) => { const mobile = (row: Device) => {
return ( return (
<Box sx={{ margin: 2 }}> <Box sx={{ margin: 2 }}>

View file

@ -2,6 +2,7 @@ import { Avatar, AvatarGroup, Box, Button, Card, Divider, Grid2, List, ListItemB
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import LoadingScreen from "app/LoadingScreen"; import LoadingScreen from "app/LoadingScreen";
import Chat from "chat/Chat"; import Chat from "chat/Chat";
import SmartBreadcrumb from "common/SmartBreadcrumb";
// import SmartBreadcrumb from "common/SmartBreadcrumb"; // import SmartBreadcrumb from "common/SmartBreadcrumb";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
@ -162,7 +163,8 @@ export default function TeamPage() {
<Grid2 container direction={"column"} spacing={isMobile ? 0 : 0.5} > <Grid2 container direction={"column"} spacing={isMobile ? 0 : 0.5} >
<Grid2 > <Grid2 >
<Typography variant={isMobile ? "h5" : "h4"} className={classes.ellipsis} > <Typography variant={isMobile ? "h5" : "h4"} className={classes.ellipsis} >
{team.name()} {/* {team.name()} */}
<SmartBreadcrumb paddingBottom={1} teamName={team.name()} />
{/* <SmartBreadcrumb /> */} {/* <SmartBreadcrumb /> */}
</Typography> </Typography>
</Grid2> </Grid2>

View file

@ -42,6 +42,20 @@ export interface IGroupAPIContext {
updateGroupPermissions: (id: number, users: User[]) => Promise<any>; updateGroupPermissions: (id: number, users: User[]) => Promise<any>;
} }
function buildQueryString(params: Record<string, string | string[] | number | null | undefined>): string {
// Filter out null, undefined, or empty string values
const validParams = Object.entries(params)
.filter(([_, value]) => value != null && value !== '')
.map(([key, value]) => `${key}=${value}`);
if (validParams.length === 0) {
return '';
}
// Join parameters with '&' and prepend '?'
return `?${validParams.join('&')}`;
}
export const GroupAPIContext = createContext<IGroupAPIContext>({} as IGroupAPIContext); export const GroupAPIContext = createContext<IGroupAPIContext>({} as IGroupAPIContext);
interface Props {} interface Props {}
@ -53,8 +67,16 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const [{ as }] = useGlobalState(); const [{ as }] = useGlobalState();
const addGroup = (group: pond.GroupSettings) => { const addGroup = (group: pond.GroupSettings) => {
let url = pondURL("/groups") // let url = pondURL("/groups")
if (as) url = pondURL("/groups?as=" + as) // let keys = getContextKeys()
// let types = getContextTypes()
// if (as) url = pondURL("/groups?as=" + as)
const params = {
keys: getContextKeys(),
types: getContextTypes(),
as: getContextTypes().includes("team") ? undefined : as
}
let url = pondURL(`/groups${buildQueryString(params)}`)
return new Promise<AxiosResponse>((resolve, reject) => { return new Promise<AxiosResponse>((resolve, reject) => {
post(url, group).then(resp => { post(url, group).then(resp => {
return resolve(resp) return resolve(resp)
@ -157,19 +179,25 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
keys?: string[], keys?: string[],
types?: string[], types?: string[],
) => { ) => {
keys = keys ? keys : getContextKeys(); // keys = keys ? keys : getContextKeys();
types = types? types : getContextTypes(); // types = types? types : getContextTypes();
let url = "/groupPermissions/" + id // let url = "/groupPermissions/" + id
if (as) { // if (as) {
url = url + "?as=" + as + // url = url + "?as=" + as +
+ // +
(keys ? "?keys=" + keys.toString() : "") + // (keys ? "?keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "") // (types ? "&types=" + types.toString() : "")
} else { // } else {
url = url + // url = url +
(keys ? "?keys=" + keys.toString() : "") + // (keys ? "?keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "") // (types ? "&types=" + types.toString() : "")
// }
const params = {
keys: getContextKeys(),
types: getContextTypes(),
as: getContextTypes().includes("team") ? undefined : as,
} }
let url = `/groupPermissions/${id}${buildQueryString(params)}`
return new Promise<AxiosResponse<pond.GetPermissionsResponse>>((resolve, reject) => { return new Promise<AxiosResponse<pond.GetPermissionsResponse>>((resolve, reject) => {
get<pond.GetPermissionsResponse>(pondURL(url)).then(resp => { get<pond.GetPermissionsResponse>(pondURL(url)).then(resp => {
resp.data = pond.GetPermissionsResponse.fromObject(resp.data) resp.data = pond.GetPermissionsResponse.fromObject(resp.data)

View file

@ -1,4 +1,4 @@
import { Avatar, Box, Button, Checkbox, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, PaletteColor, Theme, Tooltip, useTheme } from "@mui/material"; import { Avatar, Box, Button, Checkbox, Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, PaletteColor, Theme, Tooltip, useTheme } from "@mui/material";
import { useGlobalState } from "../providers/StateContainer"; import { useGlobalState } from "../providers/StateContainer";
import { getSecondaryColour, getSignatureAccentColour } from "../services/whiteLabel"; import { getSecondaryColour, getSignatureAccentColour } from "../services/whiteLabel";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
@ -270,6 +270,7 @@ export default function UserMenu() {
<ListItemText primary="View Site as Team" /> <ListItemText primary="View Site as Team" />
</MenuItem> </MenuItem>
)} )}
<Divider />
{hasAdmin && ( {hasAdmin && (
<Tooltip title="Access Object"> <Tooltip title="Access Object">
<MenuItem onClick={() => openAccessObject()} aria-label="Access Object" dense> <MenuItem onClick={() => openAccessObject()} aria-label="Access Object" dense>
@ -280,6 +281,7 @@ export default function UserMenu() {
</MenuItem> </MenuItem>
</Tooltip> </Tooltip>
)} )}
<Divider />
<MenuItem onClick={handleLogout} aria-label="Sign Out" dense> <MenuItem onClick={handleLogout} aria-label="Sign Out" dense>
<ListItemIcon> <ListItemIcon>
<ExitToApp className={classes.red} /> <ExitToApp className={classes.red} />