Move filter and sort compoenets in a separate lib (#612)

* Move filter and sort compoenets in a separate lib

* Add SortAndFilterBar to the filter lib

* Abstract hotkeys scopes

* Fix hotkeys on filters

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Emilien Chauvet 2023-07-11 20:42:15 -07:00 committed by GitHub
parent e8d77833a7
commit b5de2abd48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 213 additions and 174 deletions

View File

@ -1,10 +1,10 @@
import { FilterDropdownEntitySearchSelect } from '@/lib/filters-and-sorts/components/FilterDropdownEntitySearchSelect';
import { filterDropdownSearchInputScopedState } from '@/lib/filters-and-sorts/states/filterDropdownSearchInputScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '@/lib/filters-and-sorts/states/filterDropdownSelectedEntityIdScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/recoil-scope/hooks/useRecoilScopedValue';
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
import { FilterDropdownEntitySearchSelect } from '@/ui/components/table/table-header/FilterDropdownEntitySearchSelect';
import { TableContext } from '@/ui/tables/states/TableContext';
import { getLogoUrlFromDomainName } from '@/utils/utils';
import { useSearchCompanyQuery } from '~/generated/graphql';
@ -38,6 +38,9 @@ export function FilterDropdownCompanySearchSelect() {
});
return (
<FilterDropdownEntitySearchSelect entitiesForSelect={usersForSelect} />
<FilterDropdownEntitySearchSelect
entitiesForSelect={usersForSelect}
context={TableContext}
/>
);
}

View File

@ -4,11 +4,10 @@ import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useOutsideAlerter } from '@/ui/hooks/useOutsideAlerter';
import { IconChevronDown } from '@/ui/icons/index';
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter';
type OwnProps = {
label: string;
isActive: boolean;
@ -16,6 +15,7 @@ type OwnProps = {
isUnfolded?: boolean;
onIsUnfoldedChange?: (newIsUnfolded: boolean) => void;
resetState?: () => void;
hotkeysScope: InternalHotkeysScope;
};
const StyledDropdownButtonContainer = styled.div`
@ -160,13 +160,14 @@ function DropdownButton({
children,
isUnfolded = false,
onIsUnfoldedChange,
hotkeysScope,
}: OwnProps) {
useScopedHotkeys(
[Key.Enter, Key.Escape],
() => {
onIsUnfoldedChange?.(false);
},
InternalHotkeysScope.TableHeaderDropdownButton,
hotkeysScope,
[onIsUnfoldedChange],
);

View File

@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { Context, useCallback, useState } from 'react';
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
@ -10,7 +10,6 @@ import { filtersScopedState } from '@/lib/filters-and-sorts/states/filtersScoped
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/lib/filters-and-sorts/states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
import DropdownButton from './DropdownButton';
import { FilterDropdownDateSearchInput } from './FilterDropdownDateSearchInput';
@ -22,7 +21,13 @@ import { FilterDropdownOperandButton } from './FilterDropdownOperandButton';
import { FilterDropdownOperandSelect } from './FilterDropdownOperandSelect';
import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput';
export function FilterDropdownButton() {
export function FilterDropdownButton({
context,
hotkeysScope,
}: {
context: Context<string | null>;
hotkeysScope: InternalHotkeysScope;
}) {
const [isUnfolded, setIsUnfolded] = useState(false);
const [
@ -30,52 +35,45 @@ export function FilterDropdownButton() {
setIsFilterDropdownOperandSelectUnfolded,
] = useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState,
TableContext,
context,
);
const [
tableFilterDefinitionUsedInDropdown,
setTableFilterDefinitionUsedInDropdown,
] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
);
const [filterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown] =
useRecoilScopedState(filterDefinitionUsedInDropdownScopedState, context);
const [, setFilterDropdownSearchInput] = useRecoilScopedState(
filterDropdownSearchInputScopedState,
TableContext,
context,
);
const [activeTableFilters] = useRecoilScopedState(
filtersScopedState,
TableContext,
);
const [filters] = useRecoilScopedState(filtersScopedState, context);
const [selectedOperandInDropdown, setSelectedOperandInDropdown] =
useRecoilScopedState(selectedOperandInDropdownScopedState, TableContext);
useRecoilScopedState(selectedOperandInDropdownScopedState, context);
const resetState = useCallback(() => {
setIsFilterDropdownOperandSelectUnfolded(false);
setTableFilterDefinitionUsedInDropdown(null);
setFilterDefinitionUsedInDropdown(null);
setSelectedOperandInDropdown(null);
setFilterDropdownSearchInput('');
}, [
setTableFilterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setFilterDropdownSearchInput,
setIsFilterDropdownOperandSelectUnfolded,
]);
const isFilterSelected = (activeTableFilters?.length ?? 0) > 0;
const isFilterSelected = (filters?.length ?? 0) > 0;
const setHotkeysScope = useSetHotkeysScope();
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) {
setHotkeysScope(hotkeysScope);
setIsUnfolded(true);
} else {
if (tableFilterDefinitionUsedInDropdown?.type === 'entity') {
setHotkeysScope(InternalHotkeysScope.Table);
if (filterDefinitionUsedInDropdown?.type === 'entity') {
setHotkeysScope(hotkeysScope);
}
setIsUnfolded(false);
resetState();
@ -97,31 +95,32 @@ export function FilterDropdownButton() {
isActive={isFilterSelected}
isUnfolded={isUnfolded}
onIsUnfoldedChange={handleIsUnfoldedChange}
hotkeysScope={hotkeysScope}
>
{!tableFilterDefinitionUsedInDropdown ? (
<FilterDropdownFilterSelect />
{!filterDefinitionUsedInDropdown ? (
<FilterDropdownFilterSelect context={context} />
) : isFilterDropdownOperandSelectUnfolded ? (
<FilterDropdownOperandSelect />
<FilterDropdownOperandSelect context={context} />
) : (
selectedOperandInDropdown && (
<>
<FilterDropdownOperandButton />
<FilterDropdownOperandButton context={context} />
<DropdownButton.StyledSearchField autoFocus key={'search-filter'}>
{tableFilterDefinitionUsedInDropdown.type === 'text' && (
<FilterDropdownTextSearchInput />
{filterDefinitionUsedInDropdown.type === 'text' && (
<FilterDropdownTextSearchInput context={context} />
)}
{tableFilterDefinitionUsedInDropdown.type === 'number' && (
<FilterDropdownNumberSearchInput />
{filterDefinitionUsedInDropdown.type === 'number' && (
<FilterDropdownNumberSearchInput context={context} />
)}
{tableFilterDefinitionUsedInDropdown.type === 'date' && (
<FilterDropdownDateSearchInput />
{filterDefinitionUsedInDropdown.type === 'date' && (
<FilterDropdownDateSearchInput context={context} />
)}
{tableFilterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySearchInput />
{filterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySearchInput context={context} />
)}
</DropdownButton.StyledSearchField>
{tableFilterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySelect />
{filterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySelect context={context} />
)}
</>
)

View File

@ -1,33 +1,35 @@
import { Context } from 'react';
import styled from '@emotion/styled';
import { useUpsertFilter } from '@/lib/filters-and-sorts/hooks/useUpsertFilter';
import { filterDefinitionUsedInDropdownScopedState } from '@/lib/filters-and-sorts/states/filterDefinitionUsedInDropdownScopedState';
import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
import DatePicker from '@/ui/components/form/DatePicker';
import DatePicker from '../../form/DatePicker';
export function FilterDropdownDateSearchInput() {
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownDateSearchInput({
context,
}: {
context: Context<string | null>;
}) {
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const upsertActiveTableFilter = useUpsertFilter(TableContext);
const upsertFilter = useUpsertFilter(context);
function handleChange(date: Date) {
if (!tableFilterDefinitionUsedInDropdown || !selectedOperandInDropdown)
return;
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
upsertActiveTableFilter({
field: tableFilterDefinitionUsedInDropdown.field,
type: tableFilterDefinitionUsedInDropdown.type,
upsertFilter({
field: filterDefinitionUsedInDropdown.field,
type: filterDefinitionUsedInDropdown.type,
value: date.toISOString(),
operand: selectedOperandInDropdown,
displayValue: date.toLocaleDateString(),

View File

@ -1,32 +1,35 @@
import { ChangeEvent } from 'react';
import { ChangeEvent, Context } from 'react';
import { filterDefinitionUsedInDropdownScopedState } from '@/lib/filters-and-sorts/states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '@/lib/filters-and-sorts/states/filterDropdownSearchInputScopedState';
import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
export function FilterDropdownEntitySearchInput() {
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownEntitySearchInput({
context,
}: {
context: Context<string | null>;
}) {
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const [filterDropdownSearchInput, setFilterDropdownSearchInput] =
useRecoilScopedState(filterDropdownSearchInputScopedState, TableContext);
useRecoilScopedState(filterDropdownSearchInputScopedState, context);
return (
tableFilterDefinitionUsedInDropdown &&
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
<input
type="text"
value={filterDropdownSearchInput}
placeholder={tableFilterDefinitionUsedInDropdown.label}
placeholder={filterDefinitionUsedInDropdown.label}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setFilterDropdownSearchInput(event.target.value);
}}

View File

@ -10,36 +10,34 @@ import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState'
import { EntitiesForMultipleEntitySelect } from '@/relation-picker/components/MultipleEntitySelect';
import { SingleEntitySelectBase } from '@/relation-picker/components/SingleEntitySelectBase';
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
import { TableContext } from '@/ui/tables/states/TableContext';
export function FilterDropdownEntitySearchSelect({
entitiesForSelect,
context,
}: {
entitiesForSelect: EntitiesForMultipleEntitySelect<EntityForSelect>;
context: React.Context<string | null>;
}) {
const [filterDropdownSelectedEntityId, setFilterDropdownSelectedEntityId] =
useRecoilScopedState(
filterDropdownSelectedEntityIdScopedState,
TableContext,
);
useRecoilScopedState(filterDropdownSelectedEntityIdScopedState, context);
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const upsertActiveTableFilter = useUpsertFilter(TableContext);
const removeActiveTableFilter = useRemoveFilter(TableContext);
const upsertFilter = useUpsertFilter(context);
const removeFilter = useRemoveFilter(context);
const filterCurrentlyEdited = useFilterCurrentlyEdited(TableContext);
const filterCurrentlyEdited = useFilterCurrentlyEdited(context);
function handleUserSelected(selectedEntity: EntityForSelect) {
if (!tableFilterDefinitionUsedInDropdown || !selectedOperandInDropdown) {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) {
return;
}
@ -47,16 +45,16 @@ export function FilterDropdownEntitySearchSelect({
selectedEntity.id === filterDropdownSelectedEntityId;
if (clickedOnAlreadySelectedEntity) {
removeActiveTableFilter(tableFilterDefinitionUsedInDropdown.field);
removeFilter(filterDefinitionUsedInDropdown.field);
setFilterDropdownSelectedEntityId(null);
} else {
setFilterDropdownSelectedEntityId(selectedEntity.id);
upsertActiveTableFilter({
upsertFilter({
displayValue: selectedEntity.name,
field: tableFilterDefinitionUsedInDropdown.field,
field: filterDefinitionUsedInDropdown.field,
operand: selectedOperandInDropdown,
type: tableFilterDefinitionUsedInDropdown.type,
type: filterDefinitionUsedInDropdown.type,
value: selectedEntity.id,
});
}

View File

@ -1,17 +1,21 @@
import { Context } from 'react';
import { filterDefinitionUsedInDropdownScopedState } from '@/lib/filters-and-sorts/states/filterDefinitionUsedInDropdownScopedState';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
import { DropdownMenuSeparator } from '@/ui/components/menu/DropdownMenuSeparator';
import { DropdownMenuSeparator } from '../../menu/DropdownMenuSeparator';
export function FilterDropdownEntitySelect() {
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownEntitySelect({
context,
}: {
context: Context<string | null>;
}) {
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
if (tableFilterDefinitionUsedInDropdown?.type !== 'entity') {
if (filterDefinitionUsedInDropdown?.type !== 'entity') {
return null;
}
@ -19,7 +23,7 @@ export function FilterDropdownEntitySelect() {
<>
<DropdownMenuSeparator />
<RecoilScope>
{tableFilterDefinitionUsedInDropdown.entitySelectComponent}
{filterDefinitionUsedInDropdown.entitySelectComponent}
</RecoilScope>
</>
);

View File

@ -1,3 +1,5 @@
import { Context } from 'react';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { availableFiltersScopedState } from '@/lib/filters-and-sorts/states/availableFiltersScopedState';
@ -7,59 +9,61 @@ import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/st
import { getOperandsForFilterType } from '@/lib/filters-and-sorts/utils/getOperandsForFilterType';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/recoil-scope/hooks/useRecoilScopedValue';
import { TableContext } from '@/ui/tables/states/TableContext';
import { DropdownMenuItemContainer } from '../../menu/DropdownMenuItemContainer';
import { DropdownMenuSelectableItem } from '../../menu/DropdownMenuSelectableItem';
import { DropdownMenuItemContainer } from '@/ui/components/menu/DropdownMenuItemContainer';
import { DropdownMenuSelectableItem } from '@/ui/components/menu/DropdownMenuSelectableItem';
import DropdownButton from './DropdownButton';
export function FilterDropdownFilterSelect() {
const [, setTableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownFilterSelect({
context,
}: {
context: Context<string | null>;
}) {
const [, setFilterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const [, setSelectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const [, setFilterDropdownSearchInput] = useRecoilScopedState(
filterDropdownSearchInputScopedState,
TableContext,
context,
);
const availableTableFilters = useRecoilScopedValue(
const availableFilters = useRecoilScopedValue(
availableFiltersScopedState,
TableContext,
context,
);
const setHotkeysScope = useSetHotkeysScope();
return (
<DropdownMenuItemContainer style={{ maxHeight: '300px' }}>
{availableTableFilters.map((availableTableFilter, index) => (
{availableFilters.map((availableFilter, index) => (
<DropdownMenuSelectableItem
key={`select-filter-${index}`}
onClick={() => {
setTableFilterDefinitionUsedInDropdown(availableTableFilter);
setFilterDefinitionUsedInDropdown(availableFilter);
if (availableTableFilter.type === 'entity') {
if (availableFilter.type === 'entity') {
setHotkeysScope(InternalHotkeysScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterType(availableTableFilter.type)?.[0],
getOperandsForFilterType(availableFilter.type)?.[0],
);
setFilterDropdownSearchInput('');
}}
>
<DropdownButton.StyledIcon>
{availableTableFilter.icon}
{availableFilter.icon}
</DropdownButton.StyledIcon>
{availableTableFilter.label}
{availableFilter.label}
</DropdownMenuSelectableItem>
))}
</DropdownMenuItemContainer>

View File

@ -1,39 +1,42 @@
import { ChangeEvent } from 'react';
import { ChangeEvent, Context } from 'react';
import { useRemoveFilter } from '@/lib/filters-and-sorts/hooks/useRemoveFilter';
import { useUpsertFilter } from '@/lib/filters-and-sorts/hooks/useUpsertFilter';
import { filterDefinitionUsedInDropdownScopedState } from '@/lib/filters-and-sorts/states/filterDefinitionUsedInDropdownScopedState';
import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
export function FilterDropdownNumberSearchInput() {
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownNumberSearchInput({
context,
}: {
context: Context<string | null>;
}) {
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const upsertActiveTableFilter = useUpsertFilter(TableContext);
const removeActiveTableFilter = useRemoveFilter(TableContext);
const upsertFilter = useUpsertFilter(context);
const removeFilter = useRemoveFilter(context);
return (
tableFilterDefinitionUsedInDropdown &&
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
<input
type="number"
placeholder={tableFilterDefinitionUsedInDropdown.label}
placeholder={filterDefinitionUsedInDropdown.label}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value === '') {
removeActiveTableFilter(tableFilterDefinitionUsedInDropdown.field);
removeFilter(filterDefinitionUsedInDropdown.field);
} else {
upsertActiveTableFilter({
field: tableFilterDefinitionUsedInDropdown.field,
type: tableFilterDefinitionUsedInDropdown.type,
upsertFilter({
field: filterDefinitionUsedInDropdown.field,
type: filterDefinitionUsedInDropdown.type,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,

View File

@ -1,21 +1,26 @@
import { Context } from 'react';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/lib/filters-and-sorts/states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { getOperandLabel } from '@/lib/filters-and-sorts/utils/getOperandLabel';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
import DropdownButton from './DropdownButton';
export function FilterDropdownOperandButton() {
export function FilterDropdownOperandButton({
context,
}: {
context: Context<string | null>;
}) {
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const [isOperandSelectionUnfolded, setIsOperandSelectionUnfolded] =
useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState,
TableContext,
context,
);
if (isOperandSelectionUnfolded) {

View File

@ -1,3 +1,5 @@
import { Context } from 'react';
import { useFilterCurrentlyEdited } from '@/lib/filters-and-sorts/hooks/useFilterCurrentlyEdited';
import { useUpsertFilter } from '@/lib/filters-and-sorts/hooks/useUpsertFilter';
import { filterDefinitionUsedInDropdownScopedState } from '@/lib/filters-and-sorts/states/filterDefinitionUsedInDropdownScopedState';
@ -7,52 +9,50 @@ import { FilterOperand } from '@/lib/filters-and-sorts/types/FilterOperand';
import { getOperandLabel } from '@/lib/filters-and-sorts/utils/getOperandLabel';
import { getOperandsForFilterType } from '@/lib/filters-and-sorts/utils/getOperandsForFilterType';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
import { DropdownMenuItemContainer } from '../../menu/DropdownMenuItemContainer';
import { DropdownMenuItemContainer } from '@/ui/components/menu/DropdownMenuItemContainer';
import DropdownButton from './DropdownButton';
export function FilterDropdownOperandSelect() {
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownOperandSelect({
context,
}: {
context: Context<string | null>;
}) {
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const [, setSelectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const operandsForFilterType = getOperandsForFilterType(
tableFilterDefinitionUsedInDropdown?.type,
filterDefinitionUsedInDropdown?.type,
);
const [isOperandSelectionUnfolded, setIsOperandSelectionUnfolded] =
useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState,
TableContext,
context,
);
const activeTableFilterCurrentlyEdited =
useFilterCurrentlyEdited(TableContext);
const filterCurrentlyEdited = useFilterCurrentlyEdited(context);
const upsertActiveTableFilter = useUpsertFilter(TableContext);
const upsertFilter = useUpsertFilter(context);
function handleOperangeChange(newOperand: FilterOperand) {
setSelectedOperandInDropdown(newOperand);
setIsOperandSelectionUnfolded(false);
if (
tableFilterDefinitionUsedInDropdown &&
activeTableFilterCurrentlyEdited
) {
upsertActiveTableFilter({
field: activeTableFilterCurrentlyEdited.field,
displayValue: activeTableFilterCurrentlyEdited.displayValue,
if (filterDefinitionUsedInDropdown && filterCurrentlyEdited) {
upsertFilter({
field: filterCurrentlyEdited.field,
displayValue: filterCurrentlyEdited.displayValue,
operand: newOperand,
type: activeTableFilterCurrentlyEdited.type,
value: activeTableFilterCurrentlyEdited.value,
type: filterCurrentlyEdited.type,
value: filterCurrentlyEdited.value,
});
}
}

View File

@ -1,4 +1,4 @@
import { ChangeEvent } from 'react';
import { ChangeEvent, Context } from 'react';
import { useFilterCurrentlyEdited } from '@/lib/filters-and-sorts/hooks/useFilterCurrentlyEdited';
import { useRemoveFilter } from '@/lib/filters-and-sorts/hooks/useRemoveFilter';
@ -7,43 +7,46 @@ import { filterDefinitionUsedInDropdownScopedState } from '@/lib/filters-and-sor
import { filterDropdownSearchInputScopedState } from '@/lib/filters-and-sorts/states/filterDropdownSearchInputScopedState';
import { selectedOperandInDropdownScopedState } from '@/lib/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
export function FilterDropdownTextSearchInput() {
const [tableFilterDefinitionUsedInDropdown] = useRecoilScopedState(
export function FilterDropdownTextSearchInput({
context,
}: {
context: Context<string | null>;
}) {
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
TableContext,
context,
);
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
TableContext,
context,
);
const [filterDropdownSearchInput, setFilterDropdownSearchInput] =
useRecoilScopedState(filterDropdownSearchInputScopedState, TableContext);
useRecoilScopedState(filterDropdownSearchInputScopedState, context);
const upsertActiveTableFilter = useUpsertFilter(TableContext);
const removeActiveTableFilter = useRemoveFilter(TableContext);
const upsertFilter = useUpsertFilter(context);
const removeFilter = useRemoveFilter(context);
const filterCurrentlyEdited = useFilterCurrentlyEdited(TableContext);
const filterCurrentlyEdited = useFilterCurrentlyEdited(context);
return (
tableFilterDefinitionUsedInDropdown &&
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
<input
type="text"
placeholder={tableFilterDefinitionUsedInDropdown.label}
placeholder={filterDefinitionUsedInDropdown.label}
value={filterCurrentlyEdited?.value ?? filterDropdownSearchInput}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setFilterDropdownSearchInput(event.target.value);
if (event.target.value === '') {
removeActiveTableFilter(tableFilterDefinitionUsedInDropdown.field);
removeFilter(filterDefinitionUsedInDropdown.field);
} else {
upsertActiveTableFilter({
field: tableFilterDefinitionUsedInDropdown.field,
type: tableFilterDefinitionUsedInDropdown.type,
upsertFilter({
field: filterDefinitionUsedInDropdown.field,
type: filterDefinitionUsedInDropdown.type,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,

View File

@ -1,6 +1,8 @@
import { Context } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import SortOrFilterChip from '@/lib/filters-and-sorts/components/SortOrFilterChip';
import { useRemoveFilter } from '@/lib/filters-and-sorts/hooks/useRemoveFilter';
import { SelectedSortType } from '@/lib/filters-and-sorts/interfaces/sorts/interface';
import { availableFiltersScopedState } from '@/lib/filters-and-sorts/states/availableFiltersScopedState';
@ -8,11 +10,9 @@ import { filtersScopedState } from '@/lib/filters-and-sorts/states/filtersScoped
import { getOperandLabel } from '@/lib/filters-and-sorts/utils/getOperandLabel';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { IconArrowNarrowDown, IconArrowNarrowUp } from '@/ui/icons/index';
import { TableContext } from '@/ui/tables/states/TableContext';
import SortOrFilterChip from './SortOrFilterChip';
type OwnProps<SortField> = {
context: Context<string | null>;
sorts: Array<SelectedSortType<SortField>>;
onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
onCancelClick: () => void;
@ -60,6 +60,7 @@ const StyledCancelButton = styled.button`
`;
function SortAndFilterBar<SortField>({
context,
sorts,
onRemoveSort,
onCancelClick,
@ -68,26 +69,26 @@ function SortAndFilterBar<SortField>({
const [filters, setFilters] = useRecoilScopedState(
filtersScopedState,
TableContext,
context,
);
const [availableFilters] = useRecoilScopedState(
availableFiltersScopedState,
TableContext,
context,
);
const filtersWithDefinition = filters.map((filter) => {
const tableFilterDefinition = availableFilters.find((availableFilter) => {
const filterDefinition = availableFilters.find((availableFilter) => {
return availableFilter.field === filter.field;
});
return {
...filter,
...tableFilterDefinition,
...filterDefinition,
};
});
const removeFilter = useRemoveFilter(TableContext);
const removeFilter = useRemoveFilter(context);
function handleCancelClick() {
setFilters([]);

View File

@ -1,5 +1,6 @@
import { useCallback, useState } from 'react';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import {
SelectedSortType,
SortType,
@ -11,6 +12,7 @@ type OwnProps<SortField> = {
isSortSelected: boolean;
onSortSelect: (sort: SelectedSortType<SortField>) => void;
availableSorts: SortType<SortField>[];
hotkeysScope: InternalHotkeysScope;
};
const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc'];
@ -19,6 +21,7 @@ export function SortDropdownButton<SortField>({
isSortSelected,
availableSorts,
onSortSelect,
hotkeysScope,
}: OwnProps<SortField>) {
const [isUnfolded, setIsUnfolded] = useState(false);
@ -54,6 +57,7 @@ export function SortDropdownButton<SortField>({
isActive={isSortSelected}
isUnfolded={isUnfolded}
onIsUnfoldedChange={handleIsUnfoldedChange}
hotkeysScope={hotkeysScope}
>
{isOptionUnfolded
? options.map((option, index) => (

View File

@ -1,14 +1,15 @@
import { ReactNode, useCallback, useState } from 'react';
import styled from '@emotion/styled';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { FilterDropdownButton } from '@/lib/filters-and-sorts/components/FilterDropdownButton';
import SortAndFilterBar from '@/lib/filters-and-sorts/components/SortAndFilterBar';
import { SortDropdownButton } from '@/lib/filters-and-sorts/components/SortDropdownButton';
import {
SelectedSortType,
SortType,
} from '@/lib/filters-and-sorts/interfaces/sorts/interface';
import { FilterDropdownButton } from './FilterDropdownButton';
import SortAndFilterBar from './SortAndFilterBar';
import { SortDropdownButton } from './SortDropdownButton';
import { TableContext } from '@/ui/tables/states/TableContext';
type OwnProps<SortField> = {
viewName: string;
@ -89,15 +90,20 @@ export function TableHeader<SortField>({
{viewName}
</StyledViewSection>
<StyledFilters>
<FilterDropdownButton />
<FilterDropdownButton
context={TableContext}
hotkeysScope={InternalHotkeysScope.TableHeaderDropdownButton}
/>
<SortDropdownButton<SortField>
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
hotkeysScope={InternalHotkeysScope.TableHeaderDropdownButton}
/>
</StyledFilters>
</StyledTableHeader>
<SortAndFilterBar
context={TableContext}
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {

View File

@ -1,10 +1,10 @@
import { FilterDropdownEntitySearchSelect } from '@/lib/filters-and-sorts/components/FilterDropdownEntitySearchSelect';
import { filterDropdownSearchInputScopedState } from '@/lib/filters-and-sorts/states/filterDropdownSearchInputScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '@/lib/filters-and-sorts/states/filterDropdownSelectedEntityIdScopedState';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/recoil-scope/hooks/useRecoilScopedValue';
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
import { FilterDropdownEntitySearchSelect } from '@/ui/components/table/table-header/FilterDropdownEntitySearchSelect';
import { TableContext } from '@/ui/tables/states/TableContext';
import { useSearchUserQuery } from '~/generated/graphql';
@ -36,6 +36,9 @@ export function FilterDropdownUserSearchSelect() {
});
return (
<FilterDropdownEntitySearchSelect entitiesForSelect={usersForSelect} />
<FilterDropdownEntitySearchSelect
entitiesForSelect={usersForSelect}
context={TableContext}
/>
);
}