frontend/src/tasks/TaskViewer.tsx

432 lines
13 KiB
TypeScript

import React, { useState } from "react";
import { EventClickArg } from "@fullcalendar/core";
import { DateClickArg } from "@fullcalendar/interaction";
import { useGlobalState } from "providers";
import { useMobile, useSnackbar } 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,
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 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 --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) --Deprecated with new permission structure-- it should use the keys and types now
overlayButton?: boolean;
}
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
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 TaskViewer(props: ViewProps) {
const {
//objectKey,
label,
drawerView,
//loadKeys,
keys,
types,
overlayButton
} = props;
const [{ user, as }] = useGlobalState();
const taskAPI = useTaskAPI();
const { openSnack } = useSnackbar();
const [tasks, setTasks] = useState<Map<string, Task>>(new Map<string, Task>([]));
const [viewTask, setViewTask] = useState<Task>();
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] = 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);
}
};
//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, keys, types)
.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 tasks");
});
}, [taskAPI, as, user, openSnack, nextMonth, prevMonth, keys, types]);
useEffect(() => {
// 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;
taskAPI
.updateTask(task.key, task.settings, undefined, as)
.then(resp => {
openSnack("Task Updated");
loadTasks();
})
.catch(err => {
openSnack("Failed to update task");
});
};
const deleteTask = (task: Task) => {
taskAPI
.removeTask(task.key, as)
.then(resp => {
openSnack("Task Removed");
loadTasks();
})
.catch(err => {
openSnack("Failed to remove task");
});
};
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 height={"100%"} position={"relative"}>
<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}
markComplete={markComplete}
deleteTask={deleteTask}
reLoad={() => loadTasks()}
dateToView={drawerView ? undefined : currentDate}
openTask={(taskId: string) => openSelectedTask(taskId)}
keys={[]}
types={[]}
/>
</TabPanelMine>
<Fab
onClick={openDialog}
aria-label="Create Task"
className={overlayButton ? classes.overlayFab : classes.fab}
size={isMobile ? "medium" : "large"}>
<AddIcon />
</Fab>
</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}
markComplete={markComplete}
deleteTask={deleteTask}
reLoad={() => loadTasks()}
listHeight={"40vh"}
dateToView={currentDate}
openTask={(taskId: string) => openSelectedTask(taskId)}
keys={[]}
types={[]}
/>
</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 height={"100%"}>
{!loaded ? <LinearProgress style={{ marginTop: 20 }} /> : viewer()}
<TaskSettings
open={newTaskDialog}
task={editTask}
keys={keys}
types={types}
startDate={startDate}
onClose={r => {
loadTasks();
setEditTask(undefined);
setNewTaskDialog(false);
}}
/>
{viewTask &&
<TaskDrawer
task={viewTask}
open={openDrawer}
closeCallback={() => {
setOpenDrawer(false)
loadTasks()
}}
completeTask={markComplete}
deleteTask={deleteTask}
keys={keys ?? []}
types={types ?? []}
/>
}
</Box>
);
}