diff --git a/CHANGELOG.MD b/CHANGELOG.MD index f1003ffb5..fedf3eeac 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,3 +1,6 @@ +## August 15, 2024 +- **Feature** New engagement authoring view tab [🎟️ DESENG-674](https://citz-gdx.atlassian.net/browse/DESENG-674) + ## August 13, 2024 - **Task** Fix dagster etl error in "dev" environment [🎟️ DESENG-677](https://citz-gdx.atlassian.net/browse/DESENG-677) diff --git a/met-web/src/components/engagement/admin/view/AuthoringTab.tsx b/met-web/src/components/engagement/admin/view/AuthoringTab.tsx new file mode 100644 index 000000000..8f81689fa --- /dev/null +++ b/met-web/src/components/engagement/admin/view/AuthoringTab.tsx @@ -0,0 +1,208 @@ +import React, { useState, useEffect } from 'react'; +import { AuthoringValue, AuthoringButtonProps, StatusCircleProps } from './types'; +import { Header2 } from 'components/common/Typography'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowRightLong } from '@fortawesome/pro-light-svg-icons'; +import { faCheck } from '@fortawesome/pro-solid-svg-icons'; +import { MetLabel, MetHeader3 } from 'components/common'; +import { SystemMessage } from 'components/common/Layout/SystemMessage'; +import { When } from 'react-if'; +import { Grid, Link } from '@mui/material'; +import { colors } from 'styles/Theme'; + +const StatusCircle = (props: StatusCircleProps) => { + const statusCircleStyles = { + width: '6px', + height: '6px', + borderRadius: '50%', + backgroundColor: props.required ? colors.notification.danger.icon : colors.surface.gray[70], + marginLeft: '0.3rem', + marginTop: '-1rem', + }; + return ; +}; + +const AuthoringButton = (props: AuthoringButtonProps) => { + const buttonStyles = { + display: 'flex', + width: '100%', + height: '3rem', + backgroundColor: props.item.required ? colors.surface.blue[10] : colors.surface.gray[10], + borderRadius: '8px', + border: 'none', + padding: '0 1rem 0 2.5rem', + margin: '0 0 0.5rem', + alignItems: 'center', + justifyContent: 'flex-start', + cursor: 'pointer', + }; + const textStyles = { + fontSize: '1rem', + color: colors.type.regular.primary, + }; + const arrowStyles = { + color: colors.surface.blue[90], + fontSize: '1.3rem', + marginLeft: 'auto', + }; + const checkStyles = { + color: colors.type.regular.primary, + fontSize: '1rem', + fontWeight: 'bold', + paddingRight: '0.4rem', + }; + return ( + + + + ); +}; + +export const AuthoringTab = () => { + // Set default values + const mandatorySectionTitles = ['Hero Banner', 'Summary', 'Details', 'Provide Feedback']; + const optionalSectionTitles = ['View Results', 'Subscribe', 'More Engagements']; + const feedbackTitles = ['Survey', '3rd Party Feedback Method Link']; + const defaultAuthoringValue: AuthoringValue = { + id: 0, + title: '', + link: '#', + required: false, + completed: false, + }; + const getAuthoringValues = ( + defaultValues: AuthoringValue, + titles: string[], + required: boolean, + idOffset = 0, + ): AuthoringValue[] => { + return titles.map((title, index) => ({ + ...defaultValues, + title: title, + required: required, + id: index + idOffset, + })); + }; + const mandatorySectionValues = getAuthoringValues(defaultAuthoringValue, mandatorySectionTitles, true); + const optionalSectionValues = getAuthoringValues(defaultAuthoringValue, optionalSectionTitles, false, 100); + const defaultSectionValues = [...mandatorySectionValues, ...optionalSectionValues]; + const defaultFeedbackMethods = getAuthoringValues(defaultAuthoringValue, feedbackTitles, true, 1000); + + // Set useStates. When data is imported, it will be set with setSectionValues and setFeedbackMethods. + const [sectionValues] = useState(defaultSectionValues); + const [feedbackMethods] = useState(defaultFeedbackMethods); + const [requiredSectionsCompleted, setRequiredSectionsCompleted] = useState(false); + const [feedbackCompleted, setFeedbackCompleted] = useState(false); + + // Define styles + const systemMessageStyles = { + marginBottom: '1.5rem', + }; + const metHeaderStyles = { + marginBottom: '1.5rem', + fontSize: '1.2rem', + }; + const metLabelStyles = { + textTransform: 'uppercase', + marginBottom: '1.1rem', + fontSize: '0.9rem', + }; + const anchorContainerStyles = { + margin: '0 0 2.5rem 0', + padding: '0', + }; + + // Listen to the sectionValues useState and update the boolean value that controls the presence of the "sections" incomplete message. + useEffect(() => { + if (true !== requiredSectionsCompleted && allRequiredItemsComplete(sectionValues)) { + setRequiredSectionsCompleted(true); + } else if (false !== requiredSectionsCompleted) { + setRequiredSectionsCompleted(false); + } + }, [sectionValues]); + + // Listen to the feedbackMethods useState and update the boolean value that controls the presence of the "feedback" incomplete message. + useEffect(() => { + if (true !== feedbackCompleted && allRequiredItemsComplete(feedbackMethods)) { + setFeedbackCompleted(true); + } else if (false !== feedbackCompleted) { + setRequiredSectionsCompleted(false); + } + }, [feedbackMethods]); + + // Check if all required items are completed. + const allRequiredItemsComplete = (values: AuthoringValue[]) => { + const itemChecklist = values.map((value) => { + if (undefined === value.required || undefined === value.completed) { + return false; + } + if (true === value.required) { + return true === value.completed; + } else { + return true; + } + }); + return !itemChecklist.includes(false); + }; + + return ( + + Authoring + Page Section Authoring + + + There are incomplete or missing sections of required content in your engagement. Please complete all + required content in all of the languages included in your engagement. + + + + + Required Sections + {sectionValues.map( + (section) => section.required && , + )} + + + Optional Sections + {sectionValues.map( + (section) => !section.required && , + )} + + + + Feedback Configuration + + + There are feedback methods included in your engagement that are incomplete. Please complete + configuration for all of the feedback methods included in your engagement. + + + Feedback Methods + + {feedbackMethods.map((method) => ( + + ))} + + + + ); +}; diff --git a/met-web/src/components/engagement/admin/view/index.tsx b/met-web/src/components/engagement/admin/view/index.tsx index c8cd3aac7..2ff3924e2 100644 --- a/met-web/src/components/engagement/admin/view/index.tsx +++ b/met-web/src/components/engagement/admin/view/index.tsx @@ -1,16 +1,26 @@ -import React, { Suspense } from 'react'; +import React, { Suspense, useState } from 'react'; import { useLoaderData, Await } from 'react-router-dom'; import { Engagement } from 'models/engagement'; import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb'; import { EngagementStatus } from 'constants/engagementStatus'; -import { Theme, useMediaQuery } from '@mui/material'; +import { Tab } from '@mui/material'; +import { ResponsiveContainer } from 'components/common/Layout'; +import { AuthoringTab } from './AuthoringTab'; +import { TabContext, TabList, TabPanel } from '@mui/lab'; export const AdminEngagementView = () => { const { engagement } = useLoaderData() as { engagement: Promise }; - const isMediumScreenOrLarger: boolean = useMediaQuery((theme: Theme) => theme.breakpoints.up('md')); + const EngagementViewTabs = { + config: 'Configuration', + authoring: 'Authoring', + activity: 'Activity', + results: 'Results', + publishing: 'Publishing', + }; + const [currentTab, setCurrentTab] = useState(EngagementViewTabs.config); return ( -
+ @@ -32,6 +42,53 @@ export const AdminEngagementView = () => { )} -
+ + setCurrentTab(newValue)} + aria-label="Admin Engagement View Tabs" + TabIndicatorProps={{ sx: { display: 'none' } }} + sx={{ + '& .MuiTabs-flexContainer': { + justifyContent: 'flex-start', + width: 'max-content', + }, + }} + > + {Object.entries(EngagementViewTabs).map(([key, value]) => ( + + ))} + + + + + + + + ); }; diff --git a/met-web/src/components/engagement/admin/view/types.tsx b/met-web/src/components/engagement/admin/view/types.tsx new file mode 100644 index 000000000..b779e373c --- /dev/null +++ b/met-web/src/components/engagement/admin/view/types.tsx @@ -0,0 +1,15 @@ +export interface AuthoringValue { + id: number; + title: string; + link: string; + required: boolean; + completed: boolean; +} + +export interface StatusCircleProps { + required: boolean; +} + +export interface AuthoringButtonProps { + item: AuthoringValue; +} diff --git a/met-web/src/components/layout/Footer/index.tsx b/met-web/src/components/layout/Footer/index.tsx index 9227b46b9..bc3b9f772 100644 --- a/met-web/src/components/layout/Footer/index.tsx +++ b/met-web/src/components/layout/Footer/index.tsx @@ -177,7 +177,7 @@ const Footer = () => { {translate('footer.serviceCentres')} - + diff --git a/met-web/src/components/layout/SideNav/SideNav.tsx b/met-web/src/components/layout/SideNav/SideNav.tsx index 4078b2580..95eed2faf 100644 --- a/met-web/src/components/layout/SideNav/SideNav.tsx +++ b/met-web/src/components/layout/SideNav/SideNav.tsx @@ -60,14 +60,14 @@ const DrawerBox = ({ isMediumScreenOrLarger, setOpen }: DrawerBoxProps) => { return !route.authenticated || route.allowedRoles.some((role) => permissions.includes(role)); }); - const renderListItem = (route: Route, itemType: string) => { + const renderListItem = (route: Route, itemType: string, key: number) => { return ( <> { }} > - {allowedRoutes.map((route) => - renderListItem(route, currentBaseRoute === route.base ? 'selected' : 'other'), + {allowedRoutes.map((route, index) => + renderListItem(route, currentBaseRoute === route.base ? 'selected' : 'other', index), )}