From d3a5c24741735a598209b1eb973df6260bba7474 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 18 Mar 2026 14:38:25 -0600 Subject: [PATCH 01/24] updated get permissions to take in the keys and types to get the correct permissions using the context chain --- src/pages/Gate.tsx | 22 ++++++++-------------- src/providers/pond/permissionAPI.tsx | 11 ++++++++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/pages/Gate.tsx b/src/pages/Gate.tsx index c0de27f..c32cb84 100644 --- a/src/pages/Gate.tsx +++ b/src/pages/Gate.tsx @@ -3,7 +3,7 @@ import { Gate as IGate } from "models/Gate"; //import { Redirect, useHistory, useRouteMatch } from "react-router"; import React, { useCallback, useEffect, useState } from "react"; import PageContainer from "./PageContainer"; -import { useGlobalState, useGateAPI, useUserAPI } from "providers"; +import { useGlobalState, useGateAPI } from "providers"; import { Box, ButtonBase, @@ -20,7 +20,7 @@ import { } from "@mui/material"; import { pond } from "protobuf-ts/pond"; import DeviceLinkDrawer from "common/DeviceLinkDrawer"; -import { Component, Device, Scope } from "models"; +import { Component, Device } from "models"; import GateActions from "gate/GateActions"; import GateDevice from "gate/GateDevice"; import ObjectControls from "common/ObjectControls"; @@ -28,7 +28,7 @@ import { Link } from "@mui/icons-material"; import Chat from "chat/Chat"; import PlaneIcon from "products/AviationIcons/PlaneIcon"; import NotesIcon from "@mui/icons-material/Notes"; -import { useMobile, useSnackbar, useThemeType } from "hooks"; +import { useMobile, usePermissionAPI, useSnackbar, useThemeType } from "hooks"; import { clone } from "lodash"; import { useNavigate, useParams } from "react-router-dom"; import { makeStyles } from "@mui/styles"; @@ -90,7 +90,7 @@ export default function Gate(props: Props) { const gateID = gateKey ?? useParams<{ gateKey: string }>()?.gateKey ?? ""; const classes = useStyles(); const gateAPI = useGateAPI(); - const userAPI = useUserAPI(); + const permissionAPI = usePermissionAPI(); const [gate, setGate] = useState(IGate.create()); const [devices, setDevices] = useState>( new Map() @@ -115,16 +115,10 @@ export default function Gate(props: Props) { }; useEffect(() => { - let key = gateID; - let kind = "gate"; - if (as) { - key = as; - kind = "team"; - } - userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => { - setPermissions(resp.permissions); - }); - }, [as, gateID, userAPI, user]); + permissionAPI.getPermissions(user.id(), [gateID], ["gate"]).then(resp => { + setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions) + }) + }, [as, gateID, permissionAPI, user]); const loadGate = useCallback(() => { let id = gateID; diff --git a/src/providers/pond/permissionAPI.tsx b/src/providers/pond/permissionAPI.tsx index 91b7d6a..1c75827 100644 --- a/src/providers/pond/permissionAPI.tsx +++ b/src/providers/pond/permissionAPI.tsx @@ -8,7 +8,7 @@ import { createContext, PropsWithChildren, useContext } from "react"; import { objectQueryParams, pondURL } from "./pond"; export interface IPermissionAPIContext { - getPermissions: (user: string, scope: Scope) => Promise>; + getPermissions: (user: string, keys: string[], types: string[]) => Promise>; removePermissions: (user: string, scope: Scope) => Promise>; updatePermissions: (scope: Scope, users: User[] | Team[]) => Promise>; updateRelativePermissions: ( @@ -44,9 +44,12 @@ export default function PermissionProvider(props: PropsWithChildren) { const { get, del, put, post } = useHTTP(); const [{ as }] = useGlobalState(); - const getPermissions = (user: string, scope: Scope) => { + const getPermissions = (user: string, keys: string[], types: string[]) => { return new Promise((resolve, reject) => { - get(pondURL("/users/" + user + "/permissions" + objectQueryParams(scope))).then(resp => { + get(pondURL( + "/users/" + user + "/permissions?types=" + types.toString() + "&keys=" + keys.toString() + + (as && "&as=" + as) + )).then(resp => { return resolve(resp) }).catch(err => { return reject(err) @@ -54,6 +57,8 @@ export default function PermissionProvider(props: PropsWithChildren) { }) }; + + const removePermissions = (user: string, scope: Scope) => { return new Promise((resolve, reject) => { del(pondURL("/users/" + user + "/permissions" + objectQueryParams(scope))).then(resp => { From 7e79d0859b18a0feb88f12213c0012f3e91c2536 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 19 Mar 2026 12:43:51 -0600 Subject: [PATCH 02/24] changed the sample size for the delta temp graph --- src/gate/GateDeltaTempGraph.tsx | 4 ++-- src/gate/GateDeviceInteraction.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gate/GateDeltaTempGraph.tsx b/src/gate/GateDeltaTempGraph.tsx index fc5b253..8a6d291 100644 --- a/src/gate/GateDeltaTempGraph.tsx +++ b/src/gate/GateDeltaTempGraph.tsx @@ -109,7 +109,7 @@ export default function GateDeltaTempGraph(props: Props){ tempComponent.key(), start.toISOString(), end.toISOString(), - 500, + 300, 0, "timestamp", ).then(resp => { @@ -123,7 +123,7 @@ export default function GateDeltaTempGraph(props: Props){ ambient.key(), start.toISOString(), end.toISOString(), - 500, + 300, 0, "timestamp", ).then(resp => { diff --git a/src/gate/GateDeviceInteraction.tsx b/src/gate/GateDeviceInteraction.tsx index 0303123..305817a 100644 --- a/src/gate/GateDeviceInteraction.tsx +++ b/src/gate/GateDeviceInteraction.tsx @@ -100,7 +100,6 @@ export default function GateDeviceInteraction(props: Props) { } else if ( gate.preferences[component.key()] === pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE ) { - console.log(component) setPressureComponent(component); setPressureSource( quack.ComponentID.create({ From a288c9503a8bbd48232a571b3866b99dde279d52 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 23 Mar 2026 12:18:51 -0600 Subject: [PATCH 03/24] switched to status websocket instead of the dead full device websocket --- src/pages/Device.tsx | 100 ++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 6dd2623..55fd8b9 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -3,7 +3,8 @@ import Grid from '@mui/material/Grid2'; import { Component, Device, Interaction, User } from "models"; import { getContextKeys, getContextTypes } from "pbHelpers/Context"; import { useDeviceAPI, useGlobalState, useSnackbar } from "providers"; -import { useEffect, useMemo, useState } from "react"; +import { useDeviceStatusStreams } from "hooks/useDeviceStatusStreams"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useLocation, useParams } from "react-router-dom"; import PageContainer from "./PageContainer"; import { useHTTP, useMobile } from "hooks"; @@ -23,6 +24,25 @@ import DeviceWizard from "device/DeviceWizard"; import DeviceScannedComponents from "device/autoDetect/DeviceScannedComponents"; import { useWebSocket } from "hooks/useWebSocket"; +type TimestampedReading = { timestamp?: string }; + +function getTimestampMillis(timestamp?: string): number | undefined { + if (!timestamp) return undefined; + const parsed = Date.parse(timestamp); + return Number.isNaN(parsed) ? undefined : parsed; +} + +function pickNewestReading( + current: T | null | undefined, + incoming: T | null | undefined +): T | null | undefined { + const currentTs = getTimestampMillis(current?.timestamp); + const incomingTs = getTimestampMillis(incoming?.timestamp); + if (incomingTs === undefined) return current ?? incoming; + if (currentTs === undefined) return incoming ?? current; + return incomingTs >= currentTs ? incoming : current; +} + export interface DevicePageData { device: Device; components: Component[]; @@ -76,6 +96,55 @@ export default function DevicePage() { ? as : undefined; + const liveStatusDeviceIds = useMemo( + () => (deviceID ? [deviceID] : []), + [deviceID] + ); + + const handleLiveDeviceStatus = useCallback( + (streamDeviceId: string, status: pond.DeviceStatus) => { + setDevice((prev) => { + if (prev.id().toString() !== streamDeviceId) return prev; + const updated = Device.clone(prev); + const incomingStatus = pond.DeviceStatus.fromObject(status); + const currentStatus = prev.status ?? pond.DeviceStatus.create(); + + incomingStatus.plenum = pickNewestReading(currentStatus.plenum, incomingStatus.plenum); + incomingStatus.sen5x = pickNewestReading(currentStatus.sen5x, incomingStatus.sen5x); + incomingStatus.co = pickNewestReading(currentStatus.co, incomingStatus.co); + incomingStatus.co2 = pickNewestReading(currentStatus.co2, incomingStatus.co2); + incomingStatus.no2 = pickNewestReading(currentStatus.no2, incomingStatus.no2); + incomingStatus.o2 = pickNewestReading(currentStatus.o2, incomingStatus.o2); + incomingStatus.lel = pickNewestReading(currentStatus.lel, incomingStatus.lel); + incomingStatus.h2s = pickNewestReading(currentStatus.h2s, incomingStatus.h2s); + + const currentLastActive = getTimestampMillis(currentStatus.lastActive); + const incomingLastActive = getTimestampMillis(incomingStatus.lastActive); + if ( + currentLastActive !== undefined && + incomingLastActive !== undefined && + incomingLastActive < currentLastActive + ) { + incomingStatus.lastActive = currentStatus.lastActive; + } + + updated.status = incomingStatus; + return updated; + }); + }, + [] + ); + + useDeviceStatusStreams({ + deviceIds: liveStatusDeviceIds, + onStatusUpdate: handleLiveDeviceStatus, + token, + keys: liveContextKeys, + types: liveContextTypes, + as: liveAs, + enabled: Boolean(deviceID && token), + }); + const loadDevice = () => { if (loading) return if (state?.devicePageData) { @@ -212,35 +281,6 @@ export default function DevicePage() { enabled: !!deviceID, }); - // --- Real-time device updates (optional) --- - // Updates device-level info (name, status, lastActive, etc.) - useWebSocket({ - path: `/v1/live/devices/${deviceID}`, - parse: (e) => { - try { - const raw = JSON.parse(e.data); - const dev = Device.any(raw); - if (!dev?.settings) { - console.warn("[ws] received device without settings:", raw); - return null; - } - return dev; - } catch (err) { - console.warn("[ws] failed to parse device:", err); - return null; - } - }, - onMessage: (updatedDevice) => { - if (!updatedDevice) return; - setDevice(updatedDevice); - }, - keys: liveContextKeys, - types: liveContextTypes, - as: liveAs, - token, - enabled: !!deviceID, - }); - const loadPortScan = () => { deviceAPI.listFoundComponents(parseInt(deviceID)) .then(resp => { From a2bc841510bd077c446611486cf1a5d2dedf97ec Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 23 Mar 2026 14:57:23 -0600 Subject: [PATCH 04/24] pushing this stuff up so carter can take a look --- src/field/FieldTaskList.tsx | 160 ++++++++++++++++++ src/maps/mapDrawers/FieldDrawer.tsx | 3 +- src/providers/pond/permissionAPI.tsx | 12 +- src/providers/pond/taskAPI.tsx | 30 +++- src/providers/pond/teamAPI.tsx | 10 +- src/tasks/TaskCard.tsx | 140 ++++++++-------- src/tasks/TaskDrawer.tsx | 99 ++++++----- src/tasks/TaskList.tsx | 10 +- src/tasks/TaskSettings.tsx | 9 +- src/tasks/TaskViewer.tsx | 115 +++++++------ src/tasks/taskActions.tsx | 236 +++++++++++++++++++++++++++ src/teams/ObjectTeams.tsx | 10 +- src/teams/ShareWithTeam.tsx | 10 +- 13 files changed, 660 insertions(+), 184 deletions(-) create mode 100644 src/field/FieldTaskList.tsx create mode 100644 src/tasks/taskActions.tsx diff --git a/src/field/FieldTaskList.tsx b/src/field/FieldTaskList.tsx new file mode 100644 index 0000000..3b0d1e3 --- /dev/null +++ b/src/field/FieldTaskList.tsx @@ -0,0 +1,160 @@ +import { Fab, Theme } from "@mui/material" +import { Task } from "models" +import { useGlobalState, useTaskAPI } from "providers" +import React, { useCallback, useEffect, useState } from "react" +import TaskDrawer from "tasks/TaskDrawer" +import TaskList from "tasks/TaskList" +import TaskSettings from "tasks/TaskSettings" +import AddIcon from "@mui/icons-material/Add"; +import { makeStyles } from "@mui/styles"; + + + +interface Props { + field: string +} + +const useStyles = makeStyles((theme: Theme) => { + return ({ + active: { + color: theme.palette.getContrastText(theme.palette.secondary.main), + backgroundColor: theme.palette.secondary.main, + width: theme.spacing(5), + height: theme.spacing(5), + border: 0, + "&:hover": { + backgroundColor: theme.palette.secondary.main + } + }, + fab: { + zIndex: 20, + background: theme.palette.primary.main, + "&:hover": { + background: theme.palette.primary.main + }, + "&:focus": { + background: theme.palette.primary.main + }, + position: "absolute", + bottom: theme.spacing(8), //for mobile navigator + right: theme.spacing(2), + [theme.breakpoints.up("sm")]: { + bottom: theme.spacing(2) + } + }, + overlayFab: { + zIndex: 20, + background: theme.palette.primary.main, + "&:hover": { + background: theme.palette.primary.main + }, + "&:focus": { + background: theme.palette.primary.main + }, + position: "fixed", + bottom: theme.spacing(8), //for mobile navigator + right: theme.spacing(2), + [theme.breakpoints.up("sm")]: { + bottom: theme.spacing(2) + } + } + }); +}); + +export default function FieldTaskList(props: Props) { + const { field } = props + const [{ as }] = useGlobalState() + const taskAPI = useTaskAPI() + const classes = useStyles() + const [selectedTask, setSelectedTask] = useState(undefined) + const [openTaskDrawer, setOpenTaskDrawer] = useState(false) + const [opentaskSettings, setOpenTaskSettings] = useState(false) + const [taskMap, setTaskMap] = useState>(new Map()) + //load the tasks + const loadTasks = useCallback(() => { + taskAPI.listTasks(50, 0, "asc", "start", undefined, undefined, undefined, undefined, undefined, [field], ["field"]).then(resp => { + if(resp.data.tasks){ + let newMap: Map = new Map() + resp.data.tasks.forEach(task => { + newMap.set(task.key, Task.create(task)) + }) + setTaskMap(newMap) + } + }).catch(err => { + + }) + },[as, field]) + + useEffect(()=>{ + loadTasks() + },[loadTasks]) + + const addNewTask = (task: Task) => { + console.log() + } + + const deleteTask = () => { + + } + + const markComplete = () => { + + } + + const updateTask = () => { + + } + //return the task list + return ( + + {selectedTask && + { + setOpenTaskDrawer(false) + setSelectedTask(undefined) + }} + keys={[field]} + types={["field"]} + /> + } + { + let task = taskMap.get(id) + if(task){ + setSelectedTask(task) + setOpenTaskDrawer(true) + } + }} + tasks={Array.from(taskMap.values())} + reLoad={() => { + loadTasks() + }} + keys={[field]} + types={["field"]} + /> + { + setOpenTaskSettings(false) + }} + keys={[field]} + types={["field"]} + task={selectedTask} + /> + {setOpenTaskSettings(true)}} + aria-label="Create Task" + className={classes.overlayFab} + size={"medium"}> + + + + ) +} \ No newline at end of file diff --git a/src/maps/mapDrawers/FieldDrawer.tsx b/src/maps/mapDrawers/FieldDrawer.tsx index ae7a794..f6e864b 100644 --- a/src/maps/mapDrawers/FieldDrawer.tsx +++ b/src/maps/mapDrawers/FieldDrawer.tsx @@ -24,6 +24,7 @@ import { useMobile } from "hooks"; import { useGlobalState, useHarvestPlanAPI, useUserAPI } from "providers"; import { makeStyles } from "@mui/styles"; import FieldActions from "field/FieldActions"; +import FieldTaskList from "field/FieldTaskList"; interface TabPanelProps { children?: React.ReactNode; @@ -273,7 +274,7 @@ export default function FieldDrawer(props: Props) { - + ); diff --git a/src/providers/pond/permissionAPI.tsx b/src/providers/pond/permissionAPI.tsx index 1c75827..ea8e0b9 100644 --- a/src/providers/pond/permissionAPI.tsx +++ b/src/providers/pond/permissionAPI.tsx @@ -25,7 +25,9 @@ export interface IPermissionAPIContext { shareObjectByKey: ( scope: Scope, key: string, - permissions: pond.Permission[] + permissions: pond.Permission[], + keys?: string[], + type?: string[] ) => Promise>; addShareableLink: (scope: Scope, expiration: string) => Promise>; removeShareableLink: (scope: Scope, code: string) => Promise>; @@ -136,9 +138,11 @@ export default function PermissionProvider(props: PropsWithChildren) { }) }; - const shareObjectByKey = (scope: Scope, key: string, permissions: pond.Permission[]) => { - let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey") - if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`) + const shareObjectByKey = (scope: Scope, key: string, permissions: pond.Permission[], keys?: string[], types?: string[]) => { + let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey") + (keys ? "?keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") + if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`) + (keys ? "&keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") return new Promise((resolve, reject) => { post(url, { key: key, diff --git a/src/providers/pond/taskAPI.tsx b/src/providers/pond/taskAPI.tsx index 126f012..087b3d0 100644 --- a/src/providers/pond/taskAPI.tsx +++ b/src/providers/pond/taskAPI.tsx @@ -6,7 +6,7 @@ import { createContext, PropsWithChildren, useContext } from "react"; import { pondURL } from "./pond"; export interface ITaskAPIContext { - addTask: (task: pond.TaskSettings, otherTeam?: string) => Promise>; + addTask: (task: pond.TaskSettings, otherTeam?: string, keys?: string[], types?: string[]) => Promise>; getTask: (taskID: string, otherTeam?: string) => Promise>; listTasks: ( limit: number, @@ -17,7 +17,9 @@ export interface ITaskAPIContext { asRoot?: boolean, otherTeam?: string, from?: string, - to?: string + to?: string, + keys?: string[], + types?: string[] ) => Promise>; removeTask: (taskID: string, otherTeam?: string) => Promise>; updateTask: ( @@ -38,10 +40,20 @@ export default function TaskProvider(props: PropsWithChildren) { const { get, del, post, put } = useHTTP(); const [{ as }] = useGlobalState(); - const addTask = (task: pond.TaskSettings, otherTeam?: string) => { + const addTask = (task: pond.TaskSettings, otherTeam?: string, keys?: string[], types?: string[]) => { const view = otherTeam ? otherTeam : as - if (view) return post(pondURL("/tasks?as=" + view), task); - return post(pondURL("/tasks"), task); + if (view) return post( + pondURL( + "/tasks?as=" + view + + (keys ? "&keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") + ), task); + return post( + pondURL( + "/tasks" + + (keys ? "?keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") + ), task); }; const getTask = (taskID: string, otherTeam?: string) => { @@ -65,7 +77,9 @@ export default function TaskProvider(props: PropsWithChildren) { asRoot?: boolean, otherTeam?: string, from?: string, - to?: string + to?: string, + keys?: string[], + types?: string[] ) => { const view = otherTeam ? otherTeam : as return get( @@ -81,7 +95,9 @@ export default function TaskProvider(props: PropsWithChildren) { (asRoot ? "&asRoot=" + asRoot.toString() : "") + (view ? "&as=" + view : "") + (from ? "&from=" + from : "") + - (to ? "&to=" + to : "") + (to ? "&to=" + to : "") + + (keys ? "&keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") ) ); }; diff --git a/src/providers/pond/teamAPI.tsx b/src/providers/pond/teamAPI.tsx index 32f8a4a..911ee91 100644 --- a/src/providers/pond/teamAPI.tsx +++ b/src/providers/pond/teamAPI.tsx @@ -32,7 +32,7 @@ export interface ITeamAPIContext { prefixSearch?: string, ) => Promise>; listAllTeams: () => Promise>; - listObjectTeams: (scope: Scope, otherTeam?: string) => Promise; + listObjectTeams: (scope: Scope, otherTeam?: string, keys?: string[], types?: string[]) => Promise; removeTeam: (key: string) => Promise>; } @@ -156,10 +156,12 @@ export default function TeamProvider(props: PropsWithChildren) { }) }; - const listObjectTeams = (scope: Scope, otherTeam?: string) => { - let url = "/" + scope.kind + "s/" + scope.key + "/teams" + const listObjectTeams = (scope: Scope, otherTeam?: string, keys?: string[], types?: string[]) => { + let url = "/" + scope.kind + "s/" + scope.key + "/teams" + (keys ? "?keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") const view = otherTeam ? otherTeam : as - if (view) url = `/${scope.kind}s/${scope.key}/teams?as=${view}` + if (view) url = `/${scope.kind}s/${scope.key}/teams?as=${view}` + (keys ? "&keys=" + keys.join(",") : "") + + (types ? "&types=" + types.join(",") : "") return new Promise((resolve, reject) => { get(pondURL(url)).then(resp => { return resolve(resp) diff --git a/src/tasks/TaskCard.tsx b/src/tasks/TaskCard.tsx index bac70e0..281bbb2 100644 --- a/src/tasks/TaskCard.tsx +++ b/src/tasks/TaskCard.tsx @@ -3,25 +3,17 @@ import { Card, CardActionArea, Grid2 as Grid, - IconButton, - ListItemIcon, - ListItemText, - Menu, - MenuItem, Typography } from "@mui/material"; import { makeStyles } from "@mui/styles"; import { useCallback, useEffect, useState } from "react"; import { Task } from "models"; import { useGlobalState } from "providers"; -import { useUserAPI } from "hooks"; -import { Done, MoreVert } from "@mui/icons-material"; -import Edit from "@mui/icons-material/Edit"; -import DeleteIcon from "@mui/icons-material/Delete"; +import { usePermissionAPI } from "hooks"; import { red } from "@mui/material/colors"; import { pond } from "protobuf-ts/pond"; -import { taskScope, teamScope } from "models/Scope"; import EventBlocker from "common/EventBlocker"; +import TaskActions from "./taskActions"; interface Props { task: Task; @@ -30,6 +22,8 @@ interface Props { markComplete: (task: Task) => void; deleteTask: (task: Task) => void; openTaskPage: (taskId: string) => void; + keys: string[] + types: string[] } const useStyles = makeStyles(() => ({ @@ -59,21 +53,14 @@ export default function TaskCard(props: Props) { const classes = useStyles(); const [permissions, setPermissions] = useState([]); const [{ user, as }] = useGlobalState(); - const userAPI = useUserAPI(); - const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const permissionAPI = usePermissionAPI(); const loadPermissions = useCallback(() => { - let scope = taskScope(props.task.key); - if(as){//if viewing as a team use the permissions to the team, and not the task itself - scope = teamScope(as) - } - userAPI - .getUser(user.id(), scope) - .then(resp => { - setPermissions(resp.permissions); - }) - .catch(err => {}); - }, [props.task, userAPI, user, as]); + permissionAPI.getPermissions(user.id(), props.keys, props.types).then(resp => { + setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions) + }) + + }, [props.task, permissionAPI, user, as]); useEffect(() => { loadPermissions(); @@ -89,51 +76,51 @@ export default function TaskCard(props: Props) { setDay(date.getUTCDate()); }, [props.task]); - const taskActions = () => { - return ( - setMenuAnchorEl(null)} - disableAutoFocusItem> - { - props.markComplete(props.task); - setMenuAnchorEl(null); - }}> - - - - - - {permissions.includes(pond.Permission.PERMISSION_WRITE) && ( - { - props.editTaskMethod(props.task); - setMenuAnchorEl(null); - }}> - - - - - - )} - {permissions.includes(pond.Permission.PERMISSION_WRITE) && ( - { - props.deleteTask(props.task); - setMenuAnchorEl(null); - }}> - - - - - - )} - - ); - }; + // const taskActions = () => { + // return ( + // setMenuAnchorEl(null)} + // disableAutoFocusItem> + // { + // props.markComplete(props.task); + // setMenuAnchorEl(null); + // }}> + // + // + // + // + // + // {permissions.includes(pond.Permission.PERMISSION_WRITE) && ( + // { + // props.editTaskMethod(props.task); + // setMenuAnchorEl(null); + // }}> + // + // + // + // + // + // )} + // {permissions.includes(pond.Permission.PERMISSION_WRITE) && ( + // { + // props.deleteTask(props.task); + // setMenuAnchorEl(null); + // }}> + // + // + // + // + // + // )} + // + // ); + // }; return ( @@ -164,18 +151,31 @@ export default function TaskCard(props: Props) { - + {/* setMenuAnchorEl(event.currentTarget)}> + */} + + { + props.reLoad() + }} + keys={props.keys} + types={props.types} + /> - {taskActions()} + {/* {taskActions()} */} ); } diff --git a/src/tasks/TaskDrawer.tsx b/src/tasks/TaskDrawer.tsx index 956a36a..3c206fb 100644 --- a/src/tasks/TaskDrawer.tsx +++ b/src/tasks/TaskDrawer.tsx @@ -22,17 +22,21 @@ import NotesIcon from "@mui/icons-material/Notes"; import DeleteIcon from "@mui/icons-material/Delete"; import Chat from "chat/Chat"; import { pond } from "protobuf-ts/pond"; -import { useMobile, useUserAPI } from "hooks"; +import { useMobile, usePermissionAPI, useUserAPI } from "hooks"; import { getThemeType } from "theme"; import { makeStyles } from "@mui/styles"; +import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle"; +import TaskActions from "./taskActions"; +import { useGlobalState } from "providers"; interface Props { task: Task; open: boolean; closeCallback: () => void; - editTask: (task: Task) => void; deleteTask: (task: Task) => void; completeTask: (task: Task) => void; + keys: string[] + types: string[] } // interface TabPanelProps { @@ -103,17 +107,23 @@ export default function TaskDrawer(props: Props) { "Nov", "Dec" ]; - const { task, open, closeCallback, completeTask, deleteTask, editTask } = props; + const { task, open, closeCallback, completeTask, deleteTask } = props; const [month, setMonth] = useState(0); const [day, setDay] = useState(0); - const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [permissions, setPermissions] = useState([]) + const [{user}] = useGlobalState(); + // const [menuAnchorEl, setMenuAnchorEl] = useState(null); const [openNote, setOpenNote] = useState(false); const isMobile = useMobile(); const classes = useStyles(); const userAPI = useUserAPI(); + const permissionAPI = usePermissionAPI(); const [worker, setWorker] = useState(); useEffect(() => { + permissionAPI.getPermissions(user.id(), [task.key], ["task"]).then(resp => { + setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions) + }) if (task.start()) { let date = new Date(task.start()); setMonth(date.getUTCMonth()); @@ -122,40 +132,40 @@ export default function TaskDrawer(props: Props) { userAPI.getProfile(task.worker()).then(resp => { setWorker(resp); }); - }, [task, userAPI]); + }, [user, task, userAPI, permissionAPI]); - const taskActions = () => { - return ( - setMenuAnchorEl(null)} - disableAutoFocusItem> - { - editTask(props.task); - setMenuAnchorEl(null); - }}> - - - - - - { - deleteTask(props.task); - setMenuAnchorEl(null); - closeCallback(); - }}> - - - - - - - ); - }; + // const taskActions = () => { + // return ( + // setMenuAnchorEl(null)} + // disableAutoFocusItem> + // { + // editTask(props.task); + // setMenuAnchorEl(null); + // }}> + // + // + // + // + // + // { + // deleteTask(props.task); + // setMenuAnchorEl(null); + // closeCallback(); + // }}> + // + // + // + // + // + // + // ); + // }; const bodyButtons = () => { return ( @@ -175,11 +185,20 @@ export default function TaskDrawer(props: Props) { - setMenuAnchorEl(event.currentTarget)}> - + */} + completeTask(t)} + permissions={permissions} + task={task} + removeTask={deleteTask} + refreshCallback={closeCallback} + keys={props.keys} + types={props.types} + /> @@ -303,7 +322,7 @@ export default function TaskDrawer(props: Props) { {taskTime()} {assignees()} - {taskActions()} + {/* {taskActions()} */} {noteDrawer()} ); diff --git a/src/tasks/TaskList.tsx b/src/tasks/TaskList.tsx index 6803831..8f367ef 100644 --- a/src/tasks/TaskList.tsx +++ b/src/tasks/TaskList.tsx @@ -20,6 +20,8 @@ interface Props { listHeight?: string | number; label?: string; dateToView?: Date; + keys: string[] + types: string[] } export default function TaskList(props: Props) { @@ -31,7 +33,9 @@ export default function TaskList(props: Props) { reLoad, listHeight, label, - dateToView + dateToView, + keys, + types } = props; const [tasks, setTasks] = useState(props.tasks); const [incomplete, setIncomplete] = useState([]); @@ -128,6 +132,8 @@ export default function TaskList(props: Props) { deleteTask={(task: Task) => deleteTask(task)} reLoad={reLoad} openTaskPage={(taskId: string) => openTask(taskId)} + keys={keys} + types={types} /> )); @@ -141,6 +147,8 @@ export default function TaskList(props: Props) { deleteTask={(task: Task) => deleteTask(task)} reLoad={reLoad} openTaskPage={(taskId: string) => openTask(taskId)} + keys={keys} + types={types} /> )); diff --git a/src/tasks/TaskSettings.tsx b/src/tasks/TaskSettings.tsx index 9f92548..dcc6b91 100644 --- a/src/tasks/TaskSettings.tsx +++ b/src/tasks/TaskSettings.tsx @@ -24,14 +24,15 @@ import ResponsiveDialog from "common/ResponsiveDialog"; interface Props { open: boolean; onClose: (reLoad?: boolean) => void; - markComplete?: boolean; startDate?: any; task?: Task; - objectKey?: string; + objectKey?: string; //deprecated, with the new permission structure keys and types will be what determine what a task is connected to type?: string; hasCost?: boolean; costTitle?: string; secondaryCostTitle?: string; + keys?: string[] + types?: string[] } export default function TaskSettings(props: Props) { @@ -104,7 +105,7 @@ export default function TaskSettings(props: Props) { startTime: startTime, end: endDate, endTime: endTime, - objectKey: props.objectKey ? props.objectKey : user.id(), + objectKey: props.objectKey ? props.objectKey : user.id(), //deprecated description: taskDescription, worker: worker, complete: false, @@ -120,7 +121,7 @@ export default function TaskSettings(props: Props) { returnTask.settings = taskSettings; taskAPI - .addTask(taskSettings, as) + .addTask(taskSettings, as, props.keys, props.types) .then(resp => { props.onClose(true); }) diff --git a/src/tasks/TaskViewer.tsx b/src/tasks/TaskViewer.tsx index 12e82d7..00fed3c 100644 --- a/src/tasks/TaskViewer.tsx +++ b/src/tasks/TaskViewer.tsx @@ -26,10 +26,12 @@ import moment from "moment"; import { makeStyles } from "@mui/styles"; interface ViewProps { - objectKey?: string; //only used to save it for the object + // objectKey?: string; //only used to save it for the object --Deprecated with new permission structure-- + keys?: string[]//the keys and types are used to give th object permissions to the task, that is how they will be linked now + types?: string[] label?: string; drawerView?: boolean; - loadKeys?: string[]; //if you want to load tasks for a specific object(s) + // loadKeys?: string[]; //if you want to load tasks for a specific object(s) --Deprecated with new permission structure-- it should use the keys and types now overlayButton?: boolean; } @@ -87,7 +89,15 @@ const useStyles = makeStyles((theme: Theme) => { }); export default function TaskViewer(props: ViewProps) { - const { objectKey, label, drawerView, loadKeys, overlayButton } = props; + const { + //objectKey, + label, + drawerView, + //loadKeys, + keys, + types, + overlayButton + } = props; const [{ user, as }] = useGlobalState(); //const [{ as }] = useGlobalState(); const taskAPI = useTaskAPI(); @@ -143,36 +153,36 @@ export default function TaskViewer(props: ViewProps) { }; - const loadMultitask = useCallback(() => { - if (!loadKeys) return; - if (loadKeys.length > 0) { - let temp = new Map(); - setLoaded(false); - taskAPI - .getMultiTasks(loadKeys, as) - .then(resp => { - if(resp.data.tasks){ - resp.data.tasks.forEach(task => { - if (task.settings) { - temp.set(task.key, Task.any(task)); - } - }); - } - setTasks(temp); - setLoaded(true); - }) - .catch(err => { - openSnack("Failed to load"); - }); - } - }, [loadKeys, openSnack, taskAPI, as]); + // const loadMultitask = useCallback(() => { + // if (!loadKeys) return; + // if (loadKeys.length > 0) { + // let temp = new Map(); + // setLoaded(false); + // taskAPI + // .getMultiTasks(loadKeys, as) + // .then(resp => { + // if(resp.data.tasks){ + // resp.data.tasks.forEach(task => { + // if (task.settings) { + // temp.set(task.key, Task.any(task)); + // } + // }); + // } + // setTasks(temp); + // setLoaded(true); + // }) + // .catch(err => { + // openSnack("Failed to load"); + // }); + // } + // }, [loadKeys, openSnack, taskAPI, as]); //loads tasks from the backend database const loadTasks = useCallback(() => { if (!user.id()) return; let temp = new Map(); taskAPI - .listTasks(50, 0, "asc", "start", undefined, undefined, as, prevMonth, nextMonth) + .listTasks(50, 0, "asc", "start", undefined, undefined, as, prevMonth, nextMonth, keys, types) .then(resp => { if(resp.data.tasks){ resp.data.tasks.forEach(task => { @@ -185,20 +195,20 @@ export default function TaskViewer(props: ViewProps) { setLoaded(true); }) .catch(err => { - console.log(err) openSnack("Failed to load tasks"); }); - }, [taskAPI, as, user, openSnack, nextMonth, prevMonth]); + }, [taskAPI, as, user, openSnack, nextMonth, prevMonth, keys, types]); useEffect(() => { - if (drawerView) { - setTasks(new Map()); - setValue(1); - loadMultitask(); - } else { - loadTasks(); - } - }, [loadTasks, loadMultitask, as, drawerView]); + // if (drawerView) { + // setTasks(new Map()); + // setValue(1); + // loadMultitask(); + // } else { + // loadTasks(); + // } + loadTasks() + }, [loadTasks, as, drawerView]); const markComplete = (task: Task) => { task.settings.complete = !task.settings.complete; @@ -320,6 +330,8 @@ export default function TaskViewer(props: ViewProps) { reLoad={() => loadTasks()} dateToView={drawerView ? undefined : currentDate} openTask={(taskId: string) => openSelectedTask(taskId)} + keys={[]} + types={[]} /> openSelectedTask(taskId)} + keys={[]} + types={[]} /> @@ -423,16 +437,19 @@ export default function TaskViewer(props: ViewProps) { { - if (r) { - if (drawerView) { - loadMultitask(); - } else { - loadTasks(); - } - } + // if (r) { + // if (drawerView) { + // loadMultitask(); + // } else { + // loadTasks(); + // } + // } + loadTasks(); setEditTask(undefined); setNewTaskDialog(false); }} @@ -441,10 +458,14 @@ export default function TaskViewer(props: ViewProps) { setOpenDrawer(false)} + closeCallback={() => { + setOpenDrawer(false) + loadTasks() + }} completeTask={markComplete} deleteTask={deleteTask} - editTask={setTaskToEdit} + keys={[]} + types={[]} /> } diff --git a/src/tasks/taskActions.tsx b/src/tasks/taskActions.tsx new file mode 100644 index 0000000..d55590b --- /dev/null +++ b/src/tasks/taskActions.tsx @@ -0,0 +1,236 @@ +import { + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Theme +} from "@mui/material"; +import { blue } from "@mui/material/colors"; +import MoreIcon from "@mui/icons-material/MoreVert"; +import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle"; +import { pond } from "protobuf-ts/pond"; +import React, { useState } from "react"; +import { isOffline } from "utils/environment"; +import ObjectTeams from "teams/ObjectTeams"; +import { Task, taskScope } from "models"; +import { makeStyles } from "@mui/styles"; +import { Done, Edit } from "@mui/icons-material"; +import DeleteIcon from "@mui/icons-material/Delete"; +import TaskSettings from "./TaskSettings"; + +const useStyles = makeStyles((theme: Theme) => ({ + shareIcon: { + color: blue["500"], + "&:hover": { + color: blue["600"] + } + }, + removeIcon: { + color: "var(--status-alert)" + }, + red: { + color: "var(--status-alert)" + } +})); + +interface Props { + task: Task; + permissions: pond.Permission[]; + keys: string[] + types: string[] + refreshCallback: () => void; + markComplete: (task: Task) => void; + removeTask: (task: Task) => void; +} + +interface OpenState { + // share: boolean; + // users: boolean; + teams: boolean; + settings: boolean; + // removeSelf: boolean; + delete: boolean +} + +export default function TaskActions(props: Props) { + const classes = useStyles(); + const { task, permissions, refreshCallback, keys, types } = props; + const [anchorEl, setAnchorEl] = React.useState(null); + const [openState, setOpenState] = useState({ + // share: false, + // users: false, + teams: false, + settings: false, + // removeSelf: false + delete: false + }); + + const groupMenu = () => { + // const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE); + const canEdit = permissions.includes(pond.Permission.PERMISSION_WRITE); + const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS); + return ( + setAnchorEl(null)} + keepMounted + disableAutoFocusItem> + {/* {!isOffline() && canShare && ( + { + setOpenState({ ...openState, share: true }); + setAnchorEl(null); + }}> + + + + + + )} */} + {/* {!isOffline() && canManageUsers && ( + { + setOpenState({ ...openState, users: true }); + setAnchorEl(null); + }}> + + + + + + )} */} + {!isOffline() && canEdit && ( + { + props.markComplete(props.task); + setAnchorEl(null); + }}> + + + + + + )} + {!isOffline() && canEdit && ( + { + setOpenState({ ...openState, settings: true }); + setAnchorEl(null); + }}> + + + + + + )} + {!isOffline() && canManageUsers && ( + { + setOpenState({ ...openState, teams: true }); + setAnchorEl(null); + }}> + + + + + + )} + {permissions.includes(pond.Permission.PERMISSION_WRITE) && ( + { + props.removeTask(props.task); + setAnchorEl(null); + }}> + + + + + + )} + {/* { + setOpenState({ ...openState, removeSelf: true }); + setAnchorEl(null); + }}> + + + + + */} + + ); + }; + + const dialogs = () => { + const key = task.key; + // const label = task.title(); + return ( + + {/* setOpenState({ ...openState, share: false })} + /> */} + {/* setOpenState({ ...openState, users: false })} + refreshCallback={refreshCallback} + /> */} + {/* setOpenState({ ...openState, removeSelf: false })} + /> */} + setOpenState({ ...openState, teams: false })} + keys={keys} + types={types} + /> + { + setOpenState({ ...openState, settings: false}) + if(r){ + refreshCallback() + } + }} + /> + + ); + }; + + return ( + + ) => setAnchorEl(event.currentTarget)}> + + + {groupMenu()} + {dialogs()} + + ); +} \ No newline at end of file diff --git a/src/teams/ObjectTeams.tsx b/src/teams/ObjectTeams.tsx index d327291..595143f 100644 --- a/src/teams/ObjectTeams.tsx +++ b/src/teams/ObjectTeams.tsx @@ -100,6 +100,8 @@ interface Props { userCallback?: (users?: User[] | Team[] | undefined) => void; dialog?: string; cardMode?: boolean; + keys?: string[]; + types?: string[]; } export default function ObjectTeams(props: Props) { @@ -120,7 +122,9 @@ export default function ObjectTeams(props: Props) { refreshCallback, userCallback, dialog, - cardMode + cardMode, + keys, + types } = props; const prevPermissions = usePrevious(permissions); const prevIsDialogOpen = usePrevious(isDialogOpen); @@ -146,7 +150,7 @@ export default function ObjectTeams(props: Props) { const load = useCallback(() => { setIsLoading(true); teamAPI - .listObjectTeams(scope, as) + .listObjectTeams(scope, as, keys, types) .then((response: any) => { let rTeams: Team[] = []; or(response.data, { teams: [] }).teams.forEach((user: any) => { @@ -588,6 +592,8 @@ export default function ObjectTeams(props: Props) { permissions={permissions} isDialogOpen={isShareObjectDialogOpen} closeDialogCallback={closeShareObjectDialog} + keys={keys} + types={types} /> ); diff --git a/src/teams/ShareWithTeam.tsx b/src/teams/ShareWithTeam.tsx index 070a8c7..89c9a03 100644 --- a/src/teams/ShareWithTeam.tsx +++ b/src/teams/ShareWithTeam.tsx @@ -88,6 +88,8 @@ interface Props { permissions: pond.Permission[]; isDialogOpen: boolean; closeDialogCallback: Function; + keys?: string[], + types?: string[] } export default function ShareObject(props: Props) { @@ -96,7 +98,7 @@ export default function ShareObject(props: Props) { const classes = useStyles(); const permissionAPI = usePermissionAPI(); const { info, success } = useSnackbar(); - const { scope, label, permissions, isDialogOpen, closeDialogCallback } = props; + const { scope, label, permissions, isDialogOpen, closeDialogCallback, keys, types } = props; const [sharedPermissions, setSharedPermissions] = useState([ pond.Permission.PERMISSION_READ ]); @@ -109,7 +111,7 @@ export default function ShareObject(props: Props) { const [teamKey, setTeamKey] = useState(""); const share = () => { - permissionAPI.shareObjectByKey(scope, teamKey, sharedPermissions).then(resp => { + permissionAPI.shareObjectByKey(scope, teamKey, sharedPermissions, keys, types).then(resp => { let shareBins = true; if (resp && resp.data && resp.data.existing) { success(label + " was shared with team"); @@ -123,7 +125,7 @@ export default function ShareObject(props: Props) { resp.data.bins.forEach(bin => { if (bin.settings) { let newScope = binScope(bin.settings?.key); - permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions).catch(_err => { + permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions, keys, types).catch(_err => { successBins = false; }); } @@ -143,7 +145,7 @@ export default function ShareObject(props: Props) { resp.data.gates.forEach(gate => { if (gate) { let newScope = { key: gate.key, kind: "gate" } as Scope; - permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions).catch(_err => { + permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions, keys, types).catch(_err => { successGates = false; }); } From ce41a2dc05626a19b7e7ae21aa104dbca33da4ac Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 25 Mar 2026 15:45:59 -0600 Subject: [PATCH 05/24] replacing the getTemperatureUnit function in units that uses the local storage with a function in the user model to just get it from the user settings --- src/bin/BinConditioningInteraction.tsx | 10 +- src/bin/BinSettings.tsx | 18 +- src/bin/BinStorageConditions.tsx | 18 +- src/bin/BinVisualizerV2.tsx | 8 +- src/bin/conditioning/modeChangeDialog.tsx | 9 +- src/common/ResponsiveTable.tsx | 2 +- src/device/DevicePresetsFromPicker.tsx | 7 +- src/device/presets/devicePresetCard.tsx | 20 +- src/gate/GateDeltaTempGraph.tsx | 7 +- src/gate/GateList.tsx | 3 +- src/models/user.ts | 4 + src/objects/ObjectDescriber.tsx | 224 +++++++++--------- src/objects/ObjectTable.tsx | 15 +- src/objects/bulkEditForms/bulkBinSettings.tsx | 10 +- src/pages/Heater.tsx | 3 +- src/pbHelpers/MeasurementDescriber.ts | 8 +- 16 files changed, 188 insertions(+), 178 deletions(-) diff --git a/src/bin/BinConditioningInteraction.tsx b/src/bin/BinConditioningInteraction.tsx index bd6c3c7..0a7e694 100644 --- a/src/bin/BinConditioningInteraction.tsx +++ b/src/bin/BinConditioningInteraction.tsx @@ -21,7 +21,7 @@ import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { pond, quack } from "protobuf-ts/pond"; import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers"; import React, { useEffect, useState } from "react"; -import { avg, fahrenheitToCelsius, getTemperatureUnit } from "utils"; +import { avg, fahrenheitToCelsius } from "utils"; import { makeStyles } from "@mui/styles"; import { Mark } from "@mui/material/Slider/useSlider.types"; @@ -131,7 +131,7 @@ export default function BinConditioningInteraction(props: Props) { temp && hum ) { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { //the emc calc needs the temp to be in celsius temp = fahrenheitToCelsius(temp); } @@ -139,7 +139,7 @@ export default function BinConditioningInteraction(props: Props) { setBaseEMC(emc === hum ? undefined : emc); } - }, [sliderVals, grain]); + }, [sliderVals, grain, user]); const updateInteraction = () => { interactionAPI @@ -178,7 +178,7 @@ export default function BinConditioningInteraction(props: Props) { let marks: Mark[] = []; if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { labelTail = "°F"; } else { labelTail = "°C"; @@ -220,7 +220,7 @@ export default function BinConditioningInteraction(props: Props) { quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE ) { if ( - getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ) { return value.toFixed(1) + "°F"; } else { diff --git a/src/bin/BinSettings.tsx b/src/bin/BinSettings.tsx index 58669d6..bbfb4b5 100644 --- a/src/bin/BinSettings.tsx +++ b/src/bin/BinSettings.tsx @@ -61,7 +61,7 @@ import { pond } from "protobuf-ts/pond"; import { useBinAPI, useBinYardAPI, useGlobalState, useLibraCartProxyAPI } from "providers"; import React, { useCallback, useEffect, useState } from "react"; // import { useHistory } from "react-router"; -import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils"; +import { getDistanceUnit, or, getGrainUnit } from "utils"; import { makeStyles } from "@mui/styles"; import { useNavigate } from "react-router-dom"; import BinSelector from "./BinSelector"; @@ -290,7 +290,7 @@ export default function BinSettings(props: Props) { let high = initForm.highTemp ?? 20; let low = initForm.lowTemp ?? 10; let target = initForm.inventory?.targetTemperature ?? 15; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { high = parseFloat((high * 1.8 + 32).toFixed(2)); low = parseFloat((low * 1.8 + 32).toFixed(2)); target = parseFloat((target * 1.8 + 32).toFixed(2)); @@ -364,7 +364,7 @@ export default function BinSettings(props: Props) { } else { setInitialized(false); } - }, [bin, open, mode, openedBinYard]); + }, [bin, open, mode, openedBinYard, user]); useEffect(() => { if (mode === "remove") { @@ -2308,7 +2308,7 @@ export default function BinSettings(props: Props) { onChange={event => { let value = event?.target.value; let valueC = value; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2); } @@ -2320,7 +2320,7 @@ export default function BinSettings(props: Props) { InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "F" : "C"} @@ -2348,7 +2348,7 @@ export default function BinSettings(props: Props) { onChange={event => { let value = event?.target.value; let valueC = value; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2); } setLowTempC(+valueC); @@ -2357,7 +2357,7 @@ export default function BinSettings(props: Props) { InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "F" : "C"} @@ -2383,7 +2383,7 @@ export default function BinSettings(props: Props) { onChange={event => { let value = event?.target.value; let valueC = value; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2); } setHighTempC(+valueC); @@ -2392,7 +2392,7 @@ export default function BinSettings(props: Props) { InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "F" : "C"} diff --git a/src/bin/BinStorageConditions.tsx b/src/bin/BinStorageConditions.tsx index fa0e957..b0e7e8a 100644 --- a/src/bin/BinStorageConditions.tsx +++ b/src/bin/BinStorageConditions.tsx @@ -24,7 +24,7 @@ import Co2Icon from "products/CommonIcons/co2Icon"; import { pond, quack } from "protobuf-ts/pond"; import { useBinAPI, useGlobalState, useSnackbar } from "providers"; import React, { useEffect, useState } from "react"; -import { avg, getTemperatureUnit } from "utils"; +import { avg } from "utils"; import { makeStyles } from "@mui/styles"; import { Mark } from "@mui/material/Slider/useSlider.types"; import { CO2 } from "models/CO2"; @@ -134,7 +134,7 @@ export default function BinStorageConditions(props: Props) { const binAPI = useBinAPI(); const { openSnack } = useSnackbar(); const { bin, headspaceCO2, cables } = props; - const [{as}] = useGlobalState() + const [{as, user}] = useGlobalState() const [sliderTemps, setSliderTemps] = useState([-40, 40]); //boolean that if a cable is missing its filled to display an icon or something to let the user know not all of the cables have their fill set const [missingTopNodeWarning, setMissingTopNodeWarning] = useState(false); @@ -233,7 +233,7 @@ export default function BinStorageConditions(props: Props) { } setTempTargets(tempCounts); let tempVal = bin.settings.inventory?.targetTemperature ?? 0; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { tempVal = tempVal * 1.8 + 32; } setTargetTemp(tempVal.toFixed(1)); @@ -244,7 +244,7 @@ export default function BinStorageConditions(props: Props) { setEmcTargets(emcCounts); setTargetEMC(bin.settings.inventory?.targetMoisture.toFixed(1) ?? ""); setEmcDeviation(bin.settings.inventory?.moistureTargetDeviation.toFixed(1) ?? ""); - }, [bin, cables]); + }, [bin, cables, user]); //useEffect that watches for changes in the target emc and deviation to update the targets useEffect(() => { @@ -301,7 +301,7 @@ export default function BinStorageConditions(props: Props) { settings.highTemp = sliderTemps[1]; if (settings.inventory) { let tempVal = parseFloat(targetTemp); - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { tempVal = Math.fround(((tempVal - 32) * 5) / 9); } settings.inventory.targetTemperature = tempVal; @@ -376,14 +376,14 @@ export default function BinStorageConditions(props: Props) { let mark: Mark[] = []; if (averageTemp) { let temp = averageTemp; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = temp * 1.8 + 32; } mark = [ { label: customMark( temp.toFixed(1) + - (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"), "AVG" @@ -422,7 +422,7 @@ export default function BinStorageConditions(props: Props) { InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"} @@ -451,7 +451,7 @@ export default function BinStorageConditions(props: Props) { max={sliderEdge} valueLabelDisplay="on" valueLabelFormat={value => { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { return ((value * 9) / 5 + 32).toFixed(1) + "°F"; } return value.toFixed(1) + "°C"; diff --git a/src/bin/BinVisualizerV2.tsx b/src/bin/BinVisualizerV2.tsx index 2da6f78..1426dd0 100644 --- a/src/bin/BinVisualizerV2.tsx +++ b/src/bin/BinVisualizerV2.tsx @@ -40,7 +40,7 @@ import { FullScreen, useFullScreenHandle } from "react-full-screen"; import moment, { Moment } from "moment"; import ResponsiveDialog from "common/ResponsiveDialog"; import { getThemeType } from "theme"; -import { getGrainUnit, getTemperatureUnit, or } from "utils"; +import { getGrainUnit, or } from "utils"; import { useBinAPI } from "providers/pond/binAPI"; import BindaptIcon from "products/Bindapt/BindaptIcon"; import { @@ -605,7 +605,7 @@ export default function BinVisualizer(props: Props) { break; } if (valC) { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = CtoF(valC).toFixed(1) + "°F"; } else { temp = valC.toFixed(1) + "°C"; @@ -705,7 +705,7 @@ export default function BinVisualizer(props: Props) { break; } if (valC) { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = Math.abs(valC * 1.8).toFixed(1) + "°F"; //since this is a measurement of change a change in 1 degrre celsius is equivalent to 1.8 fahrenheit } else { temp = Math.abs(valC).toFixed(1) + "°C"; @@ -1676,7 +1676,7 @@ export default function BinVisualizer(props: Props) { fontWeight: 650, color: tempColour }}> - {ambient?.getTempString(getTemperatureUnit())} + {ambient?.getTempString(user.tempUnit())} diff --git a/src/bin/conditioning/modeChangeDialog.tsx b/src/bin/conditioning/modeChangeDialog.tsx index b31c8f8..cc17448 100644 --- a/src/bin/conditioning/modeChangeDialog.tsx +++ b/src/bin/conditioning/modeChangeDialog.tsx @@ -19,7 +19,6 @@ import { sameComponentID } from "pbHelpers/Component"; import moment from "moment"; import { lowerCase } from "lodash"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; -import { fahrenheitToCelsius, getTemperatureUnit } from "utils"; import { GetGrainExtensionMap } from "grain"; import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress"; @@ -76,7 +75,7 @@ export default function ModeChangeDialog(props: Props){ changeOutdoorHumidity, presets } = props - const [{as}] = useGlobalState() + const [{as, user}] = useGlobalState() const [componentSets, setComponentSets] = useState([]) const interactionAPI = useInteractionsAPI() const componentAPI = useComponentAPI(); @@ -296,7 +295,7 @@ if (!selectedDevice) return; conditions.push(humidityCondition); //since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = Math.round((temp * (9 / 5) + 32) * 100) / 100; } let tempVal = describeMeasurement( @@ -392,7 +391,7 @@ if (!selectedDevice) return; conditions.push(fanConditionOne); //since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100; } let tempVal = describeMeasurement( @@ -631,7 +630,7 @@ if (!selectedDevice) return; InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "℃"} diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index 4c21901..e3afd3f 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -614,7 +614,7 @@ export default function ResponsiveTable(props: Props) { {customElement && - + {customElement} diff --git a/src/device/DevicePresetsFromPicker.tsx b/src/device/DevicePresetsFromPicker.tsx index 900df87..5faaa1b 100644 --- a/src/device/DevicePresetsFromPicker.tsx +++ b/src/device/DevicePresetsFromPicker.tsx @@ -33,7 +33,6 @@ import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { pond, quack } from "protobuf-ts/pond"; import React, { useEffect, useState } from "react"; import CableTopNodeSummary from "bin/CableTopNodeSummary"; -import { getTemperatureUnit } from "utils"; import { DevicePreset } from "models/DevicePreset"; import { Ambient } from "models/Ambient"; import { useGlobalState } from "providers"; @@ -182,7 +181,7 @@ export default function DevicePresets(props: DevicePresetsProps) { const [heaters, setHeaters] = useState([]); const [fans, setFans] = useState([]); //const [subscribeBinToAutoTopNode, setSubscribeBinToAutoTopNode] = useState(false); - const [{as}] = useGlobalState(); + const [{as, user}] = useGlobalState(); useEffect(() => { if (deviceComponents.get(deviceIndex.toString())) return; @@ -445,7 +444,7 @@ export default function DevicePresets(props: DevicePresetsProps) { conditions.push(fanConditionOne); //since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100; } let tempVal = describeMeasurement( @@ -541,7 +540,7 @@ export default function DevicePresets(props: DevicePresetsProps) { } //since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = Math.round((temp * (9 / 5) + 32) * 100) / 100; } let tempVal = describeMeasurement( diff --git a/src/device/presets/devicePresetCard.tsx b/src/device/presets/devicePresetCard.tsx index 2839df3..30cbc63 100644 --- a/src/device/presets/devicePresetCard.tsx +++ b/src/device/presets/devicePresetCard.tsx @@ -18,10 +18,9 @@ import { cloneDeep } from "lodash"; import { DevicePreset } from "models/DevicePreset"; import { ObjectTypeString } from "objects/ObjectDescriber"; import { pond } from "protobuf-ts/pond"; -import { useSnackbar } from "providers"; +import { useGlobalState, useSnackbar } from "providers"; import { useDevicePresetAPI } from "providers/pond/devicePresetAPI"; import React, { useEffect, useState } from "react"; -import { getTemperatureUnit } from "utils"; interface Props { preset: DevicePreset; @@ -54,6 +53,7 @@ export default function DevicePresetCard(props: Props) { const [humString, setHumString] = useState("0"); const [hum, setHum] = useState(0); const isMobile = useMobile(); + const [{user}] = useGlobalState(); useEffect(() => { setPresetName(preset.name); @@ -63,11 +63,11 @@ export default function DevicePresetCard(props: Props) { setHum(preset.settings.humidity); setHumString(preset.settings.humidity.toString()); let displayTemp = preset.settings.temperature.toString(); - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed(); } setTempString(displayTemp); - }, [preset]); + }, [preset, user]); const saveNewPreset = () => { let typestring; @@ -296,7 +296,7 @@ export default function DevicePresetCard(props: Props) { InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"} @@ -307,7 +307,7 @@ export default function DevicePresetCard(props: Props) { onChange={e => { if (!isNaN(+e.target.value)) { let temp = +e.target.value; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10; } setTempC(temp); @@ -324,7 +324,7 @@ export default function DevicePresetCard(props: Props) { max={40} valueLabelDisplay="auto" valueLabelFormat={value => { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { return ((value * 9) / 5 + 32).toFixed() + "°F"; } return value.toFixed() + "°C"; @@ -350,7 +350,7 @@ export default function DevicePresetCard(props: Props) { max={40} valueLabelDisplay="auto" valueLabelFormat={value => { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { return ((value * 9) / 5 + 32).toFixed() + "°F"; } return value.toFixed() + "°C"; @@ -371,7 +371,7 @@ export default function DevicePresetCard(props: Props) { InputProps={{ endAdornment: ( - {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"} @@ -382,7 +382,7 @@ export default function DevicePresetCard(props: Props) { onChange={e => { if (!isNaN(+e.target.value)) { let temp = +e.target.value; - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10; } setTempC(temp); diff --git a/src/gate/GateDeltaTempGraph.tsx b/src/gate/GateDeltaTempGraph.tsx index d547b1c..cad0564 100644 --- a/src/gate/GateDeltaTempGraph.tsx +++ b/src/gate/GateDeltaTempGraph.tsx @@ -11,7 +11,6 @@ import { pond } from "protobuf-ts/pond" import { quack } from "protobuf-ts/quack" import { useGlobalState } from "providers" import { useEffect, useState } from "react" -import { getTemperatureUnit } from "utils" interface Props { @@ -190,7 +189,7 @@ export default function GateDeltaTempGraph(props: Props){ {"Delta Temp: "} 0 ? warmingColour : coolingColour, fontWeight: 500 }}> - {recent.value.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")} + {recent.value.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
@@ -213,8 +212,8 @@ export default function GateDeltaTempGraph(props: Props){ diff --git a/src/gate/GateList.tsx b/src/gate/GateList.tsx index 5910808..ba521cc 100644 --- a/src/gate/GateList.tsx +++ b/src/gate/GateList.tsx @@ -17,7 +17,6 @@ import moment from "moment"; import { pond } from "protobuf-ts/pond"; import { UnitMeasurement } from "models/UnitMeasurement"; import { quack } from "protobuf-ts/quack"; -import { getTemperatureUnit } from "utils"; import { teal } from "@mui/material/colors"; import { react } from "@babel/types"; import { getDeviceStateHelper } from "pbHelpers/DeviceState"; @@ -229,7 +228,7 @@ export default function GateList(props: Props) { }) if(ambientTemp && outletTemp){ let deltaTemp = outletTemp - ambientTemp - display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C") + display = deltaTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C") }else{ display = "Sensor Missing" } diff --git a/src/models/user.ts b/src/models/user.ts index 6c46a31..a56ec5e 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -101,4 +101,8 @@ export class User { } return this.settings.features.includes(flag); } + + public tempUnit(): pond.TemperatureUnit { + return this.settings.temperatureUnit + } } diff --git a/src/objects/ObjectDescriber.tsx b/src/objects/ObjectDescriber.tsx index 643482a..3faad15 100644 --- a/src/objects/ObjectDescriber.tsx +++ b/src/objects/ObjectDescriber.tsx @@ -2,12 +2,12 @@ import { Column } from "common/ResponsiveTable"; import { Option } from "common/SearchSelect"; import GrainDescriber from "grain/GrainDescriber"; //import { Column } from "material-table"; -import { Bin, BinYard, Field } from "models"; +import { Bin, BinYard, Field, User } from "models"; //import { Gate } from "models/Gate"; import { GrainBag } from "models/GrainBag"; //import { ObjectHeater } from "models/ObjectHeater"; import { pond } from "protobuf-ts/pond"; -import { getDistanceUnit, getTemperatureUnit } from "utils"; +import { getDistanceUnit } from "utils"; interface Sort { order: string; //what to send to the backend list to sort by @@ -20,6 +20,8 @@ export interface ObjectExtension { isTransactionObject: boolean; //this will define the columns to be used for the object in a material table tableColumns: Column[]; + // Optional factory so columns can depend on the active user settings (units, formatting, etc.) + getTableColumns?: (user?: User) => Column[]; //this map will match the title of the column to what the backend needs to perform the ordering tableSort: Map; } @@ -32,6 +34,113 @@ const defaultObject: ObjectExtension = { tableSort: new Map() }; +const binColumns = (user?: User): Column[] => { + const temperatureUnit = + user?.tempUnit() ?? pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS; + + return [ + { + title: "Name", + cellStyle: {padding: "16px"}, + render: row => { + return (
{row.settings.name}
); + } + }, + { + title: "Grain", + cellStyle: {padding: "16px"}, + render: row => { + let grain = row.settings.inventory?.grainType; + if (grain) { + return GrainDescriber(grain).name; + } + } + }, + { + title: "Bin Height", + cellStyle: {padding: "16px"}, + render: row => { + let d = row.settings.specs?.heightCm; + if (d) { + if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { + return (d / 100).toFixed(2) + " m"; + } else { + return (d / 30.48).toFixed(2) + " ft"; + } + } + } + }, + { + title: "Bin Diameter", + cellStyle: {padding: "16px"}, + render: row => { + let d = row.settings.specs?.diameterCm; + if (d) { + if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { + return (d / 100).toFixed(2) + " m"; + } else { + return (d / 30.48).toFixed(2) + " ft"; + } + } + } + }, + { + title: "Custom Grain Name", + cellStyle: {padding: "16px"}, + render: row => { + return row.settings.inventory?.customTypeName; + } + }, + { + title: "Grain Variant", + cellStyle: {padding: "16px"}, + render: row => { + return row.settings.inventory?.grainSubtype; + } + }, + { + title: "Grain Bushels", + cellStyle: {padding: "16px"}, + render: row => { + return (
{row.settings.inventory ? isNaN(row.settings.inventory.grainBushels) ? 0 : row.settings.inventory.grainBushels : 0}
); + } + }, + { + title: "Grain Capacity", + cellStyle: {padding: "16px"}, + render: row => { + return row.settings.specs?.bushelCapacity; + } + }, + { + title: "High Temp Warning", + cellStyle: {padding: "16px"}, + render: row => { + let t = row.settings.highTemp; + if (t) { + if (temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + return (t * 1.8 + 32).toFixed(2) + " °F"; + } + return t.toFixed(2) + " °C"; + } + } + }, + { + title: "Low Temp Warning", + cellStyle: {padding: "16px"}, + render: row => { + let t = row.settings.lowTemp; + if (t) { + if (temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + return (t * 1.8 + 32).toFixed(2) + " °F"; + } + return t.toFixed(2) + " °C"; + } + } + } + ] as Column[]; +}; + export const ObjectExtensions: Map = new Map([ [pond.ObjectType.OBJECT_TYPE_UNKNOWN, defaultObject], [ @@ -50,109 +159,8 @@ export const ObjectExtensions: Map = new Map([ name: "Bin", inventoryGroup: "grain", isTransactionObject: true, - tableColumns: [ - { - title: "Name", - cellStyle: {padding: "16px"}, - render: row => { - return (
{row.settings.name}
); - } - }, - { - title: "Grain", - cellStyle: {padding: "16px"}, - render: row => { - let grain = row.settings.inventory?.grainType; - if (grain) { - return GrainDescriber(grain).name; - } - } - }, - { - title: "Bin Height", - cellStyle: {padding: "16px"}, - render: row => { - let d = row.settings.specs?.heightCm; - if (d) { - if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { - return (d / 100).toFixed(2) + " m"; - } else { - return (d / 30.48).toFixed(2) + " ft"; - } - } - } - }, - { - title: "Bin Diameter", - cellStyle: {padding: "16px"}, - render: row => { - let d = row.settings.specs?.diameterCm; - if (d) { - if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { - return (d / 100).toFixed(2) + " m"; - } else { - return (d / 30.48).toFixed(2) + " ft"; - } - } - } - }, - { - title: "Custom Grain Name", - cellStyle: {padding: "16px"}, - render: row => { - return row.settings.inventory?.customTypeName; - } - }, - { - title: "Grain Variant", - cellStyle: {padding: "16px"}, - render: row => { - return row.settings.inventory?.grainSubtype; - } - }, - { - title: "Grain Bushels", - cellStyle: {padding: "16px"}, - render: row => { - return (
{row.settings.inventory ? isNaN(row.settings.inventory.grainBushels) ? 0 : row.settings.inventory.grainBushels : 0}
); - } - }, - { - title: "Grain Capacity", - cellStyle: {padding: "16px"}, - render: row => { - return row.settings.specs?.bushelCapacity; - } - }, - { - title: "High Temp Warning", - cellStyle: {padding: "16px"}, - render: row => { - let t = row.settings.highTemp; - if (t) { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { - return (t * 1.8 + 32).toFixed(2) + " °F"; - } else { - return t.toFixed(2) + " °C"; - } - } - } - }, - { - title: "Low Temp Warning", - cellStyle: {padding: "16px"}, - render: row => { - let t = row.settings.lowTemp; - if (t) { - if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { - return (t * 1.8 + 32).toFixed(2) + " °F"; - } else { - return t.toFixed(2) + " °C"; - } - } - } - } - ] as Column[], + tableColumns: binColumns(), + getTableColumns: (user?: User) => binColumns(user), tableSort: new Map([ ["Name", { order: "name", numerical: false }], ["Grain", { order: "inventory.grainType", numerical: false }], @@ -708,8 +716,7 @@ export const ObjectExtensions: Map = new Map([ ]); export default function ObjectDescriber(type: pond.ObjectType): ObjectExtension { - let describer = ObjectExtensions.get(type); - return describer ? describer : defaultObject; + return ObjectExtensions.get(type) ?? defaultObject; } /** @@ -745,7 +752,8 @@ export function SearchableObjects(): Option[] { Object.values(pond.ObjectType).forEach(obj => { if (typeof obj !== "string") { let ext = ObjectDescriber(obj); - if (ext.tableColumns.length > 0) { + const columns = ext.getTableColumns ? ext.getTableColumns() : ext.tableColumns; + if (columns.length > 0) { options.push({ label: ext.name, value: obj diff --git a/src/objects/ObjectTable.tsx b/src/objects/ObjectTable.tsx index 7a7b677..2ef6c86 100644 --- a/src/objects/ObjectTable.tsx +++ b/src/objects/ObjectTable.tsx @@ -1,6 +1,5 @@ import { Box, Button, Grid2 as Grid } from "@mui/material"; // import { getTableIcons } from "common/ResponsiveTable"; -import SearchBar from "common/SearchBar"; import SearchSelect, { Option } from "common/SearchSelect"; // import MaterialTable, { MTableToolbar } from "material-table"; // import { Bin, BinYard, Field } from "models"; @@ -22,9 +21,8 @@ import { import { useCallback, useEffect, useState } from "react"; import TeamSearch from "teams/TeamSearch"; import BulkBinSettings from "./bulkEditForms/bulkBinSettings"; -import BulkGrainBagSettings from "./bulkEditForms/bulkGrainBagSettings"; import ResponsiveTable from "common/ResponsiveTable"; -import { cloneDeep, indexOf } from "lodash"; +import { cloneDeep } from "lodash"; //import BulkGateSettings from "./bulkEditForms/bulkGateSettings"; interface customButton { @@ -61,6 +59,9 @@ export default function ObjectTable(props: Props) { const [selectedUser, setSelectedUser] = useState(as ?? user.id()); const [selectedPageRows, setSelectedPageRows] = useState>(new Map())//key is the page value is an array of row indexes for that page const [tableLoading, setTableLoading] = useState(false); + + const objectExtension = ObjectDescriber(currentType); + const columns = objectExtension.getTableColumns ? objectExtension.getTableColumns(user) : objectExtension.tableColumns; //the api's to load all the objects available to look at in this table const binAPI = useBinAPI(); const binyardAPI = useBinYardAPI(); @@ -366,7 +367,7 @@ export default function ObjectTable(props: Props) { { if (by !== -1) { - let colName = ObjectDescriber(currentType).tableColumns[by].title?.toString(); + let colName = ObjectDescriber(currentType, user).tableColumns[by].title?.toString(); let order; if (colName) { - order = ObjectDescriber(currentType).tableSort.get(colName); + order = ObjectDescriber(currentType, user).tableSort.get(colName); setCurrentOrder(order?.order); setCurrentDirection(direction); setIsNumerical(order?.numerical); diff --git a/src/objects/bulkEditForms/bulkBinSettings.tsx b/src/objects/bulkEditForms/bulkBinSettings.tsx index f3e01a9..ea8da7a 100644 --- a/src/objects/bulkEditForms/bulkBinSettings.tsx +++ b/src/objects/bulkEditForms/bulkBinSettings.tsx @@ -4,7 +4,7 @@ import { GrainOptions } from "grain"; import { pond } from "protobuf-ts/pond"; import { useBinAPI, useGlobalState, useSnackbar } from "providers"; import React, { useState } from "react"; -import { fahrenheitToCelsius, getDistanceUnit, getTemperatureUnit } from "utils"; +import { fahrenheitToCelsius, getDistanceUnit } from "utils"; interface Props { selectedBins: pond.Bin[]; @@ -19,7 +19,7 @@ export default function BulkBinSettings(props: Props) { const gridItemWidth = 3; // bin settings variables const [name, setName] = useState(); - const [{as}] = useGlobalState(); + const [{as, user}] = useGlobalState(); const [grainType, setGrainType] = useState(); const [grainOption, setGrainOption] = useState
diff --git a/src/grain/CustomGrainForm.tsx b/src/grain/CustomGrainForm.tsx index a13478c..8d5fd42 100644 --- a/src/grain/CustomGrainForm.tsx +++ b/src/grain/CustomGrainForm.tsx @@ -2,8 +2,7 @@ import {MenuItem, TextField } from "@mui/material" import { cloneDeep } from "lodash" import { pond } from "protobuf-ts/pond" import React, { useEffect, useRef, useState } from "react" -import { getGrainUnit } from "utils" - +import { useGlobalState } from "providers"; interface Props { grainSettings?: pond.GrainSettings onGrainSettingsChange: (settings: pond.GrainSettings, formValid: boolean) => void @@ -11,6 +10,7 @@ interface Props { export default function CustomGrainForm(props: Props) { const {grainSettings, onGrainSettingsChange} = props + const [{ user }] = useGlobalState(); const [newGrainSettings, setNewGrainSettings] = useState(pond.GrainSettings.create()) const [name, setName] = useState("") const [group, setGroup] = useState("") @@ -204,7 +204,7 @@ export default function CustomGrainForm(props: Props) { valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val) let settings = cloneDeep(newGrainSettings) if(!isNaN(parseFloat(val))){ - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ settings.bushelsPerTonne = parseFloat(val)/0.907 }else{ settings.bushelsPerTonne = parseFloat(val) @@ -212,7 +212,7 @@ export default function CustomGrainForm(props: Props) { } settingsChanged(settings) }} - label={"Bushels per " + (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/> + label={"Bushels per " + (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/> ) } diff --git a/src/grain/GrainTransaction.tsx b/src/grain/GrainTransaction.tsx index 5605b3a..c6b2cfe 100644 --- a/src/grain/GrainTransaction.tsx +++ b/src/grain/GrainTransaction.tsx @@ -29,7 +29,6 @@ import { useTransactionAPI } from "providers"; import { useState, useEffect } from "react"; -import { getGrainUnit } from "utils"; interface Props { mainObject: Bin | GrainBag | Field | Contract; @@ -451,7 +450,7 @@ export default function GrainTransaction(props: Props) { }; const grainUnitDisplay = () => { - switch (getGrainUnit()){ + switch (user.grainUnit()){ case pond.GrainUnit.GRAIN_UNIT_TONNE: return "mT" case pond.GrainUnit.GRAIN_UNIT_TON: @@ -525,7 +524,7 @@ export default function GrainTransaction(props: Props) { onChange={e => { //if the user is viewing the grain in weight let grainVal = e.target.value; - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) { //need to convert what the user input (weight) into what is actually stored (bushels) //use source of the conversion value if it was selected, otherwise use the destination if (!isNaN(parseFloat(e.target.value))) { @@ -535,7 +534,7 @@ export default function GrainTransaction(props: Props) { grainVal = (+grainVal * selectedDestination.value.bushelsPerTonne()).toFixed(2); } } - }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { + }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { if (!isNaN(parseFloat(e.target.value))) { if (selectedSource) { if(selectedSource.value.bushelsPerTonne){ diff --git a/src/grainBag/grainBagInventoryGraph.tsx b/src/grainBag/grainBagInventoryGraph.tsx index 6a661a2..969ab3c 100644 --- a/src/grainBag/grainBagInventoryGraph.tsx +++ b/src/grainBag/grainBagInventoryGraph.tsx @@ -6,7 +6,6 @@ import moment, { Moment } from "moment"; import { pond } from "protobuf-ts/pond"; import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers"; import { useEffect, useState } from "react"; -import { getGrainUnit } from "utils"; interface Props { grainBag: GrainBag; @@ -21,7 +20,7 @@ export default function GrainBagInventoryGraph(props: Props) { const [data, setData] = useState([]); const [loadingData, setLoadingData] = useState(false); const { openSnack } = useSnackbar(); - const [{as}] = useGlobalState(); + const [{as, user}] = useGlobalState(); useEffect(() => { if (grainBag.key() && !loadingData) { @@ -36,12 +35,12 @@ export default function GrainBagInventoryGraph(props: Props) { //let time = hist.timestamp; let val = hist.object.grainBagSettings.currentBushels ?? 0; if ( - getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && + user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1 ) { val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100; } - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100; } if (val !== lastBushels) { @@ -55,10 +54,10 @@ export default function GrainBagInventoryGraph(props: Props) { }); if (barData.length === 0) { let val = grainBag.bushels() - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){ val = grainBag.bushels() / grainBag.bushelsPerTonne() } - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ val = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907) } barData.push({ @@ -79,10 +78,10 @@ export default function GrainBagInventoryGraph(props: Props) { const maxYAxis = () => { let val = grainBag.capacity() - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100; } - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100; } return val diff --git a/src/grainBag/grainBagSettings.tsx b/src/grainBag/grainBagSettings.tsx index cf5f703..1ec5f2d 100644 --- a/src/grainBag/grainBagSettings.tsx +++ b/src/grainBag/grainBagSettings.tsx @@ -22,7 +22,6 @@ import { pond } from "protobuf-ts/pond"; import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers"; import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { getGrainUnit } from "utils"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -84,9 +83,9 @@ export default function GrainBagSettings(props: Props) { : grainBag.settings.length ); let grainVal = grainBag.bushels(); - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) { grainVal = grainBag.bushels() / grainBag.bushelsPerTonne(); - }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { + }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { grainVal = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907) } setGrainFill(grainVal.toFixed(2)); @@ -120,7 +119,7 @@ export default function GrainBagSettings(props: Props) { const submit = () => { //if a bag was passed in do an update let tonneConversion = bushelConversion - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ tonneConversion = bushelConversion / 0.907 } if (grainBag) { @@ -255,9 +254,9 @@ export default function GrainBagSettings(props: Props) { const grainUnitDisplay = () => { if(bushelConversion > 1){ - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ return "mT" - }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ + }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ return "t" } }else{ @@ -422,7 +421,7 @@ export default function GrainBagSettings(props: Props) { value={grainFill} onChange={e => { let bushelVal = +e.target.value; - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { //convert the number as a weight into the bushel value, no changing or conversions necessary // since these are both user entered fields and should be the same unit (ton or tonne) bushelVal = +e.target.value * (bushelConversion > 0 ? bushelConversion : 1); diff --git a/src/grainBag/grainBagVisualizer.tsx b/src/grainBag/grainBagVisualizer.tsx index 1d0b5d8..319ad39 100644 --- a/src/grainBag/grainBagVisualizer.tsx +++ b/src/grainBag/grainBagVisualizer.tsx @@ -19,9 +19,9 @@ import moment from "moment"; import { pond } from "protobuf-ts/pond"; import { useEffect, useState } from "react"; import { getThemeType } from "theme"; -import { getGrainUnit } from "utils"; import GrainBagSVG from "./grainBagSVG"; import { makeStyles } from "@mui/styles"; +import { useGlobalState } from "providers"; interface Props { grainBag: GrainBag; @@ -68,6 +68,7 @@ export default function GrainBagVisualizer(props: Props) { const [grainDiff, setGrainDiff] = useState(); const theme = useTheme(); const [openTransaction, setOpenTransaction] = useState(false); + const [{ user }] = useGlobalState(); useEffect(() => { setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100); @@ -83,13 +84,13 @@ export default function GrainBagVisualizer(props: Props) { const grainOverlay = () => { let displayPending = pendingGrainAmount; let displayDiff = grainDiff; - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) { if (displayPending && displayDiff) { displayPending = Math.round((displayPending / grainBag.bushelsPerTonne()) * 100) / 100; displayDiff = Math.round((displayDiff / grainBag.bushelsPerTonne()) * 100) / 100; } } - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) { if (displayPending && displayDiff) { displayPending = Math.round((displayPending / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100; displayDiff = Math.round((displayDiff / (grainBag.bushelsPerTonne()* 0.907)) * 100) / 100; @@ -148,7 +149,7 @@ export default function GrainBagVisualizer(props: Props) { }; const grainAmountDisplay = () => { - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) { return (
@@ -157,7 +158,7 @@ export default function GrainBagVisualizer(props: Props) { ({grainBag.fillPercent()}%)
); - } else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) { + } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) { return (
diff --git a/src/models/Bin.ts b/src/models/Bin.ts index e720fec..5ad8635 100644 --- a/src/models/Bin.ts +++ b/src/models/Bin.ts @@ -1,8 +1,8 @@ import GrainDescriber from "grain/GrainDescriber"; import { cloneDeep } from "lodash"; import { MarkerData } from "maps/mapMarkers/Markers"; +import { User } from "models"; import { pond } from "protobuf-ts/pond"; -import { getGrainUnit } from "utils"; import { stringToMaterialColour } from "utils/strings"; import { or } from "utils/types"; @@ -236,11 +236,11 @@ export class Bin { return bpt; } - public grainInventory(): number { + public grainInventory(user: User): number { let grain = this.bushels() - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){ grain = this.bushels() / this.bushelsPerTonne() - }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){ + }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){ grain = this.bushels() / (this.bushelsPerTonne() * 0.907) } return Math.round(grain*100)/100 diff --git a/src/models/Contract.ts b/src/models/Contract.ts index ce55093..81bc001 100644 --- a/src/models/Contract.ts +++ b/src/models/Contract.ts @@ -2,8 +2,9 @@ import { Option } from "common/SearchSelect"; import { GrainOptions, ToGrainOption } from "grain"; import GrainDescriber from "grain/GrainDescriber"; import { cloneDeep } from "lodash"; +import { User } from "models"; import { pond } from "protobuf-ts/pond"; -import { getGrainUnit, stringToMaterialColour } from "utils"; +import { stringToMaterialColour } from "utils"; import { or } from "utils/types"; export class Contract { @@ -14,26 +15,28 @@ export class Contract { public label: string = ""; private objKey: string = ""; - public static create(pb?: pond.Contract): Contract { + public static create(pb?: pond.Contract, user?: User): Contract { let my = new Contract(); if (pb) { my.settings = pond.ContractSettings.fromObject(cloneDeep(or(pb.settings, {}))); my.title = pb.name; my.objKey = pb.key; - my.unit = my.measurementUnit(); + my.unit = my.measurementUnit(user); my.colour = my.commodityColour(); my.label = my.commodityLabel(); } return my; } - private measurementUnit(): string { + private measurementUnit(user?: User): string { if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) { - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){ - return "mT" - } - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ - return "t" + if( user ){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){ + return "mT" + } + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ + return "t" + } } return "bu"; } @@ -63,21 +66,22 @@ export class Contract { return label; } - public static clone(other?: Contract): Contract { + public static clone(other?: Contract, user?: User): Contract { if (other) { return Contract.create( pond.Contract.fromObject({ title: other.title, key: other.objKey, settings: cloneDeep(other.settings) - }) + }), + user ); } - return Contract.create(); + return Contract.create(undefined, user); } - public static any(data: any): Contract { - return Contract.create(pond.Contract.fromObject(cloneDeep(data))); + public static any(data: any, user?: User): Contract { + return Contract.create(pond.Contract.fromObject(cloneDeep(data)), user); } public key(): string { @@ -204,28 +208,28 @@ export class Contract { return this.conversionValue(); } - public sizeInPreferredUnit(): number { + public sizeInPreferredUnit(user: User): number { let size = this.settings.size; switch (this.settings.type) { case pond.ContractType.CONTRACT_TYPE_GRAIN: - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) { size = size / this.conversionValue(); } - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ size = size / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons } } return Math.round(size * 100) / 100; } - public deliveredInPreferredUnit(): number { + public deliveredInPreferredUnit(user: User): number { let del = this.settings.delivered; switch (this.settings.type) { case pond.ContractType.CONTRACT_TYPE_GRAIN: - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) { + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) { del = del / this.conversionValue(); } - if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ + if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ del = del / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons } } @@ -237,14 +241,15 @@ export class Contract { public static toStoredUnit( secondaryUnitVal: number, contractType: pond.ContractType, - conversionValue: number + conversionValue: number, + user: User ): number { let storedUnitVal = secondaryUnitVal; switch (contractType) { //use the value and conversion they entered directly, it is safe to assume they are both the same unit (ton vs tonne) so dont need to convert anything //before converting to bushels case pond.ContractType.CONTRACT_TYPE_GRAIN: - if ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) { + if ((user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) { storedUnitVal = secondaryUnitVal * conversionValue; } } diff --git a/src/pages/Bins.tsx b/src/pages/Bins.tsx index 1b9c75e..ad30d23 100644 --- a/src/pages/Bins.tsx +++ b/src/pages/Bins.tsx @@ -63,7 +63,7 @@ import { } from "providers"; import React, { SetStateAction, useCallback, useEffect, useState } from "react"; // import { getThemeType } from "theme"; -import { getGrainUnit, stringToMaterialColour } from "utils"; +import { stringToMaterialColour } from "utils"; //import InfiniteScroll from "react-infinite-scroller"; import PageContainer from "./PageContainer"; import ObjectTable from "objects/ObjectTable"; @@ -934,7 +934,7 @@ export default function Bins(props: Props) { return " bu"; } - switch (getGrainUnit()) { + switch (user.grainUnit()) { case pond.GrainUnit.GRAIN_UNIT_TONNE: return " mT"; case pond.GrainUnit.GRAIN_UNIT_TON: @@ -947,9 +947,9 @@ export default function Bins(props: Props) { const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => { let amount = bushels if(customInventory.bushelsPerTonne > 1){ - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ amount = bushels/customInventory.bushelsPerTonne - }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ + }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ amount = bushels/(customInventory.bushelsPerTonne*0.907) } } @@ -959,9 +959,9 @@ export default function Bins(props: Props) { const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => { const describer = GrainDescriber(grain) let amount = bushels - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ amount = bushels/describer.bushelsPerTonne - }else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ + }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ amount = bushels/describer.bushelsPerTon } return Math.round(amount*100)/100 diff --git a/src/pages/Contracts.tsx b/src/pages/Contracts.tsx index e93850b..fee4834 100644 --- a/src/pages/Contracts.tsx +++ b/src/pages/Contracts.tsx @@ -11,7 +11,7 @@ import PageContainer from "./PageContainer"; export default function Contracts() { const [openSettings, setOpenSettings] = useState(false); - const [{ as }] = useGlobalState() + const [{ as, user }] = useGlobalState() const contractAPI = useContractAPI(); const [contracts, setContracts] = useState([]); const [loading, setLoading] = useState(false); @@ -42,7 +42,7 @@ export default function Contracts() { let contractPermissions: Map = new Map(); if (resp.data.contracts){ resp.data.contracts.forEach(contract => { - let c = Contract.create(contract); + let c = Contract.create(contract, user); contracts.push(c); let p = pond.EvaluatePermissionsResponse.fromObject( resp.data.contractPermissions[c.key()] @@ -56,7 +56,7 @@ export default function Contracts() { }) .catch(err => {}); } - }, [contractAPI, year, as]); // eslint-disable-line react-hooks/exhaustive-deps + }, [contractAPI, year, as, user]); // eslint-disable-line react-hooks/exhaustive-deps return ( diff --git a/src/transactions/transactionDataDisplay.tsx b/src/transactions/transactionDataDisplay.tsx index b64db64..3bd6220 100644 --- a/src/transactions/transactionDataDisplay.tsx +++ b/src/transactions/transactionDataDisplay.tsx @@ -2,8 +2,8 @@ import { Box, Typography } from "@mui/material"; import GrainDescriber from "grain/GrainDescriber"; import { Transaction } from "models/Transaction"; import { pond } from "protobuf-ts/pond"; +import { useGlobalState } from "providers"; import React from "react"; -import { getGrainUnit } from "utils"; interface Props { transaction: Transaction; @@ -11,13 +11,14 @@ interface Props { export default function TransactionDataDisplay(props: Props) { const { transaction } = props; + const [{ user }] = useGlobalState(); console.log(transaction) const grainDisplay = (gt: pond.GrainTransaction) => { - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){ return "Weight: " + Math.round(gt.bushels / gt.bushelsPerTonne*100)/100 + " mT" } - if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){ + if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){ return "Weight: " + Math.round(gt.bushels / (gt.bushelsPerTonne*0.907)*100)/100 + " t" } return "Bushels: " + gt.bushels diff --git a/src/utils/units.ts b/src/utils/units.ts index 3194668..075a9e1 100644 --- a/src/utils/units.ts +++ b/src/utils/units.ts @@ -57,6 +57,7 @@ export function setDistanceUnit(unit: pond.DistanceUnit) { localStorage.setItem("distance", unit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"); } +// function is deprecated, use User.grainUnit() instead export function getGrainUnit(): pond.GrainUnit { switch(localStorage.getItem("grainUnit")){ case "mT": @@ -84,12 +85,10 @@ export function setGrainUnit(unit: pond.GrainUnit) { default: localStorage.setItem("grainUnit", "bu") } - // localStorage.setItem("grainUnit", unit === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu"); } export const distanceConversion = (val: number, distanceUnit: pond.DistanceUnit) => { let converted = val; - if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) { converted = val / 3.281; } From 8633c197abbc19bc68fe09c9294b72c8066f9e2f Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 27 Mar 2026 14:35:02 -0600 Subject: [PATCH 11/24] now passing the kind for share object by key in order to be able to share things to ther objects and not just users/teams --- src/providers/pond/permissionAPI.tsx | 4 +++- src/teams/ShareWithTeam.tsx | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/providers/pond/permissionAPI.tsx b/src/providers/pond/permissionAPI.tsx index ea8e0b9..3005599 100644 --- a/src/providers/pond/permissionAPI.tsx +++ b/src/providers/pond/permissionAPI.tsx @@ -25,6 +25,7 @@ export interface IPermissionAPIContext { shareObjectByKey: ( scope: Scope, key: string, + kind: string, permissions: pond.Permission[], keys?: string[], type?: string[] @@ -138,7 +139,7 @@ export default function PermissionProvider(props: PropsWithChildren) { }) }; - const shareObjectByKey = (scope: Scope, key: string, permissions: pond.Permission[], keys?: string[], types?: string[]) => { + const shareObjectByKey = (scope: Scope, key: string, kind: string, permissions: pond.Permission[], keys?: string[], types?: string[]) => { let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey") + (keys ? "?keys=" + keys.join(",") : "") + (types ? "&types=" + types.join(",") : "") if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`) + (keys ? "&keys=" + keys.join(",") : "") + @@ -146,6 +147,7 @@ export default function PermissionProvider(props: PropsWithChildren) { return new Promise((resolve, reject) => { post(url, { key: key, + kind: kind, permissions: permissions.map(permission => permissionToString(permission)) }).then(resp => { return resolve(resp) diff --git a/src/teams/ShareWithTeam.tsx b/src/teams/ShareWithTeam.tsx index 89c9a03..ffda3f0 100644 --- a/src/teams/ShareWithTeam.tsx +++ b/src/teams/ShareWithTeam.tsx @@ -111,7 +111,8 @@ export default function ShareObject(props: Props) { const [teamKey, setTeamKey] = useState(""); const share = () => { - permissionAPI.shareObjectByKey(scope, teamKey, sharedPermissions, keys, types).then(resp => { + + permissionAPI.shareObjectByKey(scope, teamKey, "team", sharedPermissions, keys, types).then(resp => { let shareBins = true; if (resp && resp.data && resp.data.existing) { success(label + " was shared with team"); @@ -125,7 +126,7 @@ export default function ShareObject(props: Props) { resp.data.bins.forEach(bin => { if (bin.settings) { let newScope = binScope(bin.settings?.key); - permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions, keys, types).catch(_err => { + permissionAPI.shareObjectByKey(newScope, teamKey, "team", sharedPermissions, keys, types).catch(_err => { successBins = false; }); } @@ -145,7 +146,7 @@ export default function ShareObject(props: Props) { resp.data.gates.forEach(gate => { if (gate) { let newScope = { key: gate.key, kind: "gate" } as Scope; - permissionAPI.shareObjectByKey(newScope, teamKey, sharedPermissions, keys, types).catch(_err => { + permissionAPI.shareObjectByKey(newScope, teamKey, "team", sharedPermissions, keys, types).catch(_err => { successGates = false; }); } From 186604dccfb1ebbde48cc08c508ace121f98f55e Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 31 Mar 2026 13:37:49 -0600 Subject: [PATCH 12/24] final adjustments to the field task list and the field drawer and field page using the tasks with the permissions in the correct way --- src/field/FieldTaskList.tsx | 43 +++++++++++------- src/maps/mapDrawers/FieldDrawer.tsx | 3 +- src/pages/Bin.tsx | 4 +- src/pages/Field.tsx | 11 ++--- src/providers/pond/permissionAPI.tsx | 29 ++++++++++++ src/tasks/TaskCard.tsx | 1 - src/tasks/TaskDrawer.tsx | 45 ++++--------------- src/tasks/TaskList.tsx | 67 ---------------------------- src/tasks/TaskViewer.tsx | 45 +------------------ src/tasks/taskActions.tsx | 1 + src/teams/ObjectTeams.tsx | 25 +++++++++-- 11 files changed, 97 insertions(+), 177 deletions(-) diff --git a/src/field/FieldTaskList.tsx b/src/field/FieldTaskList.tsx index 3b0d1e3..66a32c3 100644 --- a/src/field/FieldTaskList.tsx +++ b/src/field/FieldTaskList.tsx @@ -7,6 +7,8 @@ import TaskList from "tasks/TaskList" import TaskSettings from "tasks/TaskSettings" import AddIcon from "@mui/icons-material/Add"; import { makeStyles } from "@mui/styles"; +import { openSnackbar } from "providers/Snackbar" +import { cloneDeep } from "lodash" @@ -70,8 +72,10 @@ export default function FieldTaskList(props: Props) { const [openTaskDrawer, setOpenTaskDrawer] = useState(false) const [opentaskSettings, setOpenTaskSettings] = useState(false) const [taskMap, setTaskMap] = useState>(new Map()) + //load the tasks const loadTasks = useCallback(() => { + console.log("loading tasks") taskAPI.listTasks(50, 0, "asc", "start", undefined, undefined, undefined, undefined, undefined, [field], ["field"]).then(resp => { if(resp.data.tasks){ let newMap: Map = new Map() @@ -89,20 +93,24 @@ export default function FieldTaskList(props: Props) { loadTasks() },[loadTasks]) - const addNewTask = (task: Task) => { - console.log() + const deleteTask = (task: Task) => { + taskAPI.removeTask(task.key).then(resp => { + openSnackbar("success", "Task has been removed") + loadTasks() + }).catch(err => { + openSnackbar("error", "There was a problem removing the task") + }) } - const deleteTask = () => { - - } - - const markComplete = () => { - - } - - const updateTask = () => { - + const markComplete = (task: Task) => { + let settings = cloneDeep(task.settings) + settings.complete = !task.settings.complete + taskAPI.updateTask(task.key, settings).then(resp => { + openSnackbar("success", "Task has been updated") + loadTasks() + }).catch(err => { + openSnackbar("error", "There was a problem updating the task") + }) } //return the task list return ( @@ -113,9 +121,12 @@ export default function FieldTaskList(props: Props) { open={openTaskDrawer} deleteTask={deleteTask} completeTask={markComplete} - closeCallback={() => { + closeCallback={(refresh) => { setOpenTaskDrawer(false) setSelectedTask(undefined) + if (refresh) { + loadTasks() + } }} keys={[field]} types={["field"]} @@ -123,7 +134,6 @@ export default function FieldTaskList(props: Props) { } { let task = taskMap.get(id) @@ -141,8 +151,11 @@ export default function FieldTaskList(props: Props) { /> { + onClose={(reload) => { setOpenTaskSettings(false) + if(reload){ + loadTasks() + } }} keys={[field]} types={["field"]} diff --git a/src/maps/mapDrawers/FieldDrawer.tsx b/src/maps/mapDrawers/FieldDrawer.tsx index f6e864b..07adbf9 100644 --- a/src/maps/mapDrawers/FieldDrawer.tsx +++ b/src/maps/mapDrawers/FieldDrawer.tsx @@ -14,7 +14,6 @@ import DisplayDrawer from "common/DisplayDrawer"; import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay"; import { Field, fieldScope, HarvestPlan, teamScope } from "models"; import React, { useEffect, useState } from "react"; -import TaskViewer from "tasks/TaskViewer"; import Weather from "weather/weather"; import { getThemeType } from "theme"; import GrainDescriber from "grain/GrainDescriber"; @@ -219,7 +218,7 @@ export default function FieldDrawer(props: Props) { TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}> - + diff --git a/src/pages/Bin.tsx b/src/pages/Bin.tsx index 48bba18..4cbbba7 100644 --- a/src/pages/Bin.tsx +++ b/src/pages/Bin.tsx @@ -512,7 +512,7 @@ export default function Bin(props: Props) { if (showTasks()) { return ( - + ); } @@ -1156,7 +1156,7 @@ export default function Bin(props: Props) { setTaskDrawer(false)}> - + )} diff --git a/src/pages/Field.tsx b/src/pages/Field.tsx index 15c29db..94159fc 100644 --- a/src/pages/Field.tsx +++ b/src/pages/Field.tsx @@ -10,12 +10,12 @@ import { Settings } from "@mui/icons-material"; import { pond } from "protobuf-ts/pond"; import FieldMinimap from "field/Fieldminimap"; import Weather from "weather/weather"; -import TaskViewer from "tasks/TaskViewer"; import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay"; import { cloneDeep } from "lodash"; import HarvestPlanTable from "harvestPlan/HarvestPlanTable"; import { makeStyles } from "@mui/styles"; import FieldSettings from "field/FieldSettings"; +import FieldTaskList from "field/FieldTaskList"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -185,12 +185,9 @@ export default function FieldPage() { } const tasks = () => { - let taskLoadKeys: string[] = []; - if (!planLoading) { - field.key() !== "" && taskLoadKeys.push(field.key()); - //hPlan.key() !== "" && taskLoadKeys.push(hPlan.key()); - } - return () + return ( + + ) } const desktopView = () => { diff --git a/src/providers/pond/permissionAPI.tsx b/src/providers/pond/permissionAPI.tsx index 3005599..68fa5c9 100644 --- a/src/providers/pond/permissionAPI.tsx +++ b/src/providers/pond/permissionAPI.tsx @@ -7,10 +7,16 @@ import { useGlobalState } from "providers"; import { createContext, PropsWithChildren, useContext } from "react"; import { objectQueryParams, pondURL } from "./pond"; +export interface PermissionChanges { + key: string; //the key of the object having its permissions changed (parent) + permissions: string[] //the new permissions the parent will have to the child +} + export interface IPermissionAPIContext { getPermissions: (user: string, keys: string[], types: string[]) => Promise>; removePermissions: (user: string, scope: Scope) => Promise>; updatePermissions: (scope: Scope, users: User[] | Team[]) => Promise>; + updateObjectPermissions: (key: string, type: string, parentType: string, changes: PermissionChanges[], keys: string[], types: string[]) => Promise>; updateRelativePermissions: ( parentScope: Scope, childScope: Scope, @@ -96,6 +102,28 @@ export default function PermissionProvider(props: PropsWithChildren) { }) }; + const updateObjectPermissions = (key: string, type: string, parentType: string, changes: PermissionChanges[], keys: string[], types: string[]) => { + let body = { + childKey: key, + childType: type, + parentType: parentType, + permissionChanges: changes + }; + + if (as) { + keys.unshift(as) + types.unshift("team") + } + + return new Promise((resolve, reject) => { + put(pondURL("/" + type + "s/" + key + "/objectPermissions?keys=" + keys + "&types=" + types ), body).then(resp => { + return resolve(resp) + }).catch(err => { + return reject(err) + }) + }) + } + const shareObject = ( scope: Scope, email: string, @@ -207,6 +235,7 @@ export default function PermissionProvider(props: PropsWithChildren) { getPermissions, removePermissions, updatePermissions, + updateObjectPermissions, updateRelativePermissions, shareObject, shareObjectByKey, diff --git a/src/tasks/TaskCard.tsx b/src/tasks/TaskCard.tsx index 281bbb2..2cae4d3 100644 --- a/src/tasks/TaskCard.tsx +++ b/src/tasks/TaskCard.tsx @@ -18,7 +18,6 @@ import TaskActions from "./taskActions"; interface Props { task: Task; reLoad: () => void; - editTaskMethod: (task: Task) => void; markComplete: (task: Task) => void; deleteTask: (task: Task) => void; openTaskPage: (taskId: string) => void; diff --git a/src/tasks/TaskDrawer.tsx b/src/tasks/TaskDrawer.tsx index 3c206fb..0eade2d 100644 --- a/src/tasks/TaskDrawer.tsx +++ b/src/tasks/TaskDrawer.tsx @@ -32,7 +32,7 @@ import { useGlobalState } from "providers"; interface Props { task: Task; open: boolean; - closeCallback: () => void; + closeCallback: (refresh: boolean) => void; deleteTask: (task: Task) => void; completeTask: (task: Task) => void; keys: string[] @@ -121,7 +121,7 @@ export default function TaskDrawer(props: Props) { const [worker, setWorker] = useState(); useEffect(() => { - permissionAPI.getPermissions(user.id(), [task.key], ["task"]).then(resp => { + permissionAPI.getPermissions(user.id(), props.keys, props.types).then(resp => { setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions) }) if (task.start()) { @@ -134,39 +134,6 @@ export default function TaskDrawer(props: Props) { }); }, [user, task, userAPI, permissionAPI]); - // const taskActions = () => { - // return ( - // setMenuAnchorEl(null)} - // disableAutoFocusItem> - // { - // editTask(props.task); - // setMenuAnchorEl(null); - // }}> - // - // - // - // - // - // { - // deleteTask(props.task); - // setMenuAnchorEl(null); - // closeCallback(); - // }}> - // - // - // - // - // - // - // ); - // }; - const bodyButtons = () => { return ( @@ -195,7 +162,9 @@ export default function TaskDrawer(props: Props) { permissions={permissions} task={task} removeTask={deleteTask} - refreshCallback={closeCallback} + refreshCallback={() => { + closeCallback(true) + }} keys={props.keys} types={props.types} /> @@ -335,7 +304,9 @@ export default function TaskDrawer(props: Props) { displayNext={() => {}} displayPrev={() => {}} drawerBody={drawerBody()} - onClose={closeCallback} + onClose={() => { + closeCallback(false)} + } open={open} /> ); diff --git a/src/tasks/TaskList.tsx b/src/tasks/TaskList.tsx index 8f367ef..f94f838 100644 --- a/src/tasks/TaskList.tsx +++ b/src/tasks/TaskList.tsx @@ -12,7 +12,6 @@ import ButtonGroup from "common/ButtonGroup"; interface Props { tasks: Task[]; - editTaskMethod: (task: Task) => void; markComplete: (task: Task) => void; deleteTask: (task: Task) => void; openTask: (taskId: string) => void; @@ -26,7 +25,6 @@ interface Props { export default function TaskList(props: Props) { const { - editTaskMethod, markComplete, deleteTask, openTask, @@ -74,60 +72,10 @@ export default function TaskList(props: Props) { setComplete(complete); }, [tasks, dateToView]); - // const StyledToggleButtonGroup = withStyles(theme => ({ - // grouped: { - // margin: theme.spacing(-0.5), - // border: "none", - // padding: theme.spacing(1), - // "&:not(:first-child):not(:last-child)": { - // borderRadius: 24, - // marginRight: theme.spacing(0.5), - // marginLeft: theme.spacing(0.5) - // }, - // "&:first-child": { - // borderRadius: 24, - // marginLeft: theme.spacing(0.25) - // }, - // "&:last-child": { - // borderRadius: 24, - // marginRight: theme.spacing(0.25) - // } - // }, - // root: { - // backgroundColor: darken( - // theme.palette.background.paper, - // getThemeType() === "light" ? 0.05 : 0.25 - // ), - // borderRadius: 24, - // content: "border-box" - // } - // }))(ToggleButtonGroup); - - // const StyledToggle = withStyles({ - // root: { - // backgroundColor: "transparent", - // overflow: "visible", - // content: "content-box", - // "&$selected": { - // backgroundColor: "gold", - // color: "black", - // borderRadius: 24, - // fontWeight: "bold" - // }, - // "&$selected:hover": { - // backgroundColor: "rgb(255, 255, 0)", - // color: "black", - // borderRadius: 24 - // } - // }, - // selected: {} - // })(ToggleButton); - const incompleteTasks = incomplete.map((task, index) => ( markComplete(task)} deleteTask={(task: Task) => deleteTask(task)} reLoad={reLoad} @@ -142,7 +90,6 @@ export default function TaskList(props: Props) { markComplete(task)} deleteTask={(task: Task) => deleteTask(task)} reLoad={reLoad} @@ -170,20 +117,6 @@ export default function TaskList(props: Props) { } ]} /> - {/* - setViewing("upcoming")}> - Upcoming - - setViewing("complete")} - value={"complete"} - aria-label="complete"> - Complete - - */} {location !== "/tasks" && ( diff --git a/src/tasks/TaskViewer.tsx b/src/tasks/TaskViewer.tsx index 00fed3c..b283c5f 100644 --- a/src/tasks/TaskViewer.tsx +++ b/src/tasks/TaskViewer.tsx @@ -99,7 +99,6 @@ export default function TaskViewer(props: ViewProps) { overlayButton } = props; const [{ user, as }] = useGlobalState(); - //const [{ as }] = useGlobalState(); const taskAPI = useTaskAPI(); const { openSnack } = useSnackbar(); const [tasks, setTasks] = useState>(new Map([])); @@ -147,36 +146,6 @@ export default function TaskViewer(props: ViewProps) { } }; - const setTaskToEdit = (task: Task) => { - setEditTask(task); - openDialog(); - }; - - - // const loadMultitask = useCallback(() => { - // if (!loadKeys) return; - // if (loadKeys.length > 0) { - // let temp = new Map(); - // setLoaded(false); - // taskAPI - // .getMultiTasks(loadKeys, as) - // .then(resp => { - // if(resp.data.tasks){ - // resp.data.tasks.forEach(task => { - // if (task.settings) { - // temp.set(task.key, Task.any(task)); - // } - // }); - // } - // setTasks(temp); - // setLoaded(true); - // }) - // .catch(err => { - // openSnack("Failed to load"); - // }); - // } - // }, [loadKeys, openSnack, taskAPI, as]); - //loads tasks from the backend database const loadTasks = useCallback(() => { if (!user.id()) return; @@ -324,7 +293,6 @@ export default function TaskViewer(props: ViewProps) { loadTasks()} @@ -385,7 +353,6 @@ export default function TaskViewer(props: ViewProps) { loadTasks()} @@ -437,18 +404,10 @@ export default function TaskViewer(props: ViewProps) { { - // if (r) { - // if (drawerView) { - // loadMultitask(); - // } else { - // loadTasks(); - // } - // } loadTasks(); setEditTask(undefined); setNewTaskDialog(false); @@ -464,8 +423,8 @@ export default function TaskViewer(props: ViewProps) { }} completeTask={markComplete} deleteTask={deleteTask} - keys={[]} - types={[]} + keys={keys ?? []} + types={types ?? []} /> } diff --git a/src/tasks/taskActions.tsx b/src/tasks/taskActions.tsx index d55590b..116cdec 100644 --- a/src/tasks/taskActions.tsx +++ b/src/tasks/taskActions.tsx @@ -224,6 +224,7 @@ export default function TaskActions(props: Props) { return ( ) => setAnchorEl(event.currentTarget)}> diff --git a/src/teams/ObjectTeams.tsx b/src/teams/ObjectTeams.tsx index 595143f..9c80fbc 100644 --- a/src/teams/ObjectTeams.tsx +++ b/src/teams/ObjectTeams.tsx @@ -41,6 +41,8 @@ import { useNavigate } from "react-router-dom"; import { makeStyles } from "@mui/styles"; import { Share, RemoveCircle as RemoveUserIcon } from "@mui/icons-material"; import CancelSubmit from "common/CancelSubmit"; +import { permissionToString } from "pbHelpers/Permission"; +import { PermissionChanges } from "providers/pond/permissionAPI"; const useStyles = makeStyles((theme: Theme) => { const avatarBG = theme.palette.secondary["700" as keyof PaletteColor]; @@ -188,7 +190,21 @@ export default function ObjectTeams(props: Props) { }; const submit = () => { - permissionAPI + if(keys && types){ + let changes: PermissionChanges[] = [] + users.forEach((user: Team) => { + changes.push({ + key: user.id(), + permissions: user.permissions.map(permission => permissionToString(permission)) + }); + }); + permissionAPI.updateObjectPermissions(scope.key, scope.kind, "team", changes, keys, types).then(resp => { + console.log("no error") + }).catch(err => { + console.log("error") + }) + }else{ + permissionAPI .updatePermissions(scope, users) .then((_response: any) => { success("Users were sucessfully updated for " + label); @@ -201,10 +217,13 @@ export default function ObjectTeams(props: Props) { }) .catch((err: any) => { err.response.data.error - ? warning(err.response.data.error) - : error("Error occured when updating users for " + label); + ? warning(err.response.data.error) + : error("Error occured when updating users for " + label); close(); }); + + } + }; const changeUserPermissions = (user: pond.ITeam) => (event: any) => { From 9d41499c27b323685376cbcc89a511c0ccb81b59 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 31 Mar 2026 15:37:56 -0600 Subject: [PATCH 13/24] added an optional param to the object users and object teams to use a button with a label and use the share icon when it isn't there --- src/pages/Team.tsx | 1 + src/teams/ObjectTeams.tsx | 22 +++++++++++++++------- src/user/ObjectUsers.tsx | 16 ++++++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/pages/Team.tsx b/src/pages/Team.tsx index 593b152..f17d965 100644 --- a/src/pages/Team.tsx +++ b/src/pages/Team.tsx @@ -247,6 +247,7 @@ export default function TeamPage() { closeDialogCallback={() => setUserDialog(false)} refreshCallback={() => {}} useImitation={false} + shareLabel="Add Team Member +" /> diff --git a/src/teams/ObjectTeams.tsx b/src/teams/ObjectTeams.tsx index 9c80fbc..2e0c610 100644 --- a/src/teams/ObjectTeams.tsx +++ b/src/teams/ObjectTeams.tsx @@ -104,6 +104,7 @@ interface Props { cardMode?: boolean; keys?: string[]; types?: string[]; + shareLabel?: string; // a custom label to pass in that will replace the share icon } export default function ObjectTeams(props: Props) { @@ -126,7 +127,8 @@ export default function ObjectTeams(props: Props) { dialog, cardMode, keys, - types + types, + shareLabel } = props; const prevPermissions = usePrevious(permissions); const prevIsDialogOpen = usePrevious(isDialogOpen); @@ -322,12 +324,18 @@ export default function ObjectTeams(props: Props) { {canShare && ( - - - + {shareLabel ? + + : + + + + } )} diff --git a/src/user/ObjectUsers.tsx b/src/user/ObjectUsers.tsx index e3221c2..981a838 100644 --- a/src/user/ObjectUsers.tsx +++ b/src/user/ObjectUsers.tsx @@ -108,6 +108,7 @@ interface Props { dialog?: string; cardMode?: boolean; useImitation?: boolean; + shareLabel?: string; // s string to replace the share icon button with a labelled button } export default function ObjectUsers(props: Props) { @@ -127,8 +128,9 @@ export default function ObjectUsers(props: Props) { refreshCallback, userCallback, dialog, - cardMode + cardMode, //useImitation + shareLabel } = props; const prevPermissions = usePrevious(permissions); const prevIsDialogOpen = usePrevious(isDialogOpen); @@ -367,11 +369,17 @@ export default function ObjectUsers(props: Props) { {canShare && ( - + {shareLabel} + + : + - - + + + } )} From 52c8a02af3f8fe33594b1c9d6f0eb7032b11c65e Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 1 Apr 2026 10:42:16 -0600 Subject: [PATCH 14/24] with changing unit preferences and such to use the user object and not local storage, the decriber needed to be updated as well and i missed that part, so now describeMeasurement takes in an optional user and uses its settings for the unit and such and when not passed in will use the defaults --- src/bin/BinConditioningInteraction.tsx | 22 +++++++++---------- src/bin/GrainNodeInteractions.tsx | 21 +++++++++++++++--- src/bin/conditioning/modeChangeDialog.tsx | 16 ++++++++++---- src/charts/MeasurementsChart.tsx | 8 ++++--- src/component/ComponentForm.tsx | 18 ++++++++++----- src/component/ExportDataSettings.tsx | 6 +++-- src/gate/GateGraphs.tsx | 4 ++-- src/interactions/InteractionSettings.tsx | 12 ++++++---- src/interactions/InteractionsOverview.tsx | 8 +++---- src/models/CO2.ts | 2 -- src/models/UnitMeasurement.ts | 2 +- src/objects/objectInteractions/Alerts.tsx | 4 +++- src/objects/objectInteractions/Controls.tsx | 4 +++- .../NewObjectInteraction.tsx | 8 ++++--- src/pages/Bins.tsx | 6 ++++- src/pbHelpers/ComponentType.tsx | 20 +++++++++++++---- src/pbHelpers/Interaction.ts | 7 +++--- src/pbHelpers/MeasurementDescriber.ts | 5 +++-- 18 files changed, 117 insertions(+), 56 deletions(-) diff --git a/src/bin/BinConditioningInteraction.tsx b/src/bin/BinConditioningInteraction.tsx index 0a7e694..d729462 100644 --- a/src/bin/BinConditioningInteraction.tsx +++ b/src/bin/BinConditioningInteraction.tsx @@ -103,7 +103,7 @@ export default function BinConditioningInteraction(props: Props) { let sliderVals: Map = new Map(); let sliderMarks: Map = new Map(); passedInteraction.conditions().forEach(condition => { - let describer = describeMeasurement(condition.measurementType, source.type()); + let describer = describeMeasurement(condition.measurementType, source.type(), undefined, undefined, user); //NOTE: toDisplay will convert the temp value to fahrenheit sliderVals.set(condition.measurementType, describer.toDisplay(condition.value)); }); @@ -173,17 +173,17 @@ export default function BinConditioningInteraction(props: Props) { return ( {interaction.conditions().map((condition, i) => { - let describer = describeMeasurement(condition.measurementType, source?.type()); - let labelTail = ""; + let describer = describeMeasurement(condition.measurementType, source?.type(), source.subType(), undefined, user); + let labelTail = describer.unit(); let marks: Mark[] = []; - if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { - if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { - labelTail = "°F"; - } else { - labelTail = "°C"; - } - } + // if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { + // if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { + // labelTail = "°F"; + // } else { + // labelTail = "°C"; + // } + // } if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) { labelTail = "%"; } @@ -198,7 +198,7 @@ export default function BinConditioningInteraction(props: Props) { return ( - {interactionConditionText(source, condition, false)} + {interactionConditionText(source, condition, false, user)} (); const [nodeMoist, setNodeMoist] = useState(); const [{ user, as }] = useGlobalState(); - const tempDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE); - const humDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT); - const moistureDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC); + const tempDescriber = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, + undefined, + undefined, + user); + const humDescriber = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, + quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, + undefined, + undefined, + user); + const moistureDescriber = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC, + quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, + undefined, + undefined, + user); const [topNode, setTopNode] = useState(false); const [excluded, setExcluded] = useState(false); const componentAPI = useComponentAPI(); diff --git a/src/bin/conditioning/modeChangeDialog.tsx b/src/bin/conditioning/modeChangeDialog.tsx index cc17448..ef2dfbd 100644 --- a/src/bin/conditioning/modeChangeDialog.tsx +++ b/src/bin/conditioning/modeChangeDialog.tsx @@ -288,7 +288,9 @@ if (!selectedDevice) return; describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, sensor.settings.type, - sensor.settings.subtype + sensor.settings.subtype, + undefined, + user ).toStored(hum) ) }); @@ -301,7 +303,9 @@ if (!selectedDevice) return; let tempVal = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, sensor.settings.type, - sensor.settings.subtype + sensor.settings.subtype, + undefined, + user ).toStored(temp); let tempCondition = pond.InteractionCondition.create({ @@ -385,7 +389,9 @@ if (!selectedDevice) return; value: describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, sensor.settings.type, - sensor.settings.subtype + sensor.settings.subtype, + undefined, + user ).toStored(humidityPreset) }); conditions.push(fanConditionOne); @@ -397,7 +403,9 @@ if (!selectedDevice) return; let tempVal = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, sensor.settings.type, - sensor.settings.subtype + sensor.settings.subtype, + undefined, + user ).toStored(tempPreset); let fanConditionTwo = pond.InteractionCondition.create({ diff --git a/src/charts/MeasurementsChart.tsx b/src/charts/MeasurementsChart.tsx index 1baeea6..c4a5d8b 100644 --- a/src/charts/MeasurementsChart.tsx +++ b/src/charts/MeasurementsChart.tsx @@ -67,7 +67,7 @@ export default function MeasurementsChart(props: Props) { const [currentMax, setCurrentMax] = useState("dataMax"); const [newChart, setNewChart] = useState(false); const [xDomain, setXDomain] = useState(["dataMin", "dataMax"]); - const [{ showErrors }, dispatch] = useGlobalState(); + const [{ showErrors, user }, dispatch] = useGlobalState(); const isMobile = useMobile(); // const [zoomStart, setZoomStart] = useState(); // const [zoomEnd, setZoomEnd] = useState(); @@ -123,7 +123,7 @@ export default function MeasurementsChart(props: Props) { if (filters?.selectedInteractions?.includes(interaction.key())) { interaction.settings.conditions.forEach((condition, j) => { if (condition.measurementType === measurementType) { - let describer = describeMeasurement(condition.measurementType); + let describer = describeMeasurement(condition.measurementType, undefined, undefined, undefined, user); selectedInteractions.push( ) @@ -823,7 +831,7 @@ export default function ComponentForm(props: Props) { const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => { const { component } = form; - return describeMeasurement(measurementType, component.type(), component.subType()); + return describeMeasurement(measurementType, component.type(), component.subType(), undefined, user); }; const availableMeasurementTypes = (type: quack.ComponentType): quack.MeasurementType[] => { diff --git a/src/component/ExportDataSettings.tsx b/src/component/ExportDataSettings.tsx index 0a310ba..a17cdc0 100644 --- a/src/component/ExportDataSettings.tsx +++ b/src/component/ExportDataSettings.tsx @@ -210,7 +210,7 @@ class ExportDataSettings extends React.Component { // } formatMeasurements(measurements: Array): Promise> { - const { device, component } = this.props; + const { device, component, user } = this.props; return new Promise(function(resolve, reject) { if (!measurements) reject("Invalid measurements"); @@ -237,7 +237,9 @@ class ExportDataSettings extends React.Component { let unit = describeMeasurement( componentMeasurement.measurementType, component.settings.type, - component.settings.subtype + component.settings.subtype, + undefined, + user ).unit(); let key = componentMeasurement.label + " (" + unit + ")"; row[key] = componentMeasurement.extract(measurement.measurement); diff --git a/src/gate/GateGraphs.tsx b/src/gate/GateGraphs.tsx index b2ec455..5a3a039 100644 --- a/src/gate/GateGraphs.tsx +++ b/src/gate/GateGraphs.tsx @@ -282,13 +282,13 @@ export default function GateGraphs(props: Props) { if (um.values[0] && um.values[0].values.length > 1) { return areaGraph( um, - describeMeasurement(um.type, component.type(), component.subType()), + describeMeasurement(um.type, component.type(), component.subType(), undefined, user), component.settings.smoothingAverages ); } else { return lineGraph( um, - describeMeasurement(um.type, component.type(), component.subType()), + describeMeasurement(um.type, component.type(), component.subType(), undefined, user), component.settings.smoothingAverages ); } diff --git a/src/interactions/InteractionSettings.tsx b/src/interactions/InteractionSettings.tsx index 87bdf98..4f2a2d3 100644 --- a/src/interactions/InteractionSettings.tsx +++ b/src/interactions/InteractionSettings.tsx @@ -195,7 +195,9 @@ export default function InteractionSettings(props: Props) { let value = describeMeasurement( condition.measurementType, or(initialComponent, Component.create()).settings.type, - or(initialComponent, Component.create()).settings.subtype + or(initialComponent, Component.create()).settings.subtype, + undefined, + user ).toDisplay(condition.value); rawConditionValues.push(value.toString()); }); @@ -425,7 +427,7 @@ export default function InteractionSettings(props: Props) { mappedComponents.get(componentIDToString(interaction.settings.source)), Component.create() ); - return describeMeasurement(measurementType, source.settings.type, source.settings.subtype); + return describeMeasurement(measurementType, source.settings.type, source.settings.subtype, undefined, user); }; const describeSink = (): MeasurementDescriber => { @@ -433,7 +435,9 @@ export default function InteractionSettings(props: Props) { return describeMeasurement( Measurement.boolean, or(sink, Component.create()).settings.type, - or(sink, Component.create()).settings.subtype + or(sink, Component.create()).settings.subtype, + undefined, + user ); }; @@ -861,7 +865,7 @@ export default function InteractionSettings(props: Props) { const conditionGroup = (index: number) => { if (index >= numConditions) return; let measurementType = interaction.settings.conditions[index].measurementType; - let source = describeSource(measurementType); + let source = describeSource(measurementType, ); let isBoolean = isBooleanValue(index); return ( diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index 127f2a6..600c88a 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -71,7 +71,7 @@ interface Props { export default function InteractionsOverview(props: Props) { const classes = useStyles(); - const [{as}] = useGlobalState(); + const [{as, user}] = useGlobalState(); const interactionsAPI = useInteractionsAPI(); const { success, error } = useSnackbar(); const { device, component, components, permissions, refreshCallback } = props; @@ -118,12 +118,12 @@ export default function InteractionsOverview(props: Props) { if (!conditions) return items; conditions.forEach((condition: pond.IInteractionCondition, conditionIndex: number) => { let measurementType = condition.measurementType; - let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype); + let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype, undefined, user); items.push( - {interactionConditionText(source, condition, conditionIndex > 0)} + {interactionConditionText(source, condition, conditionIndex > 0, user)} @@ -185,7 +185,7 @@ export default function InteractionsOverview(props: Props) { let condition = conditions[conditionIndex]; let sourceType = source && source.settings.type; let sourceSubtype = source && source.settings.subtype; - let describer = describeMeasurement(condition.measurementType, sourceType, sourceSubtype); + let describer = describeMeasurement(condition.measurementType, sourceType, sourceSubtype, undefined, user); condition.value = describer.toStored(value); updatedDirtyInteractions.set(interactionIndex, true); } diff --git a/src/models/CO2.ts b/src/models/CO2.ts index 62e5d73..fcb1ed5 100644 --- a/src/models/CO2.ts +++ b/src/models/CO2.ts @@ -3,8 +3,6 @@ import { quack } from "protobuf-ts/pond"; import { or } from "utils/types"; import { clone, cloneDeep } from "lodash"; import { Component } from "models"; -import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; -import { extractGrainCable } from "pbHelpers/ComponentTypes"; export class CO2 { public settings: pond.ComponentSettings = pond.ComponentSettings.create(); diff --git a/src/models/UnitMeasurement.ts b/src/models/UnitMeasurement.ts index d33c17d..9fc3a42 100644 --- a/src/models/UnitMeasurement.ts +++ b/src/models/UnitMeasurement.ts @@ -32,7 +32,7 @@ export class UnitMeasurement { public static create(pb?: pond.UnitMeasurementsForComponent, user?: User): UnitMeasurement { let my = new UnitMeasurement(); if (pb) { - let describer = describeMeasurement(pb.type, pb.componentType); + let describer = describeMeasurement(pb.type, pb.componentType, undefined, undefined, user); let values = pb.values; if (user) { values = unitConversion(pb.values, pb.type, user); diff --git a/src/objects/objectInteractions/Alerts.tsx b/src/objects/objectInteractions/Alerts.tsx index 8546363..01f5bec 100644 --- a/src/objects/objectInteractions/Alerts.tsx +++ b/src/objects/objectInteractions/Alerts.tsx @@ -6,6 +6,7 @@ import { pond, quack } from "protobuf-ts/pond"; import React, { useState } from "react"; import NewObjectInteraction from "./NewObjectInteraction"; import { Option } from "common/SearchSelect"; +import { useGlobalState } from "providers"; export interface Alert { conditions: pond.InteractionCondition[]; @@ -20,10 +21,11 @@ interface Props { export default function Alerts(props: Props){ const {alerts, addNew, permissions} = props + const [{user}] = useGlobalState(); // const [openNewAlert, setOpenNewAlert] = useState(false) const readableCondition = (condition: pond.InteractionCondition) => { - let describer = describeMeasurement(condition.measurementType); + let describer = describeMeasurement(condition.measurementType, undefined, undefined, undefined, user); let type = describer.label(); let comparison = condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO diff --git a/src/objects/objectInteractions/Controls.tsx b/src/objects/objectInteractions/Controls.tsx index 0346c87..6f3f817 100644 --- a/src/objects/objectInteractions/Controls.tsx +++ b/src/objects/objectInteractions/Controls.tsx @@ -4,6 +4,7 @@ import { Component, Device, Interaction } from "models"; import { interactionResultText } from "pbHelpers/Interaction"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { pond, quack } from "protobuf-ts/pond"; +import { useGlobalState } from "providers"; import React, { useEffect, useState } from "react"; export interface Control { @@ -23,6 +24,7 @@ interface Props { export default function Controls(props: Props){ const { devices, controls, addNew, permissions } = props + const [{user}] = useGlobalState(); const [deviceControls, setDeviceControls] = useState>(new Map()) const [openDeviceMenu, setOpenDeviceMenu] = useState(false) const [anchor, setAnchor] = useState(null) @@ -45,7 +47,7 @@ export default function Controls(props: Props){ let conditions: string = "" let firstSource = control.sourceComponents[0] control.conditions.forEach((condition, index) => { - let describer = describeMeasurement(condition.measurementType, firstSource.type(), firstSource.subType()); + let describer = describeMeasurement(condition.measurementType, firstSource.type(), firstSource.subType(), undefined, user); let type = describer.label(); let comparison = condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO diff --git a/src/objects/objectInteractions/NewObjectInteraction.tsx b/src/objects/objectInteractions/NewObjectInteraction.tsx index 64a63b0..40aed71 100644 --- a/src/objects/objectInteractions/NewObjectInteraction.tsx +++ b/src/objects/objectInteractions/NewObjectInteraction.tsx @@ -64,7 +64,7 @@ const useStyles = makeStyles((theme: Theme) => { export default function NewObjectInteraction(props: Props){ const { open, onClose, device, linkedComponents, componentDevices } = props const classes = useStyles() - const [{ as }] = useGlobalState(); + const [{ as, user }] = useGlobalState(); const {openSnack} = useSnackbar(); const interactionsAPI = useInteractionsAPI(); const [newAlertComponentType, setNewAlertComponentType] = useState( @@ -249,7 +249,7 @@ export default function NewObjectInteraction(props: Props){ const measurementTypeMenuItems = () => { return availableMeasurementTypes(newAlertComponentType).map(measurementType => ( - {describeMeasurement(measurementType).label()} + {describeMeasurement(measurementType, undefined, undefined, undefined, user).label()} )); }; @@ -531,7 +531,9 @@ export default function NewObjectInteraction(props: Props){ return describeMeasurement( Measurement.boolean, or(sink, Component.create()).settings.type, - or(sink, Component.create()).settings.subtype + or(sink, Component.create()).settings.subtype, + undefined, + user ); }; diff --git a/src/pages/Bins.tsx b/src/pages/Bins.tsx index ad30d23..7529f3f 100644 --- a/src/pages/Bins.tsx +++ b/src/pages/Bins.tsx @@ -569,7 +569,11 @@ export default function Bins(props: Props) { measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN, value: describeMeasurement( - quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE + quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + undefined, + undefined, + undefined, + user ).toStored(bin.settings.highTemp) }), componentLocation: quack.ComponentID.create({ diff --git a/src/pbHelpers/ComponentType.tsx b/src/pbHelpers/ComponentType.tsx index 03be638..047c852 100644 --- a/src/pbHelpers/ComponentType.tsx +++ b/src/pbHelpers/ComponentType.tsx @@ -236,16 +236,16 @@ export function unitMeasurementSummary( ): Summary { let vals: string[] = []; convertedMeasurement.values.forEach(val => { - vals.push(val + describer.unit()); + vals.push(val + convertedMeasurement.unit); }); let v = vals.join(", "); if (describer.enumerations().length > 0) { v = describer.enumerations()[parseFloat(vals[0])]; } return { - colour: describer.colour(), - label: describer.label(), - type: describer.type(), + colour: convertedMeasurement.colour, + label: convertedMeasurement.label, + type: convertedMeasurement.type, value: v, error: convertedMeasurement.error }; @@ -258,6 +258,8 @@ export function unitMeasurementSummaries( ): Summary[] { let summaries: Summary[] = []; measurements.measurementsFor.forEach(measurement => { + //the only reason the user doesnt need to be passed in here is because it is only used for enumerations, + //things like the unit and colour are in the measurement itself at this point let describer = describeMeasurement(measurement.type, compType, subtype); summaries.push(unitMeasurementSummary(measurement, describer)); }); @@ -530,6 +532,7 @@ export function simpleLineChartData( } }); } + console.log(lineData) return lineData; } @@ -945,6 +948,15 @@ function getOverlayAreas( return overlayAreas; } + +/** + * this function adds lines to victory graphs for interactions on the component + * DEPRECATED: we now use Recharts for our graphs + * @param componentType + * @param subtype + * @param interactionCondition + * @returns + */ function createInteractionLine( componentType: quack.ComponentType, subtype: number, diff --git a/src/pbHelpers/Interaction.ts b/src/pbHelpers/Interaction.ts index 5b91d00..f48bd73 100644 --- a/src/pbHelpers/Interaction.ts +++ b/src/pbHelpers/Interaction.ts @@ -1,4 +1,4 @@ -import { Interaction, Component } from "models"; +import { Interaction, Component, User } from "models"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { or, notNull } from "utils/types"; @@ -172,7 +172,8 @@ export const interactionResultText = (interaction: Interaction, sink?: Component export const interactionConditionText = ( source: Component | undefined, condition: pond.IInteractionCondition, - and: boolean + and: boolean, + user?: User ) => { let comparison = "is exactly"; switch (condition.comparison) { @@ -189,7 +190,7 @@ export const interactionConditionText = ( if (!condition.measurementType) return "Unknown " + comparison + " " + condition.value; let sourceType = source && source.settings.type; let sourceSubtype = source && source.settings.subtype; - let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype); + let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype, undefined, user); return ( (and ? "and " : "") + measurement.label() + diff --git a/src/pbHelpers/MeasurementDescriber.ts b/src/pbHelpers/MeasurementDescriber.ts index 169a7d2..947c545 100644 --- a/src/pbHelpers/MeasurementDescriber.ts +++ b/src/pbHelpers/MeasurementDescriber.ts @@ -694,11 +694,12 @@ export function describeMeasurement( componentType: quack.ComponentType | null | undefined = quack.ComponentType .COMPONENT_TYPE_INVALID, componentSubtype: number = 0, - product?: pond.DeviceProduct + product?: pond.DeviceProduct, + user?: User ): MeasurementDescriber { measurementType = measurementType ? measurementType : quack.MeasurementType.MEASUREMENT_TYPE_INVALID; componentType = componentType ? componentType : quack.ComponentType.COMPONENT_TYPE_INVALID; - return new MeasurementDescriber(measurementType, componentType, componentSubtype, product); + return new MeasurementDescriber(measurementType, componentType, componentSubtype, product, user); } From 52db64a8c6bb8c493290fa08b3107ac27c27f601 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 1 Apr 2026 11:04:33 -0600 Subject: [PATCH 15/24] missed a file --- src/objectHeater/ObjectHeaterCharts.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/objectHeater/ObjectHeaterCharts.tsx b/src/objectHeater/ObjectHeaterCharts.tsx index dd117e9..04e2e9a 100644 --- a/src/objectHeater/ObjectHeaterCharts.tsx +++ b/src/objectHeater/ObjectHeaterCharts.tsx @@ -216,12 +216,16 @@ export default function ObjectHeaterCharts(props: Props) { let tempDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, tempComponent.type(), - tempComponent.subType() + tempComponent.subType(), + undefined, + user ); let heaterDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN, heaterComponent.type(), - heaterComponent.subType() + heaterComponent.subType(), + undefined, + user ); let tempData: LineData[] = []; heaterTemps.forEach(ht => { @@ -588,7 +592,9 @@ export default function ObjectHeaterCharts(props: Props) { describer={describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, tempHum.type(), - tempHum.subType() + tempHum.subType(), + undefined, + user )} lineData={ambientTemps} numLines={1} From 324ce7807fd00334dc48854a57a65325806db76b Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 1 Apr 2026 11:05:21 -0600 Subject: [PATCH 16/24] missed some more uses of measurement describer --- src/bin/graphs/BinComponentGraph.tsx | 4 ++-- src/device/DevicePresetsFromPicker.tsx | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/bin/graphs/BinComponentGraph.tsx b/src/bin/graphs/BinComponentGraph.tsx index 3c5a625..07a9325 100644 --- a/src/bin/graphs/BinComponentGraph.tsx +++ b/src/bin/graphs/BinComponentGraph.tsx @@ -346,14 +346,14 @@ export default function BinComponentGraph(props: Props) { ? measurements?.map(um => areaGraph( um, - describeMeasurement(um.type, component.type(), component.subType()), + describeMeasurement(um.type, component.type(), component.subType(), undefined, user), component.settings.smoothingAverages ) ) : measurements?.map(um => lineGraph( um, - describeMeasurement(um.type, component.type(), component.subType()), + describeMeasurement(um.type, component.type(), component.subType(), undefined, user), component.settings.smoothingAverages ) )} diff --git a/src/device/DevicePresetsFromPicker.tsx b/src/device/DevicePresetsFromPicker.tsx index 5faaa1b..a22991b 100644 --- a/src/device/DevicePresetsFromPicker.tsx +++ b/src/device/DevicePresetsFromPicker.tsx @@ -438,7 +438,9 @@ export default function DevicePresets(props: DevicePresetsProps) { value: describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, plenum.settings.type, - plenum.settings.subtype + plenum.settings.subtype, + undefined, + user ).toStored(humidityPreset) }); conditions.push(fanConditionOne); @@ -450,7 +452,9 @@ export default function DevicePresets(props: DevicePresetsProps) { let tempVal = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, plenum.settings.type, - plenum.settings.subtype + plenum.settings.subtype, + undefined, + user ).toStored(tempPreset); let fanConditionTwo = pond.InteractionCondition.create({ @@ -532,7 +536,9 @@ export default function DevicePresets(props: DevicePresetsProps) { describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, plenum.settings.type, - plenum.settings.subtype + plenum.settings.subtype, + undefined, + user ).toStored(hum) ) }); @@ -546,7 +552,9 @@ export default function DevicePresets(props: DevicePresetsProps) { let tempVal = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, plenum.settings.type, - plenum.settings.subtype + plenum.settings.subtype, + undefined, + user ).toStored(temp); let tempCondition = pond.InteractionCondition.create({ From bd15f4a15fd518821449f78cd75a2c1d42ff9e82 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 1 Apr 2026 15:07:06 -0600 Subject: [PATCH 17/24] added the checkbox in the task settings when creating a task as an object through keys and types to share the task to the team or user automatically when it is created --- src/tasks/TaskSettings.tsx | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/tasks/TaskSettings.tsx b/src/tasks/TaskSettings.tsx index dcc6b91..c2b92ee 100644 --- a/src/tasks/TaskSettings.tsx +++ b/src/tasks/TaskSettings.tsx @@ -2,10 +2,12 @@ import { Avatar, Box, Button, + Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, + FormControlLabel, Grid2 as Grid, InputAdornment, MenuItem, @@ -15,9 +17,9 @@ import moment from "moment"; import React, { useState } from "react"; import { pond } from "protobuf-ts/pond"; import { useGlobalState, useTaskAPI } from "providers"; -import { useSnackbar, useUserAPI } from "hooks"; +import { usePermissionAPI, useSnackbar, useUserAPI } from "hooks"; import { useEffect } from "react"; -import { Task, teamScope, User } from "models"; +import { Task, teamScope, User, userScope } from "models"; import ColourPicker from "common/ColourPicker"; import ResponsiveDialog from "common/ResponsiveDialog"; @@ -46,13 +48,15 @@ export default function TaskSettings(props: Props) { const [worker, setWorker] = useState(user.id()); const [colour, setColour] = useState(""); const taskAPI = useTaskAPI(); + const userAPI = useUserAPI(); + const permissionAPI = usePermissionAPI(); const { openSnack } = useSnackbar(); const [cost, setCost] = useState(""); const [secondaryCost, setSecondaryCost] = useState(""); const [seedCost, setSeedCost] = useState(""); const [poundPerAcre, setPoundPerAcre] = useState(0); - const userAPI = useUserAPI(); const [users, setUsers] = useState([]); + const [shareToCreator, setShareToCreator] = useState(true) useEffect(() => { if (props.task) { @@ -123,6 +127,22 @@ export default function TaskSettings(props: Props) { taskAPI .addTask(taskSettings, as, props.keys, props.types) .then(resp => { + console.log(resp) + if(shareToCreator && resp.data.task){ + let parentScope = userScope(user.id()) + if(as){ + parentScope = teamScope(as) + } + + permissionAPI.shareObjectByKey( + {key: resp.data.task, kind: "task"}, + parentScope.key, + parentScope.kind, + [pond.Permission.PERMISSION_READ, pond.Permission.PERMISSION_WRITE, pond.Permission.PERMISSION_USERS, pond.Permission.PERMISSION_SHARE], + props.keys, + props.types + ) + } props.onClose(true); }) .catch(err => { @@ -319,6 +339,18 @@ export default function TaskSettings(props: Props) { value={endTime} onChange={e => setEndTime(e.target.value)} /> + {props.keys && props.types && !props.task && + setShareToCreator(e.target.checked)} + color="primary" + /> + } + label={"Share task with " + (as ? "team" : "user")} + /> + } Colour setColour(color)} /> From 3d1c11646f102ace6c3af045bf08c95e863055f2 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 1 Apr 2026 16:08:11 -0600 Subject: [PATCH 18/24] updating the readme steps for creating a new whitelabel --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b7a7555..8528fdc 100644 --- a/README.md +++ b/README.md @@ -19,28 +19,29 @@ Steps to add a new white label - add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login) -2. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result) +1. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result) - https://maskable.app/ also create a maskable icon for PWA - create a 512x512 png as well for PWA -3. Create a white-label branding folder in `src/assets/whitelabels` and create a `darkLogo.png` and `lightLogo.png` from the client's logo -4. In `src/services/whiteLabel.ts`, add a new `Whitelabel` instance with the required fields and add its hostname mapping to the `whitelabels` map (it is very similar to step one but had some constraints when dealing with the `public` folder) -5. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator +2. Create a white-label branding folder in `src/assets/whitelabels` and create a `darkLogo.png` and `lightLogo.png` from the client's logo +3. In `src/services/whiteLabel.ts`, add a new `Whitelabel` instance with the required fields and add its hostname mapping to the `whitelabels` map +4. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator -6. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel -7. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins` -8. In `pond/whitelabel.go`, add an entry for the new white label +1. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel +2. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins` +3. In `pond/whitelabel.go`, add an entry for the new white label +4. Enable the white-label sub-domain for CORS in the backend by adding it to the allowed origins http/server.go -7. Create a new `Auth0` application for the client + +1. Create a new `Auth0` application for the client - use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon - add a whitelabel logo - allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see the default application as a guideline) - customize the Universal Login page to handle the new whitelabel - check if the client ID matches this client's Auth0 application - change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/)) -8. Enable the white-label sub-domain for CORS in the backend - in [http.go](https://gitlab.com/brandx/backend/blob/master/pond/http.go), add the white-label sub-domain to the `AllowedOrigins` list From 89a83d04dd1dd6c15cc6ebd8e28ebfff87574904 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 2 Apr 2026 11:43:43 -0600 Subject: [PATCH 19/24] set up the bones for the whitelabel, just need to generate the icons from a logo once we get it --- .env | 1 + README.md | 14 +++++++++----- src/navigation/SideNavigator.tsx | 3 ++- src/services/whiteLabel.ts | 29 ++++++++++++++++++++++++++++- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.env b/.env index 9fdc8f3..90ef699 100644 --- a/.env +++ b/.env @@ -14,6 +14,7 @@ VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID=32rABabJzXRvJiWivTmeKFgwFiqh4ok7 VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl +VITE_AUTH0_INTELLIFARMS_CLIENT_ID=RYtuAyOcB4DSaaqJMLDMf3pV8SFY9PdY #Branding (Default theme) VITE_APP_WEBSITE_TITLE="Adaptive Dashboard" diff --git a/README.md b/README.md index 8528fdc..4a47964 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,10 @@ Steps to add a new white label - add a `CNAME` record pointing their custom subdomain (ex: dashboard.example.com) to brandxtech.ca - add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login) +It is recommended to start with the Auth0 stage once everything from the client has been recieved as things like the client ID will be needed in other stages + +Frontend Steps 1. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result) - https://maskable.app/ also create a maskable icon for PWA @@ -29,22 +32,23 @@ Steps to add a new white label 4. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator +Backend Steps 1. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel 2. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins` 3. In `pond/whitelabel.go`, add an entry for the new white label -4. Enable the white-label sub-domain for CORS in the backend by adding it to the allowed origins http/server.go -1. Create a new `Auth0` application for the client +Auth0 Steps +1. Create a new Single Page App on Auth0 using React as the tech +2. Once you have the client ID add it to your .env file in the frontend +3. Adjust settings accordingly in the new application - use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon - add a whitelabel logo - - allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see the default application as a guideline) + - allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see other applications/whitelabels as a guideline) - customize the Universal Login page to handle the new whitelabel - check if the client ID matches this client's Auth0 application - change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/)) -- in [http.go](https://gitlab.com/brandx/backend/blob/master/pond/http.go), add the white-label sub-domain to the `AllowedOrigins` list - ## Expanding the ESLint configuration If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index 6bab36c..efc3635 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -27,6 +27,7 @@ import { IsAdaptiveAgriculture, // hasTutorialPlaylist, IsAdCon, + IsIntellifarms, IsMiPCA, // isBXT, IsMiVent, @@ -165,7 +166,7 @@ export default function SideNavigator(props: Props) { const authenticatedSideMenu = () => { const isMiVent = IsMiVent(); - const isAg = IsAdaptiveAgriculture() + const isAg = IsAdaptiveAgriculture() || IsIntellifarms() const isStreamline = IsStreamline() const isOmni = IsOmniAir() const isMiPCA = IsMiPCA() diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts index 29c4427..8c1f56d 100644 --- a/src/services/whiteLabel.ts +++ b/src/services/whiteLabel.ts @@ -180,6 +180,32 @@ export function IsAdaptiveAgriculture(): boolean { ); } +const INTELLIFARMS_WHITE_LABEL: WhiteLabel = { + name: "Intellifarms", + primaryColour: "", + secondaryColour: "", + signatureColour: "", + signatureAccentColour: "", + auth0ClientId: import.meta.env.VITE_AUTH0_INTELLIFARMS_CLIENT_ID, + redirectOnLogout: true, + logoutRedirectTarget: "https://myintellifarms.com", + darkLogo: "", + lightLogo: "", + transparentLogoBG: true, + blacklist: [], + docs: "Platform", + protips: protips, + homePage: "/bins" +}; + +export function IsIntellifarms(): boolean { + return ( + getName() === "Intellifarms" + // window.location.origin.includes("staging") || + // window.location.origin.includes("localhost") + ); +} + export function IsStreamline(): boolean { return ( getName() === "Streamline" @@ -324,7 +350,8 @@ const whitelabels = new Map([ ["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL], ["omniair", OMNIAIR_WHITE_LABEL], ["mipca", MIPCA_WHITE_LABEL], - ["mionetech", MIPCA_WHITE_LABEL] + ["mionetech", MIPCA_WHITE_LABEL], + ["intellifarms", INTELLIFARMS_WHITE_LABEL] ]); export function getWhitelabel(): WhiteLabel { From 605752ae02a192b4aaa117fc2757e563f2f410ce Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 2 Apr 2026 12:04:53 -0600 Subject: [PATCH 20/24] adjustments to navigation to use its own boolean rather than piggyback off adaptive ag --- src/navigation/BottomNavigator.tsx | 15 ++++++++------- src/navigation/SideNavigator.tsx | 17 +++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/navigation/BottomNavigator.tsx b/src/navigation/BottomNavigator.tsx index b03a153..6395bd4 100644 --- a/src/navigation/BottomNavigator.tsx +++ b/src/navigation/BottomNavigator.tsx @@ -13,7 +13,7 @@ import BinsIcon from "products/Bindapt/BinsIcon"; import { useGlobalState } from "providers"; import { useCallback, useEffect, useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA } from "services/whiteLabel"; +import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA, IsIntellifarms } from "services/whiteLabel"; import FieldsIcon from "products/AgIcons/FieldsIcon"; import NexusSTIcon from "products/Construction/NexusSTIcon"; import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon"; @@ -36,7 +36,8 @@ export default function BottomNavigator(props: Props) { const { isAuthenticated } = useAuth0(); const [{ user }] = useGlobalState(); const [route, setRoute] = useState(sideIsOpen ? "side" : ""); - const isAdaptive = IsAdaptiveAgriculture(); + const isAg = IsAdaptiveAgriculture(); + const isIntel = IsIntellifarms(); const isMiVent = IsMiVent(); const isAdCon = IsAdCon(); const isOmni = IsOmniAir(); @@ -45,7 +46,7 @@ export default function BottomNavigator(props: Props) { const reRoute = useCallback( (path: string) => { if (path === "") { - if (isAdaptive) { + if (isAg || isIntel) { return "bins"; } if (isMiVent) { @@ -74,7 +75,7 @@ export default function BottomNavigator(props: Props) { } return path; }, - [isAdaptive, isMiVent] + [isAg, isMiVent, isIntel] ); const autoDetectRoute = useCallback(() => { @@ -105,10 +106,10 @@ export default function BottomNavigator(props: Props) { const authenticatedNavigation = () => { return ( handleRouteChange(newValue)}> - {isAdaptive && ( + {isAg || isIntel && ( } value="visualFarm" /> )} - {isAdaptive && ( + {isAg || isIntel && ( } value="bins" /> )} {isAdCon && ( @@ -136,7 +137,7 @@ export default function BottomNavigator(props: Props) { ) : isAdCon ? ( diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index efc3635..815f30b 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -166,14 +166,15 @@ export default function SideNavigator(props: Props) { const authenticatedSideMenu = () => { const isMiVent = IsMiVent(); - const isAg = IsAdaptiveAgriculture() || IsIntellifarms() + const isAg = IsAdaptiveAgriculture() + const isIntel = IsIntellifarms() const isStreamline = IsStreamline() const isOmni = IsOmniAir() const isMiPCA = IsMiPCA() const isAdCon = IsAdCon() return ( - {(isAg || isStreamline || user.hasFeature("admin")) && ( + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && ( )} - {(isAg || isStreamline || user.hasFeature("admin")) && ( + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && ( )} - {(isAg || isStreamline || user.hasFeature("admin")) && ( + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && ( } - {(isAg || isStreamline || user.hasFeature("admin")) && ( + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && ( } - {(isAg || isStreamline || user.hasFeature("admin")) && ( + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && ( )} - {(isAg || isStreamline || user.hasFeature("admin")) && ( + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && ( } - {(isAg || isStreamline || user.hasFeature("admin")) && + {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && Date: Wed, 8 Apr 2026 11:37:48 -0600 Subject: [PATCH 21/24] added the intellifarms branding to the whilelabel --- public/Intellifarms/IFND-2023-Logo-White.png | Bin 0 -> 7766 bytes public/Intellifarms/IFND-2023-Logo.png | Bin 0 -> 37147 bytes .../Intellifarms/android-chrome-192x192.png | Bin 0 -> 13834 bytes .../Intellifarms/android-chrome-512x512.png | Bin 0 -> 38128 bytes public/Intellifarms/apple-touch-icon.png | Bin 0 -> 12842 bytes public/Intellifarms/browserconfig.xml | 9 +++++++++ public/Intellifarms/favicon-16x16.png | Bin 0 -> 712 bytes public/Intellifarms/favicon-32x32.png | Bin 0 -> 1899 bytes public/Intellifarms/favicon-48x48.png | Bin 0 -> 3117 bytes public/Intellifarms/favicon.ico | Bin 0 -> 5552 bytes public/Intellifarms/manifest.json | 19 ++++++++++++++++++ public/Intellifarms/mstile-150x150.png | Bin 0 -> 10953 bytes public/Intellifarms/safari-pinned-tab.svg | 3 +++ .../Intellifarms/IFND-2023-Logo-White.png | Bin 0 -> 7766 bytes .../Intellifarms/IFND-2023-Logo.png | Bin 0 -> 37147 bytes src/services/whiteLabel.ts | 16 ++++++++------- 16 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 public/Intellifarms/IFND-2023-Logo-White.png create mode 100644 public/Intellifarms/IFND-2023-Logo.png create mode 100644 public/Intellifarms/android-chrome-192x192.png create mode 100644 public/Intellifarms/android-chrome-512x512.png create mode 100644 public/Intellifarms/apple-touch-icon.png create mode 100644 public/Intellifarms/browserconfig.xml create mode 100644 public/Intellifarms/favicon-16x16.png create mode 100644 public/Intellifarms/favicon-32x32.png create mode 100644 public/Intellifarms/favicon-48x48.png create mode 100644 public/Intellifarms/favicon.ico create mode 100644 public/Intellifarms/manifest.json create mode 100644 public/Intellifarms/mstile-150x150.png create mode 100644 public/Intellifarms/safari-pinned-tab.svg create mode 100644 src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png create mode 100644 src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png diff --git a/public/Intellifarms/IFND-2023-Logo-White.png b/public/Intellifarms/IFND-2023-Logo-White.png new file mode 100644 index 0000000000000000000000000000000000000000..0afb3bf8e3eb984e5755d021f26ba40a44635d0d GIT binary patch literal 7766 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 literal 0 HcmV?d00001 diff --git a/public/Intellifarms/IFND-2023-Logo.png b/public/Intellifarms/IFND-2023-Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..11ff1c80c8c4dbb7068e345955d430020324996a GIT binary patch literal 37147 zcmeFY=RaKE_dYyGq9wtI7G(^hMHhl#$Y>*45WPi@9-UDWBU%_GQKAPCz4stmhUi3Z zL87x1c3;cK_EO* zqFcaUpy$3v$eaInX47(iKVl-6|<6~nT?gEm6@fFOShFI2o%$#B=_XG z_ta(@p||1a={2V0b1g5|$YeRCgy$_^v=ph;2aT&Ls~sw9+R5qJ*8F8 zo|5vydIcWt_NpP7+xMPIifmu|XWPe6btkx&Sax3pq_0SZTdu9nxQ(CAc@g6Q`}zOB z{y(yImH*C$h@6=l?f`>3vEy){CgIRiJ?^bAE0~;h969dA z*nex?APUp*PEzttm2X=zLy5DurQz;`$DAvnFPI(@{kw!&Mh4a<3tiAav}M0DNvD^k zflqN2Duo#-AuM^V@BO<*CMbxYYj9C=>iJI7!5+#^r<-p!Lg1^;&PLrrYr{uWufV^{ zfOy!GEXvL@gaTlzthlr_#`hV9E#WeH( zwZJwj&LtxTvmf^15txNPp1l4V+7bGHm%zCsRbZ0+EDJb>CU^GkvX98H8AZhJ(@=5< z{=aLOWzNvM&ye2l<959@Rw-9nLvBgueg7~J_! gmz%3aD5maQ(K1pZ^0W#k~nLD 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 xo=I literal 0 HcmV?d00001 diff --git a/public/Intellifarms/android-chrome-192x192.png b/public/Intellifarms/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..11f1839bc75398c87a34c7e77bbe70931e16dfbb GIT binary patch literal 13834 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 literal 0 HcmV?d00001 diff --git a/public/Intellifarms/apple-touch-icon.png b/public/Intellifarms/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..408dde9ae352fcf86832b31eb079cd44c1a3ff56 GIT binary patch literal 12842 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 X5}r}&znI_u0RhNMt4LK!7>E21Y@=mR literal 0 HcmV?d00001 diff --git a/public/Intellifarms/browserconfig.xml b/public/Intellifarms/browserconfig.xml new file mode 100644 index 0000000..f62a123 --- /dev/null +++ b/public/Intellifarms/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #ffffff + + + diff --git a/public/Intellifarms/favicon-16x16.png b/public/Intellifarms/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f052e75697fdee4fac0ca7f66cbbdef5d4920d8a GIT binary patch literal 712 zcmV;(0yq7MP) zK!5iGGztgE)Wis-%5Uj6#mcsJz5;$YrNf zz~-U$>d7O`*0aFl5LZk-;U1nzySKdh7Klm4V}bJ(z|rQ^V>xUW(uUp}oxHrQ5f2Xf zrCGUUB|VeHmPPE?8Tw?H()~T`hj%yDzC1Q|?;}KdQ_}Mz%5=Tzlz^}S^|#wAmv8iW zadFq=L@lL9uMwY)rTRVn>iXHmW^1iZKHv)a;bBpE5UBtRl_EgP%%&^j7n^ck0bUB! z->|_~D1A{Q-tUK50vDWg;wJ_B3P4o39A+lNIyGaWrqtTP&W)%v23DOV@oG(_^uxR; z)jZ6AZlGLH*syiSFAL{y6r`!`hVMw|#bMZ0rF5adLxZpiqT6??koKjZAg&kCU5&NP z50{#)Zh#!WJJ6ntu!x@UX_J0!q`aqR)%Su9K}g!>_OoSk;tAPkP(r&_P@XMR{RcqQ$(t>q<8E90000*ejv23NTDWL}*?`14#^I34r- zSlrQ78hd7`b8j2KnVt+cw_aoB?_A-Mk50wh47)oz?y82&*SYkYLboqd!jxrC`DDc+xyAc(**!fc#{RnaTz_-h>crfZ zRN7RKqa4Fy27_JWhelwIsAKFq3y98Tc(A2?tq;28qjEaRHMq_w;Y4pW63+Z4} z`x+;<*2r&k5;Gy+Bd?uXj~_a;6cC+d_$&qCXEADE`^kf&0R5}l-jjN*HE|(kE089G z>E*2c^nQ-MFYn{oe8B-1*jDG0o1+=C79KHU*Mb8JKwgW$P)o;!M7E^Tmli^p$1vPI zargy*;l_@Q$Zm7aT@H}NP&QF<=sYtknkmuHRl7+rOK2DZq}#*3UO4g$Jf%e&3ey zgI6g{uKZ4M?PMZ;8ihsdo`Bs!w1=X(zW_GIK0A|?eGvN~?q*%On{{bd>G0F9q~kZ9 zj@hJ8ode7;cjdcYs@+Sc-^57X-`uu(xlbQH6|;R^s?x&R`z&zY4mr1ma6q$aPxYzO^jssHn%Q+@Yu1k*<1x`u-JYStf89LzPs<( zNO_((zo~WZs&r+!?)0gen_7JBo{7A=wX1Y!*HBZ(j>M@a$4C&>7!FiRw*GwT>{Uj> z0*JZlLL25eFjQQ7nT#!xU7eXd=HQD4$CjK0!hSs!^ZQ?Yd1ZWmvASm>RG;rE9ojWm zY=5M|CmT=a`PQK9(XHbz{#|fp*tM7oyNVr`8@AbqH>b|~45L>5_DG~^mY)>?11%`+ zWqp!1j(;Y@J)NXV8_c~jndjTOCq{<=b{5xO8bh>G(R1(i@0TND$$qXJ}_$`PKQvDFk)UX#mz~izadF)DTPYNEe0Vk+a^LK6G{Mq#e%%6Sp2Q9 zpLla0%bh)uncX9kPXu;W@y?5ZzD;Xd)9i0`*cYNnCu8-g*NLZSW;sRy@^6Kl2Wy;n zRibHr&>5IR$n3ZrM?AVRi*Xdl1ny05L;`v_oac7QbIY9n!|6!HN1p~V^O6OydY={! zocQDDWzB8Z)yjXOY-uLlr-f!-+ zo}{&A4=K(W2(fdJ+feyoeR z<}aJF`E@>1GLV<*reZ!`jn;)AU@;Q&8aX2I%Y=B{a-VioEJWseKXa)ykTQ$rack<7 z^e0Xqua)27Bq!PH09u-<3QQ2^ciY5BHqQ|SY&s`-YL3ko3-ZZi%nrEBJt?{G&scba z_YNtQlxS`jFl&KmP_ftNRRZKf;m^lI)9|Ww<(inQ;^vAY<}Ptb%yX+0kmL#H8{|MW z0jkndvo5S?Q1ZEH3l}7Ce?(ag@>T4|0sX>RbWtuM@Q45?QWLQI05s6lURvRj#%T*M zm#f!$s}P2f%Ha_ik%HV@NPvJ*v51+KB?`-wBnCK%a?FWl)92z>gHP+HLiUeDux%#h ztD+`!6~S4~zr7r=K+)XVy} l{5twiOB*fc>VfZ^{0kUwylOA+!)gEk002ovPDHLkV1h_&%Dw;q literal 0 HcmV?d00001 diff --git a/public/Intellifarms/favicon-48x48.png b/public/Intellifarms/favicon-48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..948fec739bbdf4f2dcc035830be30a64641d28dc GIT binary patch literal 3117 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 Hu0mjfL--)C literal 0 HcmV?d00001 diff --git a/public/Intellifarms/favicon.ico b/public/Intellifarms/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f3b5e0c1d9dd266187ffbfb14377ec4f7e69b25e GIT binary patch literal 5552 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 z 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 lwm1@- + + \ No newline at end of file diff --git a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png new file mode 100644 index 0000000000000000000000000000000000000000..0afb3bf8e3eb984e5755d021f26ba40a44635d0d GIT binary patch literal 7766 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 literal 0 HcmV?d00001 diff --git a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..11ff1c80c8c4dbb7068e345955d430020324996a GIT binary patch literal 37147 zcmeFY=RaKE_dYyGq9wtI7G(^hMHhl#$Y>*45WPi@9-UDWBU%_GQKAPCz4stmhUi3Z zL87x1c3;cK_EO* zqFcaUpy$3v$eaInX47(iKVl-6|<6~nT?gEm6@fFOShFI2o%$#B=_XG z_ta(@p||1a={2V0b1g5|$YeRCgy$_^v=ph;2aT&Ls~sw9+R5qJ*8F8 zo|5vydIcWt_NpP7+xMPIifmu|XWPe6btkx&Sax3pq_0SZTdu9nxQ(CAc@g6Q`}zOB z{y(yImH*C$h@6=l?f`>3vEy){CgIRiJ?^bAE0~;h969dA z*nex?APUp*PEzttm2X=zLy5DurQz;`$DAvnFPI(@{kw!&Mh4a<3tiAav}M0DNvD^k zflqN2Duo#-AuM^V@BO<*CMbxYYj9C=>iJI7!5+#^r<-p!Lg1^;&PLrrYr{uWufV^{ zfOy!GEXvL@gaTlzthlr_#`hV9E#WeH( zwZJwj&LtxTvmf^15txNPp1l4V+7bGHm%zCsRbZ0+EDJb>CU^GkvX98H8AZhJ(@=5< z{=aLOWzNvM&ye2l<959@Rw-9nLvBgueg7~J_! gmz%3aD5maQ(K1pZ^0W#k~nLD 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 xo=I literal 0 HcmV?d00001 diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts index 8c1f56d..e53ef6a 100644 --- a/src/services/whiteLabel.ts +++ b/src/services/whiteLabel.ts @@ -12,6 +12,8 @@ import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png"; // import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png"; import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png"; import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png" +import IntellifarmsLogo from "../assets/whitelabels/Intellifarms/IFND-2023-Logo.png" +import IntellifarmsLogoWhite from "../assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png" // import { green, yellow } from "@mui/material/colors"; const protips: string[] = [ @@ -182,15 +184,15 @@ export function IsAdaptiveAgriculture(): boolean { const INTELLIFARMS_WHITE_LABEL: WhiteLabel = { name: "Intellifarms", - primaryColour: "", - secondaryColour: "", - signatureColour: "", - signatureAccentColour: "", + primaryColour: "#9E1B32", + secondaryColour: "#4A4F55", + signatureColour: "#272727", + signatureAccentColour: "#fff", auth0ClientId: import.meta.env.VITE_AUTH0_INTELLIFARMS_CLIENT_ID, redirectOnLogout: true, logoutRedirectTarget: "https://myintellifarms.com", - darkLogo: "", - lightLogo: "", + darkLogo: IntellifarmsLogo, + lightLogo: IntellifarmsLogoWhite, transparentLogoBG: true, blacklist: [], docs: "Platform", @@ -364,7 +366,7 @@ export function getWhitelabel(): WhiteLabel { return BXT_WHITE_LABEL; } if (window.location.origin.includes("localhost")) { - return STREAMLINE_WHITE_LABEL; + return INTELLIFARMS_WHITE_LABEL; } if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') { return STAGING_WHITELABEL; From 0556fe2d8b6ca25350948d1d4b93cb7460a0d983 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 14 Apr 2026 15:29:59 -0600 Subject: [PATCH 22/24] made some changes in the bin sensors to open a new dialog when the component being removed is the last one for a device so users can remove the device from the bin along with the component --- src/bin/BinActions.tsx | 6 ++ src/bin/BinComponents.tsx | 116 ++++++++++++++++++++++++++++++---- src/bin/BinSensors.tsx | 6 ++ src/pages/Bin.tsx | 4 +- src/providers/pond/binAPI.tsx | 6 +- 5 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index a425ea6..c9f7c8f 100644 --- a/src/bin/BinActions.tsx +++ b/src/bin/BinActions.tsx @@ -57,6 +57,8 @@ interface Props { components?: Map; setComponents?: React.Dispatch>>; updateBinStatus?: (componentKeys: string[], removed?: boolean) => void; + componentDevices?: Map + setComponentDevices?: React.Dispatch>>; } interface OpenState { @@ -77,7 +79,9 @@ export default function BinActions(props: Props) { refreshCallback, userID, components, + componentDevices, setComponents, + setComponentDevices, updateBinStatus } = props; const [anchorEl, setAnchorEl] = React.useState(null); @@ -245,7 +249,9 @@ export default function BinActions(props: Props) { open={openState.sensors} userID={userID} components={components} + componentDevices={componentDevices} setComponents={setComponents} + setComponentDevices={setComponentDevices} updateBinStatus={updateBinStatus} onClose={refresh => { if (refresh === true) { diff --git a/src/bin/BinComponents.tsx b/src/bin/BinComponents.tsx index 7864c46..0b81cba 100644 --- a/src/bin/BinComponents.tsx +++ b/src/bin/BinComponents.tsx @@ -71,7 +71,9 @@ const useStyles = makeStyles((theme: Theme) => { interface Props { components?: Map; + componentDevices?: Map; setComponents?: React.Dispatch>>; + setComponentDevices?: React.Dispatch>>; bin: string; binGrain: pond.Grain; updateBinStatus?: (componentKeys: string[], removed?: boolean) => void; @@ -84,7 +86,7 @@ interface Option { } export default function BinComponents(props: Props) { - const { components, bin, setComponents, updateBinStatus, binGrain } = props; + const { components, componentDevices, bin, setComponents, setComponentDevices, updateBinStatus, binGrain } = props; const [{as}] = useGlobalState(); const classes = useStyles(); const binAPI = useBinAPI(); @@ -208,20 +210,54 @@ export default function BinComponents(props: Props) { // } // }, [selectedDevice, componentAPI, deviceComponents, snackbar]); - const removeComponent = (component: string) => { - binAPI.removeComponent(bin, component, as).then(() => { + const removeComponent = (component: string, device?: number) => { + console.log(device) + binAPI.removeComponent(bin, component, device, as).then(() => { + if (components && setComponents) { if (components.delete(component)) { let newComponents = new Map(components); setComponents(newComponents); } } + if(componentDevices && setComponentDevices) { + if (componentDevices.delete(component)) { + let newComponentDevices = new Map(componentDevices); + setComponentDevices(newComponentDevices); + } + } snackbar.info("Component removed from bin"); }); setComponentToRemove(undefined); }; - const removeComponentConfirmation = () => { + const removeComponentConfirmation = (last?: boolean) => { + let lastComponent = false; + let devId: number | undefined; + if(last){ + lastComponent = last + }else{ + if (componentDevices && componentToRemove) { + const id = componentDevices.get(componentToRemove.key()); + + if (id !== undefined) { + devId = id + let count = 0; + + for (const val of componentDevices.values()) { + if (val === devId) { + count++; + + if (count > 1) { + break; // stop early once we know it's not the last + } + } + } + + lastComponent = count === 1; + } + } + } return ( setComponentToRemove(undefined)}> Remove {componentToRemove?.name()}? - - This will remove {componentToRemove?.name()} from this bin. If you don't have direct - access to this component or the device it is attached to, you will not be able to add it - back. - + {lastComponent ? + + This component {componentToRemove?.name()} is the last component from its device. You can choose to remove only the component itself or the device along with it. + Leaving the device may have unexpected results if the device will continue to be used and it is recommended to remove it if the device is being moved to a new bin. + + : + + This will remove {componentToRemove?.name()} from this bin. If you don't have direct + access to this component or the device it is attached to, you will not be able to add it + back. + + } + {lastComponent + ? + + + + : + } ); @@ -256,7 +310,7 @@ export default function BinComponents(props: Props) { Remove All Components? - This will remove All attached components from this bin. If you don't have direct access + This will remove All attached components and devices from this bin. If you don't have direct access to these components or the devices they are attached to, you will not be able to add them back. @@ -320,6 +374,12 @@ export default function BinComponents(props: Props) { setComponents(newComponents); } } + if(componentDevices && setComponentDevices) { + if (componentDevices.set(component.key(), device)) { + let newComponentDevices = new Map(componentDevices); + setComponentDevices(newComponentDevices); + } + } //if a grain cable was added to the bin update the components grain type to match the bin if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) { let settings = component.settings; @@ -338,15 +398,40 @@ export default function BinComponents(props: Props) { } }); } else { - binAPI.removeComponent(bin, component.key(), as).then(resp => { - if (components && setComponents) { + let lastComponent = false; + if (componentDevices) { + console.log(componentDevices) + let count = 0; + for (const val of componentDevices.values()) { + if (val === device) { + count++; + + if (count > 1) { + break; // stop early once we know it's not the last + } + } + } + lastComponent = count === 1; + } + if(lastComponent){ + setComponentToRemove(component) + }else{ + binAPI.removeComponent(bin, component.key(), undefined, as).then(resp => { + if (components && setComponents) { if (components.delete(component.key())) { let newComponents = new Map(components); setComponents(newComponents); } } + if(componentDevices && setComponentDevices) { + if (componentDevices.delete(component.key())) { + let newComponentDevices = new Map(componentDevices); + setComponentDevices(newComponentDevices); + } + } snackbar.info("Component removed from bin"); - }); + }); + } } }; @@ -567,7 +652,10 @@ export default function BinComponents(props: Props) { {component.name()} - setComponentToRemove(component)}> + { + setComponentToRemove(component) + + }}> diff --git a/src/bin/BinSensors.tsx b/src/bin/BinSensors.tsx index f1c81ac..492091e 100644 --- a/src/bin/BinSensors.tsx +++ b/src/bin/BinSensors.tsx @@ -30,7 +30,9 @@ interface Props { coords?: { longitude: number; latitude: number }; binYards?: pond.BinYardSettings[]; components?: Map; + componentDevices?: Map setComponents?: React.Dispatch>>; + setComponentDevices?: React.Dispatch>>; updateBinStatus?: (componentKeys: string[], removed?: boolean) => void; } @@ -43,7 +45,9 @@ export default function BinSensors(props: Props) { mode, openedBinYard, components, + componentDevices, setComponents, + setComponentDevices, updateBinStatus } = props; const [initialized, setInitialized] = useState(false); @@ -93,7 +97,9 @@ export default function BinSensors(props: Props) { { let device = Device.create(dev); if (attachedDevIds.includes(device.id())) { - devs.push(Device.create(dev)); + devs.push(device); } }); setDevices(devs); @@ -674,6 +674,8 @@ export default function Bin(props: Props) { userID={user.id()} components={components} setComponents={setComponents} + componentDevices={componentDevices} + setComponentDevices={setComponentDevices} updateBinStatus={updateStatus} /> diff --git a/src/providers/pond/binAPI.tsx b/src/providers/pond/binAPI.tsx index def6699..e0d46f9 100644 --- a/src/providers/pond/binAPI.tsx +++ b/src/providers/pond/binAPI.tsx @@ -30,6 +30,7 @@ export interface IBinAPIContext { removeComponent: ( bin: string, component: string, + device?: number, otherTeam?: string ) => Promise>; removeAllComponents: (bin: string, otherTeam?: string) => Promise>; @@ -254,10 +255,11 @@ export default function BinProvider(props: PropsWithChildren) { }) }; - const removeComponent = (bin: string, component: string, otherTeam?: string) => { + const removeComponent = (bin: string, component: string, device?: number, otherTeam?: string) => { const view = otherTeam ? otherTeam : as let url = "/bins/" + bin + "/removeComponent/" + component; - if (view) url = url + "?as=" + view; + if (device) url = url + "?deviceToRemove=" + device + if (view) url = url + (device ? "&as=" : "?as=") + view; return new Promise>((resolve, reject)=>{ post(pondURL(url)).then(resp => { resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data) From f7b5a59c9804b2aadf384dc3c1d314e9981ec5af Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 14 Apr 2026 16:01:52 -0600 Subject: [PATCH 23/24] adding optional parameters to the Single set area chart to allow for clamping the graph so that large numbers do cause it to zoom etremely far out --- src/charts/SingleSetAreaChart.tsx | 7 +++++-- src/gate/GateDeltaTempGraph.tsx | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/charts/SingleSetAreaChart.tsx b/src/charts/SingleSetAreaChart.tsx index 22477d6..b84d150 100644 --- a/src/charts/SingleSetAreaChart.tsx +++ b/src/charts/SingleSetAreaChart.tsx @@ -38,10 +38,12 @@ interface Props { multiGraphZoom?: (domain: number[] | string[]) => void; colourAboveZero?: ColourData colourBelowZero?: ColourData + minY?: string | number + maxY?: string | number } export default function SingleSetAreaChart(props: Props) { - const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit } = props; + const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit, minY, maxY } = props; const [xDomain, setXDomain] = useState(["dataMin", "dataMax"]); const [refLeft, setRefLeft] = useState(); const [refRight, setRefRight] = useState(); @@ -152,7 +154,8 @@ export default function SingleSetAreaChart(props: Props) { /> Date: Tue, 21 Apr 2026 15:38:14 -0600 Subject: [PATCH 24/24] new component type --- package-lock.json | 4 +- package.json | 2 +- src/bin/BinVisualizerV2.tsx | 2 +- src/pbHelpers/ComponentType.tsx | 6 +- .../ComponentTypes/AnalogPressure.ts | 88 +++++++++++++++++++ src/pbHelpers/ComponentTypes/index.ts | 1 + src/products/MiVent/MiVentAvailability.ts | 4 +- 7 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 src/pbHelpers/ComponentTypes/AnalogPressure.ts diff --git a/package-lock.json b/package-lock.json index 2cd53e8..0e54b33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -11205,7 +11205,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#9c0f668d4a56b8216dd71a44c3110a818aaf3541", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#b44f5fa7183d9565e9fd9e7a9ac756114401ddfd", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 90ac71b..ba69f2e 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/bin/BinVisualizerV2.tsx b/src/bin/BinVisualizerV2.tsx index f6f9c1d..437f09d 100644 --- a/src/bin/BinVisualizerV2.tsx +++ b/src/bin/BinVisualizerV2.tsx @@ -1539,7 +1539,7 @@ export default function BinVisualizer(props: Props) { ) : ( - + {fullScreenHandler.enter()}}> )} diff --git a/src/pbHelpers/ComponentType.tsx b/src/pbHelpers/ComponentType.tsx index 047c852..c31154b 100644 --- a/src/pbHelpers/ComponentType.tsx +++ b/src/pbHelpers/ComponentType.tsx @@ -39,7 +39,8 @@ import { Voltage, VPD, Weight, - CapacitorCable + CapacitorCable, + AnalogPressure } from "pbHelpers/ComponentTypes"; //import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable"; //import { multilineCapCableData } from "./ComponentTypes/CapacitorCable"; @@ -96,7 +97,8 @@ const COMPONENT_TYPE_MAP = new Map([ [quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x], [quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable], [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle], - [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow] + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow], + [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, AnalogPressure] ]); export interface Subtype { diff --git a/src/pbHelpers/ComponentTypes/AnalogPressure.ts b/src/pbHelpers/ComponentTypes/AnalogPressure.ts new file mode 100644 index 0000000..f4d3936 --- /dev/null +++ b/src/pbHelpers/ComponentTypes/AnalogPressure.ts @@ -0,0 +1,88 @@ +import { + ComponentTypeExtension, + Summary, + Subtype, + simpleMeasurements, + simpleSummaries, + unitMeasurementSummaries, + AreaChartData, + GraphFilters, + simpleAreaChartData, + LineChartData, + simpleLineChartData + } from "pbHelpers/ComponentType"; + import PressureDarkIcon from "assets/components/pressureDark.png"; + import PressureLightIcon from "assets/components/pressureLight.png"; + import { quack } from "protobuf-ts/quack"; + import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; + import { convertedUnitMeasurement } from "models/UnitMeasurement"; + import { pond } from "protobuf-ts/pond"; + + export function AnalogPressure(subtype: number = 0): ComponentTypeExtension { + let pressure = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE, + quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, + subtype + ); + let voltage = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE, + quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, + subtype + ) + let addressTypes = [quack.AddressType.ADDRESS_TYPE_I2C]; + return { + type: quack.ComponentType.COMPONENT_TYPE_PRESSURE, + subtypes: [ + { + key: quack.AnalogPressureSubtype.ANALOG_PRESSURE_SUBTYPE_PMC21, + value: "ANALOG_PRESSURE_SUBTYPE_PMC21", + friendlyName: "PMC 21" + } as Subtype + ], + friendlyName: "Analog Pressure", + description: "Measures the atmospheric pressure or absolute pressure", + isController: false, + isSource: true, + isCalibratable: true, + hasFan: true, + addressTypes: addressTypes, + interactionResultTypes: [], + states: [], + measurements: simpleMeasurements(pressure, voltage), + measurementSummary: async function(measurement: quack.Measurement): Promise> { + return simpleSummaries(measurement, pressure); + }, + unitMeasurementSummary: ( + measurements: convertedUnitMeasurement, + ): Summary[] => { + return unitMeasurementSummaries( + measurements, + quack.ComponentType.COMPONENT_TYPE_PRESSURE, + subtype, + ); + }, + areaChartData: ( + measurement: pond.UnitMeasurementsForComponent, + smoothingAverages?: number, + filters?: GraphFilters + ): AreaChartData => { + return simpleAreaChartData(measurement, smoothingAverages, filters); + }, + lineChartData: ( + measurement: pond.UnitMeasurementsForComponent, + smoothingAverages?: number, + filters?: GraphFilters + ): LineChartData => { + return simpleLineChartData( + quack.ComponentType.COMPONENT_TYPE_PRESSURE, + measurement, + smoothingAverages, + filters + ); + }, + minMeasurementPeriodMs: 1000, + icon: (theme?: "light" | "dark"): string | undefined => { + return theme === "light" ? PressureDarkIcon : PressureLightIcon; + } + }; + } \ No newline at end of file diff --git a/src/pbHelpers/ComponentTypes/index.ts b/src/pbHelpers/ComponentTypes/index.ts index 349bbbe..39b1560 100644 --- a/src/pbHelpers/ComponentTypes/index.ts +++ b/src/pbHelpers/ComponentTypes/index.ts @@ -29,3 +29,4 @@ export * from "./Voltage"; export * from "./VPD"; export * from "./Weight"; export * from "./CapacitorCable"; +export * from "./AnalogPressure"; diff --git a/src/products/MiVent/MiVentAvailability.ts b/src/products/MiVent/MiVentAvailability.ts index 0108806..87d0db2 100644 --- a/src/products/MiVent/MiVentAvailability.ts +++ b/src/products/MiVent/MiVentAvailability.ts @@ -10,6 +10,7 @@ const MiVentV1Pins: ConfigurablePin[] = [ { address: 1024, label: "C2" } ]; +//no longer in development so if you are adding new component types DO NOT add the I2C address here because the firmware of the device will not support it export const MiVentV1Availability: DeviceAvailabilityMap = new Map< quack.AddressType, DevicePositions @@ -53,7 +54,8 @@ export const MiVentV2Availability: DeviceAvailabilityMap = new Map< [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]], - [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]] + [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]], + [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, [0x6e]] ]) ],