final adjustments to the field task list and the field drawer and field page using the tasks with the permissions in the correct way
This commit is contained in:
parent
8633c197ab
commit
186604dccf
11 changed files with 97 additions and 177 deletions
|
|
@ -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<Map<string, Task>>(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<string,Task> = 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) {
|
|||
}
|
||||
<TaskList
|
||||
deleteTask={deleteTask}
|
||||
editTaskMethod={updateTask}
|
||||
markComplete={markComplete}
|
||||
openTask={id => {
|
||||
let task = taskMap.get(id)
|
||||
|
|
@ -141,8 +151,11 @@ export default function FieldTaskList(props: Props) {
|
|||
/>
|
||||
<TaskSettings
|
||||
open={opentaskSettings}
|
||||
onClose={() => {
|
||||
onClose={(reload) => {
|
||||
setOpenTaskSettings(false)
|
||||
if(reload){
|
||||
loadTasks()
|
||||
}
|
||||
}}
|
||||
keys={[field]}
|
||||
types={["field"]}
|
||||
|
|
|
|||
|
|
@ -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)" } }}>
|
||||
<Tab label="Field Overview" />
|
||||
<Tab label="Weather" />
|
||||
<Tab label="Field Activities" />
|
||||
<Tab label="Field Tasks" />
|
||||
</Tabs>
|
||||
<TabPanelMine value={value} index={0}>
|
||||
<Box paddingLeft={2} paddingRight={2} paddingBottom={5}>
|
||||
|
|
|
|||
|
|
@ -512,7 +512,7 @@ export default function Bin(props: Props) {
|
|||
if (showTasks()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TaskViewer drawerView objectKey={binID} loadKeys={[binID]} />
|
||||
<TaskViewer drawerView keys={[binID]} types={["bin"]} overlayButton />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
@ -1156,7 +1156,7 @@ export default function Bin(props: Props) {
|
|||
<IconButton style={{ width: 50 }} onClick={() => setTaskDrawer(false)}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
<TaskViewer drawerView objectKey={binID} loadKeys={[binID]} />
|
||||
<TaskViewer drawerView keys={[binID]} types={["bin"]} overlayButton />
|
||||
</Box>
|
||||
</Drawer>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 (<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton={isMobile}/>)
|
||||
return (
|
||||
<FieldTaskList field={field.key()}/>
|
||||
)
|
||||
}
|
||||
|
||||
const desktopView = () => {
|
||||
|
|
|
|||
|
|
@ -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<AxiosResponse<pond.EvaluatePermissionsResponse>>;
|
||||
removePermissions: (user: string, scope: Scope) => Promise<AxiosResponse<any>>;
|
||||
updatePermissions: (scope: Scope, users: User[] | Team[]) => Promise<AxiosResponse<any>>;
|
||||
updateObjectPermissions: (key: string, type: string, parentType: string, changes: PermissionChanges[], keys: string[], types: string[]) => Promise<AxiosResponse<any>>;
|
||||
updateRelativePermissions: (
|
||||
parentScope: Scope,
|
||||
childScope: Scope,
|
||||
|
|
@ -96,6 +102,28 @@ export default function PermissionProvider(props: PropsWithChildren<Props>) {
|
|||
})
|
||||
};
|
||||
|
||||
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<AxiosResponse>((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<Props>) {
|
|||
getPermissions,
|
||||
removePermissions,
|
||||
updatePermissions,
|
||||
updateObjectPermissions,
|
||||
updateRelativePermissions,
|
||||
shareObject,
|
||||
shareObjectByKey,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<pond.UserProfile>();
|
||||
|
||||
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 (
|
||||
// <Menu
|
||||
// id="viewMenu"
|
||||
// anchorEl={menuAnchorEl ? menuAnchorEl : null}
|
||||
// open={menuAnchorEl !== null}
|
||||
// onClose={() => setMenuAnchorEl(null)}
|
||||
// disableAutoFocusItem>
|
||||
// <MenuItem
|
||||
// onClick={() => {
|
||||
// editTask(props.task);
|
||||
// setMenuAnchorEl(null);
|
||||
// }}>
|
||||
// <ListItemIcon>
|
||||
// <Edit />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText primary="Edit" />
|
||||
// </MenuItem>
|
||||
// <MenuItem
|
||||
// onClick={() => {
|
||||
// deleteTask(props.task);
|
||||
// setMenuAnchorEl(null);
|
||||
// closeCallback();
|
||||
// }}>
|
||||
// <ListItemIcon>
|
||||
// <DeleteIcon style={{ color: "red" }} />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText primary="Delete" />
|
||||
// </MenuItem>
|
||||
// </Menu>
|
||||
// );
|
||||
// };
|
||||
|
||||
const bodyButtons = () => {
|
||||
return (
|
||||
<Grid container direction="row" alignItems="center" justifyContent="space-between">
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
<ListItem divider key={index} disableGutters>
|
||||
<TaskCard
|
||||
task={task}
|
||||
editTaskMethod={editTaskMethod}
|
||||
markComplete={(task: Task) => markComplete(task)}
|
||||
deleteTask={(task: Task) => deleteTask(task)}
|
||||
reLoad={reLoad}
|
||||
|
|
@ -142,7 +90,6 @@ export default function TaskList(props: Props) {
|
|||
<ListItem divider key={index} disableGutters>
|
||||
<TaskCard
|
||||
task={task}
|
||||
editTaskMethod={editTaskMethod}
|
||||
markComplete={(task: Task) => markComplete(task)}
|
||||
deleteTask={(task: Task) => deleteTask(task)}
|
||||
reLoad={reLoad}
|
||||
|
|
@ -170,20 +117,6 @@ export default function TaskList(props: Props) {
|
|||
}
|
||||
]}
|
||||
/>
|
||||
{/* <ToggleButtonGroup value={viewing} exclusive size="small" aria-label="Bin Mode">
|
||||
<ToggleButton
|
||||
value={"upcoming"}
|
||||
aria-label="upcoming"
|
||||
onClick={() => setViewing("upcoming")}>
|
||||
Upcoming
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
onClick={() => setViewing("complete")}
|
||||
value={"complete"}
|
||||
aria-label="complete">
|
||||
Complete
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup> */}
|
||||
{location !== "/tasks" && (
|
||||
<IconButton id="tasks" component={Link} to="/tasks">
|
||||
<TasksIcon />
|
||||
|
|
|
|||
|
|
@ -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<Map<string, Task>>(new Map<string, Task>([]));
|
||||
|
|
@ -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<string, Task>();
|
||||
// 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) {
|
|||
<TaskList
|
||||
tasks={Array.from(tasks.values())}
|
||||
label={label}
|
||||
editTaskMethod={setTaskToEdit}
|
||||
markComplete={markComplete}
|
||||
deleteTask={deleteTask}
|
||||
reLoad={() => loadTasks()}
|
||||
|
|
@ -385,7 +353,6 @@ export default function TaskViewer(props: ViewProps) {
|
|||
<TaskList
|
||||
tasks={Array.from(tasks.values())}
|
||||
label={label}
|
||||
editTaskMethod={setTaskToEdit}
|
||||
markComplete={markComplete}
|
||||
deleteTask={deleteTask}
|
||||
reLoad={() => loadTasks()}
|
||||
|
|
@ -437,18 +404,10 @@ export default function TaskViewer(props: ViewProps) {
|
|||
<TaskSettings
|
||||
open={newTaskDialog}
|
||||
task={editTask}
|
||||
//objectKey={objectKey}
|
||||
keys={keys}
|
||||
types={types}
|
||||
startDate={startDate}
|
||||
onClose={r => {
|
||||
// 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 ?? []}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ export default function TaskActions(props: Props) {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<IconButton
|
||||
disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||
aria-owns={anchorEl ? "groupMenu" : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={(event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget)}>
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue