feat: open event details drawer on event row click (#4464)

* feat: open event details drawer on event row click

Closes #4294

* feat: review - display Calendar Event details Inline Cells in readonly mode

* fix: fix calendar event field values not being set

* chore: review - reactivate no-extra-boolean-cast eslint rule
This commit is contained in:
Thaïs 2024-03-15 13:37:36 -03:00 committed by GitHub
parent 680bb11f19
commit 38f28de4a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 530 additions and 231 deletions

View File

@ -4,10 +4,11 @@ import { format, getYear } from 'date-fns';
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard'; import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents'; import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents';
import { sortCalendarEventsDesc } from '@/activities/calendar/utils/sortCalendarEvents'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { H3Title } from '@/ui/display/typography/components/H3Title'; import { H3Title } from '@/ui/display/typography/components/H3Title';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { mockedCalendarEvents } from '~/testing/mock-data/calendar';
const StyledContainer = styled.div` const StyledContainer = styled.div`
box-sizing: border-box; box-sizing: border-box;
@ -23,9 +24,11 @@ const StyledYear = styled.span`
`; `;
export const Calendar = () => { export const Calendar = () => {
const sortedCalendarEvents = [...mockedCalendarEvents].sort( const { records: calendarEvents } = useFindManyRecords<CalendarEvent>({
sortCalendarEventsDesc, objectNameSingular: CoreObjectNameSingular.CalendarEvent,
); orderBy: { startsAt: 'DescNullsLast', endsAt: 'DescNullsLast' },
useRecordsWithoutConnection: true,
});
const { const {
calendarEventsByDayTime, calendarEventsByDayTime,
@ -35,7 +38,13 @@ export const Calendar = () => {
monthTimes, monthTimes,
monthTimesByYear, monthTimesByYear,
updateCurrentCalendarEvent, updateCurrentCalendarEvent,
} = useCalendarEvents(sortedCalendarEvents); } = useCalendarEvents(
calendarEvents.map((calendarEvent) => ({
...calendarEvent,
// TODO: retrieve CalendarChannel visibility from backend
visibility: 'SHARE_EVERYTHING',
})),
);
return ( return (
<CalendarContext.Provider <CalendarContext.Provider

View File

@ -12,6 +12,7 @@ import { AnimatePresence, motion } from 'framer-motion';
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate'; import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded'; import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
import { hasCalendarEventStarted } from '@/activities/calendar/utils/hasCalendarEventStarted'; import { hasCalendarEventStarted } from '@/activities/calendar/utils/hasCalendarEventStarted';
@ -52,12 +53,16 @@ export const CalendarCurrentEventCursor = ({
} = useContext(CalendarContext); } = useContext(CalendarContext);
const nextCalendarEvent = getNextCalendarEvent(calendarEvent); const nextCalendarEvent = getNextCalendarEvent(calendarEvent);
const nextCalendarEventStartsAt = nextCalendarEvent
? getCalendarEventStartDate(nextCalendarEvent)
: undefined;
const isNextEventThisMonth = const isNextEventThisMonth =
!!nextCalendarEvent && isThisMonth(nextCalendarEvent.startsAt); !!nextCalendarEventStartsAt && isThisMonth(nextCalendarEventStartsAt);
const calendarEventStartsAt = getCalendarEventStartDate(calendarEvent);
const calendarEventEndsAt = getCalendarEventEndDate(calendarEvent); const calendarEventEndsAt = getCalendarEventEndDate(calendarEvent);
const isCurrent = currentCalendarEvent.id === calendarEvent.id; const isCurrent = currentCalendarEvent?.id === calendarEvent.id;
const [hasStarted, setHasStarted] = useState( const [hasStarted, setHasStarted] = useState(
hasCalendarEventStarted(calendarEvent), hasCalendarEventStarted(calendarEvent),
); );
@ -66,7 +71,7 @@ export const CalendarCurrentEventCursor = ({
); );
const [isWaiting, setIsWaiting] = useState(hasEnded && !isNextEventThisMonth); const [isWaiting, setIsWaiting] = useState(hasEnded && !isNextEventThisMonth);
const dayTime = startOfDay(calendarEvent.startsAt).getTime(); const dayTime = startOfDay(calendarEventStartsAt).getTime();
const dayEvents = calendarEventsByDayTime[dayTime]; const dayEvents = calendarEventsByDayTime[dayTime];
const isFirstEventOfDay = dayEvents?.slice(-1)[0] === calendarEvent; const isFirstEventOfDay = dayEvents?.slice(-1)[0] === calendarEvent;
const isLastEventOfDay = dayEvents?.[0] === calendarEvent; const isLastEventOfDay = dayEvents?.[0] === calendarEvent;
@ -81,7 +86,7 @@ export const CalendarCurrentEventCursor = ({
transition: { transition: {
delay: Math.max( delay: Math.max(
0, 0,
differenceInSeconds(calendarEvent.startsAt, new Date()), differenceInSeconds(calendarEventStartsAt, new Date()),
), ),
}, },
}, },
@ -99,9 +104,9 @@ export const CalendarCurrentEventCursor = ({
top: `-${topOffset}px`, top: `-${topOffset}px`,
transition: { transition: {
delay: delay:
isWaiting && nextCalendarEvent isWaiting && nextCalendarEventStartsAt
? differenceInSeconds( ? differenceInSeconds(
startOfMonth(nextCalendarEvent.startsAt), startOfMonth(nextCalendarEventStartsAt),
new Date(), new Date(),
) )
: 0, : 0,

View File

@ -4,6 +4,7 @@ import { differenceInSeconds, endOfDay, format } from 'date-fns';
import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow'; import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { CardContent } from '@/ui/layout/card/components/CardContent'; import { CardContent } from '@/ui/layout/card/components/CardContent';
type CalendarDayCardContentProps = { type CalendarDayCardContentProps = {
@ -53,8 +54,8 @@ export const CalendarDayCardContent = ({
}: CalendarDayCardContentProps) => { }: CalendarDayCardContentProps) => {
const theme = useTheme(); const theme = useTheme();
const endOfDayDate = endOfDay(calendarEvents[0].startsAt); const endOfDayDate = endOfDay(getCalendarEventStartDate(calendarEvents[0]));
const endsIn = differenceInSeconds(endOfDayDate, Date.now()); const dayEndsIn = differenceInSeconds(endOfDayDate, Date.now());
const weekDayLabel = format(endOfDayDate, 'EE'); const weekDayLabel = format(endOfDayDate, 'EE');
const monthDayLabel = format(endOfDayDate, 'dd'); const monthDayLabel = format(endOfDayDate, 'dd');
@ -71,7 +72,7 @@ export const CalendarDayCardContent = ({
animate="ended" animate="ended"
variants={upcomingDayCardContentVariants} variants={upcomingDayCardContentVariants}
transition={{ transition={{
delay: Math.max(0, endsIn), delay: Math.max(0, dayEndsIn),
duration: theme.animation.duration.fast, duration: theme.animation.duration.fast,
}} }}
> >

View File

@ -0,0 +1,146 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import {
Chip,
ChipAccent,
ChipSize,
ChipVariant,
} from '@/ui/display/chip/components/Chip';
import { IconCalendarEvent } from '@/ui/display/icon';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
type CalendarEventDetailsProps = {
calendarEvent: CalendarEvent;
};
const StyledContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
align-items: flex-start;
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(6)};
padding: ${({ theme }) => theme.spacing(6)};
`;
const StyledEventChip = styled(Chip)`
gap: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledHeader = styled.header``;
const StyledTitle = styled.h2<{ canceled?: boolean }>`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: ${({ theme }) => theme.spacing(0, 0, 2)};
${({ canceled }) =>
canceled &&
css`
text-decoration: line-through;
`}
`;
const StyledCreatedAt = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledFields = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
`;
const StyledPropertyBox = styled(PropertyBox)`
height: ${({ theme }) => theme.spacing(6)};
padding: 0;
`;
export const CalendarEventDetails = ({
calendarEvent,
}: CalendarEventDetailsProps) => {
const theme = useTheme();
const { objectMetadataItem } = useObjectMetadataItemOnly({
objectNameSingular: CoreObjectNameSingular.CalendarEvent,
});
const fieldsToDisplay: Partial<
Record<
keyof CalendarEvent,
Partial<Pick<FieldDefinition<FieldMetadata>, 'label'>>
>
> = {
startsAt: { label: 'Start Date' },
endsAt: { label: 'End Date' },
conferenceUri: { label: 'Meet link' },
location: {},
description: {},
};
const fieldsByName = mapArrayToObject(
objectMetadataItem.fields,
({ name }) => name,
);
return (
<StyledContainer>
<StyledEventChip
accent={ChipAccent.TextSecondary}
size={ChipSize.Large}
variant={ChipVariant.Highlighted}
clickable={false}
leftComponent={<IconCalendarEvent size={theme.icon.size.md} />}
label="Event"
/>
<StyledHeader>
<StyledTitle canceled={calendarEvent.isCanceled}>
{calendarEvent.title}
</StyledTitle>
<StyledCreatedAt>
Created{' '}
{beautifyPastDateRelativeToNow(
new Date(calendarEvent.externalCreatedAt),
)}
</StyledCreatedAt>
</StyledHeader>
<StyledFields>
{Object.entries(fieldsToDisplay).map(([fieldName, fieldOverride]) => (
<StyledPropertyBox key={fieldName}>
<FieldContext.Provider
value={{
entityId: calendarEvent.id,
hotkeyScope: 'calendar-event-details',
recoilScopeId: `${calendarEvent.id}-${fieldName}`,
isLabelIdentifier: false,
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
field: {
...fieldsByName[fieldName],
...fieldOverride,
},
objectMetadataItem,
showLabel: true,
labelWidth: 72,
}),
useUpdateRecord: () => [() => undefined, { loading: false }],
}}
>
<RecordInlineCell readonly />
</FieldContext.Provider>
</StyledPropertyBox>
))}
</StyledFields>
</StyledContainer>
);
};

View File

@ -6,8 +6,10 @@ import { useRecoilValue } from 'recoil';
import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor'; import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor';
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate'; import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded'; import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { IconArrowRight, IconLock } from '@/ui/display/icon'; import { IconArrowRight, IconLock } from '@/ui/display/icon';
@ -22,12 +24,13 @@ type CalendarEventRowProps = {
className?: string; className?: string;
}; };
const StyledContainer = styled.div` const StyledContainer = styled.div<{ showTitle?: boolean }>`
align-items: center; align-items: center;
display: inline-flex; display: inline-flex;
gap: ${({ theme }) => theme.spacing(3)}; gap: ${({ theme }) => theme.spacing(3)};
height: ${({ theme }) => theme.spacing(6)}; height: ${({ theme }) => theme.spacing(6)};
position: relative; position: relative;
cursor: ${({ showTitle }) => (showTitle ? 'pointer' : 'not-allowed')};
`; `;
const StyledAttendanceIndicator = styled.div<{ active?: boolean }>` const StyledAttendanceIndicator = styled.div<{ active?: boolean }>`
@ -101,21 +104,32 @@ export const CalendarEventRow = ({
const theme = useTheme(); const theme = useTheme();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState()); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState());
const { displayCurrentEventCursor = false } = useContext(CalendarContext); const { displayCurrentEventCursor = false } = useContext(CalendarContext);
const { openCalendarEventRightDrawer } = useOpenCalendarEventRightDrawer();
const startsAt = getCalendarEventStartDate(calendarEvent);
const endsAt = getCalendarEventEndDate(calendarEvent); const endsAt = getCalendarEventEndDate(calendarEvent);
const hasEnded = hasCalendarEventEnded(calendarEvent); const hasEnded = hasCalendarEventEnded(calendarEvent);
const startTimeLabel = calendarEvent.isFullDay const startTimeLabel = calendarEvent.isFullDay
? 'All day' ? 'All day'
: format(calendarEvent.startsAt, 'HH:mm'); : format(startsAt, 'HH:mm');
const endTimeLabel = calendarEvent.isFullDay ? '' : format(endsAt, 'HH:mm'); const endTimeLabel = calendarEvent.isFullDay ? '' : format(endsAt, 'HH:mm');
const isCurrentWorkspaceMemberAttending = !!calendarEvent.attendees?.find( const isCurrentWorkspaceMemberAttending = calendarEvent.attendees?.some(
({ workspaceMemberId }) => workspaceMemberId === currentWorkspaceMember?.id, ({ workspaceMemberId }) => workspaceMemberId === currentWorkspaceMember?.id,
); );
const showTitle = calendarEvent.visibility === 'SHARE_EVERYTHING';
return ( return (
<StyledContainer className={className}> <StyledContainer
className={className}
showTitle={showTitle}
onClick={
showTitle
? () => openCalendarEventRightDrawer(calendarEvent.id)
: undefined
}
>
<StyledAttendanceIndicator active={isCurrentWorkspaceMemberAttending} /> <StyledAttendanceIndicator active={isCurrentWorkspaceMemberAttending} />
<StyledLabels> <StyledLabels>
<StyledTime> <StyledTime>
@ -127,17 +141,17 @@ export const CalendarEventRow = ({
</> </>
)} )}
</StyledTime> </StyledTime>
{calendarEvent.visibility === 'METADATA' ? ( {showTitle ? (
<StyledTitle active={!hasEnded} canceled={!!calendarEvent.isCanceled}>
{calendarEvent.title}
</StyledTitle>
) : (
<StyledVisibilityCard active={!hasEnded}> <StyledVisibilityCard active={!hasEnded}>
<StyledVisibilityCardContent> <StyledVisibilityCardContent>
<IconLock size={theme.icon.size.sm} /> <IconLock size={theme.icon.size.sm} />
Not shared Not shared
</StyledVisibilityCardContent> </StyledVisibilityCardContent>
</StyledVisibilityCard> </StyledVisibilityCard>
) : (
<StyledTitle active={!hasEnded} canceled={!!calendarEvent.isCanceled}>
{calendarEvent.title}
</StyledTitle>
)} )}
</StyledLabels> </StyledLabels>
{!!calendarEvent.attendees?.length && ( {!!calendarEvent.attendees?.length && (

View File

@ -4,7 +4,7 @@ import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
type CalendarContextValue = { type CalendarContextValue = {
calendarEventsByDayTime: Record<number, CalendarEvent[] | undefined>; calendarEventsByDayTime: Record<number, CalendarEvent[] | undefined>;
currentCalendarEvent: CalendarEvent; currentCalendarEvent?: CalendarEvent;
displayCurrentEventCursor?: boolean; displayCurrentEventCursor?: boolean;
getNextCalendarEvent: ( getNextCalendarEvent: (
calendarEvent: CalendarEvent, calendarEvent: CalendarEvent,
@ -14,7 +14,6 @@ type CalendarContextValue = {
export const CalendarContext = createContext<CalendarContextValue>({ export const CalendarContext = createContext<CalendarContextValue>({
calendarEventsByDayTime: {}, calendarEventsByDayTime: {},
currentCalendarEvent: {} as CalendarEvent,
getNextCalendarEvent: () => undefined, getNextCalendarEvent: () => undefined,
updateCurrentCalendarEvent: () => {}, updateCurrentCalendarEvent: () => {},
}); });

View File

@ -3,6 +3,7 @@ import { getYear, isThisMonth, startOfDay, startOfMonth } from 'date-fns';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { findUpcomingCalendarEvent } from '@/activities/calendar/utils/findUpcomingCalendarEvent'; import { findUpcomingCalendarEvent } from '@/activities/calendar/utils/findUpcomingCalendarEvent';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy'; import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { sortDesc } from '~/utils/sort'; import { sortDesc } from '~/utils/sort';
@ -10,7 +11,8 @@ import { sortDesc } from '~/utils/sort';
export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => { export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => {
const calendarEventsByDayTime = groupArrayItemsBy( const calendarEventsByDayTime = groupArrayItemsBy(
calendarEvents, calendarEvents,
({ startsAt }) => startOfDay(startsAt).getTime(), (calendarEvent) =>
startOfDay(getCalendarEventStartDate(calendarEvent)).getTime(),
); );
const sortedDayTimes = Object.keys(calendarEventsByDayTime) const sortedDayTimes = Object.keys(calendarEventsByDayTime)
@ -45,17 +47,21 @@ export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => {
() => findUpcomingCalendarEvent(calendarEvents), () => findUpcomingCalendarEvent(calendarEvents),
[calendarEvents], [calendarEvents],
); );
const lastEventInCalendar = calendarEvents[0]; const lastEventInCalendar = calendarEvents.length
? calendarEvents[0]
: undefined;
const [currentCalendarEvent, setCurrentCalendarEvent] = useState( const [currentCalendarEvent, setCurrentCalendarEvent] = useState(
(initialUpcomingCalendarEvent && (initialUpcomingCalendarEvent &&
(isThisMonth(initialUpcomingCalendarEvent.startsAt) (isThisMonth(getCalendarEventStartDate(initialUpcomingCalendarEvent))
? initialUpcomingCalendarEvent ? initialUpcomingCalendarEvent
: getPreviousCalendarEvent(initialUpcomingCalendarEvent))) || : getPreviousCalendarEvent(initialUpcomingCalendarEvent))) ||
lastEventInCalendar, lastEventInCalendar,
); );
const updateCurrentCalendarEvent = () => { const updateCurrentCalendarEvent = () => {
if (!currentCalendarEvent) return;
const nextCurrentCalendarEvent = getNextCalendarEvent(currentCalendarEvent); const nextCurrentCalendarEvent = getNextCalendarEvent(currentCalendarEvent);
if (isDefined(nextCurrentCalendarEvent)) { if (isDefined(nextCurrentCalendarEvent)) {

View File

@ -0,0 +1,22 @@
import { useRecoilValue } from 'recoil';
import { CalendarEventDetails } from '@/activities/calendar/components/CalendarEventDetails';
import { viewableCalendarEventIdState } from '@/activities/calendar/states/viewableCalendarEventIdState';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore';
export const RightDrawerCalendarEvent = () => {
const { setRecords } = useSetRecordInStore();
const calendarEventId = useRecoilValue(viewableCalendarEventIdState());
const { record: calendarEvent } = useFindOneRecord<CalendarEvent>({
objectNameSingular: CoreObjectNameSingular.CalendarEvent,
objectRecordId: calendarEventId ?? '',
onCompleted: (record) => setRecords([record]),
});
if (!calendarEvent) return null;
return <CalendarEventDetails calendarEvent={calendarEvent} />;
};

View File

@ -0,0 +1,23 @@
import { useSetRecoilState } from 'recoil';
import { viewableCalendarEventIdState } from '@/activities/calendar/states/viewableCalendarEventIdState';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
export const useOpenCalendarEventRightDrawer = () => {
const { openRightDrawer } = useRightDrawer();
const setHotkeyScope = useSetHotkeyScope();
const setViewableCalendarEventId = useSetRecoilState(
viewableCalendarEventIdState(),
);
const openCalendarEventRightDrawer = (calendarEventId: string) => {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
openRightDrawer(RightDrawerPages.ViewCalendarEvent);
setViewableCalendarEventId(calendarEventId);
};
return { openCalendarEventRightDrawer };
};

View File

@ -0,0 +1,6 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const viewableCalendarEventIdState = createState<string | null>({
key: 'viewableCalendarEventIdState',
defaultValue: null,
});

View File

@ -1,10 +1,14 @@
// TODO: use backend CalendarEvent type when ready // TODO: use backend CalendarEvent type when ready
export type CalendarEvent = { export type CalendarEvent = {
endsAt?: Date; conferenceUri?: string;
description?: string;
endsAt?: string;
externalCreatedAt: string;
id: string; id: string;
isFullDay: boolean;
startsAt: Date;
isCanceled?: boolean; isCanceled?: boolean;
isFullDay: boolean;
location?: string;
startsAt: string;
title?: string; title?: string;
visibility: 'METADATA' | 'SHARE_EVERYTHING'; visibility: 'METADATA' | 'SHARE_EVERYTHING';
attendees?: { attendees?: {

View File

@ -1,7 +1,11 @@
import { endOfDay } from 'date-fns'; import { endOfDay } from 'date-fns';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
export const getCalendarEventEndDate = ( export const getCalendarEventEndDate = (
calendarEvent: Pick<CalendarEvent, 'endsAt' | 'isFullDay' | 'startsAt'>, calendarEvent: Pick<CalendarEvent, 'endsAt' | 'isFullDay' | 'startsAt'>,
) => calendarEvent.endsAt ?? endOfDay(calendarEvent.startsAt); ) =>
calendarEvent.endsAt
? new Date(calendarEvent.endsAt)
: endOfDay(getCalendarEventStartDate(calendarEvent));

View File

@ -0,0 +1,5 @@
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
export const getCalendarEventStartDate = (
calendarEvent: Pick<CalendarEvent, 'startsAt'>,
) => new Date(calendarEvent.startsAt);

View File

@ -1,7 +1,8 @@
import { isPast } from 'date-fns'; import { isPast } from 'date-fns';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
export const hasCalendarEventStarted = ( export const hasCalendarEventStarted = (
calendarEvent: Pick<CalendarEvent, 'startsAt'>, calendarEvent: Pick<CalendarEvent, 'startsAt'>,
) => isPast(calendarEvent.startsAt); ) => isPast(getCalendarEventStartDate(calendarEvent));

View File

@ -1,5 +1,6 @@
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate'; import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { sortAsc } from '~/utils/sort'; import { sortAsc } from '~/utils/sort';
export const sortCalendarEventsAsc = ( export const sortCalendarEventsAsc = (
@ -7,18 +8,16 @@ export const sortCalendarEventsAsc = (
calendarEventB: Pick<CalendarEvent, 'startsAt' | 'endsAt' | 'isFullDay'>, calendarEventB: Pick<CalendarEvent, 'startsAt' | 'endsAt' | 'isFullDay'>,
) => { ) => {
const startsAtSort = sortAsc( const startsAtSort = sortAsc(
calendarEventA.startsAt.getTime(), getCalendarEventStartDate(calendarEventA).getTime(),
calendarEventB.startsAt.getTime(), getCalendarEventStartDate(calendarEventB).getTime(),
); );
if (startsAtSort === 0) { if (startsAtSort !== 0) return startsAtSort;
const endsAtA = getCalendarEventEndDate(calendarEventA);
const endsAtB = getCalendarEventEndDate(calendarEventB);
return sortAsc(endsAtA.getTime(), endsAtB.getTime()); return sortAsc(
} getCalendarEventEndDate(calendarEventA).getTime(),
getCalendarEventEndDate(calendarEventB).getTime(),
return startsAtSort; );
}; };
export const sortCalendarEventsDesc = ( export const sortCalendarEventsDesc = (

View File

@ -1,24 +0,0 @@
import styled from '@emotion/styled';
import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { RightDrawerTopBarCloseButton } from '../../../../ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
import { RightDrawerTopBarExpandButton } from '../../../../ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
const StyledTopBarWrapper = styled.div`
display: flex;
`;
export const RightDrawerEmailThreadTopBar = () => {
const isMobile = useIsMobile();
return (
<StyledRightDrawerTopBar>
<StyledTopBarWrapper>
<RightDrawerTopBarCloseButton />
{!isMobile && <RightDrawerTopBarExpandButton />}
</StyledTopBarWrapper>
</StyledRightDrawerTopBar>
);
};

View File

@ -1,26 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { RightDrawerEmailThreadTopBar } from '@/activities/emails/right-drawer/components/RightDrawerEmailThreadTopBar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
const meta: Meta<typeof RightDrawerEmailThreadTopBar> = {
title: 'Modules/Activities/Emails/RightDrawer/RightDrawerEmailThreadTopBar',
component: RightDrawerEmailThreadTopBar,
decorators: [
(Story) => (
<div style={{ width: '500px' }}>
<Story />
</div>
),
ComponentDecorator,
],
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof RightDrawerEmailThreadTopBar>;
export const Default: Story = {};

View File

@ -1,17 +1,20 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar'; import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
import { RightDrawerTopBarExpandButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar'; import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { RightDrawerTopBarCloseButton } from '../../../ui/layout/right-drawer/components/RightDrawerTopBarCloseButton'; type RightDrawerActivityTopBarProps = { showActionBar?: boolean };
import { RightDrawerTopBarExpandButton } from '../../../ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
const StyledTopBarWrapper = styled.div` const StyledTopBarWrapper = styled.div`
display: flex; display: flex;
`; `;
export const RightDrawerActivityTopBar = () => { export const RightDrawerActivityTopBar = ({
showActionBar = true,
}: RightDrawerActivityTopBarProps) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
return ( return (
@ -20,7 +23,7 @@ export const RightDrawerActivityTopBar = () => {
<RightDrawerTopBarCloseButton /> <RightDrawerTopBarCloseButton />
{!isMobile && <RightDrawerTopBarExpandButton />} {!isMobile && <RightDrawerTopBarExpandButton />}
</StyledTopBarWrapper> </StyledTopBarWrapper>
<ActivityActionBar /> {showActionBar && <ActivityActionBar />}
</StyledRightDrawerTopBar> </StyledRightDrawerTopBar>
); );
}; };

View File

@ -4,6 +4,7 @@ export enum CoreObjectNameSingular {
ApiKey = 'apiKey', ApiKey = 'apiKey',
Attachment = 'attachment', Attachment = 'attachment',
Blocklist = 'blocklist', Blocklist = 'blocklist',
CalendarEvent = 'calendarEvent',
Comment = 'comment', Comment = 'comment',
Company = 'company', Company = 'company',
ConnectedAccount = 'connectedAccount', ConnectedAccount = 'connectedAccount',

View File

@ -7,9 +7,5 @@ import { isFieldRating } from '../types/guards/isFieldRating';
export const useIsFieldInputOnly = () => { export const useIsFieldInputOnly = () => {
const { fieldDefinition } = useContext(FieldContext); const { fieldDefinition } = useContext(FieldContext);
if (isFieldBoolean(fieldDefinition) || isFieldRating(fieldDefinition)) { return isFieldBoolean(fieldDefinition) || isFieldRating(fieldDefinition);
return true;
}
return false;
}; };

View File

@ -15,7 +15,11 @@ import { useInlineCell } from '../hooks/useInlineCell';
import { RecordInlineCellContainer } from './RecordInlineCellContainer'; import { RecordInlineCellContainer } from './RecordInlineCellContainer';
export const RecordInlineCell = () => { type RecordInlineCellProps = {
readonly?: boolean;
};
export const RecordInlineCell = ({ readonly }: RecordInlineCellProps) => {
const { fieldDefinition, entityId } = useContext(FieldContext); const { fieldDefinition, entityId } = useContext(FieldContext);
const buttonIcon = useGetButtonIcon(); const buttonIcon = useGetButtonIcon();
@ -63,6 +67,7 @@ export const RecordInlineCell = () => {
return ( return (
<RecordInlineCellContainer <RecordInlineCellContainer
readonly={readonly}
buttonIcon={buttonIcon} buttonIcon={buttonIcon}
customEditHotkeyScope={ customEditHotkeyScope={
isFieldRelation(fieldDefinition) isFieldRelation(fieldDefinition)

View File

@ -1,6 +1,6 @@
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { Tooltip } from 'react-tooltip'; import { Tooltip } from 'react-tooltip';
import { useTheme } from '@emotion/react'; import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
@ -52,11 +52,16 @@ const StyledEditButtonContainer = styled(motion.div)`
display: flex; display: flex;
`; `;
const StyledClickableContainer = styled.div` const StyledClickableContainer = styled.div<{ readonly?: boolean }>`
cursor: pointer;
display: flex; display: flex;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
width: 100%; width: 100%;
${({ readonly }) =>
!readonly &&
css`
cursor: pointer;
`};
`; `;
const StyledInlineCellBaseContainer = styled.div` const StyledInlineCellBaseContainer = styled.div`
@ -83,6 +88,7 @@ const StyledTooltip = styled(Tooltip)`
`; `;
type RecordInlineCellContainerProps = { type RecordInlineCellContainerProps = {
readonly?: boolean;
IconLabel?: IconComponent; IconLabel?: IconComponent;
label?: string; label?: string;
labelWidth?: number; labelWidth?: number;
@ -98,6 +104,7 @@ type RecordInlineCellContainerProps = {
}; };
export const RecordInlineCellContainer = ({ export const RecordInlineCellContainer = ({
readonly,
IconLabel, IconLabel,
label, label,
labelWidth, labelWidth,
@ -115,17 +122,21 @@ export const RecordInlineCellContainer = ({
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const handleContainerMouseEnter = () => { const handleContainerMouseEnter = () => {
if (!readonly) {
setIsHovered(true); setIsHovered(true);
}
}; };
const handleContainerMouseLeave = () => { const handleContainerMouseLeave = () => {
if (!readonly) {
setIsHovered(false); setIsHovered(false);
}
}; };
const { isInlineCellInEditMode, openInlineCell } = useInlineCell(); const { isInlineCellInEditMode, openInlineCell } = useInlineCell();
const handleDisplayModeClick = () => { const handleDisplayModeClick = () => {
if (!editModeContentOnly) { if (!readonly && !editModeContentOnly) {
openInlineCell(customEditHotkeyScope); openInlineCell(customEditHotkeyScope);
} }
}; };
@ -167,10 +178,10 @@ export const RecordInlineCellContainer = ({
</StyledLabelAndIconContainer> </StyledLabelAndIconContainer>
)} )}
<StyledValueContainer> <StyledValueContainer>
{isInlineCellInEditMode ? ( {!readonly && isInlineCellInEditMode ? (
<RecordInlineCellEditMode>{editModeContent}</RecordInlineCellEditMode> <RecordInlineCellEditMode>{editModeContent}</RecordInlineCellEditMode>
) : editModeContentOnly ? ( ) : editModeContentOnly ? (
<StyledClickableContainer> <StyledClickableContainer readonly={readonly}>
<RecordInlineCellDisplayMode <RecordInlineCellDisplayMode
disableHoverEffect={disableHoverEffect} disableHoverEffect={disableHoverEffect}
isDisplayModeContentEmpty={isDisplayModeContentEmpty} isDisplayModeContentEmpty={isDisplayModeContentEmpty}
@ -182,7 +193,10 @@ export const RecordInlineCellContainer = ({
</RecordInlineCellDisplayMode> </RecordInlineCellDisplayMode>
</StyledClickableContainer> </StyledClickableContainer>
) : ( ) : (
<StyledClickableContainer onClick={handleDisplayModeClick}> <StyledClickableContainer
readonly={readonly}
onClick={handleDisplayModeClick}
>
<RecordInlineCellDisplayMode <RecordInlineCellDisplayMode
disableHoverEffect={disableHoverEffect} disableHoverEffect={disableHoverEffect}
isDisplayModeContentEmpty={isDisplayModeContentEmpty} isDisplayModeContentEmpty={isDisplayModeContentEmpty}

View File

@ -82,7 +82,7 @@ export const RecordTableWithWrappers = ({
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular }); const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
const objectLabel = foundObjectMetadataItem?.nameSingular; const objectLabel = foundObjectMetadataItem?.labelSingular;
return ( return (
<EntityDeleteContext.Provider value={deleteOneRecord}> <EntityDeleteContext.Provider value={deleteOneRecord}>

View File

@ -1,4 +1,5 @@
import { MouseEvent, ReactNode } from 'react'; import { MouseEvent, ReactNode } from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip'; import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip';
@ -34,86 +35,108 @@ type ChipProps = {
onClick?: (event: MouseEvent<HTMLDivElement>) => void; onClick?: (event: MouseEvent<HTMLDivElement>) => void;
}; };
const StyledContainer = styled.div<Partial<ChipProps>>` const StyledContainer = styled.div<
Pick<
ChipProps,
'accent' | 'clickable' | 'disabled' | 'maxWidth' | 'size' | 'variant'
>
>`
--chip-horizontal-padding: ${({ theme }) => theme.spacing(1)};
--chip-vertical-padding: ${({ theme }) => theme.spacing(1)};
align-items: center; align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${({ theme, variant }) => color: ${({ theme, disabled }) =>
variant === ChipVariant.Highlighted disabled ? theme.font.color.light : theme.font.color.secondary};
? theme.background.transparent.light cursor: ${({ clickable, disabled }) =>
: variant === ChipVariant.Rounded clickable ? 'pointer' : disabled ? 'not-allowed' : 'inherit'};
? theme.background.transparent.lighter
: 'transparent'};
border-color: ${({ theme, variant }) =>
variant === ChipVariant.Rounded ? theme.border.color.medium : 'none'};
border-radius: ${({ theme, variant }) =>
variant === ChipVariant.Rounded ? '50px' : theme.border.radius.sm};
border-style: ${({ variant }) =>
variant === ChipVariant.Rounded ? 'solid' : 'none'};
border-width: ${({ variant }) =>
variant === ChipVariant.Rounded ? '1px' : '0px'};
color: ${({ theme, disabled, accent }) =>
disabled
? theme.font.color.light
: accent === ChipAccent.TextPrimary
? theme.font.color.primary
: theme.font.color.secondary};
cursor: ${({ clickable, disabled, variant }) =>
disabled || variant === ChipVariant.Transparent
? 'inherit'
: clickable
? 'pointer'
: 'inherit'};
display: inline-flex; display: inline-flex;
font-weight: ${({ theme, accent }) =>
accent === ChipAccent.TextSecondary ? theme.font.weight.medium : 'inherit'};
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
height: ${({ theme }) => theme.spacing(3)};
height: ${({ size }) => (size === ChipSize.Large ? '16px' : '12px')};
--chip-horizontal-padding: ${({ theme, variant }) =>
variant === ChipVariant.Rounded ? theme.spacing(2) : theme.spacing(1)};
max-width: ${({ maxWidth }) => max-width: ${({ maxWidth }) =>
maxWidth maxWidth
? `calc( ${maxWidth}px - 2*var(--chip-horizontal-padding))` ? `calc(${maxWidth}px - 2 * var(--chip-horizontal-padding))`
: '200px'}; : '200px'};
--chip-vertical-padding: ${({ theme, variant }) =>
variant === ChipVariant.Rounded ? '3px' : theme.spacing(1)};
overflow: hidden; overflow: hidden;
padding: var(--chip-vertical-padding) var(--chip-horizontal-padding); padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
user-select: none; user-select: none;
// Accent style overrides
${({ accent, disabled, theme }) => {
if (accent === ChipAccent.TextPrimary) {
return (
!disabled &&
css`
color: ${theme.font.color.primary};
`
);
}
if (accent === ChipAccent.TextSecondary) {
return css`
font-weight: ${theme.font.weight.medium};
`;
}
}}
// Size style overrides
${({ theme, size }) =>
size === ChipSize.Large &&
css`
height: ${theme.spacing(4)};
`}
// Variant style overrides
${({ disabled, theme, variant }) => {
if (variant === ChipVariant.Regular) {
return (
!disabled &&
css`
:hover { :hover {
${({ variant, theme, disabled }) => { background-color: ${theme.background.transparent.light};
if (!disabled) {
return (
'background-color: ' +
(variant === ChipVariant.Highlighted
? theme.background.transparent.medium
: variant === ChipVariant.Regular
? theme.background.transparent.light
: 'transparent') +
';'
);
}
}}
} }
:active { :active {
${({ variant, theme, disabled }) => { background-color: ${theme.background.transparent.medium};
if (!disabled) { }
return ( `
'background-color: ' +
(variant === ChipVariant.Highlighted
? theme.background.transparent.strong
: variant === ChipVariant.Regular
? theme.background.transparent.medium
: 'transparent') +
';'
); );
} }
}}
if (variant === ChipVariant.Highlighted) {
return css`
background-color: ${theme.background.transparent.light};
${!disabled &&
css`
:hover {
background-color: ${theme.background.transparent.medium};
} }
:active {
background-color: ${theme.background.transparent.strong};
}
`}
`;
}
if (variant === ChipVariant.Rounded) {
return css`
--chip-horizontal-padding: ${theme.spacing(2)};
--chip-vertical-padding: 3px;
background-color: ${theme.background.transparent.lighter};
border: 1px solid ${theme.border.color.medium};
border-radius: 50px;
`;
}
if (variant === ChipVariant.Transparent) {
return css`
cursor: inherit;
`;
}
}}
`; `;
const StyledLabel = styled.span` const StyledLabel = styled.span`

View File

@ -950,6 +950,7 @@ import {
IconCalendarBolt, IconCalendarBolt,
IconCalendarCancel, IconCalendarCancel,
IconCalendarCheck, IconCalendarCheck,
IconCalendarClock,
IconCalendarCode, IconCalendarCode,
IconCalendarCog, IconCalendarCog,
IconCalendarDollar, IconCalendarDollar,
@ -4199,14 +4200,14 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
export default { export default {
Icon123,
Icon24Hours,
Icon2fa, Icon2fa,
Icon360,
Icon360View,
Icon3dCubeSphere, Icon3dCubeSphere,
Icon3dCubeSphereOff, Icon3dCubeSphereOff,
Icon3dRotate, Icon3dRotate,
Icon24Hours,
Icon123,
Icon360,
Icon360View,
IconAB, IconAB,
IconAB2, IconAB2,
IconAbacus, IconAbacus,
@ -5149,6 +5150,7 @@ export default {
IconCalendarBolt, IconCalendarBolt,
IconCalendarCancel, IconCalendarCancel,
IconCalendarCheck, IconCalendarCheck,
IconCalendarClock,
IconCalendarCode, IconCalendarCode,
IconCalendarCog, IconCalendarCog,
IconCalendarDollar, IconCalendarDollar,
@ -5440,6 +5442,9 @@ export default {
IconClockExclamation, IconClockExclamation,
IconClockHeart, IconClockHeart,
IconClockHour1, IconClockHour1,
IconClockHour10,
IconClockHour11,
IconClockHour12,
IconClockHour2, IconClockHour2,
IconClockHour3, IconClockHour3,
IconClockHour4, IconClockHour4,
@ -5448,9 +5453,6 @@ export default {
IconClockHour7, IconClockHour7,
IconClockHour8, IconClockHour8,
IconClockHour9, IconClockHour9,
IconClockHour10,
IconClockHour11,
IconClockHour12,
IconClockMinus, IconClockMinus,
IconClockOff, IconClockOff,
IconClockPause, IconClockPause,
@ -7117,10 +7119,10 @@ export default {
IconMovieOff, IconMovieOff,
IconMug, IconMug,
IconMugOff, IconMugOff,
IconMultiplier1x,
IconMultiplier2x,
IconMultiplier05x, IconMultiplier05x,
IconMultiplier15x, IconMultiplier15x,
IconMultiplier1x,
IconMultiplier2x,
IconMushroom, IconMushroom,
IconMushroomOff, IconMushroomOff,
IconMusic, IconMusic,
@ -7515,20 +7517,20 @@ export default {
IconReservedLine, IconReservedLine,
IconResize, IconResize,
IconRestore, IconRestore,
IconRewindBackward5,
IconRewindBackward10, IconRewindBackward10,
IconRewindBackward15, IconRewindBackward15,
IconRewindBackward20, IconRewindBackward20,
IconRewindBackward30, IconRewindBackward30,
IconRewindBackward40, IconRewindBackward40,
IconRewindBackward5,
IconRewindBackward50, IconRewindBackward50,
IconRewindBackward60, IconRewindBackward60,
IconRewindForward5,
IconRewindForward10, IconRewindForward10,
IconRewindForward15, IconRewindForward15,
IconRewindForward20, IconRewindForward20,
IconRewindForward30, IconRewindForward30,
IconRewindForward40, IconRewindForward40,
IconRewindForward5,
IconRewindForward50, IconRewindForward50,
IconRewindForward60, IconRewindForward60,
IconRibbonHealth, IconRibbonHealth,
@ -8080,11 +8082,11 @@ export default {
IconTiltShift, IconTiltShift,
IconTiltShiftOff, IconTiltShiftOff,
IconTimeDuration0, IconTimeDuration0,
IconTimeDuration5,
IconTimeDuration10, IconTimeDuration10,
IconTimeDuration15, IconTimeDuration15,
IconTimeDuration30, IconTimeDuration30,
IconTimeDuration45, IconTimeDuration45,
IconTimeDuration5,
IconTimeDuration60, IconTimeDuration60,
IconTimeDuration90, IconTimeDuration90,
IconTimeDurationOff, IconTimeDurationOff,

View File

@ -1,8 +1,8 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent';
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread'; import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
import { RightDrawerEmailThreadTopBar } from '@/activities/emails/right-drawer/components/RightDrawerEmailThreadTopBar';
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity'; import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity'; import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
@ -27,28 +27,31 @@ const StyledRightDrawerBody = styled.div`
position: relative; position: relative;
`; `;
const RIGHT_DRAWER_PAGES_CONFIG = {
[RightDrawerPages.CreateActivity]: {
page: <RightDrawerCreateActivity />,
topBar: <RightDrawerActivityTopBar />,
},
[RightDrawerPages.EditActivity]: {
page: <RightDrawerEditActivity />,
topBar: <RightDrawerActivityTopBar />,
},
[RightDrawerPages.ViewEmailThread]: {
page: <RightDrawerEmailThread />,
topBar: <RightDrawerActivityTopBar showActionBar={false} />,
},
[RightDrawerPages.ViewCalendarEvent]: {
page: <RightDrawerCalendarEvent />,
topBar: <RightDrawerActivityTopBar showActionBar={false} />,
},
};
export const RightDrawerRouter = () => { export const RightDrawerRouter = () => {
const [rightDrawerPage] = useRecoilState(rightDrawerPageState()); const [rightDrawerPage] = useRecoilState(rightDrawerPageState());
let page = <></>; const { topBar = null, page = null } = rightDrawerPage
let topBar = <></>; ? RIGHT_DRAWER_PAGES_CONFIG[rightDrawerPage]
: {};
switch (rightDrawerPage) {
case RightDrawerPages.CreateActivity:
page = <RightDrawerCreateActivity />;
topBar = <RightDrawerActivityTopBar />;
break;
case RightDrawerPages.EditActivity:
page = <RightDrawerEditActivity />;
topBar = <RightDrawerActivityTopBar />;
break;
case RightDrawerPages.ViewEmailThread:
page = <RightDrawerEmailThread />;
topBar = <RightDrawerEmailThreadTopBar />;
break;
default:
break;
}
return ( return (
<StyledRightDrawerPage> <StyledRightDrawerPage>

View File

@ -2,4 +2,5 @@ export enum RightDrawerPages {
CreateActivity = 'create-activity', CreateActivity = 'create-activity',
EditActivity = 'edit-activity', EditActivity = 'edit-activity',
ViewEmailThread = 'view-email-thread', ViewEmailThread = 'view-email-thread',
ViewCalendarEvent = 'view-calendar-event',
} }

View File

@ -50,9 +50,10 @@ export const SettingsAccountsCalendars = () => {
workspaceMemberId: currentWorkspaceMember?.id ?? '', workspaceMemberId: currentWorkspaceMember?.id ?? '',
}, },
], ],
endsAt: exampleEndDate, endsAt: exampleEndDate.toISOString(),
externalCreatedAt: new Date().toISOString(),
isFullDay: false, isFullDay: false,
startsAt: exampleStartDate, startsAt: exampleStartDate.toISOString(),
title: 'Onboarding call', title: 'Onboarding call',
visibility: 'SHARE_EVERYTHING', visibility: 'SHARE_EVERYTHING',
}; };

View File

@ -1,13 +1,14 @@
import { addDays, subMonths } from 'date-fns'; import { addDays, subHours, subMonths } from 'date-fns';
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
export const mockedCalendarEvents: CalendarEvent[] = [ export const mockedCalendarEvents: CalendarEvent[] = [
{ {
externalCreatedAt: new Date().toISOString(),
endsAt: addDays(new Date().setHours(11, 30), 1).toISOString(),
id: '9a6b35f1-6078-415b-9540-f62671bb81d0', id: '9a6b35f1-6078-415b-9540-f62671bb81d0',
endsAt: addDays(new Date().setHours(11, 30), 1),
isFullDay: false, isFullDay: false,
startsAt: addDays(new Date().setHours(10, 0), 1), startsAt: addDays(new Date().setHours(10, 0), 1).toISOString(),
visibility: 'METADATA', visibility: 'METADATA',
attendees: [ attendees: [
{ displayName: 'John Doe', workspaceMemberId: 'john-doe' }, { displayName: 'John Doe', workspaceMemberId: 'john-doe' },
@ -16,41 +17,46 @@ export const mockedCalendarEvents: CalendarEvent[] = [
], ],
}, },
{ {
externalCreatedAt: subHours(new Date(), 2).toISOString(),
id: '19b32878-a950-4968-9e3b-ce5da514ea41', id: '19b32878-a950-4968-9e3b-ce5da514ea41',
endsAt: new Date(new Date().setHours(18, 40)), endsAt: new Date(new Date().setHours(18, 40)).toISOString(),
isCanceled: true, isCanceled: true,
isFullDay: false, isFullDay: false,
startsAt: new Date(new Date().setHours(18, 0)), startsAt: new Date(new Date().setHours(18, 0)).toISOString(),
title: 'Bug solving', title: 'Bug solving',
visibility: 'SHARE_EVERYTHING', visibility: 'SHARE_EVERYTHING',
}, },
{ {
externalCreatedAt: subHours(new Date(), 2).toISOString(),
id: '6ad1cbcb-2ac4-409e-aff0-48165556fc0c', id: '6ad1cbcb-2ac4-409e-aff0-48165556fc0c',
endsAt: new Date(new Date().setHours(16, 30)), endsAt: new Date(new Date().setHours(16, 30)).toISOString(),
isFullDay: false, isFullDay: false,
startsAt: new Date(new Date().setHours(15, 15)), startsAt: new Date(new Date().setHours(15, 15)).toISOString(),
title: 'Onboarding Follow-Up Call', title: 'Onboarding Follow-Up Call',
visibility: 'SHARE_EVERYTHING', visibility: 'SHARE_EVERYTHING',
}, },
{ {
externalCreatedAt: subHours(new Date(), 2).toISOString(),
id: '52cc83e3-f3dc-4c25-8a7d-5ff857612142', id: '52cc83e3-f3dc-4c25-8a7d-5ff857612142',
endsAt: new Date(new Date().setHours(10, 30)), endsAt: new Date(new Date().setHours(10, 30)).toISOString(),
isFullDay: false, isFullDay: false,
startsAt: new Date(new Date().setHours(10, 0)), startsAt: new Date(new Date().setHours(10, 0)).toISOString(),
title: 'Onboarding Call', title: 'Onboarding Call',
visibility: 'SHARE_EVERYTHING', visibility: 'SHARE_EVERYTHING',
}, },
{ {
externalCreatedAt: subHours(new Date(), 2).toISOString(),
id: '5a792d11-259a-4099-af51-59eb85e15d83', id: '5a792d11-259a-4099-af51-59eb85e15d83',
isFullDay: true, isFullDay: true,
startsAt: subMonths(new Date().setHours(8, 0), 1), startsAt: subMonths(new Date().setHours(8, 0), 1).toISOString(),
visibility: 'METADATA', visibility: 'METADATA',
}, },
{ {
externalCreatedAt: subHours(new Date(), 2).toISOString(),
id: '89e2a1c7-3d3f-4e79-a492-aa5de3785fc5', id: '89e2a1c7-3d3f-4e79-a492-aa5de3785fc5',
endsAt: subMonths(new Date().setHours(14, 30), 3), endsAt: subMonths(new Date().setHours(14, 30), 3).toISOString(),
isFullDay: false, isFullDay: false,
startsAt: subMonths(new Date().setHours(14, 0), 3), startsAt: subMonths(new Date().setHours(14, 0), 3).toISOString(),
title: 'Alan x Garry', title: 'Alan x Garry',
visibility: 'SHARE_EVERYTHING', visibility: 'SHARE_EVERYTHING',
}, },

View File

@ -11,9 +11,10 @@ import { seedWorkspaceMember } from 'src/database/typeorm-seeds/workspace/worksp
import { seedPeople } from 'src/database/typeorm-seeds/workspace/people'; import { seedPeople } from 'src/database/typeorm-seeds/workspace/people';
import { seedCoreSchema } from 'src/database/typeorm-seeds/core'; import { seedCoreSchema } from 'src/database/typeorm-seeds/core';
import { EnvironmentService } from 'src/integrations/environment/environment.service'; import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; import { seedCalendarEvents } from 'src/database/typeorm-seeds/workspace/calendar-events';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectMetadataService } from 'src/engine-metadata/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/engine-metadata/object-metadata/object-metadata.service';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
// TODO: implement dry-run // TODO: implement dry-run
@Command({ @Command({
@ -105,6 +106,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
await seedPeople(workspaceDataSource, dataSourceMetadata.schema); await seedPeople(workspaceDataSource, dataSourceMetadata.schema);
await seedPipelineStep(workspaceDataSource, dataSourceMetadata.schema); await seedPipelineStep(workspaceDataSource, dataSourceMetadata.schema);
await seedOpportunity(workspaceDataSource, dataSourceMetadata.schema); await seedOpportunity(workspaceDataSource, dataSourceMetadata.schema);
await seedCalendarEvents(workspaceDataSource, dataSourceMetadata.schema);
await seedViews( await seedViews(
workspaceDataSource, workspaceDataSource,

View File

@ -0,0 +1,48 @@
import { DataSource } from 'typeorm';
const tableName = 'calendarEvent';
export const seedCalendarEvents = async (
workspaceDataSource: DataSource,
schemaName: string,
) => {
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
'id',
'title',
'isCanceled',
'isFullDay',
'startsAt',
'endsAt',
'externalCreatedAt',
'externalUpdatedAt',
'description',
'location',
'iCalUID',
'conferenceSolution',
'conferenceUri',
'recurringEventExternalId',
])
.orIgnore()
.values([
{
id: '86083141-1c0e-494c-a1b6-85b1c6fefaa5',
title: 'Meeting with Christoph',
isCanceled: false,
isFullDay: false,
startsAt: new Date(new Date().setHours(10, 0)).toISOString(),
endsAt: new Date(new Date().setHours(11, 0)).toISOString(),
externalCreatedAt: new Date().toISOString(),
externalUpdatedAt: new Date().toISOString(),
description: 'Discuss project progress',
location: 'Seattle',
iCalUID: 'event1@calendar.com',
conferenceSolution: 'Zoom',
conferenceUri: 'https://zoom.us/j/1234567890',
recurringEventExternalId: 'recurring1',
},
])
.execute();
};