Refactor Views by cleaning the code, relying on apolloCache and improving performances (#4516)

* Wip refactoring view

* Post merge conflicts

* Fix review

* Add create view capability

* Fix create object missing view

* Fix tests
This commit is contained in:
Charles Bochet
2024-03-20 14:21:58 +01:00
committed by GitHub
parent 20e14cb455
commit cfb0cce9b8
392 changed files with 3474 additions and 4410 deletions

View File

@ -1,10 +1,10 @@
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { getOperandLabelShort } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
import { ViewFilter } from '@/views/types/ViewFilter';
type EditableFilterChipProps = {
viewFilter: ViewFilter;
viewFilter: Filter;
onRemove: () => void;
};

View File

@ -1,18 +1,19 @@
import { useCallback, useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { MultipleFiltersDropdownContent } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { EditableFilterChip } from '@/views/components/EditableFilterChip';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewFilter } from '@/views/types/ViewFilter';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { isDefined } from '~/utils/isDefined';
type EditableFilterDropdownButtonProps = {
viewFilterDropdownId: string;
viewFilter: ViewFilter;
viewFilter: Filter;
hotkeyScope: HotkeyScope;
};
@ -22,7 +23,7 @@ export const EditableFilterDropdownButton = ({
hotkeyScope,
}: EditableFilterDropdownButtonProps) => {
const {
availableFilterDefinitions,
availableFilterDefinitionsState,
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setSelectedFilter,
@ -30,9 +31,13 @@ export const EditableFilterDropdownButton = ({
filterDropdownId: viewFilterDropdownId,
});
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
);
const { closeDropdown } = useDropdown(viewFilterDropdownId);
const { removeViewFilter } = useViewBar();
const { removeCombinedViewFilter } = useCombinedViewFilters();
useEffect(() => {
const filterDefinition = availableFilterDefinitions.find(
@ -57,15 +62,15 @@ export const EditableFilterDropdownButton = ({
const handleRemove = () => {
closeDropdown();
removeViewFilter(viewFilter.fieldMetadataId);
removeCombinedViewFilter(viewFilter.fieldMetadataId);
};
const handleDropdownClickOutside = useCallback(() => {
const { value, fieldMetadataId } = viewFilter;
if (!value) {
removeViewFilter(fieldMetadataId);
removeCombinedViewFilter(fieldMetadataId);
}
}, [viewFilter, removeViewFilter]);
}, [viewFilter, removeCombinedViewFilter]);
return (
<Dropdown

View File

@ -1,21 +1,22 @@
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { IconArrowDown, IconArrowUp } from '@/ui/display/icon/index';
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewSort } from '@/views/types/ViewSort';
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
type EditableSortChipProps = {
viewSort: ViewSort;
viewSort: Sort;
};
export const EditableSortChip = ({ viewSort }: EditableSortChipProps) => {
const { removeViewSort, upsertViewSort } = useViewBar();
const { removeCombinedViewSort, upsertCombinedViewSort } =
useCombinedViewSorts();
const handleRemoveClick = () => {
removeViewSort(viewSort.fieldMetadataId);
removeCombinedViewSort(viewSort.fieldMetadataId);
};
const handleClick = () => {
upsertViewSort({
upsertCombinedViewSort({
...viewSort,
direction: viewSort.direction === 'asc' ? 'desc' : 'asc',
});

View File

@ -1,38 +1,47 @@
import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isUndefined } from '@sniptt/guards';
import { useSetRecoilState } from 'recoil';
import { useFiltersFromQueryParams } from '@/views/hooks/internal/useFiltersFromQueryParams';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useResetCurrentView } from '@/views/hooks/useResetCurrentView';
export const FilterQueryParamsEffect = () => {
const { hasFiltersQueryParams, getFiltersFromQueryParams } =
useFiltersFromQueryParams();
const { currentViewFiltersState, onViewFiltersChangeState } =
useViewScopedStates();
const setCurrentViewFilters = useSetRecoilState(currentViewFiltersState);
const onViewFiltersChange = useRecoilValue(onViewFiltersChangeState);
const { resetViewBar } = useViewBar();
const { hasFiltersQueryParams, getFiltersFromQueryParams, viewIdQueryParam } =
useViewFromQueryParams();
const { unsavedToUpsertViewFiltersState, currentViewIdState } =
useViewStates();
const setUnsavedViewFilter = useSetRecoilState(
unsavedToUpsertViewFiltersState,
);
const setCurrentViewId = useSetRecoilState(currentViewIdState);
const { resetCurrentView } = useResetCurrentView();
useEffect(() => {
if (isUndefined(viewIdQueryParam) || !viewIdQueryParam) {
return;
}
setCurrentViewId(viewIdQueryParam);
}, [getFiltersFromQueryParams, setCurrentViewId, viewIdQueryParam]);
useEffect(() => {
if (!hasFiltersQueryParams) return;
getFiltersFromQueryParams().then((filtersFromParams) => {
if (Array.isArray(filtersFromParams)) {
setCurrentViewFilters(filtersFromParams);
onViewFiltersChange?.(filtersFromParams);
setUnsavedViewFilter(filtersFromParams);
}
});
return () => {
resetViewBar();
resetCurrentView();
};
}, [
getFiltersFromQueryParams,
hasFiltersQueryParams,
onViewFiltersChange,
resetViewBar,
setCurrentViewFilters,
resetCurrentView,
setUnsavedViewFilter,
]);
return null;

View File

@ -1,6 +1,6 @@
import { useCallback } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { IconChevronDown, IconPlus } from '@/ui/display/icon';
import { Button } from '@/ui/input/button/components/Button';
@ -10,9 +10,8 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { UPDATE_VIEW_DROPDOWN_ID } from '@/views/constants/UpdateViewDropdownId';
import { useViewBar } from '@/views/hooks/useViewBar';
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
const StyledContainer = styled.div`
background: ${({ theme }) => theme.color.blue};
@ -31,14 +30,11 @@ export const UpdateViewButtonGroup = ({
hotkeyScope,
onViewEditModeChange,
}: UpdateViewButtonGroupProps) => {
const { updateCurrentView, setViewEditMode } = useViewBar();
const { canPersistFiltersSelector, canPersistSortsSelector } =
useViewScopedStates();
const { canPersistViewSelector, viewEditModeState } = useViewStates();
const { saveCurrentViewFilterAndSorts } = useSaveCurrentViewFiltersAndSorts();
const canPersistFilters = useRecoilValue(canPersistFiltersSelector);
const canPersistSorts = useRecoilValue(canPersistSortsSelector);
const canPersistView = canPersistFilters || canPersistSorts;
const setViewEditMode = useSetRecoilState(viewEditModeState);
const canPersistView = useRecoilValue(canPersistViewSelector());
const handleCreateViewButtonClick = useCallback(() => {
setViewEditMode('create');
@ -46,7 +42,7 @@ export const UpdateViewButtonGroup = ({
}, [setViewEditMode, onViewEditModeChange]);
const handleViewSubmit = async () => {
await updateCurrentView?.();
await saveCurrentViewFilterAndSorts();
};
if (!canPersistView) {

View File

@ -10,12 +10,8 @@ import { FilterQueryParamsEffect } from '@/views/components/FilterQueryParamsEff
import { ViewBarEffect } from '@/views/components/ViewBarEffect';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewScope } from '@/views/scopes/ViewScope';
import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewSort } from '@/views/types/ViewSort';
import { ViewType } from '@/views/types/ViewType';
import { GraphQLView } from '@/views/types/GraphQLView';
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
@ -28,13 +24,7 @@ export type ViewBarProps = {
className?: string;
optionsDropdownButton: ReactNode;
optionsDropdownScopeId: string;
onViewSortsChange?: (sorts: ViewSort[]) => void | Promise<void>;
onViewFiltersChange?: (filters: ViewFilter[]) => void | Promise<void>;
onViewFieldsChange?: (fields: ViewField[]) => void | Promise<void>;
onViewTypeChange?: (viewType: ViewType) => void | Promise<void>;
onViewCompactModeChange?: (
isCompactModeActive: boolean,
) => void | Promise<void>;
onCurrentViewChange: (view: GraphQLView | undefined) => void | Promise<void>;
};
export const ViewBar = ({
@ -42,18 +32,12 @@ export const ViewBar = ({
className,
optionsDropdownButton,
optionsDropdownScopeId,
onViewFieldsChange,
onViewFiltersChange,
onViewSortsChange,
onViewTypeChange,
onViewCompactModeChange,
onCurrentViewChange,
}: ViewBarProps) => {
const { openDropdown: openOptionsDropdownButton } = useDropdown(
optionsDropdownScopeId,
);
const { upsertViewSort, upsertViewFilter } = useViewBar({
viewBarId,
});
const { objectNamePlural } = useParams();
const filterDropdownId = 'view-filter';
@ -62,21 +46,11 @@ export const ViewBar = ({
return (
<ViewScope
viewScopeId={viewBarId}
onViewFieldsChange={onViewFieldsChange}
onViewFiltersChange={onViewFiltersChange}
onViewSortsChange={onViewSortsChange}
onViewTypeChange={onViewTypeChange}
onViewCompactModeChange={onViewCompactModeChange}
onCurrentViewChange={onCurrentViewChange}
>
<ViewBarEffect />
<ViewBarFilterEffect
filterDropdownId={filterDropdownId}
onFilterSelect={upsertViewFilter}
/>
<ViewBarSortEffect
sortDropdownId={sortDropdownId}
onSortSelect={upsertViewSort}
/>
<ViewBarEffect viewBarId={viewBarId} />
<ViewBarFilterEffect filterDropdownId={filterDropdownId} />
<ViewBarSortEffect sortDropdownId={sortDropdownId} />
{!!objectNamePlural && <FilterQueryParamsEffect />}
<TopBar

View File

@ -9,9 +9,11 @@ import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
import { EditableSortChip } from '@/views/components/EditableSortChip';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
import { useViewBar } from '@/views/hooks/useViewBar';
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useResetCurrentView } from '@/views/hooks/useResetCurrentView';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
export type ViewBarDetailsProps = {
hasFilterButton?: boolean;
@ -25,9 +27,11 @@ const StyledBar = styled.div`
border-top: 1px solid ${({ theme }) => theme.border.color.light};
display: flex;
flex-direction: row;
height: 40px;
min-height: 32px;
justify-content: space-between;
z-index: 4;
padding-top: ${({ theme }) => theme.spacing(1)};
padding-bottom: ${({ theme }) => theme.spacing(1)};
`;
const StyledChipcontainer = styled.div`
@ -35,10 +39,9 @@ const StyledChipcontainer = styled.div`
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(1)};
height: 40px;
justify-content: space-between;
min-height: 32px;
margin-left: ${({ theme }) => theme.spacing(2)};
overflow-x: auto;
flex-wrap: wrap;
`;
const StyledCancelButton = styled.button`
@ -92,37 +95,35 @@ export const ViewBarDetails = ({
hasFilterButton = false,
rightComponent,
filterDropdownId,
viewBarId,
}: ViewBarDetailsProps) => {
const {
currentViewSortsState,
currentViewFiltersState,
canPersistFiltersSelector,
canPersistSortsSelector,
canPersistViewSelector,
isViewBarExpandedState,
} = useViewScopedStates();
availableFilterDefinitionsState,
availableSortDefinitionsState,
} = useViewStates();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const currentViewSorts = useRecoilValue(currentViewSortsState);
const currentViewFilters = useRecoilValue(currentViewFiltersState);
const canPersistFilters = useRecoilValue(canPersistFiltersSelector);
const canPersistSorts = useRecoilValue(canPersistSortsSelector);
const isViewBarExpanded = useRecoilValue(isViewBarExpandedState);
const canPersistView = useRecoilValue(canPersistViewSelector());
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
);
const availableSortDefinitions = useRecoilValue(
availableSortDefinitionsState,
);
const { resetViewBar } = useViewBar();
const canPersistView = canPersistFilters || canPersistSorts;
const { resetCurrentView } = useResetCurrentView();
const handleCancelClick = () => {
resetViewBar();
resetCurrentView();
};
const { upsertViewFilter } = useViewBar({
viewBarId: viewBarId,
});
const shouldExpandViewBar =
canPersistView ||
((currentViewSorts?.length || currentViewFilters?.length) &&
((currentViewWithCombinedFiltersAndSorts?.viewSorts?.length ||
currentViewWithCombinedFiltersAndSorts?.viewFilters?.length) &&
isViewBarExpanded);
if (!shouldExpandViewBar) {
@ -133,23 +134,29 @@ export const ViewBarDetails = ({
<StyledBar>
<StyledFilterContainer>
<StyledChipcontainer>
{currentViewSorts?.map((sort) => (
<EditableSortChip key={sort.id} viewSort={sort} />
{mapViewSortsToSorts(
currentViewWithCombinedFiltersAndSorts?.viewSorts ?? [],
availableSortDefinitions,
).map((sort) => (
<EditableSortChip key={sort.fieldMetadataId} viewSort={sort} />
))}
{!!currentViewSorts?.length && !!currentViewFilters?.length && (
<StyledSeperatorContainer>
<StyledSeperator />
</StyledSeperatorContainer>
)}
{currentViewFilters?.map((viewFilter) => (
{!!currentViewWithCombinedFiltersAndSorts?.viewSorts?.length &&
!!currentViewWithCombinedFiltersAndSorts?.viewFilters?.length && (
<StyledSeperatorContainer>
<StyledSeperator />
</StyledSeperatorContainer>
)}
{mapViewFiltersToFilters(
currentViewWithCombinedFiltersAndSorts?.viewFilters ?? [],
availableFilterDefinitions,
).map((viewFilter) => (
<ObjectFilterDropdownScope
key={viewFilter.id}
key={viewFilter.fieldMetadataId}
filterScopeId={viewFilter.fieldMetadataId}
>
<DropdownScope dropdownScopeId={viewFilter.fieldMetadataId}>
<ViewBarFilterEffect
filterDropdownId={viewFilter.fieldMetadataId}
onFilterSelect={upsertViewFilter}
/>
<EditableFilterDropdownButton
viewFilter={viewFilter}

View File

@ -1,86 +1,71 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useEffect, useState } from 'react';
import { isUndefined } from '@sniptt/guards';
import { useRecoilState, useRecoilValue } from 'recoil';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useViewBar } from '@/views/hooks/useViewBar';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { GraphQLView } from '@/views/types/GraphQLView';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
type ViewBarEffectProps = {
viewBarId: string;
};
export const ViewBarEffect = () => {
export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => {
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewBarId);
const {
loadView,
changeViewInUrl,
loadViewFields,
loadViewFilters,
loadViewSorts,
} = useViewBar();
onCurrentViewChangeState,
currentViewIdState,
availableFilterDefinitionsState,
isPersistingViewFieldsState,
} = useViewStates(viewBarId);
const [searchParams] = useSearchParams();
const currentViewIdFromUrl = searchParams.get('view');
const { viewObjectMetadataIdState, viewsState, currentViewIdState } =
useViewScopedStates();
const [views, setViews] = useRecoilState(viewsState);
const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState);
const setCurrentViewId = useSetRecoilState(currentViewIdState);
const { records: newViews } = usePrefetchedData<GraphQLView>(
PrefetchKey.AllViews,
);
const newViewsOnCurrentObject = newViews.filter(
(view) => view.objectMetadataId === viewObjectMetadataId,
const [currentViewSnapshot, setCurrentViewSnapshot] = useState<
GraphQLView | undefined
>(undefined);
const onCurrentViewChange = useRecoilValue(onCurrentViewChangeState);
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
);
const isPersistingViewFields = useRecoilValue(isPersistingViewFieldsState);
const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState);
useEffect(() => {
if (!newViewsOnCurrentObject.length) return;
if (
!isDeeplyEqual(
currentViewWithCombinedFiltersAndSorts,
currentViewSnapshot,
)
) {
setCurrentViewSnapshot(currentViewWithCombinedFiltersAndSorts);
if (!isDeeplyEqual(views, newViewsOnCurrentObject)) {
setViews(newViewsOnCurrentObject);
if (isUndefined(currentViewWithCombinedFiltersAndSorts)) {
onCurrentViewChange?.(undefined);
return;
}
if (!isPersistingViewFields) {
onCurrentViewChange?.(currentViewWithCombinedFiltersAndSorts);
}
}
const currentView =
newViewsOnCurrentObject.find(
(view) => view.id === currentViewIdFromUrl,
) ??
newViewsOnCurrentObject[0] ??
null;
if (isUndefinedOrNull(currentView)) return;
setCurrentViewId(currentView.id);
if (isDefined(currentView?.viewFields)) {
loadViewFields(currentView.viewFields, currentView.id);
loadViewFilters(currentView.viewFilters, currentView.id);
loadViewSorts(currentView.viewSorts, currentView.id);
}
if (!currentViewIdFromUrl) return changeViewInUrl(currentView.id);
}, [
changeViewInUrl,
currentViewIdFromUrl,
loadViewFields,
loadViewFilters,
loadViewSorts,
newViewsOnCurrentObject,
setCurrentViewId,
setViews,
views,
availableFilterDefinitions,
currentViewSnapshot,
currentViewWithCombinedFiltersAndSorts,
isPersistingViewFields,
onCurrentViewChange,
]);
useEffect(() => {
if (!currentViewIdFromUrl || !newViewsOnCurrentObject.length) return;
loadView(currentViewIdFromUrl);
}, [currentViewIdFromUrl, loadView, newViewsOnCurrentObject]);
if (
isDefined(currentViewWithCombinedFiltersAndSorts) &&
!isDefined(currentViewId)
) {
setCurrentViewId(currentViewWithCombinedFiltersAndSorts.id);
}
}, [currentViewWithCombinedFiltersAndSorts, currentViewId, setCurrentViewId]);
return <></>;
};

View File

@ -4,20 +4,21 @@ import { useRecoilValue } from 'recoil';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { isDefined } from '~/utils/isDefined';
type ViewBarFilterEffectProps = {
filterDropdownId: string;
onFilterSelect?: ((filter: Filter) => void) | undefined;
};
export const ViewBarFilterEffect = ({
filterDropdownId,
onFilterSelect,
}: ViewBarFilterEffectProps) => {
const { availableFilterDefinitionsState, currentViewFiltersState } =
useViewScopedStates();
const { availableFilterDefinitionsState, unsavedToUpsertViewFiltersState } =
useViewStates();
const { upsertCombinedViewFilter } = useCombinedViewFilters();
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
@ -25,32 +26,38 @@ export const ViewBarFilterEffect = ({
const {
setAvailableFilterDefinitions,
setOnFilterSelect,
filterDefinitionUsedInDropdown,
filterDefinitionUsedInDropdownState,
setObjectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedOptionValues,
isObjectFilterDropdownUnfolded,
} = useFilterDropdown({ filterDropdownId });
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
useEffect(() => {
if (isDefined(availableFilterDefinitions)) {
setAvailableFilterDefinitions(availableFilterDefinitions);
}
if (isDefined(onFilterSelect)) {
setOnFilterSelect(() => onFilterSelect);
}
setOnFilterSelect(() => (filter: Filter | null) => {
if (isDefined(filter)) {
upsertCombinedViewFilter(filter);
}
});
}, [
availableFilterDefinitions,
onFilterSelect,
setAvailableFilterDefinitions,
setOnFilterSelect,
upsertCombinedViewFilter,
]);
const currentViewFilters = useRecoilValue(currentViewFiltersState);
const unsavedToUpsertViewFilters = useRecoilValue(
unsavedToUpsertViewFiltersState,
);
useEffect(() => {
if (filterDefinitionUsedInDropdown?.type === 'RELATION') {
const viewFilterUsedInDropdown = currentViewFilters.find(
const viewFilterUsedInDropdown = unsavedToUpsertViewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown.fieldMetadataId,
@ -64,7 +71,7 @@ export const ViewBarFilterEffect = ({
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecordIds);
} else if (filterDefinitionUsedInDropdown?.type === 'SELECT') {
const viewFilterUsedInDropdown = currentViewFilters.find(
const viewFilterUsedInDropdown = unsavedToUpsertViewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown.fieldMetadataId,
@ -82,10 +89,9 @@ export const ViewBarFilterEffect = ({
}
}, [
filterDefinitionUsedInDropdown,
currentViewFilters,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownUnfolded,
setObjectFilterDropdownSelectedOptionValues,
unsavedToUpsertViewFilters,
]);
return <></>;

View File

@ -1,42 +1,52 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useSortDropdown';
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
import { isDefined } from '~/utils/isDefined';
type ViewBarSortEffectProps = {
sortDropdownId: string;
onSortSelect?: ((sort: Sort) => void) | undefined;
};
export const ViewBarSortEffect = ({
sortDropdownId,
onSortSelect,
}: ViewBarSortEffectProps) => {
const { availableSortDefinitionsState } = useViewScopedStates();
const { availableSortDefinitionsState } = useViewStates();
const { upsertCombinedViewSort } = useCombinedViewSorts();
const availableSortDefinitions = useRecoilValue(
availableSortDefinitionsState,
);
const { setAvailableSortDefinitions, setOnSortSelect } = useSortDropdown({
const {
availableSortDefinitionsState: availableSortDefinitionsInSortDropdownState,
onSortSelectState,
} = useSortDropdown({
sortDropdownId,
});
const setAvailableSortDefinitionsInSortDropdown = useSetRecoilState(
availableSortDefinitionsInSortDropdownState,
);
const setOnSortSelect = useSetRecoilState(onSortSelectState);
useEffect(() => {
if (isDefined(availableSortDefinitions)) {
setAvailableSortDefinitions(availableSortDefinitions);
}
if (isDefined(onSortSelect)) {
setOnSortSelect(() => onSortSelect);
setAvailableSortDefinitionsInSortDropdown(availableSortDefinitions);
}
setOnSortSelect(() => (sort: Sort | null) => {
if (isDefined(sort)) {
upsertCombinedViewSort(sort);
}
});
}, [
availableSortDefinitions,
onSortSelect,
setAvailableSortDefinitions,
setAvailableSortDefinitionsInSortDropdown,
setOnSortSelect,
upsertCombinedViewSort,
]);
return <></>;

View File

@ -1,7 +1,7 @@
import { MouseEvent } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import {
IconChevronDown,
@ -20,10 +20,11 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { VIEWS_DROPDOWN_ID } from '@/views/constants/ViewsDropdownId';
import { useViewBar } from '@/views/hooks/useViewBar';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useHandleViews } from '@/views/hooks/useHandleViews';
import { isDefined } from '~/utils/isDefined';
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
import { useViewStates } from '../hooks/internal/useViewStates';
const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
font-weight: ${({ theme }) => theme.font.weight.regular};
@ -65,18 +66,18 @@ export const ViewsDropdownButton = ({
optionsDropdownScopeId,
}: ViewsDropdownButtonProps) => {
const theme = useTheme();
const { removeView, changeViewInUrl } = useViewBar();
const { viewsState, currentViewSelector, entityCountInCurrentViewState } =
useViewScopedStates();
const { removeView, selectView } = useHandleViews();
const { entityCountInCurrentViewState, viewEditModeState } = useViewStates();
const { currentViewWithCombinedFiltersAndSorts, viewsOnCurrentObject } =
useGetCurrentView();
const views = useRecoilValue(viewsState);
const currentView = useRecoilValue(currentViewSelector);
const entityCountInCurrentView = useRecoilValue(
entityCountInCurrentViewState,
);
const { setViewEditMode, setCurrentViewId, loadView } = useViewBar();
const setViewEditMode = useSetRecoilState(viewEditModeState);
const {
isDropdownOpen: isViewsDropdownOpen,
@ -87,14 +88,10 @@ export const ViewsDropdownButton = ({
optionsDropdownScopeId,
);
const handleViewSelect = useRecoilCallback(
() => async (viewId: string) => {
changeViewInUrl(viewId);
loadView(viewId);
closeViewsDropdown();
},
[changeViewInUrl, closeViewsDropdown, loadView],
);
const handleViewSelect = (viewId: string) => {
selectView(viewId);
closeViewsDropdown();
};
const handleAddViewButtonClick = () => {
setViewEditMode('create');
@ -108,8 +105,7 @@ export const ViewsDropdownButton = ({
viewId: string,
) => {
event.stopPropagation();
changeViewInUrl(viewId);
setCurrentViewId(viewId);
selectView(viewId);
setViewEditMode('edit');
onViewEditModeChange?.();
closeViewsDropdown();
@ -123,11 +119,12 @@ export const ViewsDropdownButton = ({
event.stopPropagation();
await removeView(viewId);
selectView(viewsOnCurrentObject.filter((view) => view.id !== viewId)[0].id);
closeViewsDropdown();
};
const { getIcon } = useIcons();
const CurrentViewIcon = getIcon(currentView?.icon);
const CurrentViewIcon = getIcon(currentViewWithCombinedFiltersAndSorts?.icon);
return (
<Dropdown
@ -135,12 +132,14 @@ export const ViewsDropdownButton = ({
dropdownHotkeyScope={hotkeyScope}
clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isViewsDropdownOpen}>
{currentView && CurrentViewIcon ? (
{currentViewWithCombinedFiltersAndSorts && CurrentViewIcon ? (
<CurrentViewIcon size={theme.icon.size.md} />
) : (
<IconList size={theme.icon.size.md} />
)}
<StyledViewName>{currentView?.name ?? 'All'}</StyledViewName>
<StyledViewName>
{currentViewWithCombinedFiltersAndSorts?.name ?? 'All'}
</StyledViewName>
<StyledDropdownLabelAdornments>
· {entityCountInCurrentView}{' '}
<IconChevronDown size={theme.icon.size.sm} />
@ -150,16 +149,18 @@ export const ViewsDropdownButton = ({
dropdownComponents={
<>
<DropdownMenuItemsContainer>
{views.map((view) => (
{viewsOnCurrentObject.map((view) => (
<MenuItem
key={view.id}
iconButtons={[
{
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
},
views.length > 1
currentViewWithCombinedFiltersAndSorts?.id === view.id
? {
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
}
: null,
viewsOnCurrentObject.length > 1
? {
Icon: IconTrash,
onClick: (event: MouseEvent<HTMLButtonElement>) =>