pushing this stuff up so carter can take a look

This commit is contained in:
csawatzky 2026-03-23 14:57:23 -06:00
parent 7e79d0859b
commit a2bc841510
13 changed files with 660 additions and 184 deletions

160
src/field/FieldTaskList.tsx Normal file
View file

@ -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<Task | undefined>(undefined)
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(() => {
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()
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 (
<React.Fragment>
{selectedTask &&
<TaskDrawer
task={selectedTask}
open={openTaskDrawer}
deleteTask={deleteTask}
completeTask={markComplete}
closeCallback={() => {
setOpenTaskDrawer(false)
setSelectedTask(undefined)
}}
keys={[field]}
types={["field"]}
/>
}
<TaskList
deleteTask={deleteTask}
editTaskMethod={updateTask}
markComplete={markComplete}
openTask={id => {
let task = taskMap.get(id)
if(task){
setSelectedTask(task)
setOpenTaskDrawer(true)
}
}}
tasks={Array.from(taskMap.values())}
reLoad={() => {
loadTasks()
}}
keys={[field]}
types={["field"]}
/>
<TaskSettings
open={opentaskSettings}
onClose={() => {
setOpenTaskSettings(false)
}}
keys={[field]}
types={["field"]}
task={selectedTask}
/>
<Fab
onClick={() => {setOpenTaskSettings(true)}}
aria-label="Create Task"
className={classes.overlayFab}
size={"medium"}>
<AddIcon />
</Fab>
</React.Fragment>
)
}

View file

@ -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) {
<Weather longitude={field.center().longitude} latitude={field.center().latitude} />
</TabPanelMine>
<TabPanelMine value={value} index={2}>
<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton />
<FieldTaskList field={field.key()}/>
</TabPanelMine>
</React.Fragment>
);

View file

@ -25,7 +25,9 @@ export interface IPermissionAPIContext {
shareObjectByKey: (
scope: Scope,
key: string,
permissions: pond.Permission[]
permissions: pond.Permission[],
keys?: string[],
type?: string[]
) => Promise<AxiosResponse<any>>;
addShareableLink: (scope: Scope, expiration: string) => Promise<AxiosResponse<any>>;
removeShareableLink: (scope: Scope, code: string) => Promise<AxiosResponse<any>>;
@ -136,9 +138,11 @@ export default function PermissionProvider(props: PropsWithChildren<Props>) {
})
};
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<AxiosResponse>((resolve, reject) => {
post(url, {
key: key,

View file

@ -6,7 +6,7 @@ import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
export interface ITaskAPIContext {
addTask: (task: pond.TaskSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddTaskResponse>>;
addTask: (task: pond.TaskSettings, otherTeam?: string, keys?: string[], types?: string[]) => Promise<AxiosResponse<pond.AddTaskResponse>>;
getTask: (taskID: string, otherTeam?: string) => Promise<AxiosResponse<pond.Task>>;
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<AxiosResponse<pond.ListTasksResponse>>;
removeTask: (taskID: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveTaskResponse>>;
updateTask: (
@ -38,10 +40,20 @@ export default function TaskProvider(props: PropsWithChildren<Props>) {
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<pond.AddTaskResponse>(pondURL("/tasks?as=" + view), task);
return post<pond.AddTaskResponse>(pondURL("/tasks"), task);
if (view) return post<pond.AddTaskResponse>(
pondURL(
"/tasks?as=" + view +
(keys ? "&keys=" + keys.join(",") : "") +
(types ? "&types=" + types.join(",") : "")
), task);
return post<pond.AddTaskResponse>(
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<Props>) {
asRoot?: boolean,
otherTeam?: string,
from?: string,
to?: string
to?: string,
keys?: string[],
types?: string[]
) => {
const view = otherTeam ? otherTeam : as
return get<pond.ListTasksResponse>(
@ -81,7 +95,9 @@ export default function TaskProvider(props: PropsWithChildren<Props>) {
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
(view ? "&as=" + view : "") +
(from ? "&from=" + from : "") +
(to ? "&to=" + to : "")
(to ? "&to=" + to : "") +
(keys ? "&keys=" + keys.join(",") : "") +
(types ? "&types=" + types.join(",") : "")
)
);
};

View file

@ -32,7 +32,7 @@ export interface ITeamAPIContext {
prefixSearch?: string,
) => Promise<AxiosResponse<pond.ListTeamsResponse>>;
listAllTeams: () => Promise<AxiosResponse<pond.ListTeamsResponse>>;
listObjectTeams: (scope: Scope, otherTeam?: string) => Promise<any>;
listObjectTeams: (scope: Scope, otherTeam?: string, keys?: string[], types?: string[]) => Promise<any>;
removeTeam: (key: string) => Promise<AxiosResponse<pond.RemoveTeamResponse>>;
}
@ -156,10 +156,12 @@ export default function TeamProvider(props: PropsWithChildren<Props>) {
})
};
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<AxiosResponse>((resolve, reject) => {
get(pondURL(url)).then(resp => {
return resolve(resp)

View file

@ -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<pond.Permission[]>([]);
const [{ user, as }] = useGlobalState();
const userAPI = useUserAPI();
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(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);
permissionAPI.getPermissions(user.id(), props.keys, props.types).then(resp => {
setPermissions(pond.EvaluatePermissionsResponse.fromObject(resp.data).permissions)
})
.catch(err => {});
}, [props.task, userAPI, user, as]);
}, [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 (
<Menu
id="viewMenu"
anchorEl={menuAnchorEl ? menuAnchorEl : null}
open={menuAnchorEl !== null}
onClose={() => setMenuAnchorEl(null)}
disableAutoFocusItem>
<MenuItem
onClick={() => {
props.markComplete(props.task);
setMenuAnchorEl(null);
}}>
<ListItemIcon>
<Done style={{ color: "green" }} />
</ListItemIcon>
<ListItemText primary="Mark Complete" />
</MenuItem>
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
<MenuItem
onClick={() => {
props.editTaskMethod(props.task);
setMenuAnchorEl(null);
}}>
<ListItemIcon>
<Edit />
</ListItemIcon>
<ListItemText primary="Edit" />
</MenuItem>
)}
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
<MenuItem
onClick={() => {
props.deleteTask(props.task);
setMenuAnchorEl(null);
}}>
<ListItemIcon>
<DeleteIcon className={classes.red} />
</ListItemIcon>
<ListItemText primary="Delete" />
</MenuItem>
)}
</Menu>
);
};
// const taskActions = () => {
// return (
// <Menu
// id="viewMenu"
// anchorEl={menuAnchorEl ? menuAnchorEl : null}
// open={menuAnchorEl !== null}
// onClose={() => setMenuAnchorEl(null)}
// disableAutoFocusItem>
// <MenuItem
// onClick={() => {
// props.markComplete(props.task);
// setMenuAnchorEl(null);
// }}>
// <ListItemIcon>
// <Done style={{ color: "green" }} />
// </ListItemIcon>
// <ListItemText primary="Mark Complete" />
// </MenuItem>
// {permissions.includes(pond.Permission.PERMISSION_WRITE) && (
// <MenuItem
// onClick={() => {
// props.editTaskMethod(props.task);
// setMenuAnchorEl(null);
// }}>
// <ListItemIcon>
// <Edit />
// </ListItemIcon>
// <ListItemText primary="Edit" />
// </MenuItem>
// )}
// {permissions.includes(pond.Permission.PERMISSION_WRITE) && (
// <MenuItem
// onClick={() => {
// props.deleteTask(props.task);
// setMenuAnchorEl(null);
// }}>
// <ListItemIcon>
// <DeleteIcon className={classes.red} />
// </ListItemIcon>
// <ListItemText primary="Delete" />
// </MenuItem>
// )}
// </Menu>
// );
// };
return (
<Card style={{ width: "100%", paddingLeft: 5 }}>
@ -164,18 +151,31 @@ export default function TaskCard(props: Props) {
</Box>
</Grid>
<Grid size={1}>
<EventBlocker style={{ margin: "auto" }}>
{/* <EventBlocker style={{ margin: "auto" }}>
<IconButton
aria-owns={"taskOptions"}
aria-haspopup="true"
onClick={event => setMenuAnchorEl(event.currentTarget)}>
<MoreVert />
</IconButton>
</EventBlocker> */}
<EventBlocker style={{ margin: "auto" }}>
<TaskActions
markComplete={props.markComplete}
removeTask={props.deleteTask}
task={props.task}
permissions={permissions}
refreshCallback={() => {
props.reLoad()
}}
keys={props.keys}
types={props.types}
/>
</EventBlocker>
</Grid>
</Grid>
</CardActionArea>
{taskActions()}
{/* {taskActions()} */}
</Card>
);
}

View file

@ -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<Element | null>(null);
const [permissions, setPermissions] = useState<pond.Permission[]>([])
const [{user}] = useGlobalState();
// const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
const [openNote, setOpenNote] = useState(false);
const isMobile = useMobile();
const classes = useStyles();
const userAPI = useUserAPI();
const permissionAPI = usePermissionAPI();
const [worker, setWorker] = useState<pond.UserProfile>();
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 (
<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 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 (
@ -175,11 +185,20 @@ export default function TaskDrawer(props: Props) {
</IconButton>
</Tooltip>
<Tooltip title="More">
<IconButton
{/* <IconButton
className={classes.avatar}
onClick={event => setMenuAnchorEl(event.currentTarget)}>
<MoreVert />
</IconButton>
</IconButton> */}
<TaskActions
markComplete={(t) => completeTask(t)}
permissions={permissions}
task={task}
removeTask={deleteTask}
refreshCallback={closeCallback}
keys={props.keys}
types={props.types}
/>
</Tooltip>
</Grid>
</Grid>
@ -303,7 +322,7 @@ export default function TaskDrawer(props: Props) {
{taskTime()}
{assignees()}
</Box>
{taskActions()}
{/* {taskActions()} */}
{noteDrawer()}
</Box>
);

View file

@ -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<Task[]>([]);
@ -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}
/>
</ListItem>
));
@ -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}
/>
</ListItem>
));

View file

@ -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);
})

View file

@ -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<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]);
// 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;
let temp = new Map<string, Task>();
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<string, Task>());
setValue(1);
loadMultitask();
} else {
loadTasks();
}
}, [loadTasks, loadMultitask, as, drawerView]);
// if (drawerView) {
// setTasks(new Map<string, Task>());
// 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={[]}
/>
</TabPanelMine>
<Fab
@ -380,6 +392,8 @@ export default function TaskViewer(props: ViewProps) {
listHeight={"40vh"}
dateToView={currentDate}
openTask={(taskId: string) => openSelectedTask(taskId)}
keys={[]}
types={[]}
/>
</Box>
</Grid>
@ -423,16 +437,19 @@ export default function TaskViewer(props: ViewProps) {
<TaskSettings
open={newTaskDialog}
task={editTask}
objectKey={objectKey}
//objectKey={objectKey}
keys={keys}
types={types}
startDate={startDate}
onClose={r => {
if (r) {
if (drawerView) {
loadMultitask();
} else {
// if (r) {
// if (drawerView) {
// loadMultitask();
// } else {
// loadTasks();
// }
// }
loadTasks();
}
}
setEditTask(undefined);
setNewTaskDialog(false);
}}
@ -441,10 +458,14 @@ export default function TaskViewer(props: ViewProps) {
<TaskDrawer
task={viewTask}
open={openDrawer}
closeCallback={() => setOpenDrawer(false)}
closeCallback={() => {
setOpenDrawer(false)
loadTasks()
}}
completeTask={markComplete}
deleteTask={deleteTask}
editTask={setTaskToEdit}
keys={[]}
types={[]}
/>
}
</Box>

236
src/tasks/taskActions.tsx Normal file
View file

@ -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 | HTMLElement>(null);
const [openState, setOpenState] = useState<OpenState>({
// 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 (
<Menu
id="groupMenu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
keepMounted
disableAutoFocusItem>
{/* {!isOffline() && canShare && (
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, share: true });
setAnchorEl(null);
}}>
<ListItemIcon>
<ShareObjectIcon className={classes.shareIcon} />
</ListItemIcon>
<ListItemText primary="Share" />
</MenuItem>
)} */}
{/* {!isOffline() && canManageUsers && (
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, users: true });
setAnchorEl(null);
}}>
<ListItemIcon>
<ObjectUsersIcon />
</ListItemIcon>
<ListItemText primary="Users" />
</MenuItem>
)} */}
{!isOffline() && canEdit && (
<MenuItem
onClick={() => {
props.markComplete(props.task);
setAnchorEl(null);
}}>
<ListItemIcon>
<Done style={{ color: "green" }} />
</ListItemIcon>
<ListItemText primary="Mark Complete" />
</MenuItem>
)}
{!isOffline() && canEdit && (
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, settings: true });
setAnchorEl(null);
}}>
<ListItemIcon>
<Edit />
</ListItemIcon>
<ListItemText primary="Edit" />
</MenuItem>
)}
{!isOffline() && canManageUsers && (
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, teams: true });
setAnchorEl(null);
}}>
<ListItemIcon>
<ObjectTeamsIcon />
</ListItemIcon>
<ListItemText primary="Teams" />
</MenuItem>
)}
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
<MenuItem
onClick={() => {
props.removeTask(props.task);
setAnchorEl(null);
}}>
<ListItemIcon>
<DeleteIcon className={classes.red} />
</ListItemIcon>
<ListItemText primary="Delete" />
</MenuItem>
)}
{/* <MenuItem
dense
onClick={() => {
setOpenState({ ...openState, removeSelf: true });
setAnchorEl(null);
}}>
<ListItemIcon>
<RemoveSelfIcon className={classes.red} />
</ListItemIcon>
<ListItemText primary="Leave" />
</MenuItem> */}
</Menu>
);
};
const dialogs = () => {
const key = task.key;
// const label = task.title();
return (
<React.Fragment>
{/* <ShareObject
scope={fieldScope(key)}
label={label}
permissions={permissions}
isDialogOpen={openState.share}
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
/> */}
{/* <ObjectUsers
scope={fieldScope(key)}
label={label}
permissions={permissions}
isDialogOpen={openState.users}
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
refreshCallback={refreshCallback}
/> */}
{/* <RemoveSelfFromObject
scope={fieldScope(key)}
label={label}
isDialogOpen={openState.removeSelf}
closeDialogCallback={() => setOpenState({ ...openState, removeSelf: false })}
/> */}
<ObjectTeams
scope={taskScope(key)}
label="Teams"
permissions={permissions}
isDialogOpen={openState.teams}
refreshCallback={refreshCallback}
closeDialogCallback={() => setOpenState({ ...openState, teams: false })}
keys={keys}
types={types}
/>
<TaskSettings
open={openState.settings}
task={task}
//objectKey={objectKey}
//startDate={startDate}
onClose={r => {
setOpenState({ ...openState, settings: false})
if(r){
refreshCallback()
}
}}
/>
</React.Fragment>
);
};
return (
<React.Fragment>
<IconButton
aria-owns={anchorEl ? "groupMenu" : undefined}
aria-haspopup="true"
onClick={(event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget)}>
<MoreIcon />
</IconButton>
{groupMenu()}
{dialogs()}
</React.Fragment>
);
}

View file

@ -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}
/>
</React.Fragment>
);

View file

@ -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[]>([
pond.Permission.PERMISSION_READ
]);
@ -109,7 +111,7 @@ export default function ShareObject(props: Props) {
const [teamKey, setTeamKey] = useState<string>("");
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;
});
}