Skip to content

Commit 8dd6902

Browse files
Status history (#1210)
* status history WIP * status history ui * WIP * status history * status * move typography for status out of shared component --------- Co-authored-by: Tom Chapman <tchapman000@gmail.com>
1 parent 18f5851 commit 8dd6902

File tree

8 files changed

+11451
-5490
lines changed

8 files changed

+11451
-5490
lines changed

epictrack-web/package-lock.json

Lines changed: 11248 additions & 5482 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

epictrack-web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"@emotion/styled": "^11.10.8",
88
"@hookform/resolvers": "^3.1.0",
99
"@mui/icons-material": "^5.11.16",
10+
"@mui/lab": "^5.0.0-alpha.152",
1011
"@mui/material": "^5.12.2",
1112
"@mui/styles": "^5.12.0",
1213
"@mui/x-date-pickers": "^6.3.0",

epictrack-web/src/components/workPlan/WorkPlanContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ interface WorkplanContextProps {
3636
firstNations: WorkFirstNation[];
3737
setFirstNations: Dispatch<SetStateAction<WorkFirstNation[]>>;
3838
statuses: Status[];
39+
setStatuses: Dispatch<SetStateAction<Status[]>>;
3940
getWorkStatuses: () => Promise<void>;
4041
issues: WorkIssue[];
4142
setIssues: Dispatch<SetStateAction<WorkIssue[]>>;
@@ -57,6 +58,7 @@ export const WorkplanContext = createContext<WorkplanContextProps>({
5758
firstNations: [],
5859
setFirstNations: () => ({}),
5960
statuses: [],
61+
setStatuses: () => ({}),
6062
issues: [],
6163
setIssues: () => ({}),
6264
getWorkStatuses: () => new Promise((resolve) => resolve),
@@ -170,6 +172,7 @@ export const WorkplanProvider = ({
170172
return (
171173
<WorkplanContext.Provider
172174
value={{
175+
setStatuses,
173176
getWorkStatuses,
174177
selectedWorkPhase,
175178
setSelectedWorkPhase,

epictrack-web/src/components/workPlan/status/StatusContext.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ interface StatusContextProps {
1818
setStatus: Dispatch<SetStateAction<Status | undefined>>;
1919
onSave(data: any, callback: () => any): any;
2020
setShowApproveStatusDialog: Dispatch<SetStateAction<boolean>>;
21+
selectedHistoryIndex?: number;
22+
setSelectedHistoryIndex: Dispatch<SetStateAction<number>>;
2123
setIsCloning: Dispatch<SetStateAction<boolean>>;
2224
workId: string | null;
2325
isCloning: boolean;
@@ -34,6 +36,8 @@ export const StatusContext = createContext<StatusContextProps>({
3436
status: null,
3537
setStatus: () => ({}),
3638
onSave: (data: any, callback: () => any) => ({}),
39+
selectedHistoryIndex: 0,
40+
setSelectedHistoryIndex: () => ({}),
3741
setIsCloning: () => ({}),
3842
isCloning: false,
3943
workId: null,
@@ -50,9 +54,11 @@ export const StatusProvider = ({
5054
React.useState<boolean>(false);
5155
const [isCloning, setIsCloning] = React.useState<boolean>(false);
5256
const [status, setStatus] = React.useState<Status>();
57+
const [selectedHistoryIndex, setSelectedHistoryIndex] =
58+
React.useState<number>(0);
5359
const query = useSearchParams<StatusContainerRouteParams>();
5460
const workId = React.useMemo(() => query.get("work_id"), [query]);
55-
const { getWorkStatuses } = useContext(WorkplanContext);
61+
const { getWorkStatuses, setStatuses } = useContext(WorkplanContext);
5662
const { groups } = useAppSelector((state) => state.user.userDetail);
5763

5864
const onDialogClose = () => {
@@ -122,6 +128,9 @@ export const StatusProvider = ({
122128
try {
123129
await statusService.approve(Number(workId), Number(status?.id));
124130
setShowApproveStatusDialog(false);
131+
showNotification(`Status approved`, {
132+
type: "success",
133+
});
125134
setStatus(undefined);
126135
getWorkStatuses();
127136
} catch (e) {
@@ -139,6 +148,8 @@ export const StatusProvider = ({
139148
return (
140149
<StatusContext.Provider
141150
value={{
151+
setSelectedHistoryIndex,
152+
selectedHistoryIndex,
142153
groups,
143154
isCloning,
144155
workId,
@@ -172,7 +183,11 @@ export const StatusProvider = ({
172183
cancelButtonText="Cancel"
173184
isActionsRequired
174185
onCancel={closeApproveDialog}
175-
onOk={approveStatus}
186+
onOk={() => {
187+
setStatuses([]); // Status history was not being updated so manually doing this
188+
approveStatus();
189+
getWorkStatuses(); // Status history was not being updated so manually doing this
190+
}}
176191
/>
177192
</StatusContext.Provider>
178193
);
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import TimelineConnector from "@mui/lab/TimelineConnector";
2+
import TimelineContent from "@mui/lab/TimelineContent";
3+
import TimelineDot from "@mui/lab/TimelineDot";
4+
import TimelineItem from "@mui/lab/TimelineItem";
5+
import TimelineSeparator from "@mui/lab/TimelineSeparator";
6+
import { Box } from "@mui/system";
7+
import moment from "moment";
8+
import { ETPreviewText } from "../../../../shared";
9+
import { Status } from "../../../../../models/status";
10+
import { Palette } from "../../../../../styles/theme";
11+
import { Button, Typography, styled } from "@mui/material";
12+
import React, { useContext, useState } from "react";
13+
import { IconProps } from "../../../../icons/type";
14+
import Icons from "../../../../icons";
15+
import { StatusContext } from "../../StatusContext";
16+
import { WorkplanContext } from "../../../WorkPlanContext";
17+
import { If, Then } from "react-if";
18+
19+
const PencilEditIcon: React.FC<IconProps> = Icons["PencilEditIcon"];
20+
21+
type HistoryItemProps = {
22+
status: Status;
23+
};
24+
25+
const ETStatusHistoryDate = styled(Typography)(() => ({
26+
fontSize: "12px",
27+
fontStyle: "normal",
28+
fontWeight: "400",
29+
lineHeight: "16px",
30+
}));
31+
32+
const HistoryItem = ({ status }: HistoryItemProps) => {
33+
const {
34+
selectedHistoryIndex,
35+
setSelectedHistoryIndex,
36+
setShowStatusForm,
37+
setStatus,
38+
groups,
39+
} = useContext(StatusContext);
40+
const { statuses } = useContext(WorkplanContext);
41+
const [statusHighlight, setStatusHighlight] = useState<number>(0);
42+
43+
React.useEffect(() => {
44+
if (!statuses[0].is_approved) {
45+
setStatusHighlight(statuses[1].id);
46+
}
47+
setSelectedHistoryIndex(statuses[1].id);
48+
}, [statuses]);
49+
50+
return (
51+
<Box sx={{ display: "flex", paddingTop: "16px" }}>
52+
<TimelineItem sx={{ width: "30%" }}>
53+
<TimelineSeparator>
54+
<TimelineDot
55+
sx={{
56+
backgroundColor:
57+
statusHighlight === status.id ? Palette.success.light : "",
58+
}}
59+
/>
60+
<TimelineConnector
61+
sx={{
62+
backgroundColor:
63+
statusHighlight === status.id ? Palette.success.light : "",
64+
}}
65+
/>
66+
</TimelineSeparator>
67+
<Box sx={{ display: "flex" }}>
68+
<TimelineContent>
69+
<Box>
70+
<ETStatusHistoryDate>
71+
{moment(status.posted_date).format("L")}
72+
</ETStatusHistoryDate>
73+
</Box>
74+
</TimelineContent>
75+
</Box>
76+
</TimelineItem>
77+
<Box sx={{ width: "70%" }}>
78+
<Box>
79+
<ETPreviewText
80+
color={
81+
status.id === selectedHistoryIndex
82+
? Palette.neutral.dark
83+
: Palette.neutral.main
84+
}
85+
>
86+
{status.id === selectedHistoryIndex ? (
87+
<>
88+
<Box>{status.description}</Box>
89+
<If
90+
condition={
91+
groups.includes("Super User") &&
92+
statusHighlight === selectedHistoryIndex
93+
}
94+
>
95+
<Then>
96+
<Box
97+
onClick={() => {
98+
setShowStatusForm(true);
99+
setStatus(status);
100+
}}
101+
sx={{ padding: "12px 8px" }}
102+
>
103+
<PencilEditIcon />
104+
</Box>
105+
</Then>
106+
</If>
107+
</>
108+
) : (
109+
<Box>
110+
{status.description.slice(0, 50)}...
111+
<Button
112+
onClick={() => setSelectedHistoryIndex(status.id)}
113+
sx={{
114+
paddingBottom: 2,
115+
":hover": {
116+
backgroundColor: Palette.white,
117+
borderColor: Palette.white,
118+
},
119+
}}
120+
>
121+
Read More
122+
</Button>
123+
</Box>
124+
)}
125+
</ETPreviewText>
126+
</Box>
127+
</Box>
128+
</Box>
129+
);
130+
};
131+
132+
export default HistoryItem;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from "react";
2+
import { Box } from "@mui/material";
3+
import { WorkplanContext } from "../../../WorkPlanContext";
4+
import { Status } from "../../../../../models/status";
5+
import { ETCaption1 } from "../../../../shared";
6+
import Timeline from "@mui/lab/Timeline";
7+
import HistoryItem from "./HistoryItem";
8+
import { If, Then } from "react-if";
9+
10+
const StatusHistory = () => {
11+
const { statuses } = React.useContext(WorkplanContext);
12+
13+
return (
14+
<Box sx={{ width: "50%" }}>
15+
<ETCaption1 bold sx={{ letterSpacing: "0.39px", paddingBottom: "16px" }}>
16+
STATUS HISTORY
17+
</ETCaption1>
18+
<Timeline position="left" sx={{ overflowY: "scroll", flex: 1 }}>
19+
{statuses.map((status: Status) => (
20+
<If condition={status.is_approved && statuses[0].id !== status.id}>
21+
<Then>
22+
<HistoryItem status={status} />
23+
</Then>
24+
</If>
25+
))}
26+
</Timeline>
27+
</Box>
28+
);
29+
};
30+
31+
export default StatusHistory;

epictrack-web/src/components/workPlan/status/StatusOutOfDateBanner.tsx renamed to epictrack-web/src/components/workPlan/status/StatusView/StatusOutOfDateBanner.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Box } from "@mui/material";
2-
import { ETHeading4, ETParagraph } from "../../shared";
3-
import { Palette } from "../../../styles/theme";
4-
import Icons from "../../icons";
5-
import { IconProps } from "../../icons/type";
2+
import { ETHeading4, ETParagraph } from "../../../shared";
3+
import { Palette } from "../../../../styles/theme";
4+
import Icons from "../../../icons";
5+
import { IconProps } from "../../../icons/type";
66

77
const NotificationWarning: React.FC<IconProps> = Icons["NotificationWarning"];
88

epictrack-web/src/components/workPlan/status/StatusView/index.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import React from "react";
22
import NoDataEver from "../../../shared/NoDataEver";
33
import { WorkplanContext } from "../../WorkPlanContext";
44
import { StatusContext } from "../StatusContext";
5-
import StatusOutOfDateBanner from "../StatusOutOfDateBanner";
5+
import StatusOutOfDateBanner from "./StatusOutOfDateBanner";
66
import RecentStatus from "./RecentStatus";
7+
import { Box } from "@mui/material";
8+
import StatusHistory from "./StatusHistory";
9+
import { Status } from "../../../../models/status";
710

811
const StatusView = () => {
912
const { statuses } = React.useContext(WorkplanContext);
@@ -13,6 +16,13 @@ const StatusView = () => {
1316
setShowStatusForm(true);
1417
};
1518

19+
const hasHistory = () => {
20+
const approvedStatuses: Status[] = statuses.filter((status: Status) => {
21+
return status.is_approved;
22+
});
23+
return approvedStatuses?.length > 1; // No history if only 1 status is approved
24+
};
25+
1626
return (
1727
<>
1828
{statuses.length === 0 && (
@@ -26,7 +36,10 @@ const StatusView = () => {
2636
{statuses.length > 0 && !statuses[0].is_approved && (
2737
<StatusOutOfDateBanner />
2838
)}
29-
{statuses.length > 0 && <RecentStatus />}
39+
<Box sx={{ display: "flex", gap: "24px" }}>
40+
{statuses.length > 0 && <RecentStatus />}
41+
{hasHistory() && <StatusHistory />}
42+
</Box>
3043
</>
3144
);
3245
};

0 commit comments

Comments
 (0)