Skip to content

Commit

Permalink
Project Workflow Events List (#1573)
Browse files Browse the repository at this point in the history
Co-authored-by: Carson Full <[email protected]>
  • Loading branch information
andrewmurraydavid and CarsonF authored Sep 26, 2024
1 parent 83de79f commit 703e015
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 1 deletion.
11 changes: 11 additions & 0 deletions src/components/TextChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Chip } from '@mui/material';
import { ChildrenProp, extendSx, StyleProps } from '~/common';

export const TextChip = (props: ChildrenProp & StyleProps) => {
return (
<Chip
label={props.children}
sx={[{ borderRadius: 1 }, ...extendSx(props.sx)]}
/>
);
};
6 changes: 5 additions & 1 deletion src/scenes/Projects/Overview/ProjectOverview.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ fragment ProjectOverview on Project {
...ProjectChangeRequestSummary
}

# We're including this in order to update the engagament status
workflowEvents {
...projectWorkflowEvent
}

# We're including this in order to update the engagement status
# when the project step changes.
engagements {
items {
Expand Down
14 changes: 14 additions & 0 deletions src/scenes/Projects/Overview/ProjectOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { ProjectListQueryVariables } from '../List/ProjectList.graphql';
import { EditableProjectField, UpdateProjectDialog } from '../Update';
import { ProjectWorkflowDialog } from '../Update/ProjectWorkflowDialog';
import { useProjectId } from '../useProjectId';
import { WorkflowEventsDrawer, WorkflowEventsIcon } from '../WorkflowEvents';
import {
ProjectEngagementListOverviewDocument as EngagementList,
ProjectOverviewDocument,
Expand Down Expand Up @@ -117,6 +118,7 @@ export const ProjectOverview = () => {
const { projectId, changesetId } = useProjectId();
const beta = useBetaFeatures();
const formatNumber = useNumberFormatter();
const [workflowDrawerState, openWorkflowEvents] = useDialog();

const [editState, editField, fieldsBeingEdited] =
useDialog<Many<EditableProjectField>>();
Expand Down Expand Up @@ -243,6 +245,12 @@ export const ProjectOverview = () => {
}
/>
{project && <DeleteProject project={project} />}
{project && (
<WorkflowEventsIcon
onClick={openWorkflowEvents}
loading={!project}
/>
)}
</header>

<div className={classes.subheader}>
Expand Down Expand Up @@ -573,6 +581,12 @@ export const ProjectOverview = () => {
{project && (
<CreateEngagement project={project} {...createEngagementState} />
)}
{project && (
<WorkflowEventsDrawer
{...workflowDrawerState}
events={project.workflowEvents}
/>
)}
</main>
);
};
92 changes: 92 additions & 0 deletions src/scenes/Projects/WorkflowEvents/WorkflowEventsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Close } from '@mui/icons-material';
import { Box, Divider, Drawer, DrawerProps, Typography } from '@mui/material';
import { useSize } from 'ahooks';
import { useEffect, useRef, useState } from 'react';
import { extendSx } from '~/common';
import { IconButton } from '~/components/IconButton';
import { ProjectWorkflowEventFragment as WorkflowEvent } from './projectWorkflowEvent.graphql';
import { WorkflowEventsList } from './WorkflowEventsList';

type WorkflowEventsDrawerProps = DrawerProps & {
TransitionProps?: DrawerProps['SlideProps'];
events: readonly WorkflowEvent[];
};
export const WorkflowEventsDrawer = ({
events,
TransitionProps,
...props
}: WorkflowEventsDrawerProps) => {
const listRef = useRef();
const listSize = useSize(listRef);
const windowSize = useSize(() => document.querySelector('body'));
const [needsWidthCalc, setNeedsWidthCalc] = useState(false);

const isFullWidth =
!!windowSize?.width &&
!!listSize?.width &&
windowSize.width <= listSize.width;

useEffect(() => setNeedsWidthCalc(true), [events]);
useEffect(() => {
listSize?.width && setNeedsWidthCalc(false);
}, [listSize?.width]);

return (
<>
<Drawer
SlideProps={TransitionProps} // normalize with Dialog
{...props}
anchor="right"
sx={[
{
'.MuiPaper-root': {
p: 2,
maxWidth: '100vw',
// Even if the smaller "full width" event list layout
// is narrower than the larger desktop layout, keep the drawer
// full width so the width doesn't snap around.
width: isFullWidth ? '100vw' : undefined,
},
},
...extendSx(props.sx),
]}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Typography variant="h3">Status History Log</Typography>
<IconButton onClick={(e) => props.onClose?.(e, 'backdropClick')}>
<Close />
</IconButton>
</Box>
<Divider sx={{ pt: 1 }} />

{/* Actual list shown in UI */}
<WorkflowEventsList events={events} fullWidth={isFullWidth} />
</Drawer>

{/*
Hidden list to track intrinsic size to calculate full width.
Outside the drawer so it is in DOM when needed regardless of drawer state.
*/}
{needsWidthCalc && (
<WorkflowEventsList
events={events}
ref={listRef}
sx={{
p: 2, // matches above
visibility: 'hidden',
position: 'absolute',
top: 0,
right: 0,
width: 'fit-content',
}}
/>
)}
</>
);
};
14 changes: 14 additions & 0 deletions src/scenes/Projects/WorkflowEvents/WorkflowEventsIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { History as HistoryIcon } from '@mui/icons-material';
import { Tooltip } from '@mui/material';
import { IconButton, IconButtonProps } from '~/components/IconButton';
import { Feature } from '../../../components/Feature';

export const WorkflowEventsIcon = (props: IconButtonProps) => (
<Feature flag="project-workflow-history" match={true}>
<Tooltip title="View Status History Log">
<IconButton {...props}>
<HistoryIcon />
</IconButton>
</Tooltip>
</Feature>
);
110 changes: 110 additions & 0 deletions src/scenes/Projects/WorkflowEvents/WorkflowEventsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ChevronRight } from '@mui/icons-material';
import { Box, Divider, List, ListProps, Typography } from '@mui/material';
import { forwardRef } from 'react';
import { ProjectStepLabels, ProjectStepList } from '~/api/schema/enumLists';
import { extendSx } from '~/common';
import { RelativeDateTime } from '~/components/Formatters';
import { Link } from '~/components/Routing';
import { TextChip } from '~/components/TextChip';
import { ProjectWorkflowEventFragment as WorkflowEvent } from './projectWorkflowEvent.graphql';

type WorkflowEventsListProps = {
events: readonly WorkflowEvent[];
fullWidth?: boolean;
} & ListProps;

export const WorkflowEventsList = forwardRef<any, WorkflowEventsListProps>(
function WorkflowEventsList({ events, fullWidth = false, ...props }, ref) {
return (
<List
{...props}
ref={ref}
sx={[
{
display: 'flex',
flexDirection: 'column',
},
!fullWidth && {
display: 'grid',
rowGap: 1,
columnGap: 2,
alignItems: 'center',
gridTemplateColumns:
'[at] min-content [from] minmax(min-content, 1fr) [arrow] min-content [to] minmax(min-content, 1fr)',
},
...extendSx(props.sx),
]}
>
{events.toReversed().map((event, index, array) => {
const prev = index >= 1 ? array[index - 1] : null;
const prevStatus = prev ? prev.to : ProjectStepList[0]!;

return (
<Box
key={event.id}
sx={[
{
display: 'contents',
},
fullWidth &&
((theme) => ({
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: 1,
padding: 1,
borderBottom: `thin solid ${theme.palette.divider}`,
})),
]}
>
<Box
sx={{
gridColumn: 'at',
// add a bit more row padding between the two
mr: fullWidth ? 1 : 0,
}}
>
{event.who.value?.__typename === 'User' && (
<Link to={`/users/${event.who.value.id}`} color="inherit">
{event.who.value.fullName}
</Link>
)}
<Typography variant="subtitle2" color="text.secondary" noWrap>
<RelativeDateTime date={event.at} />
</Typography>
</Box>
<Box
sx={[
{ display: 'contents' },
fullWidth && {
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: 1,
},
]}
>
<TextChip sx={{ gridColumn: 'from' }}>
{ProjectStepLabels[prevStatus]}
</TextChip>
<ChevronRight
sx={{ gridColumn: 'arrow', flexGrow: 0 }}
aria-label="transitioned to"
/>
<TextChip sx={{ gridColumn: 'to' }}>
{ProjectStepLabels[event.to]}
</TextChip>
</Box>
<Divider
sx={{
gridColumn: '1/-1',
display: fullWidth ? 'none' : undefined,
}}
/>
</Box>
);
})}
</List>
);
}
);
3 changes: 3 additions & 0 deletions src/scenes/Projects/WorkflowEvents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './WorkflowEventsDrawer';
export * from './WorkflowEventsList';
export * from './WorkflowEventsIcon';
16 changes: 16 additions & 0 deletions src/scenes/Projects/WorkflowEvents/projectWorkflowEvent.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fragment projectWorkflowEvent on ProjectWorkflowEvent {
id
at
to
who {
value {
... on User {
id
fullName
}
}
}
transition {
to
}
}

0 comments on commit 703e015

Please sign in to comment.