Skip to content

Commit

Permalink
feature/deseng674: Implemented authoring tab to admin engagement view (
Browse files Browse the repository at this point in the history
…#2576)

* feature/deseng674: Implemented authoring tab to admin engagement view. Fixed a couple of issues with Footer and Admin SideNav as well.

* feature/deseng674: Removed random number generator for unique React keys.

* feature/deseng674: Removed unused useState set functions in AuthoringTab component.

* feature/deseng674: Changed component type of tabs to a nav element.

* feature/deseng674: Updated changelog.
  • Loading branch information
jareth-whitney committed Aug 15, 2024
1 parent 76fbfd4 commit ee6caab
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
208 changes: 208 additions & 0 deletions met-web/src/components/engagement/admin/view/AuthoringTab.tsx
Original file line number Diff line number Diff line change
@@ -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 <span style={statusCircleStyles}> </span>;
};

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 (
<Link style={{ textDecoration: 'none' }} href={props.item.link}>
<button style={buttonStyles}>
<When condition={props.item.completed}>
<FontAwesomeIcon style={checkStyles} icon={faCheck} />
</When>
<span style={textStyles}>{props.item.title}</span>
<When condition={!props.item.completed}>
<StatusCircle required={props.item.required} />
</When>
<FontAwesomeIcon style={arrowStyles} icon={faArrowRightLong} />
</button>
</Link>
);
};

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 (
<Grid container id="admin-authoring-section" direction="column" maxWidth={'700px'}>
<Header2 decorated>Authoring</Header2>
<MetHeader3 style={metHeaderStyles}>Page Section Authoring</MetHeader3>
<When condition={!requiredSectionsCompleted}>
<SystemMessage sx={systemMessageStyles} status="error">
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.
</SystemMessage>
</When>
<Grid
container
direction="row"
id="sections-container"
sx={{
...anchorContainerStyles,
flexWrap: { xs: 'wrap', md: 'nowrap' },
columnGap: '5rem',
rowGap: '1.25rem',
}}
>
<Grid item xs={12} md={6}>
<MetLabel sx={metLabelStyles}>Required Sections</MetLabel>
{sectionValues.map(
(section) => section.required && <AuthoringButton key={section.id} item={section} />,
)}
</Grid>
<Grid item xs={12} md={6}>
<MetLabel sx={metLabelStyles}>Optional Sections</MetLabel>
{sectionValues.map(
(section) => !section.required && <AuthoringButton key={section.id} item={section} />,
)}
</Grid>
</Grid>
<Grid container direction="column" id="feedback-container" sx={{ ...anchorContainerStyles }}>
<MetHeader3 style={metHeaderStyles}>Feedback Configuration</MetHeader3>
<When condition={!feedbackCompleted}>
<SystemMessage sx={systemMessageStyles} status="error">
There are feedback methods included in your engagement that are incomplete. Please complete
configuration for all of the feedback methods included in your engagement.
</SystemMessage>
</When>
<MetLabel sx={metLabelStyles}>Feedback Methods</MetLabel>
<Grid item xs={12} sx={{ width: '100%' }}>
{feedbackMethods.map((method) => (
<AuthoringButton item={method} key={method.id} />
))}
</Grid>
</Grid>
</Grid>
);
};
67 changes: 62 additions & 5 deletions met-web/src/components/engagement/admin/view/index.tsx
Original file line number Diff line number Diff line change
@@ -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<Engagement> };
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 (
<div style={{ marginTop: '3.125rem', padding: isMediumScreenOrLarger ? '0' : '0 1rem' }}>
<ResponsiveContainer>
<AutoBreadcrumbs />
<Suspense>
<Await resolve={engagement}>
Expand All @@ -32,6 +42,53 @@ export const AdminEngagementView = () => {
)}
</Await>
</Suspense>
</div>
<TabContext value={currentTab}>
<TabList
component="nav"
variant="scrollable"
onChange={(e, newValue) => 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]) => (
<Tab
key={key}
value={value}
label={value}
sx={{
display: 'flex',
height: '48px',
padding: '0px 24px 0px 18px',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '0px 16px 0px 0px',
borderBottom: '1px solid',
borderColor: 'gray.60',
backgroundColor: 'gray.10',
color: 'text.secondary',
boxShadow:
'0px 1px 5px 0px rgba(0, 0, 0, 0.12), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.20)',
'&.Mui-selected': {
backgroundColor: 'primary.main',
borderColor: 'primary.main',
color: 'white',
},
}}
/>
))}
</TabList>
<TabPanel value={EngagementViewTabs.authoring} style={{ paddingLeft: '0', paddingRight: '0' }}>
<Await resolve={engagement}>
<AuthoringTab />
</Await>
</TabPanel>
</TabContext>
</ResponsiveContainer>
);
};
15 changes: 15 additions & 0 deletions met-web/src/components/engagement/admin/view/types.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion met-web/src/components/layout/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ const Footer = () => {
{translate('footer.serviceCentres')}
<LinkArrow />
</Link>
<Grid container xs={12}>
<Grid container sx={{ flexBasis: { xs: 'auto', md: '100%' } }}>
<Stack direction="row" spacing={2} alignSelf={'flex-end'}>
<SocialLinkIcon iconLink="#" fontAwesomeIcon={faXTwitter} />
<SocialLinkIcon iconLink="#" fontAwesomeIcon={faInstagram} />
Expand Down
8 changes: 4 additions & 4 deletions met-web/src/components/layout/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<When condition={'Tenant Admin' === route.name}>
<Divider sx={{ backgroundColor: Palette.primary.light, height: '0.2rem' }} />
</When>
<ListItem
key={route.name}
key={key}
sx={{
...routeItemStyle,
backgroundColor: 'selected' === itemType ? colors.surface.blue[10] : Palette.background.default,
Expand Down Expand Up @@ -139,8 +139,8 @@ const DrawerBox = ({ isMediumScreenOrLarger, setOpen }: DrawerBoxProps) => {
}}
>
<List sx={{ pt: { xs: 4, md: 0 }, pb: '0' }}>
{allowedRoutes.map((route) =>
renderListItem(route, currentBaseRoute === route.base ? 'selected' : 'other'),
{allowedRoutes.map((route, index) =>
renderListItem(route, currentBaseRoute === route.base ? 'selected' : 'other', index),
)}
</List>
</Box>
Expand Down

0 comments on commit ee6caab

Please sign in to comment.