Complete Fix view work (#2272)

* Fix views

* Make view sorts and view filters functional

* Complete Company table view fix

* Fix model creation

* Start fixing board

* Complete work
This commit is contained in:
Charles Bochet
2023-10-29 16:29:00 +01:00
committed by GitHub
parent 685d342170
commit 9bab28912d
118 changed files with 1806 additions and 1413 deletions

View File

@ -168,37 +168,35 @@ export const DataTableHeader = () => {
>
<SelectAllCheckbox />
</th>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column) => (
<StyledColumnHeaderCell
key={column.fieldId}
isResizing={resizedFieldKey === column.fieldId}
columnWidth={Math.max(
tableColumnsByKey[column.fieldId].size +
(resizedFieldKey === column.fieldId ? resizeFieldOffset : 0),
COLUMN_MIN_WIDTH,
)}
>
<StyledColumnHeadContainer>
<ColumnHeadWithDropdown
column={column}
isFirstColumn={column.position === 1}
isLastColumn={
column.position === visibleTableColumns.length - 1
}
primaryColumnKey={primaryColumn?.fieldId || ''}
/>
</StyledColumnHeadContainer>
<StyledResizeHandler
className="cursor-col-resize"
role="separator"
onPointerDown={() => {
setResizedFieldKey(column.fieldId);
}}
{visibleTableColumns.map((column) => (
<StyledColumnHeaderCell
key={column.fieldId}
isResizing={resizedFieldKey === column.fieldId}
columnWidth={Math.max(
tableColumnsByKey[column.fieldId].size +
(resizedFieldKey === column.fieldId ? resizeFieldOffset : 0),
COLUMN_MIN_WIDTH,
)}
>
<StyledColumnHeadContainer>
<ColumnHeadWithDropdown
column={column}
isFirstColumn={column.position === 1}
isLastColumn={
column.position === visibleTableColumns.length - 1
}
primaryColumnKey={primaryColumn?.fieldId || ''}
/>
</StyledColumnHeaderCell>
))}
</StyledColumnHeadContainer>
<StyledResizeHandler
className="cursor-col-resize"
role="separator"
onPointerDown={() => {
setResizedFieldKey(column.fieldId);
}}
/>
</StyledColumnHeaderCell>
))}
<th>
{hiddenTableColumns.length > 0 && (
<StyledColumnHeadContainer>

View File

@ -4,7 +4,7 @@ import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFami
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { useView } from '@/views/hooks/useView';
import { availableSortsScopedState } from '@/views/states/availableSortsScopedState';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
import { SortDefinition } from '../../sort/types/SortDefinition';
import { isFetchingDataTableDataState } from '../states/isFetchingDataTableDataState';
@ -54,7 +54,7 @@ export const useSetDataTableData = () => {
setEntityCountInCurrentView(entityIds.length);
set(
availableSortsScopedState({ scopeId: tableContextScopeId }),
availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }),
sortDefinitionArray,
);

View File

@ -13,7 +13,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { useViewGetStates } from '@/views/hooks/useViewGetStates';
import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
@ -29,7 +29,7 @@ export const TableOptionsDropdownContent = ({
onImport?: () => void;
}) => {
const { setViewEditMode, handleViewNameSubmit } = useView();
const { viewEditMode, currentView } = useViewInternalStates();
const { viewEditMode, currentView } = useViewGetStates();
const { closeDropdown } = useDropdown();

View File

@ -12,12 +12,13 @@ type FilterDropdownButtonProps = {
export const FilterDropdownButton = ({
hotkeyScope,
}: FilterDropdownButtonProps) => {
const { availableFilters } = useFilter();
const { availableFilterDefinitions } = useFilter();
const hasOnlyOneEntityFilter =
availableFilters.length === 1 && availableFilters[0].type === 'entity';
availableFilterDefinitions.length === 1 &&
availableFilterDefinitions[0].type === 'entity';
if (!availableFilters.length) {
if (!availableFilterDefinitions.length) {
return <></>;
}

View File

@ -1,5 +1,4 @@
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { useUpsertFilter } from '@/views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
@ -8,19 +7,18 @@ export const FilterDropdownDateSearchInput = () => {
filterDefinitionUsedInDropdown,
selectedOperandInDropdown,
setIsFilterDropdownUnfolded,
selectFilter,
} = useFilter();
const upsertFilter = useUpsertFilter();
const handleChange = (date: Date) => {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
upsertFilter({
key: filterDefinitionUsedInDropdown.key,
type: filterDefinitionUsedInDropdown.type,
selectFilter?.({
fieldId: filterDefinitionUsedInDropdown.fieldId,
value: date.toISOString(),
operand: selectedOperandInDropdown,
displayValue: date.toLocaleDateString(),
definition: filterDefinitionUsedInDropdown,
});
setIsFilterDropdownUnfolded(false);

View File

@ -1,11 +1,8 @@
import { useEffect, useState } from 'react';
import { useFilterCurrentlyEdited } from '@/ui/data/filter/hooks/useFilterCurrentlyEdited';
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { useRemoveFilter } from '@/views/hooks/useRemoveFilter';
import { useUpsertFilter } from '@/views/hooks/useUpsertFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useFilter } from '../hooks/useFilter';
@ -16,20 +13,16 @@ export const FilterDropdownEntitySearchSelect = ({
entitiesForSelect: EntitiesForMultipleEntitySelect<EntityForSelect>;
}) => {
const {
filterDropdownSelectedEntityId,
setFilterDropdownSelectedEntityId,
filterDefinitionUsedInDropdown,
selectedOperandInDropdown,
filterDropdownSearchInput,
selectedFilter,
selectFilter,
} = useFilter();
const [isAllEntitySelected, setIsAllEntitySelected] = useState(false);
const upsertFilter = useUpsertFilter();
const removeFilter = useRemoveFilter();
const filterCurrentlyEdited = useFilterCurrentlyEdited();
const handleUserSelected = (
selectedEntity: EntityForSelect | null | undefined,
) => {
@ -45,24 +38,16 @@ export const FilterDropdownEntitySearchSelect = ({
setIsAllEntitySelected(false);
}
const clickedOnAlreadySelectedEntity =
selectedEntity.id === filterDropdownSelectedEntityId;
setFilterDropdownSelectedEntityId(selectedEntity.id);
if (clickedOnAlreadySelectedEntity) {
removeFilter(filterDefinitionUsedInDropdown.key);
setFilterDropdownSelectedEntityId(null);
} else {
setFilterDropdownSelectedEntityId(selectedEntity.id);
upsertFilter({
displayValue: selectedEntity.name,
key: filterDefinitionUsedInDropdown.key,
operand: selectedOperandInDropdown,
type: filterDefinitionUsedInDropdown.type,
value: selectedEntity.id,
displayAvatarUrl: selectedEntity.avatarUrl,
});
}
selectFilter?.({
displayValue: selectedEntity.name,
fieldId: filterDefinitionUsedInDropdown.fieldId,
operand: selectedOperandInDropdown,
value: selectedEntity.id,
displayAvatarUrl: selectedEntity.avatarUrl,
definition: filterDefinitionUsedInDropdown,
});
};
const isAllEntitySelectShown =
@ -81,36 +66,30 @@ export const FilterDropdownEntitySearchSelect = ({
) {
return;
}
if (isAllEntitySelected) {
setIsAllEntitySelected(false);
removeFilter(filterDefinitionUsedInDropdown.key);
} else {
setIsAllEntitySelected(true);
setIsAllEntitySelected(true);
setFilterDropdownSelectedEntityId(null);
setFilterDropdownSelectedEntityId(null);
upsertFilter({
displayValue: filterDefinitionUsedInDropdown.selectAllLabel,
key: filterDefinitionUsedInDropdown.key,
operand: ViewFilterOperand.IsNotNull,
type: filterDefinitionUsedInDropdown.type,
value: '',
});
}
selectFilter?.({
displayValue: filterDefinitionUsedInDropdown.selectAllLabel,
fieldId: filterDefinitionUsedInDropdown.fieldId,
operand: ViewFilterOperand.IsNotNull,
value: '',
definition: filterDefinitionUsedInDropdown,
});
};
useEffect(() => {
if (!filterCurrentlyEdited) {
if (!selectedFilter) {
setFilterDropdownSelectedEntityId(null);
} else {
setFilterDropdownSelectedEntityId(filterCurrentlyEdited.value);
setFilterDropdownSelectedEntityId(selectedFilter.value);
setIsAllEntitySelected(
filterCurrentlyEdited.operand === ViewFilterOperand.IsNotNull,
selectedFilter.operand === ViewFilterOperand.IsNotNull,
);
}
}, [
filterCurrentlyEdited,
selectedFilter,
setFilterDropdownSelectedEntityId,
entitiesForSelect.selectedEntities,
]);

View File

@ -11,32 +11,32 @@ export const FilterDropdownFilterSelect = () => {
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setFilterDropdownSearchInput,
availableFilters,
availableFilterDefinitions,
} = useFilter();
const setHotkeyScope = useSetHotkeyScope();
return (
<DropdownMenuItemsContainer>
{availableFilters.map((availableFilter, index) => (
{availableFilterDefinitions.map((availableFilterDefinition, index) => (
<MenuItem
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
setFilterDefinitionUsedInDropdown(availableFilter);
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
if (availableFilter.type === 'entity') {
if (availableFilterDefinition.type === 'entity') {
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterType(availableFilter.type)?.[0],
getOperandsForFilterType(availableFilterDefinition.type)?.[0],
);
setFilterDropdownSearchInput('');
}}
LeftIcon={availableFilter.Icon}
text={availableFilter.label}
LeftIcon={availableFilterDefinition.Icon}
text={availableFilterDefinition.label}
/>
))}
</DropdownMenuItemsContainer>

View File

@ -2,16 +2,14 @@ import { ChangeEvent } from 'react';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { useRemoveFilter } from '../../../../views/hooks/useRemoveFilter';
import { useUpsertFilter } from '../../../../views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
export const FilterDropdownNumberSearchInput = () => {
const { selectedOperandInDropdown, filterDefinitionUsedInDropdown } =
useFilter();
const upsertFilter = useUpsertFilter();
const removeFilter = useRemoveFilter();
const {
selectedOperandInDropdown,
filterDefinitionUsedInDropdown,
selectFilter,
} = useFilter();
return (
filterDefinitionUsedInDropdown &&
@ -21,17 +19,13 @@ export const FilterDropdownNumberSearchInput = () => {
type="number"
placeholder={filterDefinitionUsedInDropdown.label}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value === '') {
removeFilter(filterDefinitionUsedInDropdown.key);
} else {
upsertFilter({
key: filterDefinitionUsedInDropdown.key,
type: filterDefinitionUsedInDropdown.type,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
});
}
selectFilter?.({
fieldId: filterDefinitionUsedInDropdown.fieldId,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
definition: filterDefinitionUsedInDropdown,
});
}}
/>
)

View File

@ -2,9 +2,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useUpsertFilter } from '../../../../views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
import { getOperandLabel } from '../utils/getOperandLabel';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
@ -14,27 +12,25 @@ export const FilterDropdownOperandSelect = () => {
setSelectedOperandInDropdown,
isFilterDropdownOperandSelectUnfolded,
setIsFilterDropdownOperandSelectUnfolded,
selectedFilter,
selectFilter,
} = useFilter();
const operandsForFilterType = getOperandsForFilterType(
filterDefinitionUsedInDropdown?.type,
);
const filterCurrentlyEdited = useFilterCurrentlyEdited();
const upsertFilter = useUpsertFilter();
const handleOperangeChange = (newOperand: ViewFilterOperand) => {
setSelectedOperandInDropdown(newOperand);
setIsFilterDropdownOperandSelectUnfolded(false);
if (filterDefinitionUsedInDropdown && filterCurrentlyEdited) {
upsertFilter({
key: filterCurrentlyEdited.key,
displayValue: filterCurrentlyEdited.displayValue,
if (filterDefinitionUsedInDropdown && selectedFilter) {
selectFilter?.({
fieldId: selectedFilter.fieldId,
displayValue: selectedFilter.displayValue,
operand: newOperand,
type: filterCurrentlyEdited.type,
value: filterCurrentlyEdited.value,
value: selectedFilter.value,
definition: filterDefinitionUsedInDropdown,
});
}
};

View File

@ -2,10 +2,7 @@ import { ChangeEvent } from 'react';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { useRemoveFilter } from '../../../../views/hooks/useRemoveFilter';
import { useUpsertFilter } from '../../../../views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
export const FilterDropdownTextSearchInput = () => {
const {
@ -13,13 +10,10 @@ export const FilterDropdownTextSearchInput = () => {
selectedOperandInDropdown,
filterDropdownSearchInput,
setFilterDropdownSearchInput,
selectedFilter,
selectFilter,
} = useFilter();
const upsertFilter = useUpsertFilter();
const removeFilter = useRemoveFilter();
const filterCurrentlyEdited = useFilterCurrentlyEdited();
return (
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
@ -27,21 +21,17 @@ export const FilterDropdownTextSearchInput = () => {
autoFocus
type="text"
placeholder={filterDefinitionUsedInDropdown.label}
value={filterCurrentlyEdited?.value ?? filterDropdownSearchInput}
value={selectedFilter?.value ?? filterDropdownSearchInput}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setFilterDropdownSearchInput(event.target.value);
if (event.target.value === '') {
removeFilter(filterDefinitionUsedInDropdown.key);
} else {
upsertFilter({
key: filterDefinitionUsedInDropdown.key,
type: filterDefinitionUsedInDropdown.type,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
});
}
selectFilter?.({
fieldId: filterDefinitionUsedInDropdown.fieldId,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
definition: filterDefinitionUsedInDropdown,
});
}}
/>
)

View File

@ -21,13 +21,13 @@ export const SingleEntityFilterDropdownButton = ({
hotkeyScope: HotkeyScope;
}) => {
const {
availableFilters,
selectedFilters,
availableFilterDefinitions,
selectedFilter,
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
} = useFilter();
const availableFilter = availableFilters[0];
const availableFilter = availableFilterDefinitions[0];
React.useEffect(() => {
setFilterDefinitionUsedInDropdown(availableFilter);
@ -48,11 +48,11 @@ export const SingleEntityFilterDropdownButton = ({
dropdownOffset={{ x: 0, y: -28 }}
clickableComponent={
<StyledHeaderDropdownButton>
{selectedFilters[0] ? (
{selectedFilter ? (
<GenericEntityFilterChip
filter={selectedFilters[0]}
filter={selectedFilter}
Icon={
selectedFilters[0].operand === ViewFilterOperand.IsNotNull
selectedFilter.operand === ViewFilterOperand.IsNotNull
? availableFilter.SelectAllIcon
: undefined
}

View File

@ -1,6 +1,10 @@
import { useCallback } from 'react';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useScopeInternalContextOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopeInternalContextOrThrow';
import { FilterScopeInternalContext } from '../scopes/scope-internal-context/FilterScopeInternalContext';
import { Filter } from '../types/Filter';
import { useFilterStates } from './useFilterStates';
@ -14,8 +18,8 @@ export const useFilter = (props?: UseFilterProps) => {
props?.filterScopeId,
);
const {
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
setAvailableFilterDefinitions,
filterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
filterDropdownSearchInput,
@ -26,16 +30,28 @@ export const useFilter = (props?: UseFilterProps) => {
setIsFilterDropdownOperandSelectUnfolded,
isFilterDropdownUnfolded,
setIsFilterDropdownUnfolded,
selectedFilters,
setSelectedFilters,
selectedFilter,
setSelectedFilter,
selectedOperandInDropdown,
setSelectedOperandInDropdown,
} = useFilterStates(scopeId);
const { onFilterSelect } = useScopeInternalContextOrThrow(
FilterScopeInternalContext,
);
const selectFilter = useCallback(
(filter: Filter) => {
setSelectedFilter(filter);
onFilterSelect?.(filter);
},
[setSelectedFilter, onFilterSelect],
);
return {
scopeId,
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
setAvailableFilterDefinitions,
filterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
filterDropdownSearchInput,
@ -46,9 +62,10 @@ export const useFilter = (props?: UseFilterProps) => {
setIsFilterDropdownOperandSelectUnfolded,
isFilterDropdownUnfolded,
setIsFilterDropdownUnfolded,
selectedFilters,
setSelectedFilters,
selectedFilter,
setSelectedFilter,
selectedOperandInDropdown,
setSelectedOperandInDropdown,
selectFilter,
};
};

View File

@ -1,13 +0,0 @@
import { useMemo } from 'react';
import { useFilter } from './useFilter';
export const useFilterCurrentlyEdited = () => {
const { selectedFilters, filterDefinitionUsedInDropdown } = useFilter();
return useMemo(() => {
return selectedFilters?.find(
(filter) => filter.key === filterDefinitionUsedInDropdown?.key,
);
}, [filterDefinitionUsedInDropdown?.key, selectedFilters]);
};

View File

@ -1,19 +1,17 @@
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { availableFilterDefinitionsScopedState } from '../states/availableFilterDefinitionsScopedState';
import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '../states/filterDropdownSelectedEntityIdScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState';
import { selectedFiltersScopedState } from '../states/selectedFiltersScopedState';
import { selectedFilterScopedState } from '../states/selectedFilterScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
export const useFilterStates = (scopeId: string) => {
const [availableFilters, setAvailableFilters] = useRecoilScopedStateV2(
availableFiltersScopedState,
scopeId,
);
const [availableFilterDefinitions, setAvailableFilterDefinitions] =
useRecoilScopedStateV2(availableFilterDefinitionsScopedState, scopeId);
const [filterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown] =
useRecoilScopedStateV2(filterDefinitionUsedInDropdownScopedState, scopeId);
@ -35,8 +33,8 @@ export const useFilterStates = (scopeId: string) => {
const [isFilterDropdownUnfolded, setIsFilterDropdownUnfolded] =
useRecoilScopedStateV2(isFilterDropdownUnfoldedScopedState, scopeId);
const [selectedFilters, setSelectedFilters] = useRecoilScopedStateV2(
selectedFiltersScopedState,
const [selectedFilter, setSelectedFilter] = useRecoilScopedStateV2(
selectedFilterScopedState,
scopeId,
);
@ -44,8 +42,8 @@ export const useFilterStates = (scopeId: string) => {
useRecoilScopedStateV2(selectedOperandInDropdownScopedState, scopeId);
return {
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
setAvailableFilterDefinitions,
filterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
filterDropdownSearchInput,
@ -56,8 +54,8 @@ export const useFilterStates = (scopeId: string) => {
setIsFilterDropdownOperandSelectUnfolded,
isFilterDropdownUnfolded,
setIsFilterDropdownUnfolded,
selectedFilters,
setSelectedFilters,
selectedFilter,
setSelectedFilter,
selectedOperandInDropdown,
setSelectedOperandInDropdown,
};

View File

@ -2,25 +2,31 @@ import { ReactNode } from 'react';
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { Filter } from '../types/Filter';
import { FilterScopeInitEffect } from './init-effect/FilterScopeInitEffect';
import { FilterScopeInternalContext } from './scope-internal-context/FilterScopeInternalContext';
type FilterScopeProps = {
children: ReactNode;
filterScopeId: string;
availableFilters?: FilterDefinition[];
availableFilterDefinitions?: FilterDefinition[];
onFilterSelect?: (filter: Filter) => void;
};
export const FilterScope = ({
children,
filterScopeId,
availableFilters,
availableFilterDefinitions,
onFilterSelect,
}: FilterScopeProps) => {
return (
<FilterScopeInternalContext.Provider value={{ scopeId: filterScopeId }}>
<FilterScopeInternalContext.Provider
value={{ scopeId: filterScopeId, onFilterSelect }}
>
<FilterScopeInitEffect
filterScopeId={filterScopeId}
availableFilters={availableFilters}
availableFilterDefinitions={availableFilterDefinitions}
/>
{children}
</FilterScopeInternalContext.Provider>

View File

@ -6,20 +6,20 @@ import { useFilterStates } from '../../hooks/useFilterStates';
type FilterScopeInitEffectProps = {
filterScopeId: string;
availableFilters?: FilterDefinition[];
availableFilterDefinitions?: FilterDefinition[];
};
export const FilterScopeInitEffect = ({
filterScopeId,
availableFilters,
availableFilterDefinitions,
}: FilterScopeInitEffectProps) => {
const { setAvailableFilters } = useFilterStates(filterScopeId);
const { setAvailableFilterDefinitions } = useFilterStates(filterScopeId);
useEffect(() => {
if (availableFilters) {
setAvailableFilters(availableFilters);
if (availableFilterDefinitions) {
setAvailableFilterDefinitions(availableFilterDefinitions);
}
}, [availableFilters, setAvailableFilters]);
}, [availableFilterDefinitions, setAvailableFilterDefinitions]);
return <></>;
};

View File

@ -1,8 +1,10 @@
import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey';
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { Filter } from '../../types/Filter';
type FilterScopeInternalContextProps = ScopedStateKey & {
test?: string;
onFilterSelect?: (sort: Filter) => void;
};
export const FilterScopeInternalContext =

View File

@ -1,9 +1,9 @@
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const availableFiltersScopedState = createScopedState<
export const availableFilterDefinitionsScopedState = createScopedState<
FilterDefinition[]
>({
key: 'availableFiltersScopedState',
key: 'availableFilterDefinitionsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,8 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { Filter } from '../types/Filter';
export const selectedFilterScopedState = createScopedState<Filter | undefined>({
key: 'selectedFilterScopedState',
defaultValue: undefined,
});

View File

@ -1,8 +0,0 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { Filter } from '../types/Filter';
export const selectedFiltersScopedState = createScopedState<Filter[]>({
key: 'selectedFiltersScopedState',
defaultValue: [],
});

View File

@ -1,12 +1,12 @@
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { FilterType } from './FilterType';
import { FilterDefinition } from './FilterDefinition';
export type Filter = {
key: string;
type: FilterType;
fieldId: string;
value: string;
displayValue: string;
displayAvatarUrl?: string;
operand: ViewFilterOperand;
definition: FilterDefinition;
};

View File

@ -3,7 +3,7 @@ import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { FilterType } from './FilterType';
export type FilterDefinition = {
key: string;
fieldId: string;
label: string;
Icon: IconComponent;
type: FilterType;

View File

@ -1,5 +1,5 @@
import { FilterDefinition } from './FilterDefinition';
export type FilterDefinitionByEntity<T> = FilterDefinition & {
key: keyof T;
fieldId: keyof T;
};

View File

@ -3,28 +3,39 @@ import { QueryMode } from '~/generated/graphql';
import { Filter } from '../types/Filter';
export const turnFilterIntoWhereClause = (filter: Filter) => {
type FilterToTurnIntoWhereClause = Omit<Filter, 'definition'> & {
definition: {
type: Filter['definition']['type'];
};
};
export const turnFilterIntoWhereClause = (
filter: FilterToTurnIntoWhereClause | undefined,
) => {
if (!filter) {
return {};
}
switch (filter.operand) {
case ViewFilterOperand.IsNotNull:
return {
[filter.key]: {
[filter.fieldId]: {
not: null,
},
};
default:
switch (filter.type) {
switch (filter.definition.type) {
case 'text':
switch (filter.operand) {
case ViewFilterOperand.Contains:
return {
[filter.key]: {
[filter.fieldId]: {
contains: filter.value,
mode: QueryMode.Insensitive,
},
};
case ViewFilterOperand.DoesNotContain:
return {
[filter.key]: {
[filter.fieldId]: {
not: {
contains: filter.value,
mode: QueryMode.Insensitive,
@ -33,64 +44,64 @@ export const turnFilterIntoWhereClause = (filter: Filter) => {
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
case 'number':
switch (filter.operand) {
case ViewFilterOperand.GreaterThan:
return {
[filter.key]: {
[filter.fieldId]: {
gte: parseFloat(filter.value),
},
};
case ViewFilterOperand.LessThan:
return {
[filter.key]: {
[filter.fieldId]: {
lte: parseFloat(filter.value),
},
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
case 'date':
switch (filter.operand) {
case ViewFilterOperand.GreaterThan:
return {
[filter.key]: {
[filter.fieldId]: {
gte: filter.value,
},
};
case ViewFilterOperand.LessThan:
return {
[filter.key]: {
[filter.fieldId]: {
lte: filter.value,
},
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
case 'entity':
switch (filter.operand) {
case ViewFilterOperand.Is:
return {
[filter.key]: {
[filter.fieldId]: {
equals: filter.value,
},
};
case ViewFilterOperand.IsNot:
return {
[filter.key]: {
[filter.fieldId]: {
not: { equals: filter.value },
},
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
default:

View File

@ -35,7 +35,7 @@ export const SortDropdownButton = ({
setSelectedSortDirection('asc');
}, []);
const { availableSorts, onSortAdd, isSortSelected } = useSort();
const { availableSortDefinitions, onSortSelect, isSortSelected } = useSort();
const { toggleDropdown } = useDropdown({
dropdownScopeId: SortDropdownId,
@ -48,8 +48,8 @@ export const SortDropdownButton = ({
const handleAddSort = (selectedSortDefinition: SortDefinition) => {
toggleDropdown();
onSortAdd?.({
key: selectedSortDefinition.key,
onSortSelect?.({
fieldId: selectedSortDefinition.fieldId,
direction: selectedSortDirection,
definition: selectedSortDefinition,
});
@ -96,7 +96,7 @@ export const SortDropdownButton = ({
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
{availableSorts.map((availableSort, index) => (
{availableSortDefinitions.map((availableSort, index) => (
<MenuItem
testId={`select-sort-${index}`}
key={index}

View File

@ -15,22 +15,22 @@ export const useSort = (props?: UseSortProps) => {
props?.sortScopeId,
);
const {
availableSorts,
setAvailableSorts,
availableSortDefinitions,
setAvailableSortDefinitions,
isSortSelected,
setIsSortSelected,
} = useSortStates(scopeId);
const { onSortAdd } = useScopeInternalContextOrThrow(
const { onSortSelect } = useScopeInternalContextOrThrow(
SortScopeInternalContext,
);
return {
onSortAdd,
onSortSelect,
scopeId,
availableSorts,
availableSortDefinitions,
isSortSelected,
setIsSortSelected,
setAvailableSorts,
setAvailableSortDefinitions,
};
};

View File

@ -1,13 +1,11 @@
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
import { availableSortsScopedState } from '@/views/states/availableSortsScopedState';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
import { isSortSelectedScopedState } from '../states/isSortSelectedScopedState';
export const useSortStates = (scopeId: string) => {
const [availableSorts, setAvailableSorts] = useRecoilScopedStateV2(
availableSortsScopedState,
scopeId,
);
const [availableSortDefinitions, setAvailableSortDefinitions] =
useRecoilScopedStateV2(availableSortDefinitionsScopedState, scopeId);
const [isSortSelected, setIsSortSelected] = useRecoilScopedStateV2(
isSortSelectedScopedState,
@ -15,8 +13,8 @@ export const useSortStates = (scopeId: string) => {
);
return {
availableSorts,
setAvailableSorts,
availableSortDefinitions,
setAvailableSortDefinitions,
isSortSelected,
setIsSortSelected,
};

View File

@ -9,23 +9,23 @@ import { SortScopeInternalContext } from './scope-internal-context/SortScopeInte
type SortScopeProps = {
children: ReactNode;
sortScopeId: string;
availableSorts?: SortDefinition[];
onSortAdd?: (sort: Sort) => void | Promise<void>;
availableSortDefinitions?: SortDefinition[];
onSortSelect?: (sort: Sort) => void | Promise<void>;
};
export const SortScope = ({
children,
sortScopeId,
availableSorts,
onSortAdd,
availableSortDefinitions,
onSortSelect,
}: SortScopeProps) => {
return (
<SortScopeInternalContext.Provider
value={{ scopeId: sortScopeId, onSortAdd }}
value={{ scopeId: sortScopeId, onSortSelect }}
>
<SortScopeInitEffect
sortScopeId={sortScopeId}
availableSorts={availableSorts}
availableSortDefinitions={availableSortDefinitions}
/>
{children}
</SortScopeInternalContext.Provider>

View File

@ -6,20 +6,20 @@ import { useSortStates } from '../../hooks/useSortStates';
type SortScopeInitEffectProps = {
sortScopeId: string;
availableSorts?: SortDefinition[];
availableSortDefinitions?: SortDefinition[];
};
export const SortScopeInitEffect = ({
sortScopeId,
availableSorts,
availableSortDefinitions,
}: SortScopeInitEffectProps) => {
const { setAvailableSorts } = useSortStates(sortScopeId);
const { setAvailableSortDefinitions } = useSortStates(sortScopeId);
useEffect(() => {
if (availableSorts) {
setAvailableSorts(availableSorts);
if (availableSortDefinitions) {
setAvailableSortDefinitions(availableSortDefinitions);
}
}, [availableSorts, setAvailableSorts]);
}, [availableSortDefinitions, setAvailableSortDefinitions]);
return <></>;
};

View File

@ -2,11 +2,9 @@ import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/type
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { Sort } from '../../types/Sort';
import { SortDefinition } from '../../types/SortDefinition';
type SortScopeInternalContextProps = ScopedStateKey & {
onSortAdd?: (sort: Sort) => void;
availableSorts?: SortDefinition[];
onSortSelect?: (sort: Sort) => void;
};
export const SortScopeInternalContext =

View File

@ -2,7 +2,7 @@ import { SortDefinition } from './SortDefinition';
import { SortDirection } from './SortDirection';
export type Sort = {
key: string;
fieldId: string;
direction: SortDirection;
definition: SortDefinition;
};

View File

@ -3,7 +3,7 @@ import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { SortDirection } from './SortDirection';
export type SortDefinition = {
key: string;
fieldId: string;
label: string;
Icon?: IconComponent;
getOrderByTemplate?: (direction: SortDirection) => any[];

View File

@ -10,7 +10,7 @@ export const reduceSortsToOrderBy = (sorts: Sort[]): any[] =>
if (sort.definition.getOrderByTemplate) {
return sort.definition.getOrderByTemplate(direction);
} else {
return [{ [sort.definition.key]: direction }];
return [{ [sort.definition.fieldId]: direction }];
}
})
.flat();

View File

@ -2,8 +2,9 @@ import { useView } from '@/views/hooks/useView';
import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
import { BoardScopeIds } from '../types/enums/BoardScopeIds';
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
import { BoardOptionsDropdownId } from './constants/BoardOptionsDropdownId';
import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton';
import {
BoardOptionsDropdownContent,
@ -12,26 +13,22 @@ import {
type BoardOptionsDropdownProps = Pick<
BoardOptionsDropdownContentProps,
'customHotkeyScope' | 'onStageAdd'
'onStageAdd'
>;
export const BoardOptionsDropdown = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownProps) => {
const { setViewEditMode } = useView();
return (
<DropdownScope dropdownScopeId={BoardScopeIds.OptionsDropdown}>
<DropdownScope dropdownScopeId={BoardOptionsDropdownId}>
<Dropdown
clickableComponent={<BoardOptionsDropdownButton />}
dropdownComponents={
<BoardOptionsDropdownContent
customHotkeyScope={customHotkeyScope}
onStageAdd={onStageAdd}
/>
<BoardOptionsDropdownContent onStageAdd={onStageAdd} />
}
dropdownHotkeyScope={customHotkeyScope}
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onClickOutside={() => setViewEditMode('none')}
dropdownMenuWidth={170}
/>

View File

@ -1,12 +1,8 @@
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { BoardScopeIds } from '../types/enums/BoardScopeIds';
export const BoardOptionsDropdownButton = () => {
const { isDropdownOpen, toggleDropdown } = useDropdown({
dropdownScopeId: BoardScopeIds.OptionsDropdown,
});
const { isDropdownOpen, toggleDropdown } = useDropdown();
const handleClick = () => {
toggleDropdown();

View File

@ -1,5 +1,5 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
@ -22,25 +22,20 @@ import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemN
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState';
import { useViewGetStates } from '@/views/hooks/useViewGetStates';
import { useBoardCardFields } from '../hooks/useBoardCardFields';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardColumnsState } from '../states/boardColumnsState';
import { isCompactViewEnabledState } from '../states/isCompactViewEnabledState';
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector';
import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
export type BoardOptionsDropdownContentProps = {
customHotkeyScope: HotkeyScope;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
};
@ -54,15 +49,12 @@ type ColumnForCreate = {
};
export const BoardOptionsDropdownContent = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownContentProps) => {
const { setViewEditMode, createView, currentViewId } = useView();
const { viewEditMode, currentView } = useViewInternalStates();
const { setViewEditMode, handleViewNameSubmit } = useView();
const { viewEditMode, currentView } = useViewGetStates();
const { BoardRecoilScopeContext } = useContext(BoardContext);
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
const stageInputRef = useRef<HTMLInputElement>(null);
const viewEditInputRef = useRef<HTMLInputElement>(null);
@ -104,31 +96,6 @@ export const BoardOptionsDropdownContent = ({
onStageAdd?.(columnToCreate);
};
const handleViewNameSubmit = useRecoilCallback(
({ set, snapshot }) =>
async () => {
const viewEditMode = snapshot
.getLoadable(viewEditModeScopedState({ scopeId: boardRecoilScopeId }))
.getValue();
if (!viewEditMode) {
return;
}
const boardCardFields = await snapshot.getPromise(
boardCardFieldsScopedState(boardRecoilScopeId),
);
const isCreateMode = viewEditMode === 'create';
const name = viewEditInputRef.current?.value;
if (isCreateMode && name) {
await createView(name);
set(savedBoardCardFieldsFamilyState(currentViewId), boardCardFields);
}
},
[boardRecoilScopeId, createView, currentViewId],
);
const resetMenu = () => setCurrentMenu(undefined);
const handleMenuNavigate = (menu: BoardOptionsMenu) => {
@ -146,43 +113,39 @@ export const BoardOptionsDropdownContent = ({
setViewEditMode('none');
closeDropdown();
},
customHotkeyScope.scope,
BoardOptionsHotkeyScope.Dropdown,
);
useScopedHotkeys(
Key.Enter,
() => {
const name = viewEditInputRef.current?.value;
resetMenu();
setViewEditMode('none');
closeDropdown();
handleStageSubmit();
handleViewNameSubmit();
handleViewNameSubmit(name);
closeDropdown();
},
customHotkeyScope.scope,
BoardOptionsHotkeyScope.Dropdown,
);
return (
<>
{!currentMenu && (
<>
{viewEditMode && (
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={
viewEditMode === 'create'
? ''
: viewEditMode === 'edit'
? currentView?.name
: ''
}
/>
)}
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={viewEditMode === 'create' ? '' : currentView?.name}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemNavigate

View File

@ -40,6 +40,8 @@ export type EntityBoardProps = {
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
width: 100%;
`;

View File

@ -0,0 +1,2 @@
// We should either apply the constant all caps case or maybe define a more general enum to store those ids ?
export const BoardOptionsDropdownId = 'board-options';

View File

@ -7,6 +7,6 @@ import { PipelineProgress } from '~/generated/graphql';
export type BoardOptions = {
newCardComponent: React.ReactNode;
CardComponent: ComponentType;
filters: FilterDefinitionByEntity<PipelineProgress>[];
sorts: SortDefinition[];
filterDefinitions: FilterDefinitionByEntity<PipelineProgress>[];
sortDefinitions: SortDefinition[];
};

View File

@ -1,3 +0,0 @@
export enum BoardScopeIds {
OptionsDropdown = 'board-options',
}