Skip to content

Commit

Permalink
Merge pull request #2596 from bcgov/feature/DESENG-694-update-timelin…
Browse files Browse the repository at this point in the history
…e-widget

DESENG-694: Update timeline widget design
  • Loading branch information
NatSquared authored Oct 1, 2024
2 parents d735284 + bf9b49b commit 0b61f80
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 194 deletions.
16 changes: 12 additions & 4 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
## September 26, 2024

- **Feature** New Timeline Widget designs [🎟️ DESENG-694](https://citz-gdx.atlassian.net/browse/DESENG-694)
- Implemented Figma design
- Adjusted styling in Engagement views slightly to accommodate theme-aware widgets

## September 25, 2024

- **Feature** New Video Widget front end [🎟️ DESENG-692](https://citz-gdx.atlassian.net/browse/DESENG-692)
- Removed unneeded tables from db
- Updated all other met_api and met_web logic to accomodate this

- Removed unneeded tables from db
- Updated all other met_api and met_web logic to accomodate this

- **Feature** New Map Widget front end [🎟️ DESENG-693](https://citz-gdx.atlassian.net/browse/DESENG-693)
- Implemented Figma design
- Fixed accessibility issue with map labels (white text on white background)
- Implemented Figma design
- Fixed accessibility issue with map labels (white text on white background)

## September 23, 2024

Expand All @@ -19,6 +26,7 @@
## September 18, 2024

- **Feature** New Video Widget front end [🎟️ DESENG-692](https://citz-gdx.atlassian.net/browse/DESENG-692)

- Implemented Figma design
- Created custom layover bar for videos that shows video provider and has a custom logo
- Transcripts will not be implemented at this time
Expand Down
2 changes: 1 addition & 1 deletion met-web/src/components/common/Input/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const CommonSelect: React.FC<CommonSelectProps & Omit<MuiSelectProps, 'va
p: 1,
height: 48,
borderRadius: '2em',
background: bgColor,
backgroundColor: bgColor,
color: textColors.primary,
boxShadow: 3,
'&:hover': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
border: 1px solid #605E5C;
}

.rdw-editor-wrapper:has(.public-DraftEditor-content[contenteditable="false"]){
line-height: 1.75;
}

.rdw-editor-toolbar{
padding: 2px;
border: none;
Expand Down
2 changes: 1 addition & 1 deletion met-web/src/components/common/Typography/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { Header1, Header2 } from './Headers';
export { Header1, Header2, Header3 } from './Headers';
export { BodyText, EyebrowText } from './Body';
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const engagementCreateAction: ActionFunction = async ({ request }) => {
formData.getAll('users').forEach((user_id) => {
addTeamMemberToEngagement({ user_id: user_id.toString(), engagement_id: engagement.id });
});
return redirect(`/engagements/${engagement.id}/view`);
return redirect(`/engagements/${engagement.id}/details/config`);
};

export default engagementCreateAction;
Original file line number Diff line number Diff line change
@@ -1,190 +1,120 @@
import React, { useEffect, useState } from 'react';
import { MetPaper } from 'components/common';
import { Avatar, Grid, Skeleton, Divider } from '@mui/material';
import React, { Suspense } from 'react';
import { Grid, Skeleton, Paper, ThemeProvider, Box } from '@mui/material';
import { Widget } from 'models/widget';
import { useAppDispatch } from 'hooks';
import { openNotification } from 'services/notificationService/notificationSlice';
import { TimelineWidget, TimelineEvent } from 'models/timelineWidget';
import { TimelineWidget, TimelineEvent as TimelineEventType, EventStatus } from 'models/timelineWidget';
import { fetchTimelineWidgets } from 'services/widgetService/TimelineService';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCircleCheck } from '@fortawesome/pro-solid-svg-icons/faCircleCheck';
import { faCircleHalf } from '@fortawesome/pro-solid-svg-icons/faCircleHalf';
import { Palette } from 'styles/Theme';
import { EventStatus } from 'models/timelineWidget';
import { BodyText, Header2 } from 'components/common/Typography';

import { BaseTheme } from 'styles/Theme';
import { BodyText, Header3 } from 'components/common/Typography';
import { Await } from 'react-router-dom';
import { faCircle, faCircleHalf } from '@fortawesome/pro-solid-svg-icons';
interface TimelineWidgetProps {
widget: Widget;
}

const TimelineWidgetView = ({ widget }: TimelineWidgetProps) => {
const dispatch = useAppDispatch();
const [timelineWidget, setTimelineWidget] = useState<TimelineWidget>({
id: 0,
widget_id: 0,
engagement_id: 0,
title: '',
description: '',
events: [],
});
const [isLoading, setIsLoading] = useState(true);

const fetchTimeline = async () => {
try {
const timelines = await fetchTimelineWidgets(widget.id);
const timeline = timelines[timelines.length - 1];
timeline.events.sort((a, b) => a.position - b.position);
setTimelineWidget(timeline);
setIsLoading(false);
} catch (error) {
setIsLoading(false);
console.log(error);
dispatch(
openNotification({
severity: 'error',
text: 'Error occurred while fetching Engagement widgets information',
}),
);
}
};

useEffect(() => {
fetchTimeline();
}, [widget]);

const containerStylesObject = (index: number) => ({
minHeight: index + 1 === timelineWidget.events.length ? '60px' : '80px',
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
marginLeft: index + 1 === timelineWidget.events.length ? '18px' : '16px',
borderLeft: index + 1 === timelineWidget.events.length ? 'none' : '2px solid grey',
});
const timeline = fetchTimelineWidgets(widget.id);

const commonAvatarStyles = {
height: 30,
width: 30,
marginLeft: '-16px',
backgroundColor: Palette.info.main,
};

const commonWhiteAvatarStyles = {
height: 23,
width: 23,
backgroundColor: 'var(--bcds-surface-background-white)',
color: 'transparent',
};

const renderIcon = (status: EventStatus) => {
const icons: { [key in EventStatus]: JSX.Element } = {
[EventStatus.Pending]: (
<Avatar sx={commonAvatarStyles}>
<Avatar sx={commonWhiteAvatarStyles} />
</Avatar>
),
[EventStatus.InProgress]: (
<Avatar sx={commonAvatarStyles}>
<Avatar sx={commonWhiteAvatarStyles}>
<FontAwesomeIcon
icon={faCircleHalf}
rotation={90}
style={{
fontSize: '20px',
color: Palette.action.active,
stroke: Palette.action.active,
strokeWidth: 3,
}}
/>
</Avatar>
</Avatar>
),
[EventStatus.Completed]: (
<Avatar sx={commonAvatarStyles}>
<Avatar sx={commonWhiteAvatarStyles}>
<FontAwesomeIcon
icon={faCircleCheck}
style={{
fontSize: '20px',
color: Palette.action.active,
stroke: Palette.action.active,
strokeWidth: 3,
}}
/>
</Avatar>
</Avatar>
),
};
return (
<Suspense fallback={<Skeleton variant="rectangular" height={200} />}>
<Await resolve={timeline}>
{(timelineWidgets: TimelineWidget[]) => {
const timelineWidget = timelineWidgets[0];
return (
<Grid container gap="1rem">
<Grid item xs={12} mt="4rem">
<Header3 sx={{ fontSize: '1.375rem' }} weight="thin">
{timelineWidget.title}
</Header3>
</Grid>
<Grid item xs={12}>
<BodyText>{timelineWidget.description}</BodyText>
</Grid>
<Grid
item
xs={12}
component={Paper}
sx={{
mt: '1.5rem',
bgcolor: 'white',
padding: '2em',
borderRadius: '16px',
border: '1px solid',
borderColor: 'blue.90',
height: 'fit-content',
}}
>
<ThemeProvider theme={BaseTheme}>
{timelineWidget.events.map((event, index) => (
<Grid container item xs={12} key={event.id} direction="row">
<TimelineEvent
event={event}
isLast={index === timelineWidget.events.length - 1}
/>
</Grid>
))}
</ThemeProvider>
</Grid>
</Grid>
);
}}
</Await>
</Suspense>
);
};

return icons[status] || null;
};
export default TimelineWidgetView;

const handleRenderTimelineEvent = (tEvent: TimelineEvent, index: number) => {
return (
<Grid container item xs={12} sx={containerStylesObject(index)} key={'event' + (index + 1)}>
<Grid item fontWeight="bold" xs={0.5}>
{renderIcon(tEvent.status)}
</Grid>
<Grid item xs={11} sx={{ paddingLeft: '10px' }}>
<BodyText size="large" thin>
{tEvent.description}
</BodyText>
<BodyText
style={{
paddingBottom: index + 1 === timelineWidget.events.length ? '0' : '20px',
const TimelineEvent = ({ event, isLast }: { event: TimelineEventType; isLast: boolean }) => {
return (
<Grid container direction="row" gap={2} alignItems="stretch" justifyContent="flex-start">
{/* Left side with icon and line */}
<Grid item container alignItems="stretch" direction="column" sx={{ width: '3rem' }}>
{/* Event Icon */}
<Grid item>
<Paper
sx={{
height: '1.5em',
width: '1.5em',
borderRadius: '50%',
border: '1px solid',
borderColor: 'blue.90',
fontSize: '32px',
padding: '8px',
alignItems: 'center',
justifyContent: 'center',
display: 'flex',
color: 'blue.90',
}}
>
{tEvent.time}
</BodyText>
{event.status === EventStatus.Completed && <FontAwesomeIcon icon={faCircle} />}
{event.status === EventStatus.InProgress && (
<FontAwesomeIcon rotation={270} icon={faCircleHalf} />
)}
</Paper>
</Grid>
</Grid>
);
};

if (isLoading) {
return (
<MetPaper elevation={1} sx={{ padding: '1em' }}>
<Grid container justifyContent="flex-start" spacing={3}>
<Grid item xs={12}>
<Header2>
<Skeleton variant="rectangular" />
</Header2>
</Grid>
<Grid item xs={12}>
<Skeleton variant="rectangular" height="20em" />
{/* Dividing line */}
{!isLast && (
<Grid item xs alignItems="center" direction="column">
<Box
sx={{
height: 'calc(100% + 2em)',
width: 'calc(50% + 2px)',
borderRight: '4px solid',
borderColor: 'blue.90',
}}
/>
</Grid>
</Grid>
</MetPaper>
);
}

if (!timelineWidget) {
return null;
}
)}
</Grid>

return (
<MetPaper elevation={1} sx={{ paddingTop: '0.5em', padding: '1em', width: '100%' }}>
<Grid container justifyContent={{ xs: 'center' }} alignItems="center" rowSpacing={2}>
<Grid
item
container
justifyContent={{ xs: 'center', md: 'flex-start' }}
flexDirection={'column'}
xs={12}
paddingBottom={0}
>
<Header2>{timelineWidget.title}</Header2>
<Divider sx={{ borderWidth: 1 }} />
</Grid>
<Grid item xs={12}>
<BodyText>{timelineWidget.description}</BodyText>
</Grid>
<Grid item xs={12}>
{timelineWidget &&
timelineWidget.events.map((tEvent, index) => handleRenderTimelineEvent(tEvent, index))}
</Grid>
{/* Right side with event content */}
<Grid item xs pb={isLast ? '0' : '2em'} minHeight={isLast ? '0' : '6em'}>
<BodyText size="large" bold>
{event.description} ({['Pending', 'In Progress', 'Completed'][event.status - 1]})
</BodyText>
<BodyText>{event.time}</BodyText>
</Grid>
</MetPaper>
</Grid>
);
};

export default TimelineWidgetView;
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const EngagementDescription = () => {
<Await resolve={engagement}>
{(engagement: Engagement) => (
<>
<Header2 decorated id="description-header">
<Header2 decorated id="description-header" sx={{ mb: 1 }}>
{engagement.description_title}
</Header2>
<BodyText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,15 @@ export const EngagementSurveyBlock = () => {
// No point in rendering if there is no status block or 2nd widget
if (!statusBlock?.block_text && !widget) return null;
return (
<Grid container justifyContent="space-between" sx={gridContainerStyles}>
<Grid
component="section"
id={EngagementViewSections.PROVIDE_FEEDBACK}
container
justifyContent="space-between"
sx={gridContainerStyles}
aria-label="Survey Section"
>
<Grid
component={'section'}
id={EngagementViewSections.PROVIDE_FEEDBACK}
aria-label="Survey Section"
item
sx={{
width: { xs: '100%', md: '47.5%' },
Expand Down Expand Up @@ -166,9 +170,7 @@ export const EngagementSurveyBlock = () => {
marginBottom: '48px',
}}
>
<ThemeProvider theme={BaseTheme}>
{widget && <WidgetSwitch widget={widget} />}
</ThemeProvider>
{widget && <WidgetSwitch widget={widget} />}
</Grid>
</Grid>
);
Expand Down
Loading

0 comments on commit 0b61f80

Please sign in to comment.