diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx
new file mode 100644
index 0000000000..f5728d786a
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx
@@ -0,0 +1,84 @@
+import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
+import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
+import { useRecordCalendarContextOrThrow } from '@/object-record/record-calendar/contexts/RecordCalendarContext';
+import { useRecordCalendarQueryDateRangeFilter } from '@/object-record/record-calendar/month/hooks/useRecordCalendarQueryDateRangeFilter';
+import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
+import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { combineFilters } from '@/object-record/record-filter/utils/combineFilters';
+import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
+import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
+import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
+import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
+import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { useEffect } from 'react';
+
+export const RecordIndexCalendarDataLoaderEffect = () => {
+ const selectedDate = new Date();
+ const { objectMetadataItem } = useRecordCalendarContextOrThrow();
+
+ const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore();
+
+ const currentRecordFilterGroups = useRecoilComponentValue(
+ currentRecordFilterGroupsComponentState,
+ );
+
+ const currentRecordFilters = useRecoilComponentValue(
+ currentRecordFiltersComponentState,
+ );
+
+ const currentRecordSorts = useRecoilComponentValue(
+ currentRecordSortsComponentState,
+ );
+
+ const setRecordIndexAllRecordIdsSelector = useSetRecoilComponentState(
+ recordIndexAllRecordIdsComponentSelector,
+ );
+
+ const { filterValueDependencies } = useFilterValueDependencies();
+
+ const requestFilters = computeRecordGqlOperationFilter({
+ filterValueDependencies,
+ recordFilters: currentRecordFilters,
+ recordFilterGroups: currentRecordFilterGroups,
+ fields: objectMetadataItem.fields,
+ });
+
+ const anyFieldFilterValue = useRecoilComponentValue(
+ anyFieldFilterValueComponentState,
+ );
+
+ const { recordGqlOperationFilter: anyFieldFilter } =
+ turnAnyFieldFilterIntoRecordGqlFilter({
+ objectMetadataItem,
+ filterValue: anyFieldFilterValue,
+ });
+
+ const { dateRangeFilter } =
+ useRecordCalendarQueryDateRangeFilter(selectedDate);
+
+ const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
+
+ const combinedFilters = combineFilters([
+ anyFieldFilter,
+ requestFilters,
+ dateRangeFilter,
+ ]);
+
+ const { records } = useFindManyRecords({
+ objectNameSingular: objectMetadataItem.nameSingular,
+ filter: combinedFilters,
+ orderBy,
+ limit: 100,
+ });
+
+ useEffect(() => {
+ upsertRecordsInStore(records);
+ setRecordIndexAllRecordIdsSelector(records.map((record) => record.id));
+ }, [records, setRecordIndexAllRecordIdsSelector, upsertRecordsInStore]);
+
+ return <>>;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonthBodyDay.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonthBodyDay.tsx
index fa12479b0a..ec9526270f 100644
--- a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonthBodyDay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonthBodyDay.tsx
@@ -1,3 +1,5 @@
+import { calendarDayRecordIdsComponentFamilySelector } from '@/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector';
+import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
@@ -12,5 +14,14 @@ type RecordCalendarMonthBodyDayProps = {
export const RecordCalendarMonthBodyDay = ({
day,
}: RecordCalendarMonthBodyDayProps) => {
- return {day.getDate()};
+ const recordIds = useRecoilComponentFamilyValue(
+ calendarDayRecordIdsComponentFamilySelector,
+ day.toDateString(),
+ );
+
+ return (
+
+ {day.getDate()}: {recordIds.length}
+
+ );
};
diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarQueryDateRangeFilter.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarQueryDateRangeFilter.tsx
new file mode 100644
index 0000000000..a365fd78cb
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarQueryDateRangeFilter.tsx
@@ -0,0 +1,60 @@
+import { useRecordCalendarContextOrThrow } from '@/object-record/record-calendar/contexts/RecordCalendarContext';
+import { useRecordCalendarMonthDaysRange } from '@/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange';
+import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
+import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
+import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
+import { isDefined } from 'twenty-shared/utils';
+
+const DATE_RANGE_FILTER_AFTER_ID = 'DATE_RANGE_FILTER_AFTER_ID';
+const DATE_RANGE_FILTER_BEFORE_ID = 'DATE_RANGE_FILTER_BEFORE_ID';
+
+export const useRecordCalendarQueryDateRangeFilter = (selectedDate: Date) => {
+ const { objectMetadataItem } = useRecordCalendarContextOrThrow();
+ const { firstDayOfFirstWeek, lastDayOfLastWeek } =
+ useRecordCalendarMonthDaysRange(selectedDate);
+
+ const { currentView } = useGetCurrentViewOnly();
+
+ if (
+ !isDefined(currentView) ||
+ !isDefined(currentView.calendarFieldMetadataId)
+ ) {
+ return {
+ dateRangeFilter: {},
+ };
+ }
+
+ const dateRangeFilterFieldMetadataId = currentView.calendarFieldMetadataId;
+
+ const dateRangeFilterAfter: RecordFilter = {
+ id: DATE_RANGE_FILTER_AFTER_ID,
+ fieldMetadataId: dateRangeFilterFieldMetadataId,
+ value: `${firstDayOfFirstWeek.toISOString()}`,
+ operand: RecordFilterOperand.IsAfter,
+ type: 'DATE',
+ label: 'After',
+ displayValue: `${firstDayOfFirstWeek.toISOString()}`,
+ };
+
+ const dateRangeFilterBefore: RecordFilter = {
+ id: DATE_RANGE_FILTER_BEFORE_ID,
+ fieldMetadataId: dateRangeFilterFieldMetadataId,
+ value: `${lastDayOfLastWeek.toISOString()}`,
+ operand: RecordFilterOperand.IsBefore,
+ type: 'DATE',
+ label: 'Before',
+ displayValue: `${lastDayOfLastWeek.toISOString()}`,
+ };
+
+ const dateRangeFilter = computeRecordGqlOperationFilter({
+ filterValueDependencies: {},
+ recordFilters: [dateRangeFilterAfter, dateRangeFilterBefore],
+ recordFilterGroups: [],
+ fields: objectMetadataItem.fields,
+ });
+
+ return {
+ dateRangeFilter,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts
new file mode 100644
index 0000000000..ee604de744
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts
@@ -0,0 +1,65 @@
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext';
+import { recordIndexCalendarFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexCalendarFieldMetadataIdState';
+import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
+import { isSameDay } from 'date-fns';
+import { isDefined } from 'twenty-shared/utils';
+
+export const calendarDayRecordIdsComponentFamilySelector =
+ createComponentFamilySelector({
+ key: 'calendarDayRecordsComponentFamilySelector',
+ componentInstanceContext: RecordCalendarComponentInstanceContext,
+ get:
+ ({ instanceId, familyKey: dayAsString }) =>
+ ({ get }) => {
+ const calendarFieldMetadataId = get(
+ recordIndexCalendarFieldMetadataIdState,
+ );
+
+ const objectMetadataItems = get(objectMetadataItemsState);
+ const objectMetadataItem = objectMetadataItems.find(
+ (objectMetadataItem) =>
+ objectMetadataItem.fields.some(
+ (fieldMetadataItem) =>
+ fieldMetadataItem.id === calendarFieldMetadataId,
+ ),
+ );
+
+ if (!isDefined(objectMetadataItem)) {
+ return [];
+ }
+
+ const fieldMetadataItem = objectMetadataItem.fields.find(
+ (fieldMetadataItem) =>
+ fieldMetadataItem.id === calendarFieldMetadataId,
+ );
+
+ if (!isDefined(fieldMetadataItem)) {
+ return [];
+ }
+
+ const allRecordIds = get(
+ recordIndexAllRecordIdsComponentSelector.selectorFamily({
+ instanceId,
+ }),
+ );
+
+ const recordIds = allRecordIds.filter((recordId) => {
+ const record = get(recordStoreFamilyState(recordId));
+ const recordDate = record?.[fieldMetadataItem.name];
+
+ if (!recordDate) {
+ return false;
+ }
+
+ const dayDate = new Date(dayAsString);
+ const recordDateObj = new Date(recordDate);
+
+ return isSameDay(recordDateObj, dayDate);
+ });
+
+ return recordIds;
+ },
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexCalendarContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexCalendarContainer.tsx
index 50998c0613..ab16176efd 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexCalendarContainer.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexCalendarContainer.tsx
@@ -1,9 +1,12 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { RecordCalendar } from '@/object-record/record-calendar/components/RecordCalendar';
+import { RecordIndexCalendarDataLoaderEffect } from '@/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect';
import { RecordCalendarContextProvider } from '@/object-record/record-calendar/contexts/RecordCalendarContext';
import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
+import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
+import { isDefined } from 'twenty-shared/utils';
type RecordIndexCalendarContainerProps = {
recordCalendarInstanceId: string;
@@ -24,6 +27,15 @@ export const RecordIndexCalendarContainer = ({
objectMetadataItem.id,
);
+ const { currentView } = useGetCurrentViewOnly();
+
+ if (
+ !isDefined(currentView) ||
+ !isDefined(currentView.calendarFieldMetadataId)
+ ) {
+ return null;
+ }
+
return (
+
);
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
index e8761d901c..74429d8da3 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
@@ -5,6 +5,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
+import { recordIndexCalendarFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexCalendarFieldMetadataIdState';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
@@ -38,6 +39,10 @@ export const useLoadRecordIndexStates = () => {
const setRecordIndexViewKanbanFieldMetadataIdState = useSetRecoilState(
recordIndexKanbanFieldMetadataIdState,
);
+
+ const setRecordIndexCalendarFieldMetadataIdState = useSetRecoilState(
+ recordIndexCalendarFieldMetadataIdState,
+ );
const setRecordIndexViewKanbanAggregateOperationState = useSetRecoilState(
recordIndexKanbanAggregateOperationState,
);
@@ -182,6 +187,9 @@ export const useLoadRecordIndexStates = () => {
setRecordIndexViewKanbanFieldMetadataIdState(
view.viewGroups?.[0]?.fieldMetadataId,
);
+ setRecordIndexCalendarFieldMetadataIdState(
+ view.calendarFieldMetadataId ?? null,
+ );
const kanbanAggregateOperationFieldMetadataType =
objectMetadataItem.fields?.find(
(field) =>
@@ -202,11 +210,12 @@ export const useLoadRecordIndexStates = () => {
onViewFieldsChange,
setRecordGroupsFromViewGroups,
setContextStoreTargetedRecordsRuleComponentState,
- setRecordIndexIsCompactModeActive,
- setRecordIndexViewKanbanAggregateOperationState,
- setRecordIndexViewKanbanFieldMetadataIdState,
setRecordIndexViewType,
setRecordIndexOpenRecordIn,
+ setRecordIndexViewKanbanFieldMetadataIdState,
+ setRecordIndexCalendarFieldMetadataIdState,
+ setRecordIndexViewKanbanAggregateOperationState,
+ setRecordIndexIsCompactModeActive,
],
);
diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarFieldMetadataIdState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarFieldMetadataIdState.ts
new file mode 100644
index 0000000000..aa5d603c24
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarFieldMetadataIdState.ts
@@ -0,0 +1,8 @@
+import { createState } from 'twenty-ui/utilities';
+
+export const recordIndexCalendarFieldMetadataIdState = createState<
+ string | null
+>({
+ key: 'recordIndexCalendarFieldMetadataIdState',
+ defaultValue: null,
+});
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts
index 4324d81465..e44814cc7b 100644
--- a/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts
@@ -1,10 +1,15 @@
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
+import { type ComponentSelector } from '@/ui/utilities/state/component-state/types/ComponentSelector';
import { type ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
-import { type SetterOrUpdater, useSetRecoilState } from 'recoil';
+import {
+ type RecoilState,
+ type SetterOrUpdater,
+ useSetRecoilState,
+} from 'recoil';
export const useSetRecoilComponentState = (
- componentState: ComponentState,
+ componentState: ComponentState | ComponentSelector,
instanceIdFromProps?: string,
): SetterOrUpdater => {
const componentInstanceContext = globalComponentInstanceContextMap.get(
@@ -22,5 +27,15 @@ export const useSetRecoilComponentState = (
instanceIdFromProps,
);
- return useSetRecoilState(componentState.atomFamily({ instanceId }));
+ let state: RecoilState;
+
+ if (componentState.type === 'ComponentState') {
+ state = componentState.atomFamily({ instanceId });
+ } else if (componentState.type === 'ComponentSelector') {
+ state = componentState.selectorFamily({ instanceId });
+ } else {
+ throw new Error('Invalid component state type');
+ }
+
+ return useSetRecoilState(state);
};