-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Design: ![twenty-advanced-filters-design](https://github.com/user-attachments/assets/7d99971c-9ee1-4a78-a2fb-7ae5a9b3a836) Not ready to be merged yet! --------- Co-authored-by: Lucas Bordeau <[email protected]>
- Loading branch information
1 parent
1dfeba3
commit 315820e
Showing
99 changed files
with
3,349 additions
and
1,079 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
...rc/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; | ||
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup'; | ||
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; | ||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; | ||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; | ||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; | ||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; | ||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId'; | ||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; | ||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; | ||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; | ||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; | ||
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; | ||
import { useCallback } from 'react'; | ||
import { IconLibraryPlus, IconPlus, isDefined, LightButton } from 'twenty-ui'; | ||
import { v4 } from 'uuid'; | ||
|
||
type AdvancedFilterAddFilterRuleSelectProps = { | ||
viewFilterGroup: ViewFilterGroup; | ||
lastChildPosition?: number; | ||
}; | ||
|
||
export const AdvancedFilterAddFilterRuleSelect = ({ | ||
viewFilterGroup, | ||
lastChildPosition = 0, | ||
}: AdvancedFilterAddFilterRuleSelectProps) => { | ||
const dropdownId = `advanced-filter-add-filter-rule-${viewFilterGroup.id}`; | ||
|
||
const { currentViewId } = useGetCurrentView(); | ||
|
||
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup(); | ||
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(); | ||
|
||
const newPositionInViewFilterGroup = lastChildPosition + 1; | ||
|
||
const { closeDropdown } = useDropdown(dropdownId); | ||
|
||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); | ||
|
||
const objectMetadataId = | ||
currentViewWithCombinedFiltersAndSorts?.objectMetadataId; | ||
|
||
if (!objectMetadataId) { | ||
throw new Error('Object metadata id is missing from current view'); | ||
} | ||
|
||
const { objectMetadataItem } = useObjectMetadataItemById({ | ||
objectId: objectMetadataId, | ||
}); | ||
|
||
const availableFilterDefinitions = useRecoilComponentValueV2( | ||
availableFilterDefinitionsComponentState, | ||
); | ||
|
||
const getDefaultFilterDefinition = useCallback(() => { | ||
const defaultFilterDefinition = | ||
availableFilterDefinitions.find( | ||
(filterDefinition) => | ||
filterDefinition.fieldMetadataId === | ||
objectMetadataItem?.labelIdentifierFieldMetadataId, | ||
) ?? availableFilterDefinitions?.[0]; | ||
|
||
if (!defaultFilterDefinition) { | ||
throw new Error('Missing default filter definition'); | ||
} | ||
|
||
return defaultFilterDefinition; | ||
}, [availableFilterDefinitions, objectMetadataItem]); | ||
|
||
const handleAddFilter = () => { | ||
closeDropdown(); | ||
|
||
const defaultFilterDefinition = getDefaultFilterDefinition(); | ||
|
||
upsertCombinedViewFilter({ | ||
id: v4(), | ||
fieldMetadataId: defaultFilterDefinition.fieldMetadataId, | ||
operand: getOperandsForFilterDefinition(defaultFilterDefinition)[0], | ||
definition: defaultFilterDefinition, | ||
value: '', | ||
displayValue: '', | ||
viewFilterGroupId: viewFilterGroup.id, | ||
positionInViewFilterGroup: newPositionInViewFilterGroup, | ||
}); | ||
}; | ||
|
||
const handleAddFilterGroup = () => { | ||
closeDropdown(); | ||
|
||
if (!currentViewId) { | ||
throw new Error('Missing view id'); | ||
} | ||
|
||
const newViewFilterGroup = { | ||
id: v4(), | ||
viewId: currentViewId, | ||
logicalOperator: ViewFilterGroupLogicalOperator.AND, | ||
parentViewFilterGroupId: viewFilterGroup.id, | ||
positionInViewFilterGroup: newPositionInViewFilterGroup, | ||
}; | ||
|
||
upsertCombinedViewFilterGroup(newViewFilterGroup); | ||
|
||
const defaultFilterDefinition = getDefaultFilterDefinition(); | ||
|
||
upsertCombinedViewFilter({ | ||
id: v4(), | ||
fieldMetadataId: defaultFilterDefinition.fieldMetadataId, | ||
operand: getOperandsForFilterDefinition(defaultFilterDefinition)[0], | ||
definition: defaultFilterDefinition, | ||
value: '', | ||
displayValue: '', | ||
viewFilterGroupId: newViewFilterGroup.id, | ||
positionInViewFilterGroup: newPositionInViewFilterGroup, | ||
}); | ||
}; | ||
|
||
const isFilterRuleGroupOptionVisible = !isDefined( | ||
viewFilterGroup.parentViewFilterGroupId, | ||
); | ||
|
||
if (!isFilterRuleGroupOptionVisible) { | ||
return ( | ||
<LightButton | ||
Icon={IconPlus} | ||
title="Add filter rule" | ||
onClick={handleAddFilter} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<Dropdown | ||
disableBlur | ||
dropdownId={dropdownId} | ||
clickableComponent={ | ||
<LightButton Icon={IconPlus} title="Add filter rule" /> | ||
} | ||
dropdownComponents={ | ||
<DropdownMenuItemsContainer> | ||
<MenuItem | ||
LeftIcon={IconPlus} | ||
text="Add rule" | ||
onClick={handleAddFilter} | ||
/> | ||
{isFilterRuleGroupOptionVisible && ( | ||
<MenuItem | ||
LeftIcon={IconLibraryPlus} | ||
text="Add rule group" | ||
onClick={handleAddFilterGroup} | ||
/> | ||
)} | ||
</DropdownMenuItemsContainer> | ||
} | ||
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }} | ||
dropdownOffset={{ y: 8, x: 0 }} | ||
dropdownPlacement="bottom-start" | ||
/> | ||
); | ||
}; |
41 changes: 41 additions & 0 deletions
41
...rc/modules/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { AdvancedFilterLogicalOperatorDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorDropdown'; | ||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; | ||
import styled from '@emotion/styled'; | ||
import { capitalize } from '~/utils/string/capitalize'; | ||
|
||
const StyledText = styled.div` | ||
height: ${({ theme }) => theme.spacing(8)}; | ||
display: flex; | ||
align-items: center; | ||
`; | ||
|
||
const StyledContainer = styled.div` | ||
align-items: start; | ||
display: flex; | ||
min-width: ${({ theme }) => theme.spacing(20)}; | ||
color: ${({ theme }) => theme.font.color.tertiary}; | ||
`; | ||
|
||
type AdvancedFilterLogicalOperatorCellProps = { | ||
index: number; | ||
viewFilterGroup: ViewFilterGroup; | ||
}; | ||
|
||
export const AdvancedFilterLogicalOperatorCell = ({ | ||
index, | ||
viewFilterGroup, | ||
}: AdvancedFilterLogicalOperatorCellProps) => ( | ||
<StyledContainer> | ||
{index === 0 ? ( | ||
<StyledText>Where</StyledText> | ||
) : index === 1 ? ( | ||
<AdvancedFilterLogicalOperatorDropdown | ||
viewFilterGroup={viewFilterGroup} | ||
/> | ||
) : ( | ||
<StyledText> | ||
{capitalize(viewFilterGroup.logicalOperator.toLowerCase())} | ||
</StyledText> | ||
)} | ||
</StyledContainer> | ||
); |
33 changes: 33 additions & 0 deletions
33
...odules/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorDropdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions'; | ||
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup'; | ||
import { Select } from '@/ui/input/components/Select'; | ||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; | ||
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; | ||
|
||
type AdvancedFilterLogicalOperatorDropdownProps = { | ||
viewFilterGroup: ViewFilterGroup; | ||
}; | ||
|
||
export const AdvancedFilterLogicalOperatorDropdown = ({ | ||
viewFilterGroup, | ||
}: AdvancedFilterLogicalOperatorDropdownProps) => { | ||
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup(); | ||
|
||
const handleChange = (value: ViewFilterGroupLogicalOperator) => { | ||
upsertCombinedViewFilterGroup({ | ||
...viewFilterGroup, | ||
logicalOperator: value, | ||
}); | ||
}; | ||
|
||
return ( | ||
<Select | ||
disableBlur | ||
fullWidth | ||
dropdownId={`advanced-filter-logical-operator-${viewFilterGroup.id}`} | ||
value={viewFilterGroup.logicalOperator} | ||
onChange={handleChange} | ||
options={ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS} | ||
/> | ||
); | ||
}; |
78 changes: 78 additions & 0 deletions
78
...dules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect'; | ||
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell'; | ||
import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown'; | ||
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter'; | ||
import { AdvancedFilterViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup'; | ||
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup'; | ||
import styled from '@emotion/styled'; | ||
import { isDefined } from 'twenty-ui'; | ||
|
||
const StyledRow = styled.div` | ||
display: flex; | ||
width: 100%; | ||
gap: ${({ theme }) => theme.spacing(2)}; | ||
`; | ||
|
||
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>` | ||
align-items: start; | ||
background-color: ${({ theme, isGrayBackground }) => | ||
isGrayBackground ? theme.background.transparent.lighter : 'transparent'}; | ||
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`}; | ||
border-radius: ${({ theme }) => theme.border.radius.md}; | ||
display: flex; | ||
flex: 1; | ||
flex-direction: column; | ||
gap: ${({ theme }) => theme.spacing(2)}; | ||
padding: ${({ theme }) => theme.spacing(2)}; | ||
overflow: hidden; | ||
`; | ||
|
||
type AdvancedFilterRootLevelViewFilterGroupProps = { | ||
rootLevelViewFilterGroupId: string; | ||
}; | ||
|
||
export const AdvancedFilterRootLevelViewFilterGroup = ({ | ||
rootLevelViewFilterGroupId, | ||
}: AdvancedFilterRootLevelViewFilterGroupProps) => { | ||
const { | ||
currentViewFilterGroup: rootLevelViewFilterGroup, | ||
childViewFiltersAndViewFilterGroups, | ||
lastChildPosition, | ||
} = useCurrentViewViewFilterGroup({ | ||
viewFilterGroupId: rootLevelViewFilterGroupId, | ||
}); | ||
|
||
if (!isDefined(rootLevelViewFilterGroup)) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<StyledContainer> | ||
{childViewFiltersAndViewFilterGroups.map((child, i) => | ||
child.__typename === 'ViewFilterGroup' ? ( | ||
<StyledRow key={child.id}> | ||
<AdvancedFilterLogicalOperatorCell | ||
index={i} | ||
viewFilterGroup={rootLevelViewFilterGroup} | ||
/> | ||
<AdvancedFilterViewFilterGroup viewFilterGroupId={child.id} /> | ||
<AdvancedFilterRuleOptionsDropdown viewFilterGroupId={child.id} /> | ||
</StyledRow> | ||
) : ( | ||
<StyledRow key={child.id}> | ||
<AdvancedFilterLogicalOperatorCell | ||
index={i} | ||
viewFilterGroup={rootLevelViewFilterGroup} | ||
/> | ||
<AdvancedFilterViewFilter viewFilterId={child.id} /> | ||
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} /> | ||
</StyledRow> | ||
), | ||
)} | ||
<AdvancedFilterAddFilterRuleSelect | ||
viewFilterGroup={rootLevelViewFilterGroup} | ||
lastChildPosition={lastChildPosition} | ||
/> | ||
</StyledContainer> | ||
); | ||
}; |
Oops, something went wrong.