add date picker on calendar view (#14511)

<img width="1280" height="352" alt="Screenshot 2025-09-15 at 16 34 50"
src="https://github.com/user-attachments/assets/b3108030-24e2-46d7-be3d-68a9095d79c6"
/>
This commit is contained in:
Weiko
2025-09-15 16:43:52 +02:00
committed by GitHub
parent 31d15331db
commit e8c7dd8380
3 changed files with 142 additions and 24 deletions
@@ -1,14 +1,42 @@
import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext';
import { recordCalendarSelectedDateComponentState } from '@/object-record/record-calendar/states/recordCalendarSelectedDateComponentState';
import { recordIndexCalendarLayoutState } from '@/object-record/record-index/states/recordIndexCalendarLayoutState';
import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { Select } from '@/ui/input/components/Select';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { type DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import styled from '@emotion/styled';
import { addMonths, format, subMonths } from 'date-fns';
import { useRecoilValue } from 'recoil';
import { type Nullable } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronLeft, IconChevronRight } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { ViewCalendarLayout } from '~/generated/graphql';
const StyledContainer = styled.div`
align-items: center;
display: flex;
height: 24px;
justify-content: space-between;
width: 100%;
`;
const StyledLeftSection = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledNavigationSection = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing(1)};
`;
export const RecordCalendarTopBar = () => {
@@ -20,29 +48,106 @@ export const RecordCalendarTopBar = () => {
recordIndexCalendarLayoutState,
);
const [recordCalendarSelectedDate, setRecordCalendarSelectedDate] =
useRecoilComponentState(recordCalendarSelectedDateComponentState);
const datePickerDropdownId = `record-calendar-date-picker-${recordCalendarId}`;
const { closeDropdown } = useCloseDropdown();
const handleDateChange = (date: Nullable<Date>) => {
if (isDefined(date)) {
setRecordCalendarSelectedDate(date);
}
closeDropdown(datePickerDropdownId);
};
const handlePreviousMonth = () => {
setRecordCalendarSelectedDate(subMonths(recordCalendarSelectedDate, 1));
};
const handleNextMonth = () => {
setRecordCalendarSelectedDate(addMonths(recordCalendarSelectedDate, 1));
};
const handleTodayClick = () => {
setRecordCalendarSelectedDate(new Date());
};
const formattedDate = format(recordCalendarSelectedDate, 'MMMM yyyy');
const dropdownContentOffset = { x: 140, y: 0 } satisfies DropdownOffset;
return (
<StyledContainer>
<Select
dropdownId={`record-calendar-top-bar-layout-select-${recordCalendarId}`}
selectSizeVariant="small"
options={[
{
label: 'Month',
value: ViewCalendarLayout.MONTH,
},
{
label: 'Week',
value: ViewCalendarLayout.WEEK,
},
{
label: 'Timeline',
value: ViewCalendarLayout.DAY,
},
]}
disabled
value={recordIndexCalendarLayout}
onChange={() => {}}
/>
<StyledLeftSection>
<Select
dropdownId={`record-calendar-top-bar-layout-select-${recordCalendarId}`}
selectSizeVariant="small"
options={[
{
label: 'Month',
value: ViewCalendarLayout.MONTH,
},
{
label: 'Week',
value: ViewCalendarLayout.WEEK,
},
{
label: 'Timeline',
value: ViewCalendarLayout.DAY,
},
]}
disabled
value={recordIndexCalendarLayout}
onChange={() => {}}
/>
<Dropdown
dropdownId={datePickerDropdownId}
clickableComponent={
<SelectControl
selectedOption={{
label: formattedDate,
value: formattedDate,
}}
selectSizeVariant="small"
/>
}
dropdownComponents={
<DropdownContent widthInPixels={280}>
<DateTimePicker
date={recordCalendarSelectedDate}
isDateTimeInput={false}
onChange={handleDateChange}
onClose={handleDateChange}
clearable={false}
hideHeaderInput
/>
</DropdownContent>
}
dropdownOffset={dropdownContentOffset}
/>
</StyledLeftSection>
<StyledNavigationSection>
<Button
size="small"
variant="tertiary"
Icon={IconChevronLeft}
onClick={handlePreviousMonth}
/>
<Button
size="small"
variant="tertiary"
title="Today"
onClick={handleTodayClick}
/>
<Button
size="small"
variant="tertiary"
Icon={IconChevronRight}
onClick={handleNextMonth}
/>
</StyledNavigationSection>
</StyledContainer>
);
};
@@ -2,6 +2,7 @@ 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 { recordCalendarSelectedDateComponentState } from '@/object-record/record-calendar/states/recordCalendarSelectedDateComponentState';
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';
@@ -17,7 +18,9 @@ import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state
import { useEffect } from 'react';
export const RecordIndexCalendarDataLoaderEffect = () => {
const selectedDate = new Date();
const recordCalendarSelectedDate = useRecoilComponentValue(
recordCalendarSelectedDateComponentState,
);
const { objectMetadataItem } = useRecordCalendarContextOrThrow();
const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore();
@@ -57,8 +60,9 @@ export const RecordIndexCalendarDataLoaderEffect = () => {
filterValue: anyFieldFilterValue,
});
const { dateRangeFilter } =
useRecordCalendarQueryDateRangeFilter(selectedDate);
const { dateRangeFilter } = useRecordCalendarQueryDateRangeFilter(
recordCalendarSelectedDate,
);
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
@@ -0,0 +1,9 @@
import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const recordCalendarSelectedDateComponentState =
createComponentState<Date>({
key: 'recordCalendarSelectedDateComponentState',
defaultValue: new Date(),
componentInstanceContext: RecordCalendarComponentInstanceContext,
});