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:
@ -4,10 +4,11 @@ import { format, getYear } from 'date-fns';
|
||||
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
||||
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
||||
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 { Section } from '@/ui/layout/section/components/Section';
|
||||
import { mockedCalendarEvents } from '~/testing/mock-data/calendar';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
@ -23,9 +24,11 @@ const StyledYear = styled.span`
|
||||
`;
|
||||
|
||||
export const Calendar = () => {
|
||||
const sortedCalendarEvents = [...mockedCalendarEvents].sort(
|
||||
sortCalendarEventsDesc,
|
||||
);
|
||||
const { records: calendarEvents } = useFindManyRecords<CalendarEvent>({
|
||||
objectNameSingular: CoreObjectNameSingular.CalendarEvent,
|
||||
orderBy: { startsAt: 'DescNullsLast', endsAt: 'DescNullsLast' },
|
||||
useRecordsWithoutConnection: true,
|
||||
});
|
||||
|
||||
const {
|
||||
calendarEventsByDayTime,
|
||||
@ -35,7 +38,13 @@ export const Calendar = () => {
|
||||
monthTimes,
|
||||
monthTimesByYear,
|
||||
updateCurrentCalendarEvent,
|
||||
} = useCalendarEvents(sortedCalendarEvents);
|
||||
} = useCalendarEvents(
|
||||
calendarEvents.map((calendarEvent) => ({
|
||||
...calendarEvent,
|
||||
// TODO: retrieve CalendarChannel visibility from backend
|
||||
visibility: 'SHARE_EVERYTHING',
|
||||
})),
|
||||
);
|
||||
|
||||
return (
|
||||
<CalendarContext.Provider
|
||||
|
||||
@ -12,6 +12,7 @@ import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
|
||||
import { hasCalendarEventStarted } from '@/activities/calendar/utils/hasCalendarEventStarted';
|
||||
|
||||
@ -52,12 +53,16 @@ export const CalendarCurrentEventCursor = ({
|
||||
} = useContext(CalendarContext);
|
||||
|
||||
const nextCalendarEvent = getNextCalendarEvent(calendarEvent);
|
||||
const nextCalendarEventStartsAt = nextCalendarEvent
|
||||
? getCalendarEventStartDate(nextCalendarEvent)
|
||||
: undefined;
|
||||
const isNextEventThisMonth =
|
||||
!!nextCalendarEvent && isThisMonth(nextCalendarEvent.startsAt);
|
||||
!!nextCalendarEventStartsAt && isThisMonth(nextCalendarEventStartsAt);
|
||||
|
||||
const calendarEventStartsAt = getCalendarEventStartDate(calendarEvent);
|
||||
const calendarEventEndsAt = getCalendarEventEndDate(calendarEvent);
|
||||
|
||||
const isCurrent = currentCalendarEvent.id === calendarEvent.id;
|
||||
const isCurrent = currentCalendarEvent?.id === calendarEvent.id;
|
||||
const [hasStarted, setHasStarted] = useState(
|
||||
hasCalendarEventStarted(calendarEvent),
|
||||
);
|
||||
@ -66,7 +71,7 @@ export const CalendarCurrentEventCursor = ({
|
||||
);
|
||||
const [isWaiting, setIsWaiting] = useState(hasEnded && !isNextEventThisMonth);
|
||||
|
||||
const dayTime = startOfDay(calendarEvent.startsAt).getTime();
|
||||
const dayTime = startOfDay(calendarEventStartsAt).getTime();
|
||||
const dayEvents = calendarEventsByDayTime[dayTime];
|
||||
const isFirstEventOfDay = dayEvents?.slice(-1)[0] === calendarEvent;
|
||||
const isLastEventOfDay = dayEvents?.[0] === calendarEvent;
|
||||
@ -81,7 +86,7 @@ export const CalendarCurrentEventCursor = ({
|
||||
transition: {
|
||||
delay: Math.max(
|
||||
0,
|
||||
differenceInSeconds(calendarEvent.startsAt, new Date()),
|
||||
differenceInSeconds(calendarEventStartsAt, new Date()),
|
||||
),
|
||||
},
|
||||
},
|
||||
@ -99,9 +104,9 @@ export const CalendarCurrentEventCursor = ({
|
||||
top: `-${topOffset}px`,
|
||||
transition: {
|
||||
delay:
|
||||
isWaiting && nextCalendarEvent
|
||||
isWaiting && nextCalendarEventStartsAt
|
||||
? differenceInSeconds(
|
||||
startOfMonth(nextCalendarEvent.startsAt),
|
||||
startOfMonth(nextCalendarEventStartsAt),
|
||||
new Date(),
|
||||
)
|
||||
: 0,
|
||||
|
||||
@ -4,6 +4,7 @@ import { differenceInSeconds, endOfDay, format } from 'date-fns';
|
||||
|
||||
import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow';
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
|
||||
type CalendarDayCardContentProps = {
|
||||
@ -53,8 +54,8 @@ export const CalendarDayCardContent = ({
|
||||
}: CalendarDayCardContentProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const endOfDayDate = endOfDay(calendarEvents[0].startsAt);
|
||||
const endsIn = differenceInSeconds(endOfDayDate, Date.now());
|
||||
const endOfDayDate = endOfDay(getCalendarEventStartDate(calendarEvents[0]));
|
||||
const dayEndsIn = differenceInSeconds(endOfDayDate, Date.now());
|
||||
|
||||
const weekDayLabel = format(endOfDayDate, 'EE');
|
||||
const monthDayLabel = format(endOfDayDate, 'dd');
|
||||
@ -71,7 +72,7 @@ export const CalendarDayCardContent = ({
|
||||
animate="ended"
|
||||
variants={upcomingDayCardContentVariants}
|
||||
transition={{
|
||||
delay: Math.max(0, endsIn),
|
||||
delay: Math.max(0, dayEndsIn),
|
||||
duration: theme.animation.duration.fast,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -6,8 +6,10 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor';
|
||||
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
||||
import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer';
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { IconArrowRight, IconLock } from '@/ui/display/icon';
|
||||
@ -22,12 +24,13 @@ type CalendarEventRowProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
const StyledContainer = styled.div<{ showTitle?: boolean }>`
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
height: ${({ theme }) => theme.spacing(6)};
|
||||
position: relative;
|
||||
cursor: ${({ showTitle }) => (showTitle ? 'pointer' : 'not-allowed')};
|
||||
`;
|
||||
|
||||
const StyledAttendanceIndicator = styled.div<{ active?: boolean }>`
|
||||
@ -101,21 +104,32 @@ export const CalendarEventRow = ({
|
||||
const theme = useTheme();
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState());
|
||||
const { displayCurrentEventCursor = false } = useContext(CalendarContext);
|
||||
const { openCalendarEventRightDrawer } = useOpenCalendarEventRightDrawer();
|
||||
|
||||
const startsAt = getCalendarEventStartDate(calendarEvent);
|
||||
const endsAt = getCalendarEventEndDate(calendarEvent);
|
||||
const hasEnded = hasCalendarEventEnded(calendarEvent);
|
||||
|
||||
const startTimeLabel = calendarEvent.isFullDay
|
||||
? 'All day'
|
||||
: format(calendarEvent.startsAt, 'HH:mm');
|
||||
: format(startsAt, 'HH:mm');
|
||||
const endTimeLabel = calendarEvent.isFullDay ? '' : format(endsAt, 'HH:mm');
|
||||
|
||||
const isCurrentWorkspaceMemberAttending = !!calendarEvent.attendees?.find(
|
||||
const isCurrentWorkspaceMemberAttending = calendarEvent.attendees?.some(
|
||||
({ workspaceMemberId }) => workspaceMemberId === currentWorkspaceMember?.id,
|
||||
);
|
||||
const showTitle = calendarEvent.visibility === 'SHARE_EVERYTHING';
|
||||
|
||||
return (
|
||||
<StyledContainer className={className}>
|
||||
<StyledContainer
|
||||
className={className}
|
||||
showTitle={showTitle}
|
||||
onClick={
|
||||
showTitle
|
||||
? () => openCalendarEventRightDrawer(calendarEvent.id)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<StyledAttendanceIndicator active={isCurrentWorkspaceMemberAttending} />
|
||||
<StyledLabels>
|
||||
<StyledTime>
|
||||
@ -127,17 +141,17 @@ export const CalendarEventRow = ({
|
||||
</>
|
||||
)}
|
||||
</StyledTime>
|
||||
{calendarEvent.visibility === 'METADATA' ? (
|
||||
{showTitle ? (
|
||||
<StyledTitle active={!hasEnded} canceled={!!calendarEvent.isCanceled}>
|
||||
{calendarEvent.title}
|
||||
</StyledTitle>
|
||||
) : (
|
||||
<StyledVisibilityCard active={!hasEnded}>
|
||||
<StyledVisibilityCardContent>
|
||||
<IconLock size={theme.icon.size.sm} />
|
||||
Not shared
|
||||
</StyledVisibilityCardContent>
|
||||
</StyledVisibilityCard>
|
||||
) : (
|
||||
<StyledTitle active={!hasEnded} canceled={!!calendarEvent.isCanceled}>
|
||||
{calendarEvent.title}
|
||||
</StyledTitle>
|
||||
)}
|
||||
</StyledLabels>
|
||||
{!!calendarEvent.attendees?.length && (
|
||||
|
||||
@ -4,7 +4,7 @@ import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
|
||||
type CalendarContextValue = {
|
||||
calendarEventsByDayTime: Record<number, CalendarEvent[] | undefined>;
|
||||
currentCalendarEvent: CalendarEvent;
|
||||
currentCalendarEvent?: CalendarEvent;
|
||||
displayCurrentEventCursor?: boolean;
|
||||
getNextCalendarEvent: (
|
||||
calendarEvent: CalendarEvent,
|
||||
@ -14,7 +14,6 @@ type CalendarContextValue = {
|
||||
|
||||
export const CalendarContext = createContext<CalendarContextValue>({
|
||||
calendarEventsByDayTime: {},
|
||||
currentCalendarEvent: {} as CalendarEvent,
|
||||
getNextCalendarEvent: () => undefined,
|
||||
updateCurrentCalendarEvent: () => {},
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@ import { getYear, isThisMonth, startOfDay, startOfMonth } from 'date-fns';
|
||||
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { findUpcomingCalendarEvent } from '@/activities/calendar/utils/findUpcomingCalendarEvent';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { sortDesc } from '~/utils/sort';
|
||||
@ -10,7 +11,8 @@ import { sortDesc } from '~/utils/sort';
|
||||
export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => {
|
||||
const calendarEventsByDayTime = groupArrayItemsBy(
|
||||
calendarEvents,
|
||||
({ startsAt }) => startOfDay(startsAt).getTime(),
|
||||
(calendarEvent) =>
|
||||
startOfDay(getCalendarEventStartDate(calendarEvent)).getTime(),
|
||||
);
|
||||
|
||||
const sortedDayTimes = Object.keys(calendarEventsByDayTime)
|
||||
@ -45,17 +47,21 @@ export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => {
|
||||
() => findUpcomingCalendarEvent(calendarEvents),
|
||||
[calendarEvents],
|
||||
);
|
||||
const lastEventInCalendar = calendarEvents[0];
|
||||
const lastEventInCalendar = calendarEvents.length
|
||||
? calendarEvents[0]
|
||||
: undefined;
|
||||
|
||||
const [currentCalendarEvent, setCurrentCalendarEvent] = useState(
|
||||
(initialUpcomingCalendarEvent &&
|
||||
(isThisMonth(initialUpcomingCalendarEvent.startsAt)
|
||||
(isThisMonth(getCalendarEventStartDate(initialUpcomingCalendarEvent))
|
||||
? initialUpcomingCalendarEvent
|
||||
: getPreviousCalendarEvent(initialUpcomingCalendarEvent))) ||
|
||||
lastEventInCalendar,
|
||||
);
|
||||
|
||||
const updateCurrentCalendarEvent = () => {
|
||||
if (!currentCalendarEvent) return;
|
||||
|
||||
const nextCurrentCalendarEvent = getNextCalendarEvent(currentCalendarEvent);
|
||||
|
||||
if (isDefined(nextCurrentCalendarEvent)) {
|
||||
|
||||
@ -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} />;
|
||||
};
|
||||
@ -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 };
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { createState } from '@/ui/utilities/state/utils/createState';
|
||||
|
||||
export const viewableCalendarEventIdState = createState<string | null>({
|
||||
key: 'viewableCalendarEventIdState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,10 +1,14 @@
|
||||
// TODO: use backend CalendarEvent type when ready
|
||||
export type CalendarEvent = {
|
||||
endsAt?: Date;
|
||||
conferenceUri?: string;
|
||||
description?: string;
|
||||
endsAt?: string;
|
||||
externalCreatedAt: string;
|
||||
id: string;
|
||||
isFullDay: boolean;
|
||||
startsAt: Date;
|
||||
isCanceled?: boolean;
|
||||
isFullDay: boolean;
|
||||
location?: string;
|
||||
startsAt: string;
|
||||
title?: string;
|
||||
visibility: 'METADATA' | 'SHARE_EVERYTHING';
|
||||
attendees?: {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { endOfDay } from 'date-fns';
|
||||
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
|
||||
export const getCalendarEventEndDate = (
|
||||
calendarEvent: Pick<CalendarEvent, 'endsAt' | 'isFullDay' | 'startsAt'>,
|
||||
) => calendarEvent.endsAt ?? endOfDay(calendarEvent.startsAt);
|
||||
) =>
|
||||
calendarEvent.endsAt
|
||||
? new Date(calendarEvent.endsAt)
|
||||
: endOfDay(getCalendarEventStartDate(calendarEvent));
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
|
||||
export const getCalendarEventStartDate = (
|
||||
calendarEvent: Pick<CalendarEvent, 'startsAt'>,
|
||||
) => new Date(calendarEvent.startsAt);
|
||||
@ -1,7 +1,8 @@
|
||||
import { isPast } from 'date-fns';
|
||||
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
|
||||
export const hasCalendarEventStarted = (
|
||||
calendarEvent: Pick<CalendarEvent, 'startsAt'>,
|
||||
) => isPast(calendarEvent.startsAt);
|
||||
) => isPast(getCalendarEventStartDate(calendarEvent));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { sortAsc } from '~/utils/sort';
|
||||
|
||||
export const sortCalendarEventsAsc = (
|
||||
@ -7,18 +8,16 @@ export const sortCalendarEventsAsc = (
|
||||
calendarEventB: Pick<CalendarEvent, 'startsAt' | 'endsAt' | 'isFullDay'>,
|
||||
) => {
|
||||
const startsAtSort = sortAsc(
|
||||
calendarEventA.startsAt.getTime(),
|
||||
calendarEventB.startsAt.getTime(),
|
||||
getCalendarEventStartDate(calendarEventA).getTime(),
|
||||
getCalendarEventStartDate(calendarEventB).getTime(),
|
||||
);
|
||||
|
||||
if (startsAtSort === 0) {
|
||||
const endsAtA = getCalendarEventEndDate(calendarEventA);
|
||||
const endsAtB = getCalendarEventEndDate(calendarEventB);
|
||||
if (startsAtSort !== 0) return startsAtSort;
|
||||
|
||||
return sortAsc(endsAtA.getTime(), endsAtB.getTime());
|
||||
}
|
||||
|
||||
return startsAtSort;
|
||||
return sortAsc(
|
||||
getCalendarEventEndDate(calendarEventA).getTime(),
|
||||
getCalendarEventEndDate(calendarEventB).getTime(),
|
||||
);
|
||||
};
|
||||
|
||||
export const sortCalendarEventsDesc = (
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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 = {};
|
||||
@ -1,17 +1,20 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
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 { 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';
|
||||
type RightDrawerActivityTopBarProps = { showActionBar?: boolean };
|
||||
|
||||
const StyledTopBarWrapper = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const RightDrawerActivityTopBar = () => {
|
||||
export const RightDrawerActivityTopBar = ({
|
||||
showActionBar = true,
|
||||
}: RightDrawerActivityTopBarProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
@ -20,7 +23,7 @@ export const RightDrawerActivityTopBar = () => {
|
||||
<RightDrawerTopBarCloseButton />
|
||||
{!isMobile && <RightDrawerTopBarExpandButton />}
|
||||
</StyledTopBarWrapper>
|
||||
<ActivityActionBar />
|
||||
{showActionBar && <ActivityActionBar />}
|
||||
</StyledRightDrawerTopBar>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user