From d8a78b973b9f64a40d71895ebb2eeb2a0669a429 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Thu, 23 Nov 2023 13:33:37 -0800 Subject: [PATCH 01/18] WIP --- .../src/components/layout/SideNav/SideNav.tsx | 163 ++++++++++-------- .../layout/SideNav/SideNavElements.tsx | 9 + .../src/constants/application-constant.ts | 1 + 3 files changed, 98 insertions(+), 75 deletions(-) diff --git a/epictrack-web/src/components/layout/SideNav/SideNav.tsx b/epictrack-web/src/components/layout/SideNav/SideNav.tsx index d7bb0c47a..dbee5459f 100644 --- a/epictrack-web/src/components/layout/SideNav/SideNav.tsx +++ b/epictrack-web/src/components/layout/SideNav/SideNav.tsx @@ -22,6 +22,7 @@ import Icons from "../../icons"; import { groupBy } from "../../../utils"; import { IconProps } from "../../icons/type"; import { ETSubhead } from "../../shared"; +import { Restricted } from "../../shared/restricted"; const ListItemStyled = styled(ListItem)({ padding: "0px 0px 0px 0px", @@ -77,6 +78,9 @@ const DrawerBox = () => { [] ); + // console.log(Routes); + // console.log(groupedRoutes); + return ( { {Object.keys(groupedRoutes).map((groupKey) => { return ( <> - {groupedRoutes[groupKey].map((route, i) => { - return ( - <> - - handleClick(route)} - > - {route.icon && ( - - {renderIcon( - route.icon, - location.pathname === route.path - )} - - )} - - + <> + {groupedRoutes[groupKey].map((route, i) => { + return ( + <> + + handleClick(route)} > - {route.name} - - - {route?.routes && - (route?.routes?.length > 0 && !!open[route.name] ? ( - - ) : ( - - ))} - - - {route.routes && route.routes?.length > 0 && ( - - - {route.routes?.map((subRoute, i) => ( - - handleClick(subRoute)} + {route.icon && ( + - + )} + + + {route.name} + + + {route?.routes && + (route?.routes?.length > 0 && + !!open[route.name] ? ( + + ) : ( + + ))} + + + {route.routes && route.routes?.length > 0 && ( + + + {route.routes?.map((subRoute, i) => ( + - handleClick(subRoute)} > - {subRoute.name} - - - - - ))} - - - )} - - ); - })} - + + + {subRoute.name} + + + + + ))} + + + )} + + ); + })} + + + ); })} diff --git a/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx b/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx index be6469d00..0d63e24b8 100644 --- a/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx +++ b/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx @@ -1,3 +1,4 @@ +import { ROLES } from "../../../constants/application-constant"; import { Icon } from "../../icons/type"; export const Routes: RouteType[] = [ @@ -6,18 +7,21 @@ export const Routes: RouteType[] = [ icon: "DashboardIcon", path: "/", group: "Group1", + roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], }, { name: "All Works", icon: "AllIcon", path: "/works", group: "Group1", + roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], }, { name: "Reports", icon: "ReportIcon", path: "/reports", group: "Group2", + roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], routes: [ { name: "Referral Schedule", @@ -42,18 +46,21 @@ export const Routes: RouteType[] = [ icon: "InsightIcon", path: "/insights", group: "Group2", + roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], }, { name: "Task Templates", path: "/templates", group: "Group3", icon: "PenIcon", + roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], }, { name: "List Management", icon: "GridIcon", path: "/list-management", group: "Group3", + roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], routes: [ { name: "Staff", @@ -82,6 +89,7 @@ export const Routes: RouteType[] = [ path: "/admin", icon: "GearIcon", group: "Group4", + roles: [ROLES.CREATE], routes: [ { name: "Users", @@ -97,4 +105,5 @@ export interface RouteType { group?: string; icon?: Icon; routes?: RouteType[]; + roles?: string[]; } diff --git a/epictrack-web/src/constants/application-constant.ts b/epictrack-web/src/constants/application-constant.ts index 4a2c14b34..a698b7cd3 100644 --- a/epictrack-web/src/constants/application-constant.ts +++ b/epictrack-web/src/constants/application-constant.ts @@ -84,4 +84,5 @@ export const ROLES = { CREATE: "create", EDIT: "edit", DELETE: "delete", + DEFAULT_ROLES_EAO_EPIC: "default-roles-eao-epic", }; From a1aa679f1ff965b24f226e53eee596084119d639 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Fri, 24 Nov 2023 11:17:08 -0800 Subject: [PATCH 02/18] add restricted views to routes and side bars --- .../src/components/layout/SideNav/SideNav.tsx | 203 ++++++++++-------- .../layout/SideNav/SideNavElements.tsx | 46 +++- .../components/shared/restricted/index.tsx | 2 +- epictrack-web/src/routes/AuthGate.tsx | 17 ++ .../src/routes/AuthenticatedRoutes.tsx | 11 +- epictrack-web/src/routes/Unauthorized.tsx | 77 +++++++ 6 files changed, 251 insertions(+), 105 deletions(-) create mode 100644 epictrack-web/src/routes/AuthGate.tsx create mode 100644 epictrack-web/src/routes/Unauthorized.tsx diff --git a/epictrack-web/src/components/layout/SideNav/SideNav.tsx b/epictrack-web/src/components/layout/SideNav/SideNav.tsx index dbee5459f..d2984acab 100644 --- a/epictrack-web/src/components/layout/SideNav/SideNav.tsx +++ b/epictrack-web/src/components/layout/SideNav/SideNav.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { ListItemButton, List, @@ -14,7 +14,7 @@ import { styled } from "@mui/system"; import ExpandLess from "@mui/icons-material/ExpandLess"; import ExpandMore from "@mui/icons-material/ExpandMore"; import Collapse from "@mui/material/Collapse"; -import { Routes } from "./SideNavElements"; +import { RouteType, Routes } from "./SideNavElements"; import { Palette } from "../../../styles/theme"; import { SideNavProps } from "./types"; import { useAppSelector } from "../../../hooks"; @@ -22,7 +22,7 @@ import Icons from "../../icons"; import { groupBy } from "../../../utils"; import { IconProps } from "../../icons/type"; import { ETSubhead } from "../../shared"; -import { Restricted } from "../../shared/restricted"; +import { hasPermission } from "../../shared/restricted"; const ListItemStyled = styled(ListItem)({ padding: "0px 0px 0px 0px", @@ -58,6 +58,8 @@ const DrawerBox = () => { const [open, setOpen] = React.useState<{ [x: string]: boolean }>({}); const uiState = useAppSelector((state) => state.uiState); const location = useLocation(); + const { roles } = useAppSelector((state) => state.user.userDetail); + const allowedRoutes: RouteType[] = []; const handleClick = (route: any) => { if (route.routes && route.routes.length > 0) { setOpen((prevState: any) => ({ @@ -73,13 +75,37 @@ const DrawerBox = () => { return ; }, []); - const groupedRoutes = React.useMemo( - () => groupBy(Routes, (p) => p.group), - [] - ); + const getAllowedRoutes = () => { + return Routes.map((route: RouteType, index) => { + if (!route["hasNested"]) { + const allowed = route["allowedRoles"] ?? []; + if (hasPermission({ roles, allowed }) || !route["isAuthenticated"]) { + allowedRoutes[index] = route; + } + } + if (route["hasNested"]) { + const routes = route["routes"] ?? []; + const filteredRoutes = routes.filter((route: RouteType) => { + const allowed = route["allowedRoles"] ?? []; + return hasPermission({ roles, allowed }) || !route["isAuthenticated"]; + }); + if (filteredRoutes.length > 0) { + allowedRoutes[index] = route; + allowedRoutes[index]["routes"] = filteredRoutes; + } + } + }); + }; - // console.log(Routes); - // console.log(groupedRoutes); + useMemo(() => { + getAllowedRoutes(); + }, [roles]); + + getAllowedRoutes(); + const groupedRoutes = useMemo( + () => groupBy(allowedRoutes, (p) => p.group), + [allowedRoutes] + ); return ( { {Object.keys(groupedRoutes).map((groupKey) => { return ( <> - - <> - {groupedRoutes[groupKey].map((route, i) => { - return ( - <> - - handleClick(route)} - > - {route.icon && ( - - {renderIcon( - route.icon, - location.pathname === route.path - )} - + {groupedRoutes[groupKey].map((route, i) => { + return ( + <> + + handleClick(route)} + > + {route.icon && ( + + {renderIcon( + route.icon, + location.pathname === route.path )} - - - {route.name} - - - {route?.routes && - (route?.routes?.length > 0 && - !!open[route.name] ? ( - - ) : ( - - ))} - - - {route.routes && route.routes?.length > 0 && ( - + )} + + - - {route.routes?.map((subRoute, i) => ( - + + {route?.routes && + (route?.routes?.length > 0 && !!open[route.name] ? ( + + ) : ( + + ))} + + + {route.routes && route.routes?.length > 0 && ( + + + {route.routes?.map((subRoute, i) => ( + + handleClick(subRoute)} + > + - handleClick(subRoute)} + - - - {subRoute.name} - - - - - ))} - - - )} - - ); - })} - - - + {subRoute.name} + + + + + ))} + + + )} + + ); + })} + ); })} diff --git a/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx b/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx index 0d63e24b8..de34ab875 100644 --- a/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx +++ b/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx @@ -7,37 +7,49 @@ export const Routes: RouteType[] = [ icon: "DashboardIcon", path: "/", group: "Group1", - roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], + allowedRoles: [], + isAuthenticated: false, + hasNested: false, }, { name: "All Works", icon: "AllIcon", path: "/works", group: "Group1", - roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], + allowedRoles: [], + isAuthenticated: false, + hasNested: false, }, { name: "Reports", icon: "ReportIcon", path: "/reports", group: "Group2", - roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], + hasNested: true, routes: [ { name: "Referral Schedule", path: "/reports/referral-schedule", + allowedRoles: [], + isAuthenticated: false, }, { name: "Resource Forecast", path: "/reports/resource-forecast", + allowedRoles: [], + isAuthenticated: false, }, { name: "30-60-90", path: "/reports/30-60-90", + allowedRoles: [], + isAuthenticated: false, }, { name: "Event Calendar", path: "/reports/event-calendar", + allowedRoles: [], + isAuthenticated: false, }, ], }, @@ -46,41 +58,55 @@ export const Routes: RouteType[] = [ icon: "InsightIcon", path: "/insights", group: "Group2", - roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], + allowedRoles: [], + isAuthenticated: false, + hasNested: false, }, { name: "Task Templates", path: "/templates", group: "Group3", icon: "PenIcon", - roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], + allowedRoles: [], + isAuthenticated: false, + hasNested: false, }, { name: "List Management", icon: "GridIcon", path: "/list-management", group: "Group3", - roles: [ROLES.DEFAULT_ROLES_EAO_EPIC], + hasNested: true, routes: [ { name: "Staff", path: "/list-management/staffs", + allowedRoles: [], + isAuthenticated: false, }, { name: "First Nations", path: "/list-management/first-nations", + allowedRoles: [], + isAuthenticated: false, }, { name: "Proponents", path: "/list-management/proponents", + allowedRoles: [], + isAuthenticated: false, }, { name: "Projects", path: "/list-management/projects", + allowedRoles: [], + isAuthenticated: false, }, { name: "Work Staff", path: "/list-management/work-staff", + allowedRoles: [], + isAuthenticated: false, }, ], }, @@ -89,11 +115,13 @@ export const Routes: RouteType[] = [ path: "/admin", icon: "GearIcon", group: "Group4", - roles: [ROLES.CREATE], + hasNested: true, routes: [ { name: "Users", path: "/admin/users", + allowedRoles: [ROLES.CREATE], + isAuthenticated: true, }, ], }, @@ -105,5 +133,7 @@ export interface RouteType { group?: string; icon?: Icon; routes?: RouteType[]; - roles?: string[]; + allowedRoles?: string[]; + isAuthenticated?: boolean; + hasNested?: boolean; } diff --git a/epictrack-web/src/components/shared/restricted/index.tsx b/epictrack-web/src/components/shared/restricted/index.tsx index 4a8ec1873..b72fd3686 100644 --- a/epictrack-web/src/components/shared/restricted/index.tsx +++ b/epictrack-web/src/components/shared/restricted/index.tsx @@ -5,7 +5,7 @@ interface HasPermissionProps { roles: string[]; allowed: string[]; } -const hasPermission = ({ roles, allowed }: HasPermissionProps) => { +export const hasPermission = ({ roles, allowed }: HasPermissionProps) => { const allowedMap: { [scope: string]: boolean } = {}; allowed.forEach((allowedGroup) => { diff --git a/epictrack-web/src/routes/AuthGate.tsx b/epictrack-web/src/routes/AuthGate.tsx new file mode 100644 index 000000000..9a9ff4d76 --- /dev/null +++ b/epictrack-web/src/routes/AuthGate.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { useAppSelector } from "../hooks"; +import { useLocation, Navigate, Outlet } from "react-router-dom"; +import { hasPermission } from "../components/shared/restricted"; + +const AuthGate = ({ allowed }: { allowed: string[] }) => { + const { roles } = useAppSelector((state) => state.user.userDetail); + const location = useLocation(); + + return hasPermission({ roles, allowed }) ? ( + + ) : ( + + ); +}; + +export default AuthGate; diff --git a/epictrack-web/src/routes/AuthenticatedRoutes.tsx b/epictrack-web/src/routes/AuthenticatedRoutes.tsx index bbfcb1f55..35288ce73 100644 --- a/epictrack-web/src/routes/AuthenticatedRoutes.tsx +++ b/epictrack-web/src/routes/AuthenticatedRoutes.tsx @@ -16,11 +16,14 @@ import WorkStaffList from "../components/work/workStaff/WorkStaffList"; import WorkPlan from "../components/workPlan"; import EventCalendar from "../components/eventCalendar/EventCalendar"; import ComingSoon from "./ComingSoon"; +import { ROLES } from "../constants/application-constant"; +import AuthGate from "./AuthGate"; +import Unauthorized from "./Unauthorized"; const AuthenticatedRoutes = () => { return ( - {/* } /> */} + } /> { /> } /> } /> - } /> - } /> + }> + } /> + } /> + } /> } /> ); diff --git a/epictrack-web/src/routes/Unauthorized.tsx b/epictrack-web/src/routes/Unauthorized.tsx new file mode 100644 index 000000000..4e1ccbfcd --- /dev/null +++ b/epictrack-web/src/routes/Unauthorized.tsx @@ -0,0 +1,77 @@ +import { Box, Button, Container } from "@mui/material"; +import { + ETHeading1, + ETHeading3, + ETPageContainer, +} from "../components/shared/index"; +import { useNavigate } from "react-router-dom"; +import { Palette } from "../styles/theme"; + +const Unauthorized = () => { + const navigate = useNavigate(); + return ( + + + + + + + Unauthorized + + + You do not have the permission to view this page. + + + + + + ); +}; + +export default Unauthorized; From b61d3dc56b5232a2138ebdb1b3d2ed2d9062da02 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Fri, 24 Nov 2023 14:30:22 -0800 Subject: [PATCH 03/18] auto add work lead as team lead --- .../7a42d4f57279_project_lead_to_team_lead.py | 24 +++++++++++++++++++ epictrack-api/src/api/resources/work.py | 9 ++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 epictrack-api/migrations/versions/7a42d4f57279_project_lead_to_team_lead.py diff --git a/epictrack-api/migrations/versions/7a42d4f57279_project_lead_to_team_lead.py b/epictrack-api/migrations/versions/7a42d4f57279_project_lead_to_team_lead.py new file mode 100644 index 000000000..33e3c3fb3 --- /dev/null +++ b/epictrack-api/migrations/versions/7a42d4f57279_project_lead_to_team_lead.py @@ -0,0 +1,24 @@ +"""project lead to team lead + +Revision ID: 7a42d4f57279 +Revises: 1e1a2af1605b +Create Date: 2023-11-24 13:39:01.142953 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7a42d4f57279' +down_revision = '1e1a2af1605b' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("Update roles set name='Team Lead' where name='Project Lead'") + + +def downgrade(): + pass diff --git a/epictrack-api/src/api/resources/work.py b/epictrack-api/src/api/resources/work.py index 9d94dae3a..f88187e83 100644 --- a/epictrack-api/src/api/resources/work.py +++ b/epictrack-api/src/api/resources/work.py @@ -25,6 +25,7 @@ from api.utils import auth, profiletime from api.utils.util import cors_preflight from api.utils.datetime_helper import get_start_of_day +from api.services.code import CodeService API = Namespace("works", description="Works") @@ -74,6 +75,12 @@ def post(): request_json = req.WorkBodyParameterSchema().load(API.payload) request_json["start_date"] = get_start_of_day(request_json["start_date"]) work = WorkService.create_work(request_json) + role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") + WorkService.create_work_staff(work.id, { + "staff_id": request_json["work_lead_id"], + "role_id": role_id, + "is_active": True + }) return res.WorkResponseSchema().dump(work), HTTPStatus.CREATED @@ -170,7 +177,7 @@ def get(work_id): @auth.require @profiletime def post(work_id): - """Get all the active staff allocated to the work""" + """Add staff member to a work""" req.WorkIdPathParameterSchema().load(request.view_args) request_json = req.StaffWorkBodyParamSchema().load(API.payload) staff = WorkService.create_work_staff(work_id, request_json) From d3ac23529d9b707abe83300d782d9c1926081cee Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Fri, 24 Nov 2023 15:04:03 -0800 Subject: [PATCH 04/18] trying to fix linting --- epictrack-api/src/api/resources/work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epictrack-api/src/api/resources/work.py b/epictrack-api/src/api/resources/work.py index f88187e83..20bead079 100644 --- a/epictrack-api/src/api/resources/work.py +++ b/epictrack-api/src/api/resources/work.py @@ -80,7 +80,7 @@ def post(): "staff_id": request_json["work_lead_id"], "role_id": role_id, "is_active": True - }) + }) return res.WorkResponseSchema().dump(work), HTTPStatus.CREATED From 65b10b4f12633ce89be60b522268f083a6737ee1 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Fri, 24 Nov 2023 15:07:14 -0800 Subject: [PATCH 05/18] trying to fix linting --- epictrack-api/src/api/resources/work.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/epictrack-api/src/api/resources/work.py b/epictrack-api/src/api/resources/work.py index 20bead079..86d257588 100644 --- a/epictrack-api/src/api/resources/work.py +++ b/epictrack-api/src/api/resources/work.py @@ -76,11 +76,7 @@ def post(): request_json["start_date"] = get_start_of_day(request_json["start_date"]) work = WorkService.create_work(request_json) role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") - WorkService.create_work_staff(work.id, { - "staff_id": request_json["work_lead_id"], - "role_id": role_id, - "is_active": True - }) + WorkService.create_work_staff(work.id, { "staff_id": request_json["work_lead_id"], "role_id": role_id, "is_active": True }) return res.WorkResponseSchema().dump(work), HTTPStatus.CREATED From 888424ba5d9eafc223ac8698d5dab8efee91c909 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 27 Nov 2023 14:45:23 -0800 Subject: [PATCH 06/18] move logic to inside create_work --- epictrack-api/src/api/resources/work.py | 2 -- epictrack-api/src/api/services/work.py | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/epictrack-api/src/api/resources/work.py b/epictrack-api/src/api/resources/work.py index 86d257588..efbddebb3 100644 --- a/epictrack-api/src/api/resources/work.py +++ b/epictrack-api/src/api/resources/work.py @@ -75,8 +75,6 @@ def post(): request_json = req.WorkBodyParameterSchema().load(API.payload) request_json["start_date"] = get_start_of_day(request_json["start_date"]) work = WorkService.create_work(request_json) - role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") - WorkService.create_work_staff(work.id, { "staff_id": request_json["work_lead_id"], "role_id": role_id, "is_active": True }) return res.WorkResponseSchema().dump(work), HTTPStatus.CREATED diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index af2b1f156..83ec1721a 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -39,7 +39,7 @@ from api.services.event_template import EventTemplateService from api.services.outcome_template import OutcomeTemplateService from api.services.phaseservice import PhaseService - +from api.services.code import CodeService class WorkService: # pylint: disable=too-many-public-methods """Service to manage work related operations.""" @@ -148,6 +148,10 @@ def create_work(cls, payload, commit: bool = True): if sort_order == 1: work.current_work_phase_id = work_phase_id sort_order = sort_order + 1 + + role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") + WorkService.create_work_staff(work.id, { "staff_id": payload["work_lead_id"], "role_id": role_id, "is_active": True }) + if commit: db.session.commit() return work From edbf5702fec04d973f3e881505d8d71bb3a1acf2 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 27 Nov 2023 15:56:42 -0800 Subject: [PATCH 07/18] unauthorized page changes --- epictrack-web/src/components/shared/index.tsx | 4 +- epictrack-web/src/routes/Unauthorized.tsx | 88 ++++++++----------- 2 files changed, 37 insertions(+), 55 deletions(-) diff --git a/epictrack-web/src/components/shared/index.tsx b/epictrack-web/src/components/shared/index.tsx index ec4038507..dd569d8eb 100644 --- a/epictrack-web/src/components/shared/index.tsx +++ b/epictrack-web/src/components/shared/index.tsx @@ -62,8 +62,8 @@ export const ETPageContainer = (props: PageContainerProps) => { sx={{ ...props.sx, padding: `${state.showEnvBanner ? "9" : "7"}rem 2rem 1rem 2.5rem`, - justifyContent: "flex-start", - alignItems: "flex-start", + // justifyContent: "flex-start", + // alignItems: "flex-start", }} > {props.children} diff --git a/epictrack-web/src/routes/Unauthorized.tsx b/epictrack-web/src/routes/Unauthorized.tsx index 4e1ccbfcd..2131b27c4 100644 --- a/epictrack-web/src/routes/Unauthorized.tsx +++ b/epictrack-web/src/routes/Unauthorized.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Container } from "@mui/material"; +import { Box, Button, Container, Grid } from "@mui/material"; import { ETHeading1, ETHeading3, @@ -12,64 +12,46 @@ const Unauthorized = () => { return ( - - + + + + + Unauthorized + + + + - - - - Unauthorized - - - You do not have the permission to view this page. - - - - + You do not have the permission to view this page. + + ); }; From acd45823d3f21c1ff9bf96281161d14bf1fd82a5 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Mon, 27 Nov 2023 15:59:48 -0800 Subject: [PATCH 08/18] unauthorized page changes --- epictrack-web/src/components/shared/index.tsx | 4 +- epictrack-web/src/routes/Unauthorized.tsx | 58 ++++++++++--------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/epictrack-web/src/components/shared/index.tsx b/epictrack-web/src/components/shared/index.tsx index dd569d8eb..ec4038507 100644 --- a/epictrack-web/src/components/shared/index.tsx +++ b/epictrack-web/src/components/shared/index.tsx @@ -62,8 +62,8 @@ export const ETPageContainer = (props: PageContainerProps) => { sx={{ ...props.sx, padding: `${state.showEnvBanner ? "9" : "7"}rem 2rem 1rem 2.5rem`, - // justifyContent: "flex-start", - // alignItems: "flex-start", + justifyContent: "flex-start", + alignItems: "flex-start", }} > {props.children} diff --git a/epictrack-web/src/routes/Unauthorized.tsx b/epictrack-web/src/routes/Unauthorized.tsx index 2131b27c4..b02011206 100644 --- a/epictrack-web/src/routes/Unauthorized.tsx +++ b/epictrack-web/src/routes/Unauthorized.tsx @@ -12,37 +12,41 @@ const Unauthorized = () => { return ( - - - - - - Unauthorized - - - + + + + + + + Unauthorized + + + + Date: Tue, 28 Nov 2023 15:16:28 -0800 Subject: [PATCH 09/18] refactors --- .../src/components/layout/SideNav/SideNav.tsx | 43 +++++++++---------- .../layout/SideNav/SideNavElements.tsx | 8 ---- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/epictrack-web/src/components/layout/SideNav/SideNav.tsx b/epictrack-web/src/components/layout/SideNav/SideNav.tsx index d2984acab..7c8c72ca8 100644 --- a/epictrack-web/src/components/layout/SideNav/SideNav.tsx +++ b/epictrack-web/src/components/layout/SideNav/SideNav.tsx @@ -59,7 +59,6 @@ const DrawerBox = () => { const uiState = useAppSelector((state) => state.uiState); const location = useLocation(); const { roles } = useAppSelector((state) => state.user.userDetail); - const allowedRoutes: RouteType[] = []; const handleClick = (route: any) => { if (route.routes && route.routes.length > 0) { setOpen((prevState: any) => ({ @@ -75,33 +74,33 @@ const DrawerBox = () => { return ; }, []); - const getAllowedRoutes = () => { - return Routes.map((route: RouteType, index) => { - if (!route["hasNested"]) { - const allowed = route["allowedRoles"] ?? []; - if (hasPermission({ roles, allowed }) || !route["isAuthenticated"]) { - allowedRoutes[index] = route; + const isRouteAllowed = (route: RouteType, roles: Array) => { + const allowed = route["allowedRoles"] ?? []; + return hasPermission({ roles, allowed }) || !route["isAuthenticated"]; + }; + + const filterRoutes = (routes: RouteType[], roles: Array) => { + return routes.reduce((allowedRoutes: any, route) => { + if (isRouteAllowed(route, roles)) { + const newRoute: RouteType = { ...route }; + if (newRoute["routes"]) { + newRoute["routes"] = filterRoutes(newRoute["routes"], roles); } - } - if (route["hasNested"]) { - const routes = route["routes"] ?? []; - const filteredRoutes = routes.filter((route: RouteType) => { - const allowed = route["allowedRoles"] ?? []; - return hasPermission({ roles, allowed }) || !route["isAuthenticated"]; - }); - if (filteredRoutes.length > 0) { - allowedRoutes[index] = route; - allowedRoutes[index]["routes"] = filteredRoutes; + + const addRoute = + (newRoute["routes"] && newRoute["routes"].length > 0) || + !newRoute["routes"]; + + if (addRoute) { + allowedRoutes.push(newRoute); } } - }); + return allowedRoutes; + }, []); }; - useMemo(() => { - getAllowedRoutes(); - }, [roles]); + const allowedRoutes: RouteType[] = filterRoutes(Routes, roles); - getAllowedRoutes(); const groupedRoutes = useMemo( () => groupBy(allowedRoutes, (p) => p.group), [allowedRoutes] diff --git a/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx b/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx index de34ab875..4df714223 100644 --- a/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx +++ b/epictrack-web/src/components/layout/SideNav/SideNavElements.tsx @@ -9,7 +9,6 @@ export const Routes: RouteType[] = [ group: "Group1", allowedRoles: [], isAuthenticated: false, - hasNested: false, }, { name: "All Works", @@ -18,14 +17,12 @@ export const Routes: RouteType[] = [ group: "Group1", allowedRoles: [], isAuthenticated: false, - hasNested: false, }, { name: "Reports", icon: "ReportIcon", path: "/reports", group: "Group2", - hasNested: true, routes: [ { name: "Referral Schedule", @@ -60,7 +57,6 @@ export const Routes: RouteType[] = [ group: "Group2", allowedRoles: [], isAuthenticated: false, - hasNested: false, }, { name: "Task Templates", @@ -69,14 +65,12 @@ export const Routes: RouteType[] = [ icon: "PenIcon", allowedRoles: [], isAuthenticated: false, - hasNested: false, }, { name: "List Management", icon: "GridIcon", path: "/list-management", group: "Group3", - hasNested: true, routes: [ { name: "Staff", @@ -115,7 +109,6 @@ export const Routes: RouteType[] = [ path: "/admin", icon: "GearIcon", group: "Group4", - hasNested: true, routes: [ { name: "Users", @@ -135,5 +128,4 @@ export interface RouteType { routes?: RouteType[]; allowedRoles?: string[]; isAuthenticated?: boolean; - hasNested?: boolean; } From 3c56fd4cc10e99af84d03ddcbe7bb6ad649bd980 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 21:51:23 -0800 Subject: [PATCH 10/18] lint issues --- epictrack-api/src/api/resources/work.py | 1 - 1 file changed, 1 deletion(-) diff --git a/epictrack-api/src/api/resources/work.py b/epictrack-api/src/api/resources/work.py index efbddebb3..9bb80127c 100644 --- a/epictrack-api/src/api/resources/work.py +++ b/epictrack-api/src/api/resources/work.py @@ -25,7 +25,6 @@ from api.utils import auth, profiletime from api.utils.util import cors_preflight from api.utils.datetime_helper import get_start_of_day -from api.services.code import CodeService API = Namespace("works", description="Works") From da150ecff0701889be4deac639d2e3161e2b7692 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 22:00:20 -0800 Subject: [PATCH 11/18] lint issues --- epictrack-api/src/api/services/work.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index 83ec1721a..c417de45f 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -147,11 +147,9 @@ def create_work(cls, payload, commit: bool = True): phase_start_date = end_date + timedelta(days=1) if sort_order == 1: work.current_work_phase_id = work_phase_id - sort_order = sort_order + 1 - + sort_order = sort_order + 1 role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") WorkService.create_work_staff(work.id, { "staff_id": payload["work_lead_id"], "role_id": role_id, "is_active": True }) - if commit: db.session.commit() return work From d174ea22b84a2e57a32fc346cb7f40cbf2d486f1 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 22:03:53 -0800 Subject: [PATCH 12/18] lint issues --- epictrack-api/src/api/services/work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index c417de45f..e10322138 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -147,7 +147,7 @@ def create_work(cls, payload, commit: bool = True): phase_start_date = end_date + timedelta(days=1) if sort_order == 1: work.current_work_phase_id = work_phase_id - sort_order = sort_order + 1 + sort_order = sort_order + 1 role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") WorkService.create_work_staff(work.id, { "staff_id": payload["work_lead_id"], "role_id": role_id, "is_active": True }) if commit: From d1bd384cfab85ba13301b0dd0ad95eb35a3a263b Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 22:11:18 -0800 Subject: [PATCH 13/18] lint issues --- epictrack-api/src/api/services/work.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index e10322138..002208bb4 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -41,6 +41,7 @@ from api.services.phaseservice import PhaseService from api.services.code import CodeService + class WorkService: # pylint: disable=too-many-public-methods """Service to manage work related operations.""" @@ -148,8 +149,17 @@ def create_work(cls, payload, commit: bool = True): if sort_order == 1: work.current_work_phase_id = work_phase_id sort_order = sort_order + 1 - role_id = CodeService.find_code_values_by_type("roles", { "name": "Team Lead" }).get("codes")[0].get("id") - WorkService.create_work_staff(work.id, { "staff_id": payload["work_lead_id"], "role_id": role_id, "is_active": True }) + role_id = CodeService.find_code_values_by_type( + "roles", + { "name": "Team Lead" } + ).get("codes")[0].get("id") + WorkService.create_work_staff( + work.id, + { + "staff_id": payload["work_lead_id"], + "role_id": role_id, "is_active": True + } + ) if commit: db.session.commit() return work From bbf7f0a9653a4bada3a9dc7e1f59ae246a89754a Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 22:14:37 -0800 Subject: [PATCH 14/18] lint issues --- epictrack-api/src/api/services/work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index 002208bb4..f51fcf12a 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -155,7 +155,7 @@ def create_work(cls, payload, commit: bool = True): ).get("codes")[0].get("id") WorkService.create_work_staff( work.id, - { + { "staff_id": payload["work_lead_id"], "role_id": role_id, "is_active": True } From 2ebf3ab23c1a99931c8baa45c5e713081ac34494 Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 22:18:14 -0800 Subject: [PATCH 15/18] lint issues --- epictrack-api/src/api/services/work.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index f51fcf12a..3e8c5b652 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -151,7 +151,9 @@ def create_work(cls, payload, commit: bool = True): sort_order = sort_order + 1 role_id = CodeService.find_code_values_by_type( "roles", - { "name": "Team Lead" } + { + "name": "Team Lead" + } ).get("codes")[0].get("id") WorkService.create_work_staff( work.id, From 7dd6797c25e800c41c235a0ac172555c2cc8521f Mon Sep 17 00:00:00 2001 From: Tom Chapman Date: Wed, 29 Nov 2023 22:23:34 -0800 Subject: [PATCH 16/18] lint issues --- epictrack-api/src/api/services/work.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/epictrack-api/src/api/services/work.py b/epictrack-api/src/api/services/work.py index 3e8c5b652..815df7602 100644 --- a/epictrack-api/src/api/services/work.py +++ b/epictrack-api/src/api/services/work.py @@ -154,12 +154,12 @@ def create_work(cls, payload, commit: bool = True): { "name": "Team Lead" } - ).get("codes")[0].get("id") + ).get("codes")[0].get("id") WorkService.create_work_staff( work.id, { - "staff_id": payload["work_lead_id"], - "role_id": role_id, "is_active": True + "staff_id": payload["work_lead_id"], + "role_id": role_id, "is_active": True } ) if commit: From ca570d9457929bea4956ed1b7eb3a71a88a324d7 Mon Sep 17 00:00:00 2001 From: jadmsaadaot <91914654+jadmsaadaot@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:06:18 -0800 Subject: [PATCH 17/18] Add empty filters (#1374) --- .../src/components/myWorkplans/CardList.tsx | 2 +- .../myWorkplans/Filters/AssigneeToggle.tsx | 27 ++++++ .../myWorkplans/Filters/NameFilter.tsx | 12 +++ .../components/myWorkplans/Filters/index.tsx | 88 +++++++++++++++++++ .../myWorkplans/MyWorkplanContainer.tsx | 15 +++- .../src/components/myWorkplans/index.tsx | 26 +++--- .../src/routes/AuthenticatedRoutes.tsx | 2 +- 7 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 epictrack-web/src/components/myWorkplans/Filters/AssigneeToggle.tsx create mode 100644 epictrack-web/src/components/myWorkplans/Filters/NameFilter.tsx create mode 100644 epictrack-web/src/components/myWorkplans/Filters/index.tsx diff --git a/epictrack-web/src/components/myWorkplans/CardList.tsx b/epictrack-web/src/components/myWorkplans/CardList.tsx index 89d4d333f..82d09fd65 100644 --- a/epictrack-web/src/components/myWorkplans/CardList.tsx +++ b/epictrack-web/src/components/myWorkplans/CardList.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Box } from "@mui/material"; const CardList = () => { - return ; + return This is a card list; }; export default CardList; diff --git a/epictrack-web/src/components/myWorkplans/Filters/AssigneeToggle.tsx b/epictrack-web/src/components/myWorkplans/Filters/AssigneeToggle.tsx new file mode 100644 index 000000000..10cc7c14e --- /dev/null +++ b/epictrack-web/src/components/myWorkplans/Filters/AssigneeToggle.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { FormControlLabel, Stack, Switch } from "@mui/material"; +import { useAppSelector } from "../../../hooks"; +import { ETCaption2 } from "../../shared"; +import { Palette } from "../../../styles/theme"; +import { CustomSwitch } from "../../shared/CustomSwitch"; + +export const AssigneeToggle = () => { + const user = useAppSelector((state) => state.user.userDetail); + + const [isUsersWorkPlans, setIsUsersWorkPlans] = React.useState(false); + return ( + + <> + + {user.firstName}'s{" "} + + Workplans + + setIsUsersWorkPlans(!isUsersWorkPlans)} + /> + + ); +}; diff --git a/epictrack-web/src/components/myWorkplans/Filters/NameFilter.tsx b/epictrack-web/src/components/myWorkplans/Filters/NameFilter.tsx new file mode 100644 index 000000000..56031e831 --- /dev/null +++ b/epictrack-web/src/components/myWorkplans/Filters/NameFilter.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { TextField } from "@mui/material"; + +export const NameFilter = () => { + return ( + + ); +}; diff --git a/epictrack-web/src/components/myWorkplans/Filters/index.tsx b/epictrack-web/src/components/myWorkplans/Filters/index.tsx new file mode 100644 index 000000000..697206642 --- /dev/null +++ b/epictrack-web/src/components/myWorkplans/Filters/index.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { Grid } from "@mui/material"; +import FilterSelect from "../../shared/filterSelect/FilterSelect"; +import { NameFilter } from "./NameFilter"; + +const Filters = () => { + return ( + + + + + + + { + return; + }} + name="team" + isMulti + info={true} + /> + + + { + return; + }} + name="work_type" + isMulti + info={true} + /> + + + { + return; + }} + name="project_type" + isMulti + info={true} + /> + + + { + return; + }} + name="env_region" + isMulti + info={true} + /> + + + { + return; + }} + name="work_state" + isMulti + info={true} + /> + + + + ); +}; + +export default Filters; diff --git a/epictrack-web/src/components/myWorkplans/MyWorkplanContainer.tsx b/epictrack-web/src/components/myWorkplans/MyWorkplanContainer.tsx index 0757d4c81..6a32fa0c0 100644 --- a/epictrack-web/src/components/myWorkplans/MyWorkplanContainer.tsx +++ b/epictrack-web/src/components/myWorkplans/MyWorkplanContainer.tsx @@ -2,6 +2,9 @@ import { useContext } from "react"; import { ETPageContainer } from "../shared"; import { MyWorkplansContext } from "./MyWorkPlanContext"; import CardList from "./CardList"; +import { Grid } from "@mui/material"; +import Filters from "./Filters"; +import { AssigneeToggle } from "./Filters/AssigneeToggle"; const WorkPlanContainer = () => { const {} = useContext(MyWorkplansContext); @@ -11,8 +14,18 @@ const WorkPlanContainer = () => { sx={{ paddingBottom: "0rem !important", }} + container + spacing={2} > - + + + + + + + + + ); }; diff --git a/epictrack-web/src/components/myWorkplans/index.tsx b/epictrack-web/src/components/myWorkplans/index.tsx index 6328ebf4b..e370c444b 100644 --- a/epictrack-web/src/components/myWorkplans/index.tsx +++ b/epictrack-web/src/components/myWorkplans/index.tsx @@ -1,13 +1,13 @@ -import React from "react"; -import { MyWorkplansProvider } from "./MyWorkPlanContext"; -import WorkPlanContainer from "./MyWorkplanContainer"; - -const MyWorkPlans = () => { - return ( - - - - ); -}; - -export default MyWorkPlans; +import React from "react"; +import { MyWorkplansProvider } from "./MyWorkPlanContext"; +import WorkPlanContainer from "./MyWorkplanContainer"; + +const MyWorkPlans = () => { + return ( + + + + ); +}; + +export default MyWorkPlans; diff --git a/epictrack-web/src/routes/AuthenticatedRoutes.tsx b/epictrack-web/src/routes/AuthenticatedRoutes.tsx index f9f061d49..434a0331d 100644 --- a/epictrack-web/src/routes/AuthenticatedRoutes.tsx +++ b/epictrack-web/src/routes/AuthenticatedRoutes.tsx @@ -21,7 +21,7 @@ import MyWorkPlans from "../components/myWorkplans"; const AuthenticatedRoutes = () => { return ( - {/* } /> */} + } /> Date: Thu, 30 Nov 2023 16:06:49 -0800 Subject: [PATCH 18/18] add coming soon to about (#1373) Co-authored-by: Tom Chapman --- .../src/components/workPlan/WorkPlanContainer.tsx | 10 ++++++++++ epictrack-web/src/routes/ComingSoon.tsx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/epictrack-web/src/components/workPlan/WorkPlanContainer.tsx b/epictrack-web/src/components/workPlan/WorkPlanContainer.tsx index 93f9f5cdc..00fe0dc9e 100644 --- a/epictrack-web/src/components/workPlan/WorkPlanContainer.tsx +++ b/epictrack-web/src/components/workPlan/WorkPlanContainer.tsx @@ -15,6 +15,7 @@ import Icons from "../icons"; import { IconProps } from "../icons/type"; import Issues from "./issues"; import WorkState from "./WorkState"; +import ComingSoon from "../../routes/ComingSoon"; const IndicatorIcon: React.FC = Icons["IndicatorIcon"]; @@ -114,6 +115,15 @@ const WorkPlanContainer = () => { > + + + { alignItems="center" spacing={1} padding={"2em 2em 1em 2em"} - sx={{ height: "800px" }} + sx={{ height: "600px" }} >