added tasks page and functionality
This commit is contained in:
parent
b573e10707
commit
2ef22a8bc2
19 changed files with 1992 additions and 15 deletions
128
src/tasks/TaskCalendar.tsx
Normal file
128
src/tasks/TaskCalendar.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import FullCalendar from "@fullcalendar/react";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import interactionPlugin, { DateClickArg } from "@fullcalendar/interaction";
|
||||
import React, { useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { Task } from "models";
|
||||
import { EventClickArg } from "@fullcalendar/core";
|
||||
|
||||
interface Props {
|
||||
dateClickMethod: (args: DateClickArg) => void;
|
||||
taskClickMethod: (arg: EventClickArg) => void;
|
||||
tasks: Task[];
|
||||
changeDateCallback: (date: Date) => void;
|
||||
initialView?: string;
|
||||
initialDate?: Date;
|
||||
centerToolbar?: string;
|
||||
calHeight?: string | number;
|
||||
hideToday?: boolean;
|
||||
hideTitle?: boolean;
|
||||
hideCal?: boolean;
|
||||
hideCalCallback?: () => void;
|
||||
}
|
||||
|
||||
interface Event {
|
||||
id: string;
|
||||
title: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export default function TaskCalendar(props: Props) {
|
||||
const {
|
||||
dateClickMethod,
|
||||
taskClickMethod,
|
||||
hideCalCallback,
|
||||
changeDateCallback,
|
||||
tasks,
|
||||
initialView,
|
||||
centerToolbar,
|
||||
calHeight,
|
||||
hideToday,
|
||||
hideTitle,
|
||||
hideCal
|
||||
} = props;
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
let ref = React.createRef<FullCalendar>();
|
||||
|
||||
useEffect(() => {
|
||||
setEvents([]);
|
||||
let e: Event[] = [];
|
||||
tasks.forEach(task => {
|
||||
let event: Event = {
|
||||
id: task.key,
|
||||
title: task.title(),
|
||||
start: new Date(task.start() + "T" + task.startTime()),
|
||||
end: new Date(task.end() + "T" + task.endTime()),
|
||||
backgroundColor: task.settings.colour
|
||||
};
|
||||
e.push(event);
|
||||
setEvents([...e]);
|
||||
});
|
||||
}, [tasks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current && props.initialDate) {
|
||||
let api = ref.current.getApi();
|
||||
api.gotoDate(props.initialDate);
|
||||
}
|
||||
}, [props.initialDate, ref]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<FullCalendar
|
||||
ref={ref}
|
||||
contentHeight={hideCal ? 0 : calHeight}
|
||||
headerToolbar={{
|
||||
start: hideTitle ? "" : "title",
|
||||
center: centerToolbar,
|
||||
end: hideToday ? "back,forward" : "today back,forward"
|
||||
}}
|
||||
titleFormat={{ month: "short", year: "numeric" }}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
//options for view dayGridWeek, dayGridMonth, timeGridWeek, timeGridMonth, timeGridDay
|
||||
initialView={initialView}
|
||||
dateClick={dateClickMethod}
|
||||
eventClick={taskClickMethod}
|
||||
events={events}
|
||||
allDaySlot={false}
|
||||
slotEventOverlap={false}
|
||||
nowIndicator
|
||||
dayHeaders={!hideCal}
|
||||
customButtons={{
|
||||
hide: {
|
||||
text: "Hide",
|
||||
click: () => {
|
||||
if (hideCalCallback) {
|
||||
hideCalCallback();
|
||||
}
|
||||
}
|
||||
},
|
||||
back: {
|
||||
text: "<",
|
||||
click: () => {
|
||||
if (ref.current) {
|
||||
let api = ref.current.getApi();
|
||||
api.prev();
|
||||
changeDateCallback(api.getDate());
|
||||
}
|
||||
}
|
||||
},
|
||||
forward: {
|
||||
text: ">",
|
||||
click: () => {
|
||||
if (ref.current) {
|
||||
let api = ref.current.getApi();
|
||||
api.next();
|
||||
changeDateCallback(api.getDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
178
src/tasks/TaskCard.tsx
Normal file
178
src/tasks/TaskCard.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import {
|
||||
Box,
|
||||
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 { red } from "@mui/material/colors";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { taskScope } from "models/Scope";
|
||||
import EventBlocker from "common/EventBlocker";
|
||||
|
||||
interface Props {
|
||||
task: Task;
|
||||
reLoad: () => void;
|
||||
editTaskMethod: (task: Task) => void;
|
||||
markComplete: (task: Task) => void;
|
||||
deleteTask: (task: Task) => void;
|
||||
openTaskPage: (taskId: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
red: {
|
||||
color: red["500"]
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export default function TaskCard(props: Props) {
|
||||
const monthList = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"Aug",
|
||||
"Sept",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec"
|
||||
];
|
||||
const [month, setMonth] = useState(0);
|
||||
const [day, setDay] = useState(0);
|
||||
const classes = useStyles();
|
||||
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||
const [{ user }] = useGlobalState();
|
||||
const userAPI = useUserAPI();
|
||||
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
|
||||
|
||||
const loadPermissions = useCallback(() => {
|
||||
let scope = taskScope(props.task.key);
|
||||
userAPI
|
||||
.getUser(user.id(), scope)
|
||||
.then(resp => {
|
||||
setPermissions(resp.permissions);
|
||||
})
|
||||
.catch(err => {});
|
||||
}, [props.task, userAPI, user]);
|
||||
|
||||
useEffect(() => {
|
||||
loadPermissions();
|
||||
}, [loadPermissions]);
|
||||
|
||||
const openTaskPage = () => {
|
||||
props.openTaskPage(props.task.key);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let date = new Date(props.task.start());
|
||||
setMonth(date.getUTCMonth());
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card style={{ width: "100%", paddingLeft: 5 }}>
|
||||
<CardActionArea onClick={openTaskPage} component="div">
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid size={2}>
|
||||
<Box
|
||||
style={{
|
||||
border: "1px solid white",
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
borderRadius: 5,
|
||||
margin: "auto",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: "50px",
|
||||
textAlign: "center"
|
||||
}}>
|
||||
<Typography>{monthList[month]}</Typography>
|
||||
<Typography style={{ fontWeight: 750 }}>{day}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={8}>
|
||||
<Box margin={1}>
|
||||
<Typography style={{ fontWeight: 700 }}>{props.task.title()}</Typography>
|
||||
</Box>
|
||||
<Box margin={1}>
|
||||
<Typography noWrap>{props.task.description()}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={1}>
|
||||
<EventBlocker style={{ margin: "auto" }}>
|
||||
<IconButton
|
||||
aria-owns={"taskOptions"}
|
||||
aria-haspopup="true"
|
||||
onClick={event => setMenuAnchorEl(event.currentTarget)}>
|
||||
<MoreVert />
|
||||
</IconButton>
|
||||
</EventBlocker>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardActionArea>
|
||||
{taskActions()}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
323
src/tasks/TaskDrawer.tsx
Normal file
323
src/tasks/TaskDrawer.tsx
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Card,
|
||||
Drawer,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Slider,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Done, Edit, MoreVert } from "@mui/icons-material";
|
||||
import DisplayDrawer from "common/DisplayDrawer";
|
||||
import { Task } from "models";
|
||||
import { useEffect, useState } from "react";
|
||||
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 { getThemeType } from "theme";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
interface Props {
|
||||
task: Task;
|
||||
open: boolean;
|
||||
closeCallback: () => void;
|
||||
editTask: (task: Task) => void;
|
||||
deleteTask: (task: Task) => void;
|
||||
completeTask: (task: Task) => void;
|
||||
}
|
||||
|
||||
// interface TabPanelProps {
|
||||
// children?: React.ReactNode;
|
||||
// index: any;
|
||||
// value: any;
|
||||
// }
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
avatar: {
|
||||
color: getThemeType() === "light" ? theme.palette.common.black : theme.palette.common.white,
|
||||
backgroundColor: "transparent",
|
||||
width: theme.spacing(5),
|
||||
height: theme.spacing(5),
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.divider
|
||||
},
|
||||
sliderRoot: {},
|
||||
sliderThumb: {
|
||||
height: 15,
|
||||
width: 15,
|
||||
marginTop: 0,
|
||||
marginLeft: 0,
|
||||
backgroundColor: "yellow"
|
||||
},
|
||||
sliderTrack: {
|
||||
height: 3,
|
||||
backgroundColor: "white"
|
||||
},
|
||||
sliderRail: {
|
||||
height: 3,
|
||||
backgroundColor: "white"
|
||||
},
|
||||
sliderValLabel: {
|
||||
left: -20,
|
||||
top: 40,
|
||||
background: "transparent",
|
||||
"& *": {
|
||||
color: "#fff"
|
||||
}
|
||||
},
|
||||
sliderMark: {
|
||||
visibility: "hidden"
|
||||
},
|
||||
sliderMarked: {
|
||||
marginTop: 25,
|
||||
marginBottom: 0
|
||||
},
|
||||
sliderMarkLabel: {
|
||||
top: -25,
|
||||
left: -10
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export default function TaskDrawer(props: Props) {
|
||||
const monthList = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"Aug",
|
||||
"Sept",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec"
|
||||
];
|
||||
const { task, open, closeCallback, completeTask, deleteTask, editTask } = props;
|
||||
const [month, setMonth] = useState(0);
|
||||
const [day, setDay] = useState(0);
|
||||
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
|
||||
const [openNote, setOpenNote] = useState(false);
|
||||
const isMobile = useMobile();
|
||||
const classes = useStyles();
|
||||
const userAPI = useUserAPI();
|
||||
const [worker, setWorker] = useState<pond.UserProfile>();
|
||||
|
||||
useEffect(() => {
|
||||
if (task.start()) {
|
||||
let date = new Date(task.start());
|
||||
setMonth(date.getUTCMonth());
|
||||
setDay(date.getUTCDate());
|
||||
}
|
||||
userAPI.getProfile(task.worker()).then(resp => {
|
||||
setWorker(resp);
|
||||
});
|
||||
}, [task, userAPI]);
|
||||
|
||||
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">
|
||||
<Grid>
|
||||
<Tooltip title="Task Notes">
|
||||
<IconButton className={classes.avatar} onClick={() => setOpenNote(true)}>
|
||||
<NotesIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<IconButton style={{ visibility: "hidden" }}>3</IconButton> {/** go to bin/field */}
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Tooltip title="Mark Complete">
|
||||
<IconButton className={classes.avatar} onClick={() => completeTask(task)}>
|
||||
<Done style={{ color: task.complete() ? "green" : "white" }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="More">
|
||||
<IconButton
|
||||
className={classes.avatar}
|
||||
onClick={event => setMenuAnchorEl(event.currentTarget)}>
|
||||
<MoreVert />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const noteDrawer = () => {
|
||||
return (
|
||||
<Drawer
|
||||
anchor={isMobile ? "bottom" : "right"}
|
||||
open={openNote}
|
||||
onClose={() => setOpenNote(false)}>
|
||||
<Box height={isMobile ? "50vh" : "100vh"} padding={2}>
|
||||
<Typography style={{ fontWeight: 650 }}>Notes</Typography>
|
||||
<Chat type={pond.NoteType.NOTE_TYPE_TASK} objectKey={task.key} />
|
||||
</Box>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
const taskCard = () => {
|
||||
return (
|
||||
<Card style={{ height: "35%", paddingTop: 10 }}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid size={2}>
|
||||
<Box
|
||||
style={{
|
||||
border: "1px solid white",
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
borderRadius: 5,
|
||||
margin: "auto",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: "50px",
|
||||
textAlign: "center"
|
||||
}}>
|
||||
<Typography>{monthList[month]}</Typography>
|
||||
<Typography style={{ fontWeight: 750 }}>{day}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={10}>
|
||||
<Typography style={{ fontWeight: 700, fontSize: 30 }}>{task.title()}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box padding={3}>{task.description()}</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const taskTime = () => {
|
||||
let start = task
|
||||
? new Date(task.start() + "T" + task.startTime()).valueOf()
|
||||
: new Date().valueOf();
|
||||
let end = task ? new Date(task.end() + "T" + task.endTime()).valueOf() : new Date().valueOf();
|
||||
return (
|
||||
<Box style={{ height: "30%" }}>
|
||||
<Grid container direction="row" alignItems="center" style={{ height: "100%" }}>
|
||||
<Grid size={3}>
|
||||
<Box style={{ textAlign: "center" }}>
|
||||
<Typography>{task.start()}</Typography>
|
||||
<Typography>{task.startTime()}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<Slider
|
||||
classes={{
|
||||
root: classes.sliderRoot,
|
||||
rail: classes.sliderRail,
|
||||
track: classes.sliderTrack,
|
||||
thumb: classes.sliderThumb,
|
||||
valueLabel: classes.sliderValLabel,
|
||||
marked: classes.sliderMarked,
|
||||
mark: classes.sliderMark,
|
||||
markLabel: classes.sliderMarkLabel
|
||||
}}
|
||||
min={start}
|
||||
max={end}
|
||||
value={new Date().valueOf()} />
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Box style={{ textAlign: "center" }}>
|
||||
<Typography>{task.end()}</Typography>
|
||||
<Typography>{task.endTime()}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const assignees = () => {
|
||||
return (
|
||||
<Card style={{ height: "35%" }}>
|
||||
<Box padding={2}>
|
||||
<Typography style={{ fontWeight: 700, fontSize: 30 }}>Assigned To:</Typography>
|
||||
{/* TODO: change this to get the user and display the user name and avatar */}
|
||||
{worker && (
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid>
|
||||
<Avatar
|
||||
alt={worker.name}
|
||||
src={worker.avatar && worker.avatar !== "" ? worker.avatar : undefined}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Box marginLeft={2}>{worker.name}</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const drawerBody = () => {
|
||||
return (
|
||||
<Box padding={2}>
|
||||
{bodyButtons()}
|
||||
<Box marginTop={3} style={{ height: "65vh" }}>
|
||||
{taskCard()}
|
||||
{taskTime()}
|
||||
{assignees()}
|
||||
</Box>
|
||||
{taskActions()}
|
||||
{noteDrawer()}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DisplayDrawer
|
||||
width={500}
|
||||
title={task.title()}
|
||||
displayNext={() => {}}
|
||||
displayPrev={() => {}}
|
||||
drawerBody={drawerBody()}
|
||||
onClose={closeCallback}
|
||||
open={open}
|
||||
/>
|
||||
);
|
||||
}
|
||||
176
src/tasks/TaskList.tsx
Normal file
176
src/tasks/TaskList.tsx
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import { Box, IconButton, List, ListItem, Typography, ToggleButton, ToggleButtonGroup } from "@mui/material";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import TaskCard from "./TaskCard";
|
||||
import { Task } from "models";
|
||||
//import { getThemeType } from "theme";
|
||||
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import TasksIcon from "products/Construction/TasksIcon";
|
||||
import moment from "moment";
|
||||
|
||||
interface Props {
|
||||
tasks: Task[];
|
||||
editTaskMethod: (task: Task) => void;
|
||||
markComplete: (task: Task) => void;
|
||||
deleteTask: (task: Task) => void;
|
||||
openTask: (taskId: string) => void;
|
||||
reLoad: () => void;
|
||||
listHeight?: string | number;
|
||||
label?: string;
|
||||
dateToView?: Date;
|
||||
}
|
||||
|
||||
export default function TaskList(props: Props) {
|
||||
const {
|
||||
editTaskMethod,
|
||||
markComplete,
|
||||
deleteTask,
|
||||
openTask,
|
||||
reLoad,
|
||||
listHeight,
|
||||
label,
|
||||
dateToView
|
||||
} = props;
|
||||
const [tasks, setTasks] = useState(props.tasks);
|
||||
const [incomplete, setIncomplete] = useState<Task[]>([]);
|
||||
const [complete, setComplete] = useState<Task[]>([]);
|
||||
const [viewing, setViewing] = useState<"upcoming" | "complete">("upcoming");
|
||||
|
||||
useEffect(() => {
|
||||
setTasks(props.tasks);
|
||||
}, [setTasks, props.tasks]);
|
||||
let location = useLocation().pathname;
|
||||
|
||||
useEffect(() => {
|
||||
let incomplete: Task[] = [];
|
||||
let complete: Task[] = [];
|
||||
tasks.forEach(task => {
|
||||
let within = true;
|
||||
if (dateToView) {
|
||||
within = false;
|
||||
let current = moment(dateToView);
|
||||
let start = moment(task.start());
|
||||
let end = moment(task.end());
|
||||
if (current.isBetween(start, end) || current.isSame(start) || current.isSame(end)) {
|
||||
within = true;
|
||||
}
|
||||
}
|
||||
if (within) {
|
||||
if (!task.complete()) {
|
||||
incomplete.push(task);
|
||||
} else {
|
||||
complete.push(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
//TODO sort tasks complete=asc, incomplete=desc
|
||||
setIncomplete(incomplete);
|
||||
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}
|
||||
openTaskPage={(taskId: string) => openTask(taskId)}
|
||||
/>
|
||||
</ListItem>
|
||||
));
|
||||
|
||||
const completeTasks = complete.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}
|
||||
openTaskPage={(taskId: string) => openTask(taskId)}
|
||||
/>
|
||||
</ListItem>
|
||||
));
|
||||
|
||||
return (
|
||||
<Box padding="5px" textAlign="center">
|
||||
<Typography variant="h5">{label ? label : ""}</Typography>
|
||||
<Box textAlign="center">
|
||||
<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 />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
<List style={{ height: listHeight, overflow: "auto" }}>
|
||||
{viewing === "upcoming" ? incompleteTasks : completeTasks}
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
351
src/tasks/TaskSettings.tsx
Normal file
351
src/tasks/TaskSettings.tsx
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField
|
||||
} from "@mui/material";
|
||||
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 { useEffect } from "react";
|
||||
import { Task, teamScope, User } from "models";
|
||||
import ColourPicker from "common/ColourPicker";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: (reLoad?: boolean) => void;
|
||||
markComplete?: boolean;
|
||||
startDate?: any;
|
||||
task?: Task;
|
||||
objectKey?: string;
|
||||
type?: string;
|
||||
hasCost?: boolean;
|
||||
costTitle?: string;
|
||||
secondaryCostTitle?: string;
|
||||
}
|
||||
|
||||
export default function TaskSettings(props: Props) {
|
||||
const [taskName, setTaskName] = useState("");
|
||||
const [startDate, setStartDate] = useState(moment().format("YYYY-MM-DD"));
|
||||
const [startTime, setStartTime] = useState("00:00");
|
||||
const [endDate, setEndDate] = useState("");
|
||||
const [endTime, setEndTime] = useState("00:00");
|
||||
const [taskDescription, setTaskDescription] = useState("");
|
||||
const [{ user, as }] = useGlobalState();
|
||||
const [worker, setWorker] = useState(user.id());
|
||||
const [colour, setColour] = useState("");
|
||||
const taskAPI = useTaskAPI();
|
||||
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<User[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.task) {
|
||||
setTaskName(props.task.title());
|
||||
setStartDate(props.task.start());
|
||||
setStartTime(props.task.startTime());
|
||||
setEndDate(props.task.end());
|
||||
setEndTime(props.task.endTime());
|
||||
setTaskDescription(props.task.description());
|
||||
setWorker(props.task.worker());
|
||||
setColour(props.task.settings.colour);
|
||||
setCost(props.task.cost().toString());
|
||||
setSecondaryCost(props.task.secondaryCost().toString());
|
||||
}
|
||||
if (props.startDate) {
|
||||
setStartDate(props.startDate);
|
||||
}
|
||||
}, [props.startDate, props.task]);
|
||||
|
||||
useEffect(() => {
|
||||
if (as === "") {
|
||||
setWorker(user.id());
|
||||
setUsers([user]);
|
||||
} else {
|
||||
userAPI.listObjectUsers(teamScope(as)).then(resp => {
|
||||
setUsers(resp.data.users.map((u: pond.User) => User.any(u)));
|
||||
});
|
||||
}
|
||||
}, [as, userAPI, user]);
|
||||
|
||||
const formComplete = () => {
|
||||
if (
|
||||
taskName.length < 1 ||
|
||||
taskDescription.length < 1 ||
|
||||
worker.length < 1 ||
|
||||
endDate.length < 1
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//saves new tasks into the database
|
||||
const newTask = () => {
|
||||
let taskSettings = pond.TaskSettings.create({
|
||||
userId: user.id(),
|
||||
title: taskName,
|
||||
start: startDate,
|
||||
startTime: startTime,
|
||||
end: endDate,
|
||||
endTime: endTime,
|
||||
objectKey: props.objectKey ? props.objectKey : user.id(),
|
||||
description: taskDescription,
|
||||
worker: worker,
|
||||
complete: false,
|
||||
colour: colour,
|
||||
type: props.type,
|
||||
cost: !isNaN(parseFloat(cost)) ? Math.round((parseFloat(cost) * 100) / 100) : 0,
|
||||
secondaryCost: !isNaN(parseFloat(secondaryCost))
|
||||
? Math.round((parseFloat(secondaryCost) * 100) / 100)
|
||||
: 0
|
||||
});
|
||||
|
||||
let returnTask = Task.create();
|
||||
returnTask.settings = taskSettings;
|
||||
|
||||
taskAPI
|
||||
.addTask(taskSettings)
|
||||
.then(resp => {
|
||||
props.onClose(true);
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("Failed to add new task");
|
||||
});
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
const updateTask = () => {
|
||||
let task: Task = Task.any(props.task);
|
||||
task.settings.title = taskName;
|
||||
task.settings.start = startDate;
|
||||
task.settings.startTime = startTime;
|
||||
task.settings.end = endDate;
|
||||
task.settings.endTime = endTime;
|
||||
task.settings.description = taskDescription;
|
||||
task.settings.worker = worker;
|
||||
task.settings.colour = colour;
|
||||
task.settings.cost = !isNaN(parseFloat(cost)) ? Math.round(parseFloat(cost) * 100) / 100 : 0;
|
||||
task.settings.secondaryCost = !isNaN(parseFloat(secondaryCost))
|
||||
? Math.round(parseFloat(secondaryCost) * 100) / 100
|
||||
: 0;
|
||||
|
||||
taskAPI
|
||||
.updateTask(task.key, task.settings)
|
||||
.then(resp => {
|
||||
props.onClose(true);
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("Failed to update task");
|
||||
});
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={closeDialog}>
|
||||
<DialogTitle id="form-dialog-title">New Task</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
label="Task Title"
|
||||
type="text"
|
||||
fullWidth
|
||||
value={taskName}
|
||||
onChange={e => setTaskName(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
id="description"
|
||||
label="Task Description"
|
||||
type="text"
|
||||
fullWidth
|
||||
multiline
|
||||
value={taskDescription}
|
||||
onChange={e => setTaskDescription(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
disabled={as === ""}
|
||||
margin="dense"
|
||||
id="worker"
|
||||
label="Assigned Worker"
|
||||
select
|
||||
fullWidth
|
||||
value={worker}
|
||||
onChange={e => setWorker(e.target.value)}>
|
||||
{users.map(u => (
|
||||
<MenuItem key={u.id()} value={u.id()}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid>
|
||||
<Avatar
|
||||
alt={u.name()}
|
||||
src={
|
||||
u.settings.avatar && u.settings.avatar !== "" ? u.settings.avatar : undefined
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Box marginLeft={2}>{u.name()}</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
{props.type === "seed" && (
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
style={{ width: "45%" }}
|
||||
margin="normal"
|
||||
id="costPerPound"
|
||||
label="Seed Cost Per Pound"
|
||||
type="text"
|
||||
helperText="Must be a valid number"
|
||||
value={seedCost}
|
||||
error={isNaN(+seedCost) && seedCost !== ""}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">$</InputAdornment>,
|
||||
endAdornment: <InputAdornment position="end">/Lb</InputAdornment>
|
||||
}}
|
||||
onChange={e => {
|
||||
setSeedCost(e.target.value);
|
||||
setCost((parseFloat(e.target.value) * poundPerAcre).toString());
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
style={{ width: "45%", marginLeft: "10%" }}
|
||||
margin="normal"
|
||||
id="applicationRate"
|
||||
label="Application Rate"
|
||||
type="number"
|
||||
value={poundPerAcre}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">Lb/Acre</InputAdornment>
|
||||
}}
|
||||
onChange={e => {
|
||||
setPoundPerAcre(+e.target.value);
|
||||
setCost((parseFloat(seedCost) * +e.target.value).toString());
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{props.hasCost && (
|
||||
<TextField
|
||||
margin="dense"
|
||||
id="cost"
|
||||
label={props.costTitle ? props.costTitle : "Cost"}
|
||||
type="text"
|
||||
fullWidth
|
||||
helperText="Must be a valid number"
|
||||
value={cost}
|
||||
error={isNaN(+cost) && cost !== ""}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">$</InputAdornment>
|
||||
}}
|
||||
onChange={e => {
|
||||
setCost(e.target.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{props.hasCost && (
|
||||
<TextField
|
||||
margin="dense"
|
||||
id="secondaryCost"
|
||||
label={props.secondaryCostTitle ? props.secondaryCostTitle : "Secondary Cost"}
|
||||
type="text"
|
||||
fullWidth
|
||||
helperText="Must be a valid number"
|
||||
value={secondaryCost}
|
||||
error={isNaN(+secondaryCost) && secondaryCost !== ""}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">$</InputAdornment>
|
||||
}}
|
||||
onChange={e => {
|
||||
setSecondaryCost(e.target.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* if the calendar was clicked gets the start date from that otherwise it will default to today */}
|
||||
<TextField
|
||||
style={{ width: "45%" }}
|
||||
margin="normal"
|
||||
type="date"
|
||||
label="Start Date"
|
||||
value={startDate}
|
||||
onChange={e => setStartDate(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{ width: "45%", marginLeft: "10%" }}
|
||||
margin="normal"
|
||||
type="time"
|
||||
label="Start Time"
|
||||
value={startTime}
|
||||
onChange={e => setStartTime(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{ width: "45%" }}
|
||||
margin="normal"
|
||||
type="date"
|
||||
label="End Date"
|
||||
value={endDate}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
onChange={e => setEndDate(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{ width: "45%", marginLeft: "10%" }}
|
||||
margin="normal"
|
||||
type="time"
|
||||
label="End Time"
|
||||
value={endTime}
|
||||
onChange={e => setEndTime(e.target.value)}
|
||||
/>
|
||||
<Box marginTop={3}>
|
||||
Colour
|
||||
<ColourPicker onChange={color => setColour(color)} />
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={closeDialog} variant="contained" style={{ margin: 5 }} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
{props.task ? (
|
||||
<Button
|
||||
onClick={updateTask}
|
||||
variant="contained"
|
||||
style={{ margin: 5 }}
|
||||
color="primary"
|
||||
disabled={!formComplete()}>
|
||||
Update
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={newTask}
|
||||
variant="contained"
|
||||
style={{ margin: 5 }}
|
||||
color="primary"
|
||||
disabled={!formComplete()}>
|
||||
Create Task
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
470
src/tasks/TaskViewer.tsx
Normal file
470
src/tasks/TaskViewer.tsx
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
import React, { useState } from "react";
|
||||
import { EventClickArg } from "@fullcalendar/core";
|
||||
import { DateClickArg } from "@fullcalendar/interaction";
|
||||
import { useGlobalState } from "providers";
|
||||
import { useMobile, useSnackbar, useThemeType } from "hooks";
|
||||
import { useTaskAPI } from "providers";
|
||||
import { useEffect, useCallback } from "react";
|
||||
import TaskCalendar from "./TaskCalendar";
|
||||
import TaskList from "./TaskList";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Fab,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
Theme,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Task } from "models";
|
||||
import TaskSettings from "./TaskSettings";
|
||||
import { DayPicker } from "react-day-picker";
|
||||
import "react-day-picker/dist/style.css";
|
||||
import { CalendarToday } from "@mui/icons-material";
|
||||
import NotesIcon from "@mui/icons-material/Notes";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import TaskDrawer from "./TaskDrawer";
|
||||
import moment from "moment";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
interface ViewProps {
|
||||
objectKey?: string; //only used to save it for the object
|
||||
label?: string;
|
||||
drawerView?: boolean;
|
||||
loadKeys?: string[]; //if you want to load tasks for a specific object(s)
|
||||
}
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: any;
|
||||
value: any;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
const themeType = useThemeType();
|
||||
|
||||
return ({
|
||||
avatar: {
|
||||
color: themeType === "light" ? theme.palette.common.black : theme.palette.common.white,
|
||||
backgroundColor: "transparent",
|
||||
width: theme.spacing(5),
|
||||
height: theme.spacing(5),
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.divider
|
||||
},
|
||||
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: "fixed",
|
||||
bottom: theme.spacing(8), //for mobile navigator
|
||||
right: theme.spacing(2),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
bottom: theme.spacing(2)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default function TaskViewer(props: ViewProps) {
|
||||
const { objectKey, label, drawerView, loadKeys } = props;
|
||||
const [{ user }] = useGlobalState();
|
||||
const [{ as }] = useGlobalState();
|
||||
const taskAPI = useTaskAPI();
|
||||
const { openSnack } = useSnackbar();
|
||||
const [tasks, setTasks] = useState<Map<string, Task>>(new Map<string, Task>());
|
||||
const [viewTask, setViewTask] = useState<Task>(Task.create());
|
||||
const [newTaskDialog, setNewTaskDialog] = useState(false);
|
||||
const [editTask, setEditTask] = useState<Task>();
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const isMobile = useMobile();
|
||||
const [value, setValue] = React.useState(0);
|
||||
const classes = useStyles();
|
||||
const [expand, setExpand] = useState(false);
|
||||
const [hideCal, setHideCal] = useState(false);
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [nextMonth, setNextMonth] = useState(
|
||||
moment(currentDate)
|
||||
.add(2, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
const [prevMonth, setPrevMonth] = useState(
|
||||
moment(currentDate)
|
||||
.subtract(1, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
|
||||
//initial states for the task variables
|
||||
const [startDate, setStartDate] = useState<string>();
|
||||
|
||||
//functions for opening and closing the newTaskDialog
|
||||
const openCalendarDialog = (args: DateClickArg) => {
|
||||
//assign the date of task when the dialog box is opened
|
||||
setStartDate(args.dateStr);
|
||||
openDialog();
|
||||
};
|
||||
const openDialog = () => {
|
||||
setNewTaskDialog(true);
|
||||
};
|
||||
|
||||
//functions for opening and closing the selectedTaskDialog if selected from the calendar
|
||||
const openSelectedTask = (taskId: string) => {
|
||||
let task = tasks.get(taskId);
|
||||
if (task) {
|
||||
setOpenDrawer(true);
|
||||
setViewTask(task);
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
.then(resp => {
|
||||
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]);
|
||||
|
||||
//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)
|
||||
.then(resp => {
|
||||
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 tasks");
|
||||
});
|
||||
}, [taskAPI, as, user, openSnack, nextMonth, prevMonth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (drawerView) {
|
||||
setTasks(new Map<string, Task>());
|
||||
setValue(1);
|
||||
loadMultitask();
|
||||
} else {
|
||||
loadTasks();
|
||||
}
|
||||
}, [loadTasks, loadMultitask, as, drawerView]);
|
||||
|
||||
const markComplete = (task: Task) => {
|
||||
task.settings.complete = !task.settings.complete;
|
||||
|
||||
taskAPI
|
||||
.updateTask(task.key, task.settings)
|
||||
.then(resp => {
|
||||
openSnack("Task Updated");
|
||||
loadTasks();
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("Failed to update task");
|
||||
});
|
||||
};
|
||||
|
||||
const deleteTask = (task: Task) => {
|
||||
taskAPI
|
||||
.removeTask(task.key)
|
||||
.then(resp => {
|
||||
openSnack("Task Removed");
|
||||
loadTasks();
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("Failed to remove task");
|
||||
});
|
||||
};
|
||||
|
||||
const tabIcons = () => {
|
||||
return (
|
||||
<Box display="flex" justifyContent="left" alignItems="center" zIndex={5}>
|
||||
<IconButton
|
||||
className={value === 0 ? classes.active : classes.avatar}
|
||||
component="span"
|
||||
style={{
|
||||
marginBottom: "0.75rem",
|
||||
marginRight: "0.5rem"
|
||||
}}
|
||||
onClick={() => setValue(0)}>
|
||||
<CalendarToday />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
className={value === 1 ? classes.active : classes.avatar}
|
||||
component="span"
|
||||
style={{
|
||||
marginBottom: "0.75rem",
|
||||
marginRight: "0.5rem"
|
||||
}}
|
||||
onClick={() => setValue(1)}>
|
||||
<NotesIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function TabPanelMine(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}>
|
||||
{value === index && <React.Fragment>{children}</React.Fragment>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
setHideCal(!hideCal);
|
||||
setExpand(!expand);
|
||||
};
|
||||
|
||||
const viewer = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isMobile || drawerView ? (
|
||||
<Box>
|
||||
<Fab
|
||||
onClick={openDialog}
|
||||
aria-label="Create Task"
|
||||
className={classes.fab}
|
||||
size={isMobile ? "medium" : "large"}>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
{!drawerView && tabIcons()}
|
||||
<TabPanelMine value={value} index={0}>
|
||||
<Box paddingBottom={2}>
|
||||
<TaskCalendar
|
||||
tasks={Array.from(tasks.values())}
|
||||
dateClickMethod={openCalendarDialog}
|
||||
taskClickMethod={(arg: EventClickArg) => openSelectedTask(arg.event.id)}
|
||||
centerToolbar={"hide"}
|
||||
initialView="dayGridWeek"
|
||||
initialDate={currentDate}
|
||||
calHeight={"22vh"}
|
||||
hideToday
|
||||
hideCal={hideCal}
|
||||
hideCalCallback={resize}
|
||||
changeDateCallback={date => {
|
||||
if (currentDate.getMonth() !== date.getMonth()) {
|
||||
setCurrentDate(date);
|
||||
setNextMonth(
|
||||
moment(date)
|
||||
.add(2, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
setPrevMonth(
|
||||
moment(date)
|
||||
.subtract(1, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
loadTasks();
|
||||
}
|
||||
setCurrentDate(date);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TaskCalendar
|
||||
tasks={Array.from(tasks.values())}
|
||||
dateClickMethod={openCalendarDialog}
|
||||
taskClickMethod={(arg: EventClickArg) => openSelectedTask(arg.event.id)}
|
||||
initialView="timeGridDay"
|
||||
initialDate={currentDate}
|
||||
calHeight={expand ? "59vh" : "37vh"}
|
||||
hideTitle
|
||||
changeDateCallback={date => {
|
||||
if (currentDate.getMonth() !== date.getMonth()) {
|
||||
setCurrentDate(date);
|
||||
setNextMonth(
|
||||
moment(date)
|
||||
.add(2, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
setPrevMonth(
|
||||
moment(date)
|
||||
.subtract(1, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
loadTasks();
|
||||
}
|
||||
setCurrentDate(date);
|
||||
}}
|
||||
/>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={value} index={1}>
|
||||
<TaskList
|
||||
tasks={Array.from(tasks.values())}
|
||||
label={label}
|
||||
editTaskMethod={setTaskToEdit}
|
||||
markComplete={markComplete}
|
||||
deleteTask={deleteTask}
|
||||
reLoad={() => loadTasks()}
|
||||
dateToView={drawerView ? undefined : currentDate}
|
||||
openTask={(taskId: string) => openSelectedTask(taskId)}
|
||||
/>
|
||||
</TabPanelMine>
|
||||
</Box>
|
||||
) : (
|
||||
<Grid container direction="row" alignContent="center" alignItems="center">
|
||||
<Grid size={{lg: 3, md: 4}}>
|
||||
<Box textAlign={"center"}>
|
||||
<Button
|
||||
onClick={openDialog}
|
||||
style={{ margin: 5, borderRadius: 20, backgroundColor: "green", width: "50%" }}>
|
||||
<Typography style={{ fontWeight: 750, color: "white" }}>+ Task</Typography>
|
||||
</Button>
|
||||
</Box>
|
||||
<Box
|
||||
style={{
|
||||
width: "300px",
|
||||
height: "280px",
|
||||
margin: "auto"
|
||||
}}>
|
||||
<DayPicker
|
||||
mode="single"
|
||||
selected={currentDate}
|
||||
onSelect={date => {
|
||||
if (!date) return;
|
||||
if (currentDate.getMonth() !== date.getMonth()) {
|
||||
setCurrentDate(date);
|
||||
setNextMonth(
|
||||
moment(date)
|
||||
.add(2, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
setPrevMonth(
|
||||
moment(date)
|
||||
.subtract(1, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
loadTasks();
|
||||
}
|
||||
setCurrentDate(date);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box marginY={2}>
|
||||
<TaskList
|
||||
tasks={Array.from(tasks.values())}
|
||||
label={label}
|
||||
editTaskMethod={setTaskToEdit}
|
||||
markComplete={markComplete}
|
||||
deleteTask={deleteTask}
|
||||
reLoad={() => loadTasks()}
|
||||
listHeight={"40vh"}
|
||||
dateToView={currentDate}
|
||||
openTask={(taskId: string) => openSelectedTask(taskId)}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={{lg:9, md:8}}>
|
||||
<TaskCalendar
|
||||
tasks={Array.from(tasks.values())}
|
||||
dateClickMethod={openCalendarDialog}
|
||||
taskClickMethod={(arg: EventClickArg) => openSelectedTask(arg.event.id)}
|
||||
initialView="timeGridWeek"
|
||||
centerToolbar="dayGridMonth,timeGridWeek,timeGridDay"
|
||||
initialDate={currentDate}
|
||||
calHeight={"80vh"}
|
||||
changeDateCallback={date => {
|
||||
if (currentDate.getMonth() !== date.getMonth()) {
|
||||
setCurrentDate(date);
|
||||
setNextMonth(
|
||||
moment(date)
|
||||
.add(2, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
setPrevMonth(
|
||||
moment(date)
|
||||
.subtract(1, "month")
|
||||
.format("YYYY-MM")
|
||||
);
|
||||
loadTasks();
|
||||
}
|
||||
setCurrentDate(date);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{!loaded ? <LinearProgress style={{ marginTop: 20 }} /> : viewer()}
|
||||
<TaskSettings
|
||||
open={newTaskDialog}
|
||||
task={editTask}
|
||||
objectKey={objectKey}
|
||||
startDate={startDate}
|
||||
onClose={r => {
|
||||
if (r) {
|
||||
if (drawerView) {
|
||||
loadMultitask();
|
||||
} else {
|
||||
loadTasks();
|
||||
}
|
||||
}
|
||||
setEditTask(undefined);
|
||||
setNewTaskDialog(false);
|
||||
}}
|
||||
/>
|
||||
{
|
||||
<TaskDrawer
|
||||
task={viewTask}
|
||||
open={openDrawer}
|
||||
closeCallback={() => setOpenDrawer(false)}
|
||||
completeTask={markComplete}
|
||||
deleteTask={deleteTask}
|
||||
editTask={setTaskToEdit}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue