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:
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
});
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 <></>;
|
||||
};
|
||||
|
||||
@ -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 <></>;
|
||||
|
||||
@ -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 <></>;
|
||||
|
||||
@ -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>) =>
|
||||
|
||||
Reference in New Issue
Block a user