diff --git a/packages/twenty-front/src/modules/activities/components/ActivityEditor.tsx b/packages/twenty-front/src/modules/activities/components/ActivityEditor.tsx index 34578d1bd..eda471017 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityEditor.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityEditor.tsx @@ -90,6 +90,7 @@ export const ActivityEditor = ({ objectRecordId: activity.id, fieldMetadataName: 'dueAt', fieldPosition: 0, + clearable: true, }); const { FieldContextProvider: AssigneeFieldContextProvider } = @@ -98,6 +99,7 @@ export const ActivityEditor = ({ objectRecordId: activity.id, fieldMetadataName: 'assignee', fieldPosition: 1, + clearable: true, }); const updateTitle = useCallback( diff --git a/packages/twenty-front/src/modules/object-record/field/components/FieldInput.tsx b/packages/twenty-front/src/modules/object-record/field/components/FieldInput.tsx index 702b9e8b8..8f7328710 100644 --- a/packages/twenty-front/src/modules/object-record/field/components/FieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/field/components/FieldInput.tsx @@ -91,8 +91,6 @@ export const FieldInput = ({ onEnter={onEnter} onEscape={onEscape} onClickOutside={onClickOutside} - onTab={onTab} - onShiftTab={onShiftTab} /> ) : isFieldNumber(fieldDefinition) ? ( ( diff --git a/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useDateTimeField.ts b/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useDateTimeField.ts index b5d153292..bbe23b5a2 100644 --- a/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useDateTimeField.ts +++ b/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useDateTimeField.ts @@ -7,7 +7,8 @@ import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; import { isFieldDateTime } from '../../types/guards/isFieldDateTime'; export const useDateTimeField = () => { - const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + const { entityId, fieldDefinition, hotkeyScope, clearable } = + useContext(FieldContext); assertFieldMetadata('DATE_TIME', isFieldDateTime, fieldDefinition); @@ -25,5 +26,6 @@ export const useDateTimeField = () => { fieldValue, setFieldValue, hotkeyScope, + clearable, }; }; diff --git a/packages/twenty-front/src/modules/object-record/field/meta-types/input/components/DateFieldInput.tsx b/packages/twenty-front/src/modules/object-record/field/meta-types/input/components/DateFieldInput.tsx index b6d089c36..e22f4601a 100644 --- a/packages/twenty-front/src/modules/object-record/field/meta-types/input/components/DateFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/field/meta-types/input/components/DateFieldInput.tsx @@ -10,8 +10,6 @@ export type DateFieldInputProps = { onClickOutside?: FieldInputEvent; onEnter?: FieldInputEvent; onEscape?: FieldInputEvent; - onTab?: FieldInputEvent; - onShiftTab?: FieldInputEvent; }; export const DateFieldInput = ({ @@ -19,13 +17,13 @@ export const DateFieldInput = ({ onEscape, onClickOutside, }: DateFieldInputProps) => { - const { fieldValue, hotkeyScope } = useDateTimeField(); + const { fieldValue, hotkeyScope, clearable } = useDateTimeField(); const persistField = usePersistField(); const persistDate = (newDate: Nullable) => { if (!newDate) { - persistField(''); + persistField(null); } else { const newDateISO = newDate?.toISOString(); @@ -57,6 +55,7 @@ export const DateFieldInput = ({ onEnter={handleEnter} onEscape={handleEscape} value={dateValue} + clearable={clearable} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/field/types/guards/isFieldDateTimeValue.ts b/packages/twenty-front/src/modules/object-record/field/types/guards/isFieldDateTimeValue.ts index 7222b1cd5..2a559e329 100644 --- a/packages/twenty-front/src/modules/object-record/field/types/guards/isFieldDateTimeValue.ts +++ b/packages/twenty-front/src/modules/object-record/field/types/guards/isFieldDateTimeValue.ts @@ -1,4 +1,4 @@ -import { isString } from '@sniptt/guards'; +import { isNull, isString } from '@sniptt/guards'; import { FieldDateTimeValue } from '../FieldMetadata'; @@ -6,4 +6,5 @@ import { FieldDateTimeValue } from '../FieldMetadata'; export const isFieldDateTimeValue = ( fieldValue: unknown, ): fieldValue is FieldDateTimeValue => - isString(fieldValue) && !isNaN(Date.parse(fieldValue)); + (isString(fieldValue) && !isNaN(Date.parse(fieldValue))) || + isNull(fieldValue); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFieldContext.tsx b/packages/twenty-front/src/modules/object-record/hooks/useFieldContext.tsx index 3e0590583..1ec53e0a3 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFieldContext.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/useFieldContext.tsx @@ -15,11 +15,13 @@ export const useFieldContext = ({ fieldMetadataName, objectRecordId, fieldPosition, + clearable, }: { objectNameSingular: string; objectRecordId: string; fieldMetadataName: string; fieldPosition: number; + clearable?: boolean; }) => { const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, @@ -60,6 +62,7 @@ export const useFieldContext = ({ }), useUpdateRecord: useUpdateOneObjectMutation, hotkeyScope: InlineCellHotkeyScope.InlineCell, + clearable, }} > {children} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx index e9612e630..a7154459a 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx @@ -1,5 +1,6 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; +import { isDefined } from '~/utils/isDefined'; export const ObjectFilterDropdownDateSearchInput = () => { const { @@ -9,14 +10,14 @@ export const ObjectFilterDropdownDateSearchInput = () => { selectFilter, } = useFilterDropdown(); - const handleChange = (date: Date) => { + const handleChange = (date: Date | null) => { if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; selectFilter?.({ fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, - value: date.toISOString(), + value: isDefined(date) ? date.toISOString() : '', operand: selectedOperandInDropdown, - displayValue: date.toLocaleDateString(), + displayValue: isDefined(date) ? date.toLocaleString() : '', definition: filterDefinitionUsedInDropdown, }); diff --git a/packages/twenty-front/src/modules/ui/display/icon/index.ts b/packages/twenty-front/src/modules/ui/display/icon/index.ts index 7fa10d40c..3de488f8e 100644 --- a/packages/twenty-front/src/modules/ui/display/icon/index.ts +++ b/packages/twenty-front/src/modules/ui/display/icon/index.ts @@ -24,6 +24,7 @@ export { IconBuildingSkyscraper, IconCalendar, IconCalendarEvent, + IconCalendarX, IconCheck, IconCheckbox, IconChevronDown, diff --git a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx index fb9ac3833..9a52f0389 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx @@ -37,6 +37,7 @@ export type DateInputProps = { newDate: Nullable, ) => void; hotkeyScope: string; + clearable?: boolean; }; export const DateInput = ({ @@ -45,6 +46,7 @@ export const DateInput = ({ onEnter, onEscape, onClickOutside, + clearable, }: DateInputProps) => { const theme = useTheme(); @@ -91,9 +93,10 @@ export const DateInput = ({ { + onMouseSelect={(newDate: Date | null) => { onEnter(newDate); }} + clearable={clearable ? clearable : false} /> diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index 3d76158eb..313e6ee21 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -2,6 +2,8 @@ import React from 'react'; import ReactDatePicker from 'react-datepicker'; import styled from '@emotion/styled'; +import { IconCalendarX } from '@/ui/display/icon'; +import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { overlayBackground } from '@/ui/theme/constants/effects'; import 'react-datepicker/dist/react-datepicker.css'; @@ -218,36 +220,76 @@ const StyledContainer = styled.div` & .react-datepicker__day:hover { color: ${({ theme }) => theme.font.color.tertiary}; } + + & .clearable { + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + } +`; + +const StyledButtonContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + padding-left: ${({ theme }) => theme.spacing(2)}; + + & .menu-item { + height: ${({ theme }) => theme.spacing(8)}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; + margin-top: ${({ theme }) => theme.spacing(1)}; + padding: 0 ${({ theme }) => theme.spacing(2)}; + width: fit-content; + } `; export type InternalDatePickerProps = { - date: Date; - onMouseSelect?: (date: Date) => void; + date: Date | null; + onMouseSelect?: (date: Date | null) => void; onChange?: (date: Date) => void; + clearable?: boolean; }; export const InternalDatePicker = ({ date, onChange, onMouseSelect, -}: InternalDatePickerProps) => ( - - { - // We need to use onSelect here but onChange is almost redundant with onSelect but is required - }} - customInput={<>} - onSelect={(date: Date, event) => { - if (event?.type === 'click') { - onMouseSelect?.(date); - } else { - onChange?.(date); - } - }} - /> - -); + clearable = true, +}: InternalDatePickerProps) => { + const handleClear = () => { + onMouseSelect?.(null); + }; + + return ( + +
+ { + // We need to use onSelect here but onChange is almost redundant with onSelect but is require + }} + customInput={<>} + onSelect={(date: Date, event) => { + if (event?.type === 'click') { + onMouseSelect?.(date); + } else { + onChange?.(date); + } + }} + > +
+ {clearable && ( + + + + )} +
+ ); +};