diff --git a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx
new file mode 100644
index 0000000000..85d9c603d7
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx
@@ -0,0 +1,47 @@
+import styled from '@emotion/styled';
+import { startOfMonth } from 'date-fns';
+
+import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
+import { sortCalendarEventsDesc } from '@/activities/calendar/utils/sortCalendarEvents';
+import { mockedCalendarEvents } from '~/testing/mock-data/calendar';
+import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
+import { sortDesc } from '~/utils/sort';
+
+const StyledContainer = styled.div`
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(8)};
+ padding: ${({ theme }) => theme.spacing(6)};
+ width: 100%;
+`;
+
+export const Calendar = () => {
+ const sortedCalendarEvents = [...mockedCalendarEvents].sort(
+ sortCalendarEventsDesc,
+ );
+ const calendarEventsByMonthTime = groupArrayItemsBy(
+ sortedCalendarEvents,
+ ({ startsAt }) => startOfMonth(startsAt).getTime(),
+ );
+ const sortedMonthTimes = Object.keys(calendarEventsByMonthTime)
+ .map(Number)
+ .sort(sortDesc);
+
+ return (
+
+ {sortedMonthTimes.map((monthTime) => {
+ const monthCalendarEvents = calendarEventsByMonthTime[monthTime];
+
+ return (
+ !!monthCalendarEvents?.length && (
+
+ )
+ );
+ })}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx
new file mode 100644
index 0000000000..8dd80ef74b
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx
@@ -0,0 +1,79 @@
+import { css } from '@emotion/react';
+import styled from '@emotion/styled';
+import { endOfDay, format, isPast } from 'date-fns';
+
+import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow';
+import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
+import { CardContent } from '@/ui/layout/card/components/CardContent';
+
+type CalendarDayCardContentProps = {
+ calendarEvents: CalendarEvent[];
+ divider?: boolean;
+};
+
+const StyledCardContent = styled(CardContent)<{ active: boolean }>`
+ align-items: flex-start;
+ border-color: ${({ theme }) => theme.border.color.light};
+ display: flex;
+ flex-direction: row;
+ gap: ${({ theme }) => theme.spacing(3)};
+ padding: ${({ theme }) => theme.spacing(2, 3)};
+
+ ${({ active }) =>
+ !active &&
+ css`
+ background-color: transparent;
+ `}
+`;
+
+const StyledDayContainer = styled.div`
+ text-align: center;
+ width: ${({ theme }) => theme.spacing(6)};
+`;
+
+const StyledWeekDay = styled.div`
+ color: ${({ theme }) => theme.font.color.tertiary};
+ font-size: ${({ theme }) => theme.font.size.xxs};
+ font-weight: ${({ theme }) => theme.font.weight.semiBold};
+`;
+
+const StyledDateDay = styled.div`
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+`;
+
+const StyledEvents = styled.div`
+ align-items: stretch;
+ display: flex;
+ flex: 1 0 auto;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(3)};
+`;
+
+const StyledEventRow = styled(CalendarEventRow)`
+ flex: 1 0 auto;
+`;
+
+export const CalendarDayCardContent = ({
+ calendarEvents,
+ divider,
+}: CalendarDayCardContentProps) => {
+ const endOfDayDate = endOfDay(calendarEvents[0].startsAt);
+ const isPastDay = isPast(endOfDayDate);
+
+ return (
+
+
+ {format(endOfDayDate, 'EE')}
+ {format(endOfDayDate, 'dd')}
+
+
+ {calendarEvents.map((calendarEvent) => (
+
+ ))}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx
new file mode 100644
index 0000000000..1e61d9be6a
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx
@@ -0,0 +1,131 @@
+import { css, useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import { endOfDay, format, isPast } from 'date-fns';
+
+import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
+import { IconArrowRight, IconLock } from '@/ui/display/icon';
+import { Card } from '@/ui/layout/card/components/Card';
+import { CardContent } from '@/ui/layout/card/components/CardContent';
+
+type CalendarEventRowProps = {
+ calendarEvent: CalendarEvent;
+ className?: string;
+};
+
+const StyledContainer = styled.div`
+ align-items: center;
+ display: inline-flex;
+ gap: ${({ theme }) => theme.spacing(3)};
+ height: ${({ theme }) => theme.spacing(6)};
+`;
+
+const StyledAttendanceIndicator = styled.div<{ active?: boolean }>`
+ background-color: ${({ theme }) => theme.tag.background.gray};
+ height: 100%;
+ width: ${({ theme }) => theme.spacing(1)};
+
+ ${({ active, theme }) =>
+ active &&
+ css`
+ background-color: ${theme.tag.background.red};
+ `}
+`;
+
+const StyledLabels = styled.div`
+ align-items: center;
+ display: flex;
+ color: ${({ theme }) => theme.font.color.tertiary};
+ gap: ${({ theme }) => theme.spacing(2)};
+ flex: 1 0 auto;
+`;
+
+const StyledTime = styled.div`
+ align-items: center;
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(1)};
+ width: ${({ theme }) => theme.spacing(26)};
+`;
+
+const StyledTitle = styled.div<{ active: boolean; canceled: boolean }>`
+ flex: 1 0 auto;
+
+ ${({ theme, active }) =>
+ active &&
+ css`
+ color: ${theme.font.color.primary};
+ font-weight: ${theme.font.weight.medium};
+ `}
+
+ ${({ canceled }) =>
+ canceled &&
+ css`
+ text-decoration: line-through;
+ `}
+`;
+
+const StyledVisibilityCard = styled(Card)<{ active: boolean }>`
+ color: ${({ active, theme }) =>
+ active ? theme.font.color.primary : theme.font.color.light};
+ border-color: ${({ theme }) => theme.border.color.light};
+ flex: 1 0 auto;
+`;
+
+const StyledVisibilityCardContent = styled(CardContent)`
+ align-items: center;
+ font-size: ${({ theme }) => theme.font.size.sm};
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(1)};
+ padding: ${({ theme }) => theme.spacing(0, 1)};
+ height: ${({ theme }) => theme.spacing(6)};
+ background-color: ${({ theme }) => theme.background.transparent.lighter};
+`;
+
+export const CalendarEventRow = ({
+ calendarEvent,
+ className,
+}: CalendarEventRowProps) => {
+ const theme = useTheme();
+
+ const hasEventEnded = calendarEvent.endsAt
+ ? isPast(calendarEvent.endsAt)
+ : calendarEvent.isFullDay && isPast(endOfDay(calendarEvent.startsAt));
+
+ return (
+
+
+
+
+ {calendarEvent.isFullDay ? (
+ 'All Day'
+ ) : (
+ <>
+ {format(calendarEvent.startsAt, 'HH:mm')}
+ {!!calendarEvent.endsAt && (
+ <>
+
+ {format(calendarEvent.endsAt, 'HH:mm')}
+ >
+ )}
+ >
+ )}
+
+ {calendarEvent.visibility === 'METADATA' ? (
+
+
+
+ Not shared
+
+
+ ) : (
+
+ {calendarEvent.title}
+
+ )}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarMonthCard.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarMonthCard.tsx
new file mode 100644
index 0000000000..45ccf37c83
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarMonthCard.tsx
@@ -0,0 +1,41 @@
+import { startOfDay } from 'date-fns';
+
+import { CalendarDayCardContent } from '@/activities/calendar/components/CalendarDayCardContent';
+import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
+import { Card } from '@/ui/layout/card/components/Card';
+import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
+import { sortDesc } from '~/utils/sort';
+
+type CalendarMonthCardProps = {
+ calendarEvents: CalendarEvent[];
+};
+
+export const CalendarMonthCard = ({
+ calendarEvents,
+}: CalendarMonthCardProps) => {
+ const calendarEventsByDayTime = groupArrayItemsBy(
+ calendarEvents,
+ ({ startsAt }) => startOfDay(startsAt).getTime(),
+ );
+ const sortedDayTimes = Object.keys(calendarEventsByDayTime)
+ .map(Number)
+ .sort(sortDesc);
+
+ return (
+
+ {sortedDayTimes.map((dayTime, index) => {
+ const dayCalendarEvents = calendarEventsByDayTime[dayTime];
+
+ return (
+ !!dayCalendarEvents?.length && (
+
+ )
+ );
+ })}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx b/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx
new file mode 100644
index 0000000000..26c9083c2f
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/components/__stories__/Calendar.stories.tsx
@@ -0,0 +1,18 @@
+import { Meta, StoryObj } from '@storybook/react';
+
+import { Calendar } from '@/activities/calendar/components/Calendar';
+import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
+
+const meta: Meta = {
+ title: 'Modules/Activities/Calendar/Calendar',
+ component: Calendar,
+ decorators: [ComponentDecorator],
+ parameters: {
+ container: { width: 728 },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
diff --git a/packages/twenty-front/src/modules/activities/calendar/types/CalendarEvent.ts b/packages/twenty-front/src/modules/activities/calendar/types/CalendarEvent.ts
new file mode 100644
index 0000000000..db48befed0
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/types/CalendarEvent.ts
@@ -0,0 +1,10 @@
+// TODO: use backend CalendarEvent type when ready
+export type CalendarEvent = {
+ endsAt?: Date;
+ id: string;
+ isFullDay: boolean;
+ startsAt: Date;
+ isCanceled?: boolean;
+ title?: string;
+ visibility: 'METADATA' | 'SHARE_EVERYTHING';
+};
diff --git a/packages/twenty-front/src/modules/activities/calendar/utils/__tests__/sortCalendarEvents.test.ts b/packages/twenty-front/src/modules/activities/calendar/utils/__tests__/sortCalendarEvents.test.ts
new file mode 100644
index 0000000000..c56b434c27
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/utils/__tests__/sortCalendarEvents.test.ts
@@ -0,0 +1,285 @@
+import { addHours } from 'date-fns';
+
+import {
+ sortCalendarEventsAsc,
+ sortCalendarEventsDesc,
+} from '../sortCalendarEvents';
+
+const someDate = new Date(2000, 1, 1);
+const someDatePlusOneHour = addHours(someDate, 1);
+const someDatePlusTwoHours = addHours(someDate, 2);
+const someDatePlusThreeHours = addHours(someDate, 3);
+
+describe('sortCalendarEventsAsc', () => {
+ it('sorts non-intersecting events by ascending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusOneHour,
+ };
+ const eventB = {
+ startsAt: someDatePlusTwoHours,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(-1);
+ expect(invertedArgsResult).toBe(1);
+ });
+
+ it('sorts intersecting events by start date ascending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusTwoHours,
+ };
+ const eventB = {
+ startsAt: someDatePlusOneHour,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(-1);
+ expect(invertedArgsResult).toBe(1);
+ });
+
+ it('sorts events with same start date by end date ascending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusTwoHours,
+ };
+ const eventB = {
+ startsAt: someDate,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(-1);
+ expect(invertedArgsResult).toBe(1);
+ });
+
+ it('sorts events with same end date by start date ascending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusThreeHours,
+ };
+ const eventB = {
+ startsAt: someDatePlusOneHour,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(-1);
+ expect(invertedArgsResult).toBe(1);
+ });
+
+ it('sorts events without end date by start date ascending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ };
+ const eventB = {
+ startsAt: someDatePlusOneHour,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(-1);
+ expect(invertedArgsResult).toBe(1);
+ });
+
+ it('returns 0 for events with same start date and no end date', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ };
+ const eventB = {
+ startsAt: someDate,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(0);
+ expect(invertedArgsResult).toBe(0);
+ });
+
+ it('returns 0 for events with same start date if one of them has no end date', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusOneHour,
+ };
+ const eventB = {
+ startsAt: someDate,
+ };
+
+ // When
+ const result = sortCalendarEventsAsc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsAsc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(0);
+ expect(invertedArgsResult).toBe(0);
+ });
+});
+
+describe('sortCalendarEventsDesc', () => {
+ it('sorts non-intersecting events by descending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusOneHour,
+ };
+ const eventB = {
+ startsAt: someDatePlusTwoHours,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(1);
+ expect(invertedArgsResult).toBe(-1);
+ });
+
+ it('sorts intersecting events by start date descending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusTwoHours,
+ };
+ const eventB = {
+ startsAt: someDatePlusOneHour,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(1);
+ expect(invertedArgsResult).toBe(-1);
+ });
+
+ it('sorts events with same start date by end date descending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusTwoHours,
+ };
+ const eventB = {
+ startsAt: someDate,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(1);
+ expect(invertedArgsResult).toBe(-1);
+ });
+
+ it('sorts events with same end date by start date descending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusThreeHours,
+ };
+ const eventB = {
+ startsAt: someDatePlusOneHour,
+ endsAt: someDatePlusThreeHours,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(1);
+ expect(invertedArgsResult).toBe(-1);
+ });
+
+ it('sorts events without end date by start date descending order', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ };
+ const eventB = {
+ startsAt: someDatePlusOneHour,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result).toBe(1);
+ expect(invertedArgsResult).toBe(-1);
+ });
+
+ it('returns 0 for events with same start date and no end date', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ };
+ const eventB = {
+ startsAt: someDate,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result === 0).toBe(true);
+ expect(invertedArgsResult === 0).toBe(true);
+ });
+
+ it('returns 0 for events with same start date if one of them has no end date', () => {
+ // Given
+ const eventA = {
+ startsAt: someDate,
+ endsAt: someDatePlusOneHour,
+ };
+ const eventB = {
+ startsAt: someDate,
+ };
+
+ // When
+ const result = sortCalendarEventsDesc(eventA, eventB);
+ const invertedArgsResult = sortCalendarEventsDesc(eventB, eventA);
+
+ // Then
+ expect(result === 0).toBe(true);
+ expect(invertedArgsResult === 0).toBe(true);
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts b/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts
new file mode 100644
index 0000000000..6150037e8d
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/calendar/utils/sortCalendarEvents.ts
@@ -0,0 +1,26 @@
+import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
+import { sortAsc } from '~/utils/sort';
+
+export const sortCalendarEventsAsc = (
+ calendarEventA: Pick,
+ calendarEventB: Pick,
+) => {
+ const startsAtSort = sortAsc(
+ calendarEventA.startsAt.getTime(),
+ calendarEventB.startsAt.getTime(),
+ );
+
+ if (startsAtSort === 0 && calendarEventA.endsAt && calendarEventB.endsAt) {
+ return sortAsc(
+ calendarEventA.endsAt.getTime(),
+ calendarEventB.endsAt.getTime(),
+ );
+ }
+
+ return startsAtSort;
+};
+
+export const sortCalendarEventsDesc = (
+ calendarEventA: Pick,
+ calendarEventB: Pick,
+) => -sortCalendarEventsAsc(calendarEventA, calendarEventB);
diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx
index 678a3b2757..2dc6846bac 100644
--- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
+import { Calendar } from '@/activities/calendar/components/Calendar';
import { EmailThreads } from '@/activities/emails/components/EmailThreads';
import { Attachments } from '@/activities/files/components/Attachments';
import { Notes } from '@/activities/notes/components/Notes';
@@ -136,6 +137,7 @@ export const ShowPageRightContainer = ({
)}
{activeTabId === 'emails' && }
+ {activeTabId === 'calendar' && }
);
};
diff --git a/packages/twenty-front/src/testing/mock-data/calendar.ts b/packages/twenty-front/src/testing/mock-data/calendar.ts
new file mode 100644
index 0000000000..7435ef582a
--- /dev/null
+++ b/packages/twenty-front/src/testing/mock-data/calendar.ts
@@ -0,0 +1,52 @@
+import { addDays, subMonths } from 'date-fns';
+
+import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
+
+export const mockedCalendarEvents: CalendarEvent[] = [
+ {
+ id: '9a6b35f1-6078-415b-9540-f62671bb81d0',
+ endsAt: addDays(new Date().setHours(11, 30), 1),
+ isFullDay: false,
+ startsAt: addDays(new Date().setHours(10, 0), 1),
+ visibility: 'METADATA',
+ },
+ {
+ id: '19b32878-a950-4968-9e3b-ce5da514ea41',
+ endsAt: new Date(new Date().setHours(18, 40)),
+ isCanceled: true,
+ isFullDay: false,
+ startsAt: new Date(new Date().setHours(18, 0)),
+ title: 'Bug solving',
+ visibility: 'SHARE_EVERYTHING',
+ },
+ {
+ id: '6ad1cbcb-2ac4-409e-aff0-48165556fc0c',
+ endsAt: new Date(new Date().setHours(16, 30)),
+ isFullDay: false,
+ startsAt: new Date(new Date().setHours(15, 15)),
+ title: 'Onboarding Follow-Up Call',
+ visibility: 'SHARE_EVERYTHING',
+ },
+ {
+ id: '52cc83e3-f3dc-4c25-8a7d-5ff857612142',
+ endsAt: new Date(new Date().setHours(10, 30)),
+ isFullDay: false,
+ startsAt: new Date(new Date().setHours(10, 0)),
+ title: 'Onboarding Call',
+ visibility: 'SHARE_EVERYTHING',
+ },
+ {
+ id: '5a792d11-259a-4099-af51-59eb85e15d83',
+ isFullDay: true,
+ startsAt: subMonths(new Date().setHours(8, 0), 1),
+ visibility: 'METADATA',
+ },
+ {
+ id: '89e2a1c7-3d3f-4e79-a492-aa5de3785fc5',
+ endsAt: subMonths(new Date().setHours(14, 30), 3),
+ isFullDay: false,
+ startsAt: subMonths(new Date().setHours(14, 0), 3),
+ title: 'Alan x Garry',
+ visibility: 'SHARE_EVERYTHING',
+ },
+];
diff --git a/packages/twenty-front/src/utils/array/groupArrayItemsBy.ts b/packages/twenty-front/src/utils/array/groupArrayItemsBy.ts
index 37b1c9b704..1aa3fac710 100644
--- a/packages/twenty-front/src/utils/array/groupArrayItemsBy.ts
+++ b/packages/twenty-front/src/utils/array/groupArrayItemsBy.ts
@@ -17,10 +17,7 @@
* vegetable: [{ id: '2', type: 'vegetable' }],
* }
*/
-export const groupArrayItemsBy = <
- ArrayItem extends Record,
- Key extends string,
->(
+export const groupArrayItemsBy = (
array: ArrayItem[],
computeGroupKey: (item: ArrayItem) => Key,
) =>