Skip to content

Commit

Permalink
Basic log styling (#4634)
Browse files Browse the repository at this point in the history
* basic log styling

* fixed mobile wrap and changed default event icon

* add group by test
  • Loading branch information
brendanlaschke authored Mar 25, 2024
1 parent 0a15994 commit 922d632
Show file tree
Hide file tree
Showing 10 changed files with 504 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';

import { EventRow } from '@/activities/events/components/EventRow';
import { EventsGroup } from '@/activities/events/components/EventsGroup';
import { Event } from '@/activities/events/types/Event';
import { groupEventsByMonth } from '@/activities/events/utils/groupEventsByMonth';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';

type EventListProps = {
targetableObject: ActivityTargetableObject;
Expand All @@ -11,12 +14,43 @@ type EventListProps = {
button?: ReactElement | false;
};

export const EventList = ({ events }: EventListProps) => {
const StyledTimelineContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
flex: 1 0 0;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: flex-start;
padding: ${({ theme }) => theme.spacing(4)};
width: calc(100% - ${({ theme }) => theme.spacing(8)});
`;

export const EventList = ({ events, targetableObject }: EventListProps) => {
const groupedEvents = groupEventsByMonth(events);

return (
<>
{events &&
events.length > 0 &&
events.map((event: Event) => <EventRow key={event.id} event={event} />)}
</>
<ScrollWrapper>
<StyledTimelineContainer>
{groupedEvents.map((group, index) => (
<EventsGroup
targetableObject={targetableObject}
key={group.year.toString() + group.month}
group={group}
month={new Date(group.items[0].createdAt).toLocaleString(
'default',
{ month: 'long' },
)}
year={
index === 0 || group.year !== groupedEvents[index - 1].year
? group.year
: undefined
}
/>
))}
</StyledTimelineContainer>
</ScrollWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,203 @@
import { Tooltip } from 'react-tooltip';
import styled from '@emotion/styled';

import { EventUpdateProperty } from '@/activities/events/components/EventUpdateProperty';
import { Event } from '@/activities/events/types/Event';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import {
IconCirclePlus,
IconEditCircle,
IconFocusCentered,
} from '@/ui/display/icon';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import {
beautifyExactDateTime,
beautifyPastDateRelativeToNow,
} from '~/utils/date-utils';

const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
user-select: none;
height: 16px;
margin: 5px;
justify-content: center;
text-decoration-line: underline;
width: 16px;
z-index: 2;
`;

const StyledActionName = styled.span`
overflow: hidden;
flex: none;
white-space: nowrap;
`;

const StyledItemContainer = styled.div`
align-content: center;
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(1)};
span {
color: ${({ theme }) => theme.font.color.secondary};
}
overflow: hidden;
`;

const StyledItemTitleContainer = styled.div`
display: flex;
flex: 1;
flex-flow: row ${() => (useIsMobile() ? 'wrap' : 'nowrap')};
gap: ${({ theme }) => theme.spacing(1)};
overflow: hidden;
`;

const StyledItemAuthorText = styled.span`
display: flex;
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(1)};
white-space: nowrap;
`;

const StyledItemTitle = styled.span`
display: flex;
flex-flow: row nowrap;
overflow: hidden;
white-space: nowrap;
`;

const StyledItemTitleDate = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: flex-end;
margin-left: auto;
`;

const StyledVerticalLineContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: center;
width: 26px;
z-index: 2;
`;

const StyledVerticalLine = styled.div`
align-self: stretch;
background: ${({ theme }) => theme.border.color.light};
flex-shrink: 0;
width: 2px;
`;

const StyledTooltip = styled(Tooltip)`
background-color: ${({ theme }) => theme.background.primary};
box-shadow: 0px 2px 4px 3px
${({ theme }) => theme.background.transparent.light};
box-shadow: 2px 4px 16px 6px
${({ theme }) => theme.background.transparent.light};
color: ${({ theme }) => theme.font.color.primary};
opacity: 1;
padding: ${({ theme }) => theme.spacing(2)};
`;

const StyledTimelineItemContainer = styled.div<{ isGap?: boolean }>`
align-items: center;
align-self: stretch;
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
height: ${({ isGap, theme }) =>
isGap ? (useIsMobile() ? theme.spacing(6) : theme.spacing(3)) : 'auto'};
overflow: hidden;
white-space: nowrap;
`;

type EventRowProps = {
targetableObject: ActivityTargetableObject;
isLastEvent?: boolean;
event: Event;
};

export const EventRow = ({
isLastEvent,
event,
targetableObject,
}: EventRowProps) => {
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt);
const exactCreatedAt = beautifyExactDateTime(event.createdAt);

const properties = JSON.parse(event.properties);
const diff: Record<string, { before: any; after: any }> = properties?.diff;

const isEventType = (type: 'created' | 'updated') => {
return (
event.name === type + '.' + targetableObject.targetObjectNameSingular
);
};

export const EventRow = ({ event }: { event: Event }) => {
return (
<>
<p>
{event.name}:<pre>{event.properties}</pre>
</p>
<StyledTimelineItemContainer>
<StyledIconContainer>
{isEventType('created') && <IconCirclePlus />}
{isEventType('updated') && <IconEditCircle />}
{!isEventType('created') && !isEventType('updated') && (
<IconFocusCentered />
)}
</StyledIconContainer>
<StyledItemContainer>
<StyledItemTitleContainer>
<StyledItemAuthorText>
{event.workspaceMember?.name.firstName}{' '}
{event.workspaceMember?.name.lastName}
</StyledItemAuthorText>
<StyledActionName>
{isEventType('created') && 'created'}
{isEventType('updated') && 'updated'}
{!isEventType('created') && !isEventType('updated') && event.name}
</StyledActionName>
<StyledItemTitle>
{isEventType('created') &&
`a new ${targetableObject.targetObjectNameSingular}`}
{isEventType('updated') &&
Object.entries(diff).map(([key, value]) => (
<EventUpdateProperty
propertyName={key}
after={value?.after}
></EventUpdateProperty>
))}
{!isEventType('created') &&
!isEventType('updated') &&
JSON.stringify(diff)}
</StyledItemTitle>
</StyledItemTitleContainer>
<StyledItemTitleDate id={`id-${event.id}`}>
{beautifiedCreatedAt}
</StyledItemTitleDate>
<StyledTooltip
anchorSelect={`#id-${event.id}`}
content={exactCreatedAt}
clickable
noArrow
/>
</StyledItemContainer>
</StyledTimelineItemContainer>
{!isLastEvent && (
<StyledTimelineItemContainer isGap>
<StyledVerticalLineContainer>
<StyledVerticalLine></StyledVerticalLine>
</StyledVerticalLineContainer>
</StyledTimelineItemContainer>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';

import { IconArrowRight } from '@/ui/display/icon';

type EventUpdatePropertyProps = {
propertyName: string;
after?: string;
};

const StyledContainer = styled.div`
display: flex;
margin-right: ${({ theme }) => theme.spacing(1)};
gap: ${({ theme }) => theme.spacing(1)};
white-space: nowrap;
`;

const StyledPropertyName = styled.div``;

export const EventUpdateProperty = ({
propertyName,
after,
}: EventUpdatePropertyProps) => {
const theme = useTheme();
return (
<StyledContainer>
<StyledPropertyName>{propertyName}</StyledPropertyName>
<IconArrowRight size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
{after}
</StyledContainer>
);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards';

import { EventList } from '@/activities/events/components/EventList';
import { useEvents } from '@/activities/events/hooks/useEvents';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';

const StyledMainContainer = styled.div`
align-items: flex-start;
align-self: stretch;
border-top: ${({ theme }) =>
useIsMobile() ? `1px solid ${theme.border.color.medium}` : 'none'};
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
`;

export const Events = ({
targetableObject,
Expand All @@ -12,14 +33,28 @@ export const Events = ({
const { events } = useEvents(targetableObject);

if (!isNonEmptyArray(events)) {
return <div>No log yet</div>;
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholder type="emptyTimeline" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
No Events
</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
There are no events associated with this record.{' '}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
</AnimatedPlaceholderEmptyContainer>
);
}

return (
<EventList
targetableObject={targetableObject}
title="All"
events={events ?? []}
/>
<StyledMainContainer>
<EventList
targetableObject={targetableObject}
title="All"
events={events ?? []}
/>
</StyledMainContainer>
);
};
Loading

0 comments on commit 922d632

Please sign in to comment.