Skip to content

Commit

Permalink
feat/13: schedule details on task cards
Browse files Browse the repository at this point in the history
  • Loading branch information
duckbytes committed Dec 9, 2024
1 parent 0ae6e43 commit 4b961a0
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 79 deletions.
35 changes: 34 additions & 1 deletion src/scenes/Dashboard/components/TaskCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as models from "../../../models";
import ScheduleIcon from "@mui/icons-material/Schedule";
import {
AvatarGroup,
Box,
Expand All @@ -16,6 +17,7 @@ import { makeStyles } from "tss-react/mui";
import useTaskDeliverablesRedux from "../../../hooks/useTaskDeliverablesRedux";
import useCommentsRedux from "../../../hooks/useCommentsRedux";
import UserAvatar from "../../../components/UserAvatar";
import TaskScheduleIconText from "../../sharedTaskComponents/TaskScheduleIconText";

const colourBarPercent = "90%";

Expand Down Expand Up @@ -159,14 +161,45 @@ const TaskCard: React.FC<TaskCardProps> = ({ task }) => {
nullLocationText="No delivery address"
location={task.dropOffLocation}
/>
<Stack direction="row" spacing={2}>
<Stack direction="row" spacing={2} alignItems="flex-end">
{(task?.createdAt || task?.timeOfCall) && (
<TaskCardTimestamp
timestamp={task.createdAt || task.timeOfCall || ""}
/>
)}
{taskBadge}
</Stack>
{task.dropOffSchedule &&
!task.pickUpSchedule &&
!task.timePickedUp && (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
}}
>
<ScheduleIcon sx={{ color: "gray" }} />{" "}
<span style={{ color: "gray" }}>++</span>
</Box>
)}
{task.pickUpSchedule && !task.timePickedUp && (
<TaskScheduleIconText
showWarning
schedule={task.pickUpSchedule}
appendText={task.dropOffSchedule ? "++" : ""}
smallText
/>
)}
{task.dropOffSchedule &&
!task.timeDroppedOff &&
task.timePickedUp && (
<TaskScheduleIconText
showWarning
schedule={task.dropOffSchedule}
smallText
/>
)}
</Stack>
</Paper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ const LocationDetailAndSelector: React.FC<LocationDetailAndSelectorProps> = ({
</LabelItemPair>
)}
</Box>
<Divider />
</>
)}
{editMode && (
Expand Down
11 changes: 1 addition & 10 deletions src/scenes/sharedTaskComponents/LocationDetailsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import LocationDetailAndSelector from "./LocationDetailAndSelector";
import { GraphQLQuery } from "@aws-amplify/api";
import React, { useEffect, useRef, useState } from "react";
import {
Box,
Divider,
Paper,
Skeleton,
Stack,
Typography,
} from "@mui/material";
import { Paper, Skeleton, Stack, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useDispatch, useSelector } from "react-redux";
import { displayErrorNotification } from "../../redux/notifications/NotificationsActions";
Expand Down Expand Up @@ -663,9 +656,7 @@ const LocationDetailsPanel = <T extends models.Task | models.ScheduledTask>({
/>
)}
</Stack>
<Divider />
{contents}
<Divider />
<TaskScheduleDetails
onClear={handleClearSchedule}
onChange={handleEditSchedule}
Expand Down
73 changes: 5 additions & 68 deletions src/scenes/sharedTaskComponents/TaskScheduleDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,63 +8,18 @@ import {
TextField,
Typography,
} from "@mui/material";
import ScheduleIcon from "@mui/icons-material/Schedule";
import EditIcon from "@mui/icons-material/Edit";
import humanReadableScheduleString from "../../utilities/humanReadableScheduleString";
import ConfirmationDialog from "../../components/ConfirmationDialog";
import ClearIcon from "@mui/icons-material/Clear";
import TimeRelationPicker from "./TimeRelationPicker";
import { DatePicker } from "@mui/lab";
import TaskScheduleIconText from "./TaskScheduleIconText";

const isValidTime = (time: string) => {
const [hours, minutes] = time.split(":").map((value) => parseInt(value));
return hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60;
};

const isDueInOneHour = (schedule?: models.Schedule | null) => {
if (
[models.TimeRelation.ANYTIME, models.TimeRelation.AFTER].includes(
schedule?.relation as models.TimeRelation
)
) {
return false;
}
if (!schedule) {
return false;
}
const now = new Date();
const scheduleDate = new Date(schedule?.date ?? "");
scheduleDate.setUTCHours(parseInt(schedule.time?.split(":")[0] ?? "0"));
scheduleDate.setUTCMinutes(parseInt(schedule.time?.split(":")[1] ?? "0"));
scheduleDate.setUTCHours(scheduleDate.getUTCHours() - 1);

if (scheduleDate < now) {
return true;
}
return false;
};

const isOverDue = (schedule?: models.Schedule | null) => {
if (
[models.TimeRelation.ANYTIME, models.TimeRelation.AFTER].includes(
schedule?.relation as models.TimeRelation
)
) {
return false;
}
if (!schedule) {
return false;
}
const now = new Date();
const scheduleDate = new Date(schedule?.date ?? "");
scheduleDate.setUTCHours(parseInt(schedule.time?.split(":")[0] ?? "0"));
scheduleDate.setUTCMinutes(parseInt(schedule.time?.split(":")[1] ?? "0"));
if (scheduleDate < now) {
return true;
}
return false;
};

type TaskScheduleDetailsProps = {
schedule: models.Schedule | null;
onClear: () => void;
Expand Down Expand Up @@ -149,16 +104,6 @@ const TaskScheduleDetails: React.FC<TaskScheduleDetailsProps> = ({
});
};

let iconColor = "";
if (!noWarning) {
if (isDueInOneHour(schedule)) {
iconColor = "orange";
}
if (isOverDue(schedule)) {
iconColor = "red";
}
}

return (
<>
{schedule && (
Expand All @@ -167,18 +112,10 @@ const TaskScheduleDetails: React.FC<TaskScheduleDetailsProps> = ({
justifyContent="space-between"
spacing={1}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
}}
>
<ScheduleIcon sx={{ color: iconColor }} />
<Typography sx={{ fontWeight: "bold" }}>
{humanReadableScheduleString(schedule)}
</Typography>
</Box>
<TaskScheduleIconText
schedule={schedule}
showWarning={!noWarning}
/>
<Box
sx={{
display: "flex",
Expand Down
68 changes: 68 additions & 0 deletions src/scenes/sharedTaskComponents/TaskScheduleIconText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from "react";
import * as models from "../../models";
import ScheduleIcon from "@mui/icons-material/Schedule";
import { Box, Typography } from "@mui/material";
import taskScheduleDueStatus from "../../utilities/taskScheduleDueStatus";
import taskScheduleOverDueStatus from "../../utilities/taskScheduleOverDueStatus";
import humanReadableScheduleString from "../../utilities/humanReadableScheduleString";

type TaskScheduleIconTextProps = {
schedule?: models.Schedule | null;
showWarning?: boolean;
dueTime?: number;
appendText?: string;
smallText?: boolean;
};

const TaskScheduleIconText: React.FC<TaskScheduleIconTextProps> = ({
schedule,
showWarning = true,
dueTime = 1,
appendText = "",
smallText = false,
}) => {
if (!schedule) return null;
let iconColor = "";
if (showWarning) {
if (taskScheduleDueStatus(schedule, dueTime)) {
iconColor = "orange";
}
if (taskScheduleOverDueStatus(schedule)) {
iconColor = "red";
}
}
return (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
}}
>
<ScheduleIcon
sx={{ color: iconColor }}
fontSize={smallText ? "small" : undefined}
/>
<Typography
sx={{
fontWeight: "bold",
fontSize: smallText ? "0.9rem" : undefined,
}}
>
{humanReadableScheduleString(schedule)}{" "}
<span
style={{
fontWeight: "normal",
color: "gray",
fontSize: "0.9rem",
}}
>
{" "}
{appendText}
</span>
</Typography>
</Box>
);
};

export default TaskScheduleIconText;
40 changes: 40 additions & 0 deletions src/utilities/taskScheduleDueStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as models from "../models";
import taskScheduleDueStatus from "./taskScheduleDueStatus";

describe("taskScheduleDueStatus", () => {
const isoDate = "2021-01-01T12:00:58.987Z";
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date(isoDate));
});
afterEach(() => {
jest.restoreAllMocks();
jest.useRealTimers();
});
test("should return false if [models.TimeRelation.ANYTIME, models.TimeRelation.AFTER].includes(schedule?.relation as models.TimeRelation)", () => {
const schedule = new models.Schedule({
relation: models.TimeRelation.ANYTIME,
});
const result = taskScheduleDueStatus(schedule);
expect(result).toEqual(false);
const schedule2 = new models.Schedule({
relation: models.TimeRelation.AFTER,
});
const result2 = taskScheduleDueStatus(schedule2);
expect(result2).toEqual(false);
});
test("should return false if !schedule", () => {
const schedule = null;
const result = taskScheduleDueStatus(schedule);
expect(result).toEqual(false);
});
test("return true if due in the next hour", () => {
const schedule = new models.Schedule({
relation: models.TimeRelation.BEFORE,
date: "2021-01-01",
time: "12:59:58.987Z",
});
const result = taskScheduleDueStatus(schedule, 1);
expect(result).toEqual(true);
});
});
30 changes: 30 additions & 0 deletions src/utilities/taskScheduleDueStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as models from "../models";

const taskScheduleDueStatus = (
schedule: models.Schedule | null,
hours: number = 1
) => {
debugger;
if (
[models.TimeRelation.ANYTIME, models.TimeRelation.AFTER].includes(
schedule?.relation as models.TimeRelation
)
) {
return false;
}
if (!schedule) {
return false;
}
const now = new Date();
const scheduleDate = new Date(schedule?.date ?? "");
scheduleDate.setUTCHours(parseInt(schedule.time?.split(":")[0] ?? "0"));
scheduleDate.setUTCMinutes(parseInt(schedule.time?.split(":")[1] ?? "0"));
scheduleDate.setUTCHours(scheduleDate.getUTCHours() - hours);

if (scheduleDate < now) {
return true;
}
return false;
};

export default taskScheduleDueStatus;
42 changes: 42 additions & 0 deletions src/utilities/taskScheduleOverDueStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as models from "../models";
import taskScheduleOverDueStatus from "./taskScheduleOverDueStatus";

describe("taskScheduleOverDueStatus", () => {
it("should return false if [models.TimeRelation.ANYTIME, models.TimeRelation.AFTER].includes(schedule?.relation as models.TimeRelation)", () => {
const schedule = new models.Schedule({
relation: models.TimeRelation.ANYTIME,
});
const result = taskScheduleOverDueStatus(schedule);
expect(result).toEqual(false);
const schedule2 = new models.Schedule({
relation: models.TimeRelation.AFTER,
});
const result2 = taskScheduleOverDueStatus(schedule2);
expect(result2).toEqual(false);
});

it("should return false if !schedule", () => {
const schedule = null;
const result = taskScheduleOverDueStatus(schedule);
expect(result).toEqual(false);
});

it("should return true if overdue", () => {
const schedule = new models.Schedule({
relation: models.TimeRelation.BEFORE,
date: "2022-01-01",
time: "10:00",
});
const result = taskScheduleOverDueStatus(schedule);
expect(result).toEqual(true);
});
it("should return false if not overdue", () => {
const schedule = new models.Schedule({
relation: models.TimeRelation.BEFORE,
date: "2199-01-01",
time: "10:00",
});
const result = taskScheduleOverDueStatus(schedule);
expect(result).toEqual(false);
});
});
Loading

0 comments on commit 4b961a0

Please sign in to comment.