Load data into calendar view (#14494)
<img width="1919" height="956" alt="image" src="https://github.com/user-attachments/assets/1389b1e9-63d5-4967-9587-681d8b151b07" />
This commit is contained in:
+84
@@ -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 <></>;
|
||||
};
|
||||
+12
-1
@@ -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 <StyledContainer>{day.getDate()}</StyledContainer>;
|
||||
const recordIds = useRecoilComponentFamilyValue(
|
||||
calendarDayRecordIdsComponentFamilySelector,
|
||||
day.toDateString(),
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{day.getDate()}: {recordIds.length}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+60
@@ -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,
|
||||
};
|
||||
};
|
||||
+65
@@ -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<string[], string>({
|
||||
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;
|
||||
},
|
||||
});
|
||||
+13
@@ -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 (
|
||||
<RecordCalendarComponentInstanceContext.Provider
|
||||
value={{
|
||||
@@ -40,6 +52,7 @@ export const RecordIndexCalendarContainer = ({
|
||||
}}
|
||||
>
|
||||
<RecordCalendar />
|
||||
<RecordIndexCalendarDataLoaderEffect />
|
||||
</RecordCalendarContextProvider>
|
||||
</RecordCalendarComponentInstanceContext.Provider>
|
||||
);
|
||||
|
||||
+12
-3
@@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const recordIndexCalendarFieldMetadataIdState = createState<
|
||||
string | null
|
||||
>({
|
||||
key: 'recordIndexCalendarFieldMetadataIdState',
|
||||
defaultValue: null,
|
||||
});
|
||||
+18
-3
@@ -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 = <ValueType>(
|
||||
componentState: ComponentState<ValueType>,
|
||||
componentState: ComponentState<ValueType> | ComponentSelector<ValueType>,
|
||||
instanceIdFromProps?: string,
|
||||
): SetterOrUpdater<ValueType> => {
|
||||
const componentInstanceContext = globalComponentInstanceContextMap.get(
|
||||
@@ -22,5 +27,15 @@ export const useSetRecoilComponentState = <ValueType>(
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
return useSetRecoilState(componentState.atomFamily({ instanceId }));
|
||||
let state: RecoilState<ValueType>;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user