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>) =>

View File

@ -1,2 +0,0 @@
// TODO: find a better pattern than using '' as a fallback
export const UNDEFINED_FAMILY_ITEM_ID = '';

View File

@ -1,27 +1,11 @@
import { act } from 'react-dom/test-utils';
import { MemoryRouter, useSearchParams } from 'react-router-dom';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook, waitFor } from '@testing-library/react';
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDeleteOneRecordMutation } from '@/object-record/utils/generateDeleteOneRecordMutation';
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
import {
filterDefinition,
viewFilter,
} from '@/views/hooks/__tests__/useViewBar_ViewFilters.test';
import {
sortDefinition,
viewSort,
} from '@/views/hooks/__tests__/useViewBar_ViewSorts.test';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewScope } from '@/views/scopes/ViewScope';
import { entityCountInCurrentViewScopedState } from '@/views/states/entityCountInCurrentViewScopedState';
import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState';
import { viewObjectMetadataIdScopeState } from '@/views/states/viewObjectMetadataIdScopeState';
const mockedUuid = 'mocked-uuid';
jest.mock('uuid');
@ -49,225 +33,19 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
>
<MockedProvider mocks={mocks} addTypename={false}>
<RecoilRoot>
<ViewScope viewScopeId="viewScopeId">{children}</ViewScope>
<ViewScope viewScopeId="viewScopeId" onCurrentViewChange={() => {}}>
{children}
</ViewScope>
</RecoilRoot>
</MockedProvider>
</MemoryRouter>
);
const renderHookConfig = {
const _renderHookConfig = {
wrapper: Wrapper,
};
const viewBarId = 'viewBarTestId';
const _viewBarId = 'viewBarTestId';
describe('useViewBar', () => {
it('should set and get current view Id', () => {
const { result } = renderHook(
() => useViewBar({ viewBarId }),
renderHookConfig,
);
expect(result.current.scopeId).toBe(viewBarId);
expect(result.current.currentViewId).toBeUndefined();
act(() => {
result.current.setCurrentViewId('testId');
});
expect(result.current.currentViewId).toBe('testId');
});
it('should create view and update url params', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
const searchParams = useSearchParams();
return {
viewBar,
searchParams,
};
}, renderHookConfig);
await act(async () => {
await result.current.viewBar.createView('Test View');
});
expect(result.current.searchParams[0].get('view')).toBe(mockedUuid);
});
it('should delete current view and remove id from params', async () => {
const { result } = renderHook(
() => ({
viewBar: useViewBar({ viewBarId }),
searchParams: useSearchParams(),
}),
renderHookConfig,
);
await act(async () => {
await result.current.viewBar.createView('Test View');
result.current.viewBar.setCurrentViewId(mockedUuid);
});
expect(result.current.searchParams[0].get('view')).toBe(mockedUuid);
await act(async () => {
await result.current.viewBar.removeView(mockedUuid);
});
expect(result.current.searchParams[0].get('view')).toBeNull();
const addBookMutationMock = mocks[0].result;
await waitFor(() => expect(addBookMutationMock).toHaveBeenCalled());
});
it('should resetViewBar', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
const {
currentViewFiltersState,
currentViewSortsState,
viewEditModeState,
} = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewFilters = useRecoilValue(currentViewFiltersState);
const currentViewSorts = useRecoilValue(currentViewSortsState);
const viewEditMode = useRecoilValue(viewEditModeState);
return {
viewBar,
currentViewFilters,
currentViewSorts,
viewEditMode,
};
}, renderHookConfig);
act(() => {
result.current.viewBar.resetViewBar();
});
expect(result.current.currentViewFilters).toStrictEqual([]);
expect(result.current.currentViewSorts).toStrictEqual([]);
expect(result.current.viewEditMode).toBe('none');
});
it('should handleViewNameSubmit', async () => {
const { result } = renderHook(
() => useViewBar({ viewBarId }),
renderHookConfig,
);
await act(async () => {
await result.current.handleViewNameSubmit('New View Name');
});
});
it('should update edit mode', async () => {
const { result } = renderHook(
() => ({
viewBar: useViewBar({ viewBarId }),
editMode: useRecoilState(
getScopedStateDeprecated(viewEditModeScopedState, viewBarId),
)[0],
}),
renderHookConfig,
);
expect(result.current.editMode).toBe('none');
await act(async () => {
result.current.viewBar.setViewEditMode('create');
});
expect(result.current.editMode).toBe('create');
await act(async () => {
result.current.viewBar.setViewEditMode('edit');
});
expect(result.current.editMode).toBe('edit');
});
it('should update url param', async () => {
const { result } = renderHook(
() => ({
viewBar: useViewBar({ viewBarId }),
searchParams: useSearchParams(),
}),
renderHookConfig,
);
expect(result.current.searchParams[0].get('view')).toBeNull();
await act(async () => {
result.current.viewBar.changeViewInUrl('view1');
});
expect(result.current.searchParams[0].get('view')).toBe('view1');
});
it('should update object metadata id', async () => {
const { result } = renderHook(
() => ({
viewBar: useViewBar({ viewBarId }),
metadataId: useRecoilState(
getScopedStateDeprecated(viewObjectMetadataIdScopeState, viewBarId),
)[0],
}),
renderHookConfig,
);
expect(result.current.metadataId).toBeUndefined();
await act(async () => {
result.current.viewBar.setViewObjectMetadataId('newId');
});
expect(result.current.metadataId).toBe('newId');
});
it('should update count in current view', async () => {
const { result } = renderHook(
() => ({
viewBar: useViewBar({ viewBarId }),
count: useRecoilState(
getScopedStateDeprecated(
entityCountInCurrentViewScopedState,
viewBarId,
),
)[0],
}),
renderHookConfig,
);
expect(result.current.count).toBe(0);
await act(async () => {
result.current.viewBar.setEntityCountInCurrentView(1);
});
expect(result.current.count).toBe(1);
});
it('should loadView', async () => {
const { result } = renderHook(
() => useViewBar({ viewBarId }),
renderHookConfig,
);
act(() => {
result.current.loadView(mockedUuid);
});
});
it('should updateCurrentView', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
viewBar.setCurrentViewId(mockedUuid);
viewBar.setAvailableSortDefinitions([sortDefinition]);
viewBar.loadViewSorts([viewSort], mockedUuid);
viewBar.setAvailableFilterDefinitions([filterDefinition]);
viewBar.loadViewFilters([viewFilter], mockedUuid);
return { viewBar };
}, renderHookConfig);
await act(async () => {
await result.current.viewBar.updateCurrentView();
});
});
it('should set and get current view Id', () => {});
});

View File

@ -1,170 +0,0 @@
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import { gql } from '@apollo/client';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook, waitFor } from '@testing-library/react';
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { getScopedFamilyStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedFamilyStateDeprecated';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewScope } from '@/views/scopes/ViewScope';
import { currentViewFieldsScopedFamilyState } from '@/views/states/currentViewFieldsScopedFamilyState';
import { ViewField } from '@/views/types/ViewField';
const fieldMetadataId = '12ecdf87-506f-44a7-98c6-393e5f05b225';
const fieldDefinition: ColumnDefinition<FieldMetadata> = {
size: 1,
position: 1,
fieldMetadataId,
label: 'label',
iconName: 'icon',
type: 'TEXT',
metadata: {
placeHolder: 'placeHolder',
fieldName: 'fieldName',
},
};
const viewField: ViewField = {
id: '88930a16-685f-493b-a96b-91ca55666bba',
fieldMetadataId,
position: 1,
isVisible: true,
size: 1,
definition: fieldDefinition,
};
const viewBarId = 'viewBarTestId';
const currentViewId = '23f5dceb-3482-4e3a-9bb4-2f52f2556be9';
const mocks = [
{
request: {
query: gql`
mutation CreateOneViewField($input: ViewFieldCreateInput!) {
createViewField(data: $input) {
__typename
position
isVisible
fieldMetadataId
viewId
id
size
createdAt
updatedAt
}
}
`,
variables: {
input: {
fieldMetadataId,
viewId: currentViewId,
isVisible: true,
size: 1,
position: 1,
},
},
},
result: jest.fn(() => ({
data: { createViewField: { id: '' } },
})),
},
];
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
<MockedProvider mocks={mocks} addTypename={false}>
<RecoilRoot>
<ViewScope viewScopeId="viewScopeId">{children}</ViewScope>
</RecoilRoot>
</MockedProvider>
</MemoryRouter>
);
const renderHookConfig = {
wrapper: Wrapper,
};
describe('useViewBar > viewFields', () => {
it('should update current fields', async () => {
const { result } = renderHook(
() => ({
viewBar: useViewBar({ viewBarId }),
currentFields: useRecoilState(
getScopedFamilyStateDeprecated(
currentViewFieldsScopedFamilyState,
viewBarId,
currentViewId,
),
)[0],
}),
renderHookConfig,
);
expect(result.current.currentFields).toStrictEqual([]);
await act(async () => {
result.current.viewBar.setCurrentViewId(currentViewId);
result.current.viewBar.setViewObjectMetadataId('newId');
result.current.viewBar.persistViewFields([viewField]);
});
await waitFor(() =>
expect(result.current.currentFields).toEqual([viewField]),
);
});
it('should persist view fields', async () => {
const { result } = renderHook(
() => useViewBar({ viewBarId }),
renderHookConfig,
);
await act(async () => {
result.current.setCurrentViewId(currentViewId);
result.current.setViewObjectMetadataId('newId');
await result.current.persistViewFields([viewField]);
});
const persistViewFieldsMutation = mocks[0];
await waitFor(() =>
expect(persistViewFieldsMutation.result).toHaveBeenCalled(),
);
});
it('should load view fields', async () => {
const currentViewId = 'ac8807fd-0065-436d-bdf6-94333d75af6e';
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
const { currentViewFieldsState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewFields = useRecoilValue(currentViewFieldsState);
return {
viewBar,
currentViewFields,
};
}, renderHookConfig);
expect(result.current.currentViewFields).toStrictEqual([]);
await act(async () => {
result.current.viewBar.setAvailableFieldDefinitions([fieldDefinition]);
await result.current.viewBar.loadViewFields([viewField], currentViewId);
result.current.viewBar.setCurrentViewId(currentViewId);
});
expect(result.current.currentViewFields).toStrictEqual([viewField]);
});
});

View File

@ -1,178 +0,0 @@
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewScope } from '@/views/scopes/ViewScope';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
<MockedProvider addTypename={false}>
<RecoilRoot>
<ViewScope viewScopeId="viewScopeId">{children}</ViewScope>
</RecoilRoot>
</MockedProvider>
</MemoryRouter>
);
const renderHookConfig = {
wrapper: Wrapper,
};
const viewBarId = 'viewBarTestId';
export const filterDefinition: FilterDefinition = {
fieldMetadataId: '113ea8f8-1908-4c9c-9984-3f23c96b92f5',
label: 'label',
iconName: 'iconName',
type: 'TEXT',
};
export const viewFilter: ViewFilter = {
id: 'id',
fieldMetadataId: '113ea8f8-1908-4c9c-9984-3f23c96b92f5',
operand: ViewFilterOperand.Is,
value: 'value',
displayValue: 'displayValue',
definition: filterDefinition,
};
const currentViewId = '23f5dceb-3482-4e3a-9bb4-2f52f2556be9';
describe('useViewBar > viewFilters', () => {
it('should load view filters', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
const { currentViewFiltersState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewFilters = useRecoilValue(currentViewFiltersState);
return {
viewBar,
currentViewFilters,
};
}, renderHookConfig);
expect(result.current.currentViewFilters).toStrictEqual([]);
await act(async () => {
result.current.viewBar.setAvailableFilterDefinitions([filterDefinition]);
await result.current.viewBar.loadViewFilters([viewFilter], currentViewId);
result.current.viewBar.setCurrentViewId(currentViewId);
});
expect(result.current.currentViewFilters).toStrictEqual([viewFilter]);
});
it('should upsertViewFilter', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
viewBar.setAvailableFilterDefinitions([filterDefinition]);
viewBar.loadViewFilters([viewFilter], currentViewId);
viewBar.setCurrentViewId(currentViewId);
const { currentViewFiltersState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewFilters = useRecoilValue(currentViewFiltersState);
return {
viewBar,
currentViewFilters,
};
}, renderHookConfig);
expect(result.current.currentViewFilters).toStrictEqual([viewFilter]);
const newFilters: Filter[] = [
{
fieldMetadataId: '113ea8f8-1908-4c9c-9984-3f23c96b92f5',
value: 'value',
displayValue: 'displayValue',
operand: ViewFilterOperand.IsNot,
definition: {
fieldMetadataId: 'id',
label: 'label',
iconName: 'icon',
type: 'TEXT',
},
},
{
fieldMetadataId: 'd9487757-183e-4fa0-a554-a980850cb23d',
value: 'value',
displayValue: 'displayValue',
operand: ViewFilterOperand.Contains,
definition: {
fieldMetadataId: 'id',
label: 'label',
iconName: 'icon',
type: 'TEXT',
},
},
];
// upsert an existing filter
act(() => {
result.current.viewBar.upsertViewFilter(newFilters[0]);
});
expect(result.current.currentViewFilters).toStrictEqual([
{ ...newFilters[0], id: viewFilter.id },
]);
// upsert a new filter
act(() => {
result.current.viewBar.upsertViewFilter(newFilters[1]);
});
// expect currentViewFilters to contain both filters
expect(result.current.currentViewFilters).toStrictEqual([
{ ...newFilters[0], id: viewFilter.id },
{ ...newFilters[1], id: undefined },
]);
});
it('should remove view filter', () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
viewBar.setAvailableFilterDefinitions([filterDefinition]);
viewBar.loadViewFilters([viewFilter], currentViewId);
viewBar.setCurrentViewId(currentViewId);
const { currentViewFiltersState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewFilters = useRecoilValue(currentViewFiltersState);
return {
viewBar,
currentViewFilters,
};
}, renderHookConfig);
expect(result.current.currentViewFilters).toStrictEqual([viewFilter]);
// remove an existing filter
act(() => {
result.current.viewBar.removeViewFilter(filterDefinition.fieldMetadataId);
});
expect(result.current.currentViewFilters).toStrictEqual([]);
});
});

View File

@ -1,165 +0,0 @@
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewScope } from '@/views/scopes/ViewScope';
import { ViewSort } from '@/views/types/ViewSort';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
<MockedProvider addTypename={false}>
<RecoilRoot>
<ViewScope viewScopeId="viewScopeId">{children}</ViewScope>
</RecoilRoot>
</MockedProvider>
</MemoryRouter>
);
const renderHookConfig = {
wrapper: Wrapper,
};
const viewBarId = 'viewBarTestId';
export const sortDefinition: SortDefinition = {
fieldMetadataId: '12ecdf87-506f-44a7-98c6-393e5f05b225',
label: 'label',
iconName: 'icon',
};
export const viewSort: ViewSort = {
id: '88930a16-685f-493b-a96b-91ca55666bba',
fieldMetadataId: '12ecdf87-506f-44a7-98c6-393e5f05b225',
direction: 'asc',
definition: sortDefinition,
};
describe('View Sorts', () => {
const currentViewId = 'ac8807fd-0065-436d-bdf6-94333d75af6e';
it('should load view sorts', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
const { currentViewSortsState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewSorts = useRecoilValue(currentViewSortsState);
return {
viewBar,
currentViewSorts,
};
}, renderHookConfig);
expect(result.current.currentViewSorts).toStrictEqual([]);
await act(async () => {
result.current.viewBar.setAvailableSortDefinitions([sortDefinition]);
await result.current.viewBar.loadViewSorts([viewSort], currentViewId);
result.current.viewBar.setCurrentViewId(currentViewId);
});
expect(result.current.currentViewSorts).toStrictEqual([viewSort]);
});
it('should upsertViewSort', async () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
viewBar.setAvailableSortDefinitions([sortDefinition]);
viewBar.loadViewSorts([viewSort], currentViewId);
viewBar.setCurrentViewId(currentViewId);
const { currentViewSortsState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewSorts = useRecoilValue(currentViewSortsState);
return {
viewBar,
currentViewSorts,
};
}, renderHookConfig);
expect(result.current.currentViewSorts).toStrictEqual([viewSort]);
const newSortFieldMetadataId = 'd9487757-183e-4fa0-a554-a980850cb23d';
const newSorts: Sort[] = [
{
fieldMetadataId: viewSort.fieldMetadataId,
direction: 'desc',
definition: sortDefinition,
},
{
fieldMetadataId: newSortFieldMetadataId,
direction: 'asc',
definition: {
...sortDefinition,
fieldMetadataId: newSortFieldMetadataId,
},
},
];
// upsert an existing sort
act(() => {
result.current.viewBar.upsertViewSort(newSorts[0]);
});
expect(result.current.currentViewSorts).toStrictEqual([
{ ...newSorts[0], id: viewSort.id },
]);
// upsert a new sort
act(() => {
result.current.viewBar.upsertViewSort(newSorts[1]);
});
// expect currentViewSorts to contain both sorts
expect(result.current.currentViewSorts).toStrictEqual([
{ ...newSorts[0], id: viewSort.id },
{ ...newSorts[1], id: undefined },
]);
});
it('should remove view sort', () => {
const { result } = renderHook(() => {
const viewBar = useViewBar({ viewBarId });
viewBar.setAvailableSortDefinitions([sortDefinition]);
viewBar.loadViewSorts([viewSort], currentViewId);
viewBar.setCurrentViewId(currentViewId);
const { currentViewSortsState } = useViewScopedStates({
viewScopeId: viewBarId,
});
const currentViewSorts = useRecoilValue(currentViewSortsState);
return {
viewBar,
currentViewSorts,
};
}, renderHookConfig);
expect(result.current.currentViewSorts).toStrictEqual([viewSort]);
// remove an existing sort
act(() => {
result.current.viewBar.removeViewSort(sortDefinition.fieldMetadataId);
});
expect(result.current.currentViewSorts).toStrictEqual([]);
});
});

View File

@ -0,0 +1,115 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { v4 } from 'uuid';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { GraphQLView } from '@/views/types/GraphQLView';
import { ViewField } from '@/views/types/ViewField';
export const usePersistViewFieldRecords = () => {
const {
updateOneRecordMutation,
createOneRecordMutation,
getRecordFromCache,
objectMetadataItem,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewField,
});
const { objectMetadataItems } = useObjectMetadataItems();
const apolloClient = useApolloClient();
const createViewFieldRecords = useCallback(
(viewFieldsToCreate: ViewField[], view: GraphQLView) => {
if (!viewFieldsToCreate.length) return;
return Promise.all(
viewFieldsToCreate.map((viewField) =>
apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: {
fieldMetadataId: viewField.fieldMetadataId,
viewId: view.id,
isVisible: viewField.isVisible,
position: viewField.position,
size: viewField.size,
id: v4(),
},
},
update: (cache, { data }) => {
const record = data?.['createViewField'];
if (!record) return;
triggerCreateRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToCreate: [record],
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
createOneRecordMutation,
objectMetadataItem,
objectMetadataItems,
],
);
const updateViewFieldRecords = useCallback(
(viewFieldsToUpdate: ViewField[]) => {
if (!viewFieldsToUpdate.length) return;
return Promise.all(
viewFieldsToUpdate.map((viewField) =>
apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewField.id,
input: {
isVisible: viewField.isVisible,
position: viewField.position,
size: viewField.size,
},
},
update: (cache, { data }) => {
const record = data?.['updateViewField'];
if (!record) return;
const cachedRecord = getRecordFromCache<ObjectRecord>(record.id);
if (!cachedRecord) return;
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
currentRecord: cachedRecord,
updatedRecord: record,
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
getRecordFromCache,
objectMetadataItem,
objectMetadataItems,
updateOneRecordMutation,
],
);
return {
createViewFieldRecords,
updateViewFieldRecords,
};
};

View File

@ -0,0 +1,156 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { GraphQLView } from '@/views/types/GraphQLView';
import { ViewFilter } from '@/views/types/ViewFilter';
export const usePersistViewFilterRecords = () => {
const {
updateOneRecordMutation,
createOneRecordMutation,
deleteOneRecordMutation,
objectMetadataItem,
getRecordFromCache,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewFilter,
});
const { objectMetadataItems } = useObjectMetadataItems();
const apolloClient = useApolloClient();
const createViewFilterRecords = useCallback(
(viewFiltersToCreate: ViewFilter[], view: GraphQLView) => {
if (!viewFiltersToCreate.length) return;
return Promise.all(
viewFiltersToCreate.map((viewFilter) =>
apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: {
fieldMetadataId: viewFilter.fieldMetadataId,
viewId: view.id,
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
},
},
update: (cache, { data }) => {
const record = data?.['createViewFilter'];
if (!record) return;
triggerCreateRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToCreate: [record],
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
createOneRecordMutation,
objectMetadataItem,
objectMetadataItems,
],
);
const updateViewFilterRecords = useCallback(
(viewFiltersToUpdate: ViewFilter[]) => {
if (!viewFiltersToUpdate.length) return;
return Promise.all(
viewFiltersToUpdate.map((viewFilter) =>
apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewFilter.id,
input: {
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
},
},
update: (cache, { data }) => {
const record = data?.['updateViewFilter'];
if (!record) return;
const cachedRecord = getRecordFromCache<ObjectRecord>(record.id);
if (!cachedRecord) return;
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
currentRecord: cachedRecord,
updatedRecord: record,
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
getRecordFromCache,
objectMetadataItem,
objectMetadataItems,
updateOneRecordMutation,
],
);
const deleteViewFilterRecords = useCallback(
(viewFilterIdsToDelete: string[]) => {
if (!viewFilterIdsToDelete.length) return;
return Promise.all(
viewFilterIdsToDelete.map((viewFilterId) =>
apolloClient.mutate({
mutation: deleteOneRecordMutation,
variables: {
idToDelete: viewFilterId,
},
update: (cache, { data }) => {
const record = data?.['deleteViewFilter'];
if (!record) return;
const cachedRecord = getRecordFromCache(record.id, cache);
if (!cachedRecord) return;
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDelete: [cachedRecord],
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
deleteOneRecordMutation,
getRecordFromCache,
objectMetadataItem,
objectMetadataItems,
],
);
return {
createViewFilterRecords,
updateViewFilterRecords,
deleteViewFilterRecords,
};
};

View File

@ -0,0 +1,151 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { GraphQLView } from '@/views/types/GraphQLView';
import { ViewSort } from '@/views/types/ViewSort';
export const usePersistViewSortRecords = () => {
const {
updateOneRecordMutation,
createOneRecordMutation,
deleteOneRecordMutation,
objectMetadataItem,
getRecordFromCache,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewSort,
});
const { objectMetadataItems } = useObjectMetadataItems();
const apolloClient = useApolloClient();
const createViewSortRecords = useCallback(
(viewSortsToCreate: ViewSort[], view: GraphQLView) => {
if (!viewSortsToCreate.length) return;
return Promise.all(
viewSortsToCreate.map((viewSort) =>
apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: {
fieldMetadataId: viewSort.fieldMetadataId,
viewId: view.id,
direction: viewSort.direction,
},
},
update: (cache, { data }) => {
const record = data?.['createViewSort'];
if (!record) return;
triggerCreateRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToCreate: [record],
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
createOneRecordMutation,
objectMetadataItem,
objectMetadataItems,
],
);
const updateViewSortRecords = useCallback(
(viewSortsToUpdate: ViewSort[]) => {
if (!viewSortsToUpdate.length) return;
return Promise.all(
viewSortsToUpdate.map((viewSort) =>
apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewSort.id,
input: {
direction: viewSort.direction,
},
},
update: (cache, { data }) => {
const record = data?.['updateViewSort'];
if (!record) return;
const cachedRecord = getRecordFromCache<ObjectRecord>(record.id);
if (!cachedRecord) return;
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
currentRecord: cachedRecord,
updatedRecord: record,
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
getRecordFromCache,
objectMetadataItem,
objectMetadataItems,
updateOneRecordMutation,
],
);
const deleteViewSortRecords = useCallback(
(viewSortIdsToDelete: string[]) => {
if (!viewSortIdsToDelete.length) return;
return Promise.all(
viewSortIdsToDelete.map((viewSortId) =>
apolloClient.mutate({
mutation: deleteOneRecordMutation,
variables: {
idToDelete: viewSortId,
},
update: (cache, { data }) => {
const record = data?.['deleteViewSort'];
if (!record) return;
const cachedRecord = getRecordFromCache(record.id, cache);
if (!cachedRecord) return;
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDelete: [cachedRecord],
objectMetadataItems,
});
},
}),
),
);
},
[
apolloClient,
deleteOneRecordMutation,
getRecordFromCache,
objectMetadataItem,
objectMetadataItems,
],
);
return {
createViewSortRecords,
updateViewSortRecords,
deleteViewSortRecords,
};
};

View File

@ -1,160 +0,0 @@
import { Reference, useApolloClient } from '@apollo/client';
import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ViewField } from '@/views/types/ViewField';
import { getViewScopedStatesFromSnapshot } from '@/views/utils/getViewScopedStatesFromSnapshot';
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
export const useViewFields = (viewScopeId: string) => {
const { updateOneRecordMutation, createOneRecordMutation } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewField,
});
const { modifyRecordFromCache } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.View,
});
const apolloClient = useApolloClient();
const persistViewFields = useRecoilCallback(
({ snapshot, set }) =>
async (viewFieldsToPersist: ViewField[], viewId?: string) => {
const {
viewObjectMetadataId,
currentViewId,
savedViewFieldsByKey,
onViewFieldsChange,
views,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
viewId,
});
const {
isPersistingViewState,
currentViewFieldsState,
savedViewFieldsState,
} = getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId,
viewId,
});
const viewIdToPersist = viewId ?? currentViewId;
if (!currentViewId || !savedViewFieldsByKey || !viewObjectMetadataId) {
return;
}
const _createViewFields = (viewFieldsToCreate: ViewField[]) => {
if (!viewFieldsToCreate.length) {
return;
}
return Promise.all(
viewFieldsToCreate.map((viewField) =>
apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: {
fieldMetadataId: viewField.fieldMetadataId,
viewId: viewIdToPersist,
isVisible: viewField.isVisible,
size: viewField.size,
position: viewField.position,
},
},
}),
),
);
};
const _updateViewFields = (viewFieldsToUpdate: ViewField[]) => {
if (!viewFieldsToUpdate.length) {
return;
}
return Promise.all(
viewFieldsToUpdate.map((viewField) =>
apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewField.id,
input: {
isVisible: viewField.isVisible,
size: viewField.size,
position: viewField.position,
},
},
}),
),
);
};
const viewFieldsToCreate = viewFieldsToPersist.filter(
(viewField) => !savedViewFieldsByKey[viewField.fieldMetadataId],
);
const viewFieldsToUpdate = viewFieldsToPersist.filter(
(viewFieldToPersit) =>
savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId] &&
(savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId].size !==
viewFieldToPersit.size ||
savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId]
.position !== viewFieldToPersit.position ||
savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId]
.isVisible !== viewFieldToPersit.isVisible),
);
set(isPersistingViewState, true);
await _createViewFields(viewFieldsToCreate);
await _updateViewFields(viewFieldsToUpdate);
set(isPersistingViewState, false);
set(currentViewFieldsState, viewFieldsToPersist);
set(savedViewFieldsState, viewFieldsToPersist);
const existingView = views.find((view) => view.id === viewIdToPersist);
if (!existingView) {
return;
}
modifyRecordFromCache(viewIdToPersist ?? '', {
viewFields: (viewFieldsRef, { readField }) => {
const edges = readField<{ node: Reference }[]>(
'edges',
viewFieldsRef,
);
if (!edges) return viewFieldsRef;
return {
...viewFieldsRef,
edges: viewFieldsToPersist.map((viewField) => ({
node: viewField,
cursor: '',
})),
};
},
});
onViewFieldsChange?.(viewFieldsToPersist);
},
[
viewScopeId,
modifyRecordFromCache,
apolloClient,
createOneRecordMutation,
updateOneRecordMutation,
],
);
return { persistViewFields };
};

View File

@ -1,246 +0,0 @@
import { Reference, useApolloClient } from '@apollo/client';
import { produce } from 'immer';
import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { savedViewFiltersScopedFamilyState } from '@/views/states/savedViewFiltersScopedFamilyState';
import { ViewFilter } from '@/views/types/ViewFilter';
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
import { useViewScopedStates } from './useViewScopedStates';
export const useViewFilters = (viewScopeId: string) => {
const {
updateOneRecordMutation,
createOneRecordMutation,
deleteOneRecordMutation,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewFilter,
});
const { modifyRecordFromCache } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.View,
});
const apolloClient = useApolloClient();
const { currentViewFiltersState } = useViewScopedStates({
viewScopeId: viewScopeId,
});
const persistViewFilters = useRecoilCallback(
({ snapshot, set }) =>
async (viewId?: string) => {
const {
currentViewId,
currentViewFilters,
savedViewFiltersByKey,
views,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId || !currentViewFilters || !savedViewFiltersByKey) {
return;
}
const createViewFilters = (viewFiltersToCreate: ViewFilter[]) => {
if (!viewFiltersToCreate.length) return;
return Promise.all(
viewFiltersToCreate.map((viewFilter) =>
apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: {
fieldMetadataId: viewFilter.fieldMetadataId,
viewId: viewId ?? currentViewId,
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
},
},
}),
),
);
};
const updateViewFilters = (viewFiltersToUpdate: ViewFilter[]) => {
if (!viewFiltersToUpdate.length) return;
return Promise.all(
viewFiltersToUpdate.map((viewFilter) =>
apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewFilter.id,
input: {
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
},
},
}),
),
);
};
const deleteViewFilters = (viewFilterIdsToDelete: string[]) => {
if (!viewFilterIdsToDelete.length) return;
return Promise.all(
viewFilterIdsToDelete.map((viewFilterId) =>
apolloClient.mutate({
mutation: deleteOneRecordMutation,
variables: {
idToDelete: viewFilterId,
},
}),
),
);
};
const filtersToCreate = currentViewFilters.filter(
(filter) => !savedViewFiltersByKey[filter.fieldMetadataId],
);
await createViewFilters(filtersToCreate);
const filtersToUpdate = currentViewFilters.filter(
(filter) =>
savedViewFiltersByKey[filter.fieldMetadataId] &&
(savedViewFiltersByKey[filter.fieldMetadataId].operand !==
filter.operand ||
savedViewFiltersByKey[filter.fieldMetadataId].value !==
filter.value),
);
await updateViewFilters(filtersToUpdate);
const filterKeys = currentViewFilters.map(
(filter) => filter.fieldMetadataId,
);
const filterKeysToDelete = Object.keys(savedViewFiltersByKey).filter(
(previousFilterKey) => !filterKeys.includes(previousFilterKey),
);
const filterIdsToDelete = filterKeysToDelete.map(
(filterKeyToDelete) =>
savedViewFiltersByKey[filterKeyToDelete].id ?? '',
);
await deleteViewFilters(filterIdsToDelete);
set(
savedViewFiltersScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId ?? currentViewId,
}),
currentViewFilters,
);
const existingViewId = viewId ?? currentViewId;
const existingView = views.find((view) => view.id === existingViewId);
if (!existingView) {
return;
}
modifyRecordFromCache(existingViewId, {
viewFilters: (viewFiltersRef, { readField }) => {
const edges = readField<{ node: Reference }[]>(
'edges',
viewFiltersRef,
);
if (!edges) return viewFiltersRef;
return {
...viewFiltersRef,
edges: currentViewFilters.map((viewFilter) => ({
node: viewFilter,
cursor: '',
})),
};
},
});
},
[
apolloClient,
createOneRecordMutation,
deleteOneRecordMutation,
modifyRecordFromCache,
updateOneRecordMutation,
viewScopeId,
],
);
const upsertViewFilter = useRecoilCallback(
({ snapshot, set }) =>
(filterToUpsert: Filter) => {
const { currentViewId, savedViewFiltersByKey, onViewFiltersChange } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId) {
return;
}
if (!savedViewFiltersByKey) {
return;
}
const existingSavedFilterId =
savedViewFiltersByKey[filterToUpsert.fieldMetadataId]?.id;
set(currentViewFiltersState, (filters) => {
const newViewFilters = produce(filters, (filtersDraft) => {
const existingFilterIndex = filtersDraft.findIndex(
(filter) =>
filter.fieldMetadataId === filterToUpsert.fieldMetadataId,
);
if (existingFilterIndex === -1 && filterToUpsert.value !== '') {
filtersDraft.push({
...filterToUpsert,
id: existingSavedFilterId,
});
return filtersDraft;
}
filtersDraft[existingFilterIndex] = {
...filterToUpsert,
id: existingSavedFilterId,
};
});
onViewFiltersChange?.(newViewFilters);
return newViewFilters;
});
},
[currentViewFiltersState, viewScopeId],
);
const removeViewFilter = useRecoilCallback(
({ snapshot, set }) =>
(fieldMetadataId: string) => {
const { currentViewId, currentViewFilters, onViewFiltersChange } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId) {
return;
}
const newViewFilters = currentViewFilters.filter((filter) => {
return filter.fieldMetadataId !== fieldMetadataId;
});
set(currentViewFiltersState, newViewFilters);
onViewFiltersChange?.(newViewFilters);
},
[currentViewFiltersState, viewScopeId],
);
return { persistViewFilters, removeViewFilter, upsertViewFilter };
};

View File

@ -19,17 +19,20 @@ import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
const filterQueryParamsSchema = z.object({
filter: z.record(
z.record(
z.nativeEnum(ViewFilterOperand),
z.string().or(z.array(z.string())),
),
),
view: z.string().optional(),
filter: z
.record(
z.record(
z.nativeEnum(ViewFilterOperand),
z.string().or(z.array(z.string())),
),
)
.optional(),
});
export type FilterQueryParams = z.infer<typeof filterQueryParamsSchema>;
export const useFiltersFromQueryParams = () => {
export const useViewFromQueryParams = () => {
const apolloClient = useApolloClient();
const [searchParams] = useSearchParams();
const { objectNamePlural = '' } = useParams();
@ -39,15 +42,21 @@ export const useFiltersFromQueryParams = () => {
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const generateFindManyRecordsQuery = useGenerateFindManyRecordsQuery();
const filterParamsValidation = filterQueryParamsSchema.safeParse(
const queryParamsValidation = filterQueryParamsSchema.safeParse(
qs.parse(searchParams.toString()),
);
const filterQueryParams = useMemo(
() =>
filterParamsValidation.success ? filterParamsValidation.data.filter : {},
[filterParamsValidation],
queryParamsValidation.success ? queryParamsValidation.data.filter : {},
[queryParamsValidation],
);
const hasFiltersQueryParams = filterParamsValidation.success;
const viewIdQueryParam = useMemo(
() => queryParamsValidation.success && queryParamsValidation.data.view,
[queryParamsValidation],
);
const hasFiltersQueryParams = filterQueryParams;
const getFiltersFromQueryParams = useRecoilCallback(
({ snapshot }) =>
@ -129,6 +138,7 @@ export const useFiltersFromQueryParams = () => {
: filterValueFromURL;
return {
__typename: 'ViewFilter',
id: `tmp-${[
fieldName,
filterOperandFromURL,
@ -140,6 +150,7 @@ export const useFiltersFromQueryParams = () => {
displayValue:
relationRecordNames?.join(', ') ?? filterValueAsString,
definition: filterDefinition,
persistAction: 'NONE',
};
},
),
@ -156,6 +167,7 @@ export const useFiltersFromQueryParams = () => {
);
return {
viewIdQueryParam,
hasFiltersQueryParams,
getFiltersFromQueryParams,
};

View File

@ -1,90 +0,0 @@
import { useRecoilState } from 'recoil';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
import { UNDEFINED_FAMILY_ITEM_ID } from '../../constants';
import { ViewScopeInternalContext } from '../../scopes/scope-internal-context/ViewScopeInternalContext';
import { currentViewIdScopedState } from '../../states/currentViewIdScopedState';
import { getViewScopedStates } from '../../utils/internal/getViewScopedStates';
export const useViewScopedStates = (args?: { viewScopeId?: string }) => {
const { viewScopeId } = args ?? {};
const scopeId = useAvailableScopeIdOrThrow(
ViewScopeInternalContext,
viewScopeId,
);
// View
const [currentViewId] = useRecoilState(
getScopedStateDeprecated(currentViewIdScopedState, scopeId),
);
const viewId = currentViewId ?? UNDEFINED_FAMILY_ITEM_ID;
const {
availableFieldDefinitionsState,
availableFilterDefinitionsState,
availableSortDefinitionsState,
canPersistFiltersSelector,
canPersistSortsSelector,
currentViewFieldsState,
currentViewFiltersState,
currentViewIdState,
currentViewSelector,
currentViewSortsState,
entityCountInCurrentViewState,
isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState,
onViewFiltersChangeState,
onViewSortsChangeState,
onViewTypeChangeState,
onViewCompactModeChangeState,
savedViewFieldsByKeySelector,
savedViewFieldsState,
savedViewFiltersByKeySelector,
savedViewFiltersState,
savedViewSortsByKeySelector,
savedViewSortsState,
viewEditModeState,
viewObjectMetadataIdState,
viewTypeState,
viewsState,
} = getViewScopedStates({
viewScopeId: scopeId,
viewId,
});
return {
availableFieldDefinitionsState,
availableFilterDefinitionsState,
availableSortDefinitionsState,
canPersistFiltersSelector,
canPersistSortsSelector,
currentViewFieldsState,
currentViewFiltersState,
currentViewIdState,
currentViewSelector,
currentViewSortsState,
entityCountInCurrentViewState,
isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState,
onViewFiltersChangeState,
onViewSortsChangeState,
onViewTypeChangeState,
onViewCompactModeChangeState,
savedViewFieldsByKeySelector,
savedViewFieldsState,
savedViewFiltersByKeySelector,
savedViewFiltersState,
savedViewSortsByKeySelector,
savedViewSortsState,
viewEditModeState,
viewObjectMetadataIdState,
viewTypeState,
viewsState,
};
};

View File

@ -1,236 +0,0 @@
import { Reference, useApolloClient } from '@apollo/client';
import { produce } from 'immer';
import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { savedViewSortsScopedFamilyState } from '@/views/states/savedViewSortsScopedFamilyState';
import { ViewSort } from '@/views/types/ViewSort';
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { useViewScopedStates } from './useViewScopedStates';
export const useViewSorts = (viewScopeId: string) => {
const {
updateOneRecordMutation,
createOneRecordMutation,
deleteOneRecordMutation,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewSort,
});
const { modifyRecordFromCache } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.View,
});
const apolloClient = useApolloClient();
const { currentViewSortsState } = useViewScopedStates({
viewScopeId: viewScopeId,
});
const persistViewSorts = useRecoilCallback(
({ snapshot, set }) =>
async (viewId?: string) => {
const { currentViewId, currentViewSorts, savedViewSortsByKey, views } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId) {
return;
}
if (isUndefinedOrNull(currentViewSorts)) {
return;
}
if (!savedViewSortsByKey) {
return;
}
const createViewSorts = (viewSortsToCreate: ViewSort[]) => {
if (!viewSortsToCreate.length) return;
return Promise.all(
viewSortsToCreate.map((viewSort) =>
apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: {
fieldMetadataId: viewSort.fieldMetadataId,
viewId: viewId ?? currentViewId,
direction: viewSort.direction,
},
},
}),
),
);
};
const updateViewSorts = (viewSortsToUpdate: ViewSort[]) => {
if (!viewSortsToUpdate.length) return;
return Promise.all(
viewSortsToUpdate.map((viewSort) =>
apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewSort.id,
input: {
direction: viewSort.direction,
},
},
}),
),
);
};
const deleteViewSorts = (viewSortIdsToDelete: string[]) => {
if (!viewSortIdsToDelete.length) return;
return Promise.all(
viewSortIdsToDelete.map((viewSortId) =>
apolloClient.mutate({
mutation: deleteOneRecordMutation,
variables: {
idToDelete: viewSortId,
},
}),
),
);
};
const sortsToCreate = currentViewSorts.filter(
(sort) => !savedViewSortsByKey[sort.fieldMetadataId],
);
await createViewSorts(sortsToCreate);
const sortsToUpdate = currentViewSorts.filter(
(sort) =>
savedViewSortsByKey[sort.fieldMetadataId] &&
savedViewSortsByKey[sort.fieldMetadataId].direction !==
sort.direction,
);
await updateViewSorts(sortsToUpdate);
const sortKeys = currentViewSorts.map((sort) => sort.fieldMetadataId);
const sortKeysToDelete = Object.keys(savedViewSortsByKey).filter(
(previousSortKey) => !sortKeys.includes(previousSortKey),
);
const sortIdsToDelete = sortKeysToDelete.map(
(sortKeyToDelete) => savedViewSortsByKey[sortKeyToDelete].id ?? '',
);
await deleteViewSorts(sortIdsToDelete);
set(
savedViewSortsScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId ?? currentViewId,
}),
currentViewSorts,
);
const existingViewId = viewId ?? currentViewId;
const existingView = views.find((view) => view.id === existingViewId);
if (!existingView) {
return;
}
modifyRecordFromCache(existingViewId, {
viewSorts: (viewSortsRef, { readField }) => {
const edges = readField<{ node: Reference }[]>(
'edges',
viewSortsRef,
);
if (!edges) return viewSortsRef;
return {
...viewSortsRef,
edges: currentViewSorts.map((viewSort) => ({
node: viewSort,
cursor: '',
})),
};
},
});
},
[
apolloClient,
createOneRecordMutation,
deleteOneRecordMutation,
modifyRecordFromCache,
updateOneRecordMutation,
viewScopeId,
],
);
const upsertViewSort = useRecoilCallback(
({ snapshot, set }) =>
(sortToUpsert: Sort) => {
const { currentViewId, onViewSortsChange, savedViewSortsByKey } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId) {
return;
}
if (!savedViewSortsByKey) {
return;
}
const existingSavedSortId =
savedViewSortsByKey[sortToUpsert.fieldMetadataId]?.id;
set(currentViewSortsState, (sorts) => {
const newViewSorts = produce(sorts, (sortsDraft) => {
const existingSortIndex = sortsDraft.findIndex(
(sort) => sort.fieldMetadataId === sortToUpsert.fieldMetadataId,
);
if (existingSortIndex === -1) {
sortsDraft.push({ ...sortToUpsert, id: existingSavedSortId });
return sortsDraft;
}
sortsDraft[existingSortIndex] = {
...sortToUpsert,
id: existingSavedSortId,
};
});
onViewSortsChange?.(newViewSorts);
return newViewSorts;
});
},
[currentViewSortsState, viewScopeId],
);
const removeViewSort = useRecoilCallback(
({ snapshot, set }) =>
(fieldMetadataId: string) => {
const { currentViewId, onViewSortsChange, currentViewSorts } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId) {
return;
}
const newViewSorts = currentViewSorts.filter((filter) => {
return filter.fieldMetadataId !== fieldMetadataId;
});
set(currentViewSortsState, newViewSorts);
onViewSortsChange?.(newViewSorts);
},
[currentViewSortsState, viewScopeId],
);
return { persistViewSorts, upsertViewSort, removeViewSort };
};

View File

@ -0,0 +1,96 @@
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { extractComponentReadOnlySelector } from '@/ui/utilities/state/component-state/utils/extractComponentReadOnlySelector';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { availableFieldDefinitionsComponentState } from '@/views/states/availableFieldDefinitionsComponentState';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
import { isCurrentViewKeyIndexComponentState } from '@/views/states/isCurrentViewIndexComponentState';
import { isPersistingViewFieldsComponentState } from '@/views/states/isPersistingViewFieldsComponentState';
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
import { onCurrentViewChangeComponentState } from '@/views/states/onCurrentViewChangeComponentState';
import { canPersistViewComponentSelector } from '@/views/states/selectors/canPersistViewComponentSelector';
import { unsavedToDeleteViewFilterIdsComponentState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentState';
import { unsavedToDeleteViewSortIdsComponentState } from '@/views/states/unsavedToDeleteViewSortIdsComponentState';
import { unsavedToUpsertViewFiltersComponentState } from '@/views/states/unsavedToUpsertViewFiltersComponentState';
import { unsavedToUpsertViewSortsComponentState } from '@/views/states/unsavedToUpsertViewSortsComponentState';
import { viewEditModeComponentState } from '@/views/states/viewEditModeComponentState';
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
import { ViewScopeInternalContext } from '../../scopes/scope-internal-context/ViewScopeInternalContext';
export const useViewStates = (viewComponentId?: string) => {
const componentId = useAvailableScopeIdOrThrow(
ViewScopeInternalContext,
viewComponentId,
);
return {
componentId,
currentViewIdState: extractComponentState(
currentViewIdComponentState,
componentId,
),
availableFieldDefinitionsState: extractComponentState(
availableFieldDefinitionsComponentState,
componentId,
),
availableFilterDefinitionsState: extractComponentState(
availableFilterDefinitionsComponentState,
componentId,
),
availableSortDefinitionsState: extractComponentState(
availableSortDefinitionsComponentState,
componentId,
),
canPersistViewSelector: extractComponentReadOnlySelector(
canPersistViewComponentSelector,
componentId,
),
isViewBarExpandedState: extractComponentState(
isViewBarExpandedComponentState,
componentId,
),
onCurrentViewChangeState: extractComponentState(
onCurrentViewChangeComponentState,
componentId,
),
entityCountInCurrentViewState: extractComponentState(
entityCountInCurrentViewComponentState,
componentId,
),
viewEditModeState: extractComponentState(
viewEditModeComponentState,
componentId,
),
viewObjectMetadataIdState: extractComponentState(
viewObjectMetadataIdComponentState,
componentId,
),
unsavedToUpsertViewFiltersState: extractComponentState(
unsavedToUpsertViewFiltersComponentState,
componentId,
),
unsavedToUpsertViewSortsState: extractComponentState(
unsavedToUpsertViewSortsComponentState,
componentId,
),
unsavedToDeleteViewFilterIdsState: extractComponentState(
unsavedToDeleteViewFilterIdsComponentState,
componentId,
),
unsavedToDeleteViewSortIdsState: extractComponentState(
unsavedToDeleteViewSortIdsComponentState,
componentId,
),
isPersistingViewFieldsState: extractComponentState(
isPersistingViewFieldsComponentState,
componentId,
),
isCurrentViewKeyIndexState: extractComponentState(
isCurrentViewKeyIndexComponentState,
componentId,
),
};
};

View File

@ -1,80 +0,0 @@
import { useApolloClient } from '@apollo/client';
import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { GraphQLView } from '@/views/types/GraphQLView';
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
export const useViews = (scopeId: string) => {
const {
updateOneRecordMutation: updateOneMutation,
createOneRecordMutation: createOneMutation,
deleteOneRecordMutation: deleteOneMutation,
findManyRecordsQuery: findManyQuery,
} = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.View,
});
const apolloClient = useApolloClient();
const createView = useRecoilCallback(
({ snapshot }) =>
async (view: Pick<GraphQLView, 'id' | 'name'>) => {
const { viewObjectMetadataId, viewType } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
});
if (!viewObjectMetadataId || !viewType) {
return;
}
await apolloClient.mutate({
mutation: createOneMutation,
variables: {
input: {
id: view.id,
name: view.name,
objectMetadataId: viewObjectMetadataId,
type: viewType,
},
},
refetchQueries: [findManyQuery],
});
},
[scopeId, apolloClient, createOneMutation, findManyQuery],
);
const updateView = async (view: GraphQLView) => {
await apolloClient.mutate({
mutation: updateOneMutation,
variables: {
idToUpdate: view.id,
input: {
id: view.id,
name: view.name,
isCompact: view.isCompact,
},
},
refetchQueries: [findManyQuery],
});
};
const deleteView = async (viewId: string) => {
await apolloClient.mutate({
mutation: deleteOneMutation,
variables: {
idToDelete: viewId,
},
refetchQueries: [findManyQuery],
});
};
return {
createView,
deleteView,
isFetchingViews: false,
updateView,
};
};

View File

@ -0,0 +1,158 @@
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
import { ViewFilter } from '@/views/types/ViewFilter';
import { isDefined } from '~/utils/isDefined';
export const useCombinedViewFilters = (viewBarComponentId?: string) => {
const {
unsavedToUpsertViewFiltersState,
unsavedToDeleteViewFilterIdsState,
currentViewIdState,
} = useViewStates(viewBarComponentId);
const { getViewFromCache } = useGetViewFromCache();
const upsertCombinedViewFilter = useRecoilCallback(
({ snapshot, set }) =>
async (upsertedFilter: Filter) => {
const unsavedToUpsertViewFilters = getSnapshotValue(
snapshot,
unsavedToUpsertViewFiltersState,
);
const unsavedToDeleteViewFilterIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterIdsState,
);
const currentViewId = getSnapshotValue(snapshot, currentViewIdState);
if (!currentViewId) {
return;
}
const currentView = await getViewFromCache(currentViewId);
if (!currentView) {
return;
}
const matchingFilterInCurrentView = currentView.viewFilters.find(
(viewFilter) =>
viewFilter.fieldMetadataId === upsertedFilter.fieldMetadataId,
);
const matchingFilterInUnsavedFilters = unsavedToUpsertViewFilters.find(
(viewFilter) =>
viewFilter.fieldMetadataId === upsertedFilter.fieldMetadataId,
);
if (isDefined(matchingFilterInUnsavedFilters)) {
const updatedFilters = unsavedToUpsertViewFilters.map((viewFilter) =>
viewFilter.id === matchingFilterInUnsavedFilters.id
? { ...viewFilter, ...upsertedFilter }
: viewFilter,
);
set(unsavedToUpsertViewFiltersState, updatedFilters);
return;
}
if (isDefined(matchingFilterInCurrentView)) {
set(unsavedToUpsertViewFiltersState, [
...unsavedToUpsertViewFilters,
{ ...matchingFilterInCurrentView, ...upsertedFilter },
]);
set(
unsavedToDeleteViewFilterIdsState,
unsavedToDeleteViewFilterIds.filter(
(id) => id !== matchingFilterInCurrentView.id,
),
);
return;
}
set(unsavedToUpsertViewFiltersState, [
...unsavedToUpsertViewFilters,
{
...upsertedFilter,
id: v4(),
__typename: 'ViewFilter',
} satisfies ViewFilter,
]);
},
[
currentViewIdState,
getViewFromCache,
unsavedToDeleteViewFilterIdsState,
unsavedToUpsertViewFiltersState,
],
);
const removeCombinedViewFilter = useRecoilCallback(
({ snapshot, set }) =>
async (fieldMetadataId: string) => {
const unsavedToUpsertViewFilters = getSnapshotValue(
snapshot,
unsavedToUpsertViewFiltersState,
);
const unsavedToDeleteViewFilterIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterIdsState,
);
const currentViewId = getSnapshotValue(snapshot, currentViewIdState);
if (!currentViewId) {
return;
}
const currentView = await getViewFromCache(currentViewId);
if (!currentView) {
return;
}
const matchingFilterInCurrentView = currentView.viewFilters.find(
(viewFilter) => viewFilter.fieldMetadataId === fieldMetadataId,
);
const matchingFilterInUnsavedFilters = unsavedToUpsertViewFilters.find(
(viewFilter) => viewFilter.fieldMetadataId === fieldMetadataId,
);
if (isDefined(matchingFilterInUnsavedFilters)) {
set(
unsavedToUpsertViewFiltersState,
unsavedToUpsertViewFilters.filter(
(viewFilter) => viewFilter.fieldMetadataId !== fieldMetadataId,
),
);
}
if (isDefined(matchingFilterInCurrentView)) {
set(unsavedToDeleteViewFilterIdsState, [
...new Set([
...unsavedToDeleteViewFilterIds,
matchingFilterInCurrentView.id,
]),
]);
}
},
[
currentViewIdState,
getViewFromCache,
unsavedToDeleteViewFilterIdsState,
unsavedToUpsertViewFiltersState,
],
);
return {
upsertCombinedViewFilter,
removeCombinedViewFilter,
};
};

View File

@ -0,0 +1,159 @@
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
import { ViewSort } from '@/views/types/ViewSort';
import { isDefined } from '~/utils/isDefined';
export const useCombinedViewSorts = (viewBarComponentId?: string) => {
const {
unsavedToUpsertViewSortsState,
unsavedToDeleteViewSortIdsState,
currentViewIdState,
} = useViewStates(viewBarComponentId);
const { getViewFromCache } = useGetViewFromCache();
const upsertCombinedViewSort = useRecoilCallback(
({ snapshot, set }) =>
async (upsertedSort: Sort) => {
const unsavedToUpsertViewSorts = getSnapshotValue(
snapshot,
unsavedToUpsertViewSortsState,
);
const unsavedToDeleteViewSortIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewSortIdsState,
);
const currentViewId = getSnapshotValue(snapshot, currentViewIdState);
if (!currentViewId) {
return;
}
const currentView = await getViewFromCache(currentViewId);
if (!currentView) {
return;
}
const matchingSortInCurrentView = currentView.viewSorts.find(
(viewSort) =>
viewSort.fieldMetadataId === upsertedSort.fieldMetadataId,
);
const matchingSortInUnsavedSorts = unsavedToUpsertViewSorts.find(
(viewSort) =>
viewSort.fieldMetadataId === upsertedSort.fieldMetadataId,
);
if (isDefined(matchingSortInUnsavedSorts)) {
const updatedSorts = unsavedToUpsertViewSorts.map((viewSort) =>
viewSort.id === matchingSortInUnsavedSorts.id
? { ...viewSort, ...upsertedSort }
: viewSort,
);
set(unsavedToUpsertViewSortsState, updatedSorts);
return;
}
if (isDefined(matchingSortInCurrentView)) {
set(unsavedToUpsertViewSortsState, [
...unsavedToUpsertViewSorts,
{ ...matchingSortInCurrentView, ...upsertedSort },
]);
set(
unsavedToDeleteViewSortIdsState,
unsavedToDeleteViewSortIds.filter(
(id) => id !== matchingSortInCurrentView.id,
),
);
return;
}
set(unsavedToUpsertViewSortsState, [
...unsavedToUpsertViewSorts,
{
...upsertedSort,
id: v4(),
__typename: 'ViewSort',
} satisfies ViewSort,
]);
},
[
currentViewIdState,
getViewFromCache,
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewSortsState,
],
);
const removeCombinedViewSort = useRecoilCallback(
({ snapshot, set }) =>
async (fieldMetadataId: string) => {
const unsavedToUpsertViewSorts = getSnapshotValue(
snapshot,
unsavedToUpsertViewSortsState,
);
const unsavedToDeleteViewSortIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewSortIdsState,
);
const currentViewId = getSnapshotValue(snapshot, currentViewIdState);
if (!currentViewId) {
return;
}
const currentView = await getViewFromCache(currentViewId);
if (!currentView) {
return;
}
const matchingSortInCurrentView = currentView.viewSorts.find(
(viewSort) => viewSort.fieldMetadataId === fieldMetadataId,
);
const matchingSortInUnsavedSorts = unsavedToUpsertViewSorts.find(
(viewSort) => viewSort.fieldMetadataId === fieldMetadataId,
);
if (isDefined(matchingSortInUnsavedSorts)) {
set(
unsavedToUpsertViewSortsState,
unsavedToUpsertViewSorts.filter(
(viewSort) => viewSort.fieldMetadataId !== fieldMetadataId,
),
);
return;
}
if (isDefined(matchingSortInCurrentView)) {
set(unsavedToDeleteViewSortIdsState, [
...new Set([
...unsavedToDeleteViewSortIds,
matchingSortInCurrentView.id,
]),
]);
}
},
[
currentViewIdState,
getViewFromCache,
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewSortsState,
],
);
return {
upsertCombinedViewSort,
removeCombinedViewSort,
};
};

View File

@ -0,0 +1,107 @@
import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { ViewScopeInternalContext } from '@/views/scopes/scope-internal-context/ViewScopeInternalContext';
import { GraphQLView } from '@/views/types/GraphQLView';
import { combinedViewFilters } from '@/views/utils/combinedViewFilters';
import { combinedViewSorts } from '@/views/utils/combinedViewSorts';
import { isDefined } from '~/utils/isDefined';
export const useGetCurrentView = (viewBarComponentId?: string) => {
const componentId = useAvailableScopeIdOrThrow(
ViewScopeInternalContext,
viewBarComponentId,
);
const { records: views } = usePrefetchedData<GraphQLView>(
PrefetchKey.AllViews,
);
const {
currentViewIdState,
viewObjectMetadataIdState,
unsavedToUpsertViewFiltersState,
unsavedToDeleteViewFilterIdsState,
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewSortsState,
isCurrentViewKeyIndexState,
} = useViewStates(componentId);
const currentViewId = useRecoilValue(currentViewIdState);
const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState);
const setIsCurrentViewKeyIndex = useSetRecoilState(
isCurrentViewKeyIndexState,
);
const currentViewFromCurrentViewId = views.find(
(view) => view.id === currentViewId,
);
const indexView = views.find(
(view) =>
view.key === 'INDEX' && view.objectMetadataId === viewObjectMetadataId,
);
const currentView = currentViewId ? currentViewFromCurrentViewId : indexView;
useEffect(() => {
setIsCurrentViewKeyIndex(currentView?.key === 'INDEX');
}, [currentView, setIsCurrentViewKeyIndex]);
const viewsOnCurrentObject = views
.filter((view) => view.objectMetadataId === viewObjectMetadataId)
.map((view) => ({
id: view.id,
name: view.name,
type: view.type,
key: view.key,
objectMetadataId: view.objectMetadataId,
icon: view.icon,
}));
const unsavedToUpsertViewFilters = useRecoilValue(
unsavedToUpsertViewFiltersState,
);
const unsavedToUpsertViewSorts = useRecoilValue(
unsavedToUpsertViewSortsState,
);
const unsavedToDeleteViewFilterIds = useRecoilValue(
unsavedToDeleteViewFilterIdsState,
);
const unsavedToDeleteViewSortIds = useRecoilValue(
unsavedToDeleteViewSortIdsState,
);
if (!isDefined(currentView)) {
return {
componentId,
currentViewWithSavedFiltersAndSorts: undefined,
currentViewWithCombinedFiltersAndSorts: undefined,
viewsOnCurrentObject: viewsOnCurrentObject ?? [],
};
}
const currentViewWithCombinedFiltersAndSorts = {
...currentView,
viewFilters: combinedViewFilters(
currentView.viewFilters,
unsavedToUpsertViewFilters,
unsavedToDeleteViewFilterIds,
),
viewSorts: combinedViewSorts(
currentView.viewSorts,
unsavedToUpsertViewSorts,
unsavedToDeleteViewSortIds,
),
};
return {
componentId,
currentViewWithSavedFiltersAndSorts: currentView,
currentViewWithCombinedFiltersAndSorts,
viewsOnCurrentObject: viewsOnCurrentObject ?? [],
};
};

View File

@ -0,0 +1,48 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
import { GraphQLView } from '@/views/types/GraphQLView';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useGetViewFromCache = () => {
const client = useApolloClient();
const cache = client.cache;
const { getRecordFromCache } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.View,
});
const getViewFromCache = useCallback(
async (viewId: string) => {
// Todo Fix typing once we have figured out record connections
const viewWithConnections = getRecordFromCache<any>(viewId, cache);
if (isUndefinedOrNull(viewWithConnections)) {
return;
}
const view = {
...viewWithConnections,
viewFilters: viewWithConnections.viewFilters?.edges.map(
(edge: ObjectRecordEdge) => edge.node,
),
viewSorts: viewWithConnections.viewSorts?.edges.map(
(edge: ObjectRecordEdge) => edge.node,
),
viewFields: viewWithConnections.viewFields?.edges.map(
(edge: ObjectRecordEdge) => edge.node,
),
} as GraphQLView;
return view;
},
[cache, getRecordFromCache],
);
return {
getViewFromCache,
};
};

View File

@ -0,0 +1,130 @@
import { useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
import { useResetCurrentView } from '@/views/hooks/useResetCurrentView';
import { GraphQLView } from '@/views/types/GraphQLView';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useHandleViews = (viewBarComponentId?: string) => {
const { resetCurrentView } = useResetCurrentView(viewBarComponentId);
const { currentViewIdState } = useViewStates(viewBarComponentId);
const { getViewFromCache } = useGetViewFromCache();
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.View,
});
const { createOneRecord } = useCreateOneRecord<GraphQLView>({
objectNameSingular: CoreObjectNameSingular.View,
});
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: CoreObjectNameSingular.View,
});
const { createViewFieldRecords } = usePersistViewFieldRecords();
const createViewFromCurrent = useRecoilCallback(() => () => {}, []);
const [_, setSearchParams] = useSearchParams();
const removeView = useRecoilCallback(
() => async (viewId: string) => {
await deleteOneRecord(viewId);
},
[deleteOneRecord],
);
const createEmptyView = useRecoilCallback(
({ snapshot }) =>
async (id: string, name: string) => {
const currentViewId = getSnapshotValue(snapshot, currentViewIdState);
if (!isDefined(currentViewId)) {
return;
}
const view = await getViewFromCache(currentViewId);
if (!isDefined(view)) {
return;
}
const newView = await createOneRecord({
id: id ?? v4(),
name: name,
objectMetadataId: view.objectMetadataId,
type: view.type,
});
if (isUndefinedOrNull(newView)) {
throw new Error('Failed to create view');
}
await createViewFieldRecords(view.viewFields, newView);
},
[
createOneRecord,
createViewFieldRecords,
currentViewIdState,
getViewFromCache,
],
);
const changeViewInUrl = useCallback(
(viewId: string) => {
setSearchParams((previousSearchParams) => {
previousSearchParams.set('view', viewId);
return previousSearchParams;
});
},
[setSearchParams],
);
const selectView = useRecoilCallback(
({ set }) =>
async (viewId: string) => {
set(currentViewIdState, viewId);
changeViewInUrl(viewId);
resetCurrentView();
},
[changeViewInUrl, currentViewIdState, resetCurrentView],
);
const updateCurrentView = useRecoilCallback(
({ snapshot }) =>
async (view: Partial<GraphQLView>) => {
const currentViewId = snapshot
.getLoadable(currentViewIdState)
.getValue();
if (isDefined(currentViewId)) {
await updateOneRecord({
idToUpdate: currentViewId,
updateOneRecordInput: view,
});
}
},
[currentViewIdState, updateOneRecord],
);
return {
selectView,
updateCurrentView,
removeView,
createEmptyView,
createViewFromCurrent,
};
};

View File

@ -0,0 +1,31 @@
import { useSetRecoilState } from 'recoil';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
export const useInitViewBar = (viewBarComponentId?: string) => {
const {
availableFieldDefinitionsState,
availableSortDefinitionsState,
availableFilterDefinitionsState,
viewObjectMetadataIdState,
} = useViewStates(viewBarComponentId);
const setAvailableFieldDefinitions = useSetRecoilState(
availableFieldDefinitionsState,
);
const setAvailableSortDefinitions = useSetRecoilState(
availableSortDefinitionsState,
);
const setAvailableFilterDefinitions = useSetRecoilState(
availableFilterDefinitionsState,
);
const setViewObjectMetadataId = useSetRecoilState(viewObjectMetadataIdState);
return {
setAvailableFieldDefinitions,
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setViewObjectMetadataId,
};
};

View File

@ -0,0 +1,32 @@
import { useRecoilCallback } from 'recoil';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
export const useResetCurrentView = (viewBarComponentId?: string) => {
const {
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewSortsState,
unsavedToDeleteViewFilterIdsState,
unsavedToUpsertViewFiltersState,
} = useViewStates(viewBarComponentId);
const resetCurrentView = useRecoilCallback(
({ set }) =>
async () => {
set(unsavedToDeleteViewFilterIdsState, []);
set(unsavedToDeleteViewSortIdsState, []);
set(unsavedToUpsertViewFiltersState, []);
set(unsavedToUpsertViewSortsState, []);
},
[
unsavedToDeleteViewFilterIdsState,
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewFiltersState,
unsavedToUpsertViewSortsState,
],
);
return {
resetCurrentView,
};
};

View File

@ -0,0 +1,87 @@
import { useRecoilCallback } from 'recoil';
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
import { ViewField } from '@/views/types/ViewField';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useSaveCurrentViewFields = (viewBarComponentId?: string) => {
const { createViewFieldRecords, updateViewFieldRecords } =
usePersistViewFieldRecords();
const { getViewFromCache } = useGetViewFromCache();
const { isPersistingViewFieldsState, currentViewIdState } =
useViewStates(viewBarComponentId);
const saveViewFields = useRecoilCallback(
({ set, snapshot }) =>
async (fields: ViewField[]) => {
const currentViewId = snapshot
.getLoadable(currentViewIdState)
.getValue();
if (!currentViewId) {
return;
}
set(isPersistingViewFieldsState, true);
const view = await getViewFromCache(currentViewId);
if (isUndefinedOrNull(view)) {
return;
}
const viewFieldsToUpdate = fields
.map((field) => {
const existingField = view.viewFields.find(
(viewField) => viewField.id === field.id,
);
if (isUndefinedOrNull(existingField)) {
return undefined;
}
if (
isDeeplyEqual(
{
position: existingField.position,
size: existingField.size,
isVisible: existingField.isVisible,
},
{
position: field.position,
size: field.size,
isVisible: field.isVisible,
},
)
) {
return undefined;
}
return field;
})
.filter(isDefined);
const viewFieldsToCreate = fields.filter((field) => !field.id);
await Promise.all([
createViewFieldRecords(viewFieldsToCreate, view),
updateViewFieldRecords(viewFieldsToUpdate),
]);
set(isPersistingViewFieldsState, false);
},
[
createViewFieldRecords,
currentViewIdState,
getViewFromCache,
isPersistingViewFieldsState,
updateViewFieldRecords,
],
);
return {
saveViewFields,
};
};

View File

@ -0,0 +1,147 @@
import { useRecoilCallback } from 'recoil';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
import { useResetCurrentView } from '@/views/hooks/useResetCurrentView';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useSaveCurrentViewFiltersAndSorts = (
viewBarComponentId?: string,
) => {
const { getViewFromCache } = useGetViewFromCache();
const {
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewSortsState,
unsavedToDeleteViewFilterIdsState,
unsavedToUpsertViewFiltersState,
currentViewIdState,
} = useViewStates(viewBarComponentId);
const {
createViewSortRecords,
updateViewSortRecords,
deleteViewSortRecords,
} = usePersistViewSortRecords();
const {
createViewFilterRecords,
updateViewFilterRecords,
deleteViewFilterRecords,
} = usePersistViewFilterRecords();
const { resetCurrentView } = useResetCurrentView(viewBarComponentId);
const saveViewSorts = useRecoilCallback(
({ snapshot }) =>
async (viewId: string) => {
const unsavedToDeleteViewSortIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewSortIdsState,
);
const unsavedToUpsertViewSorts = getSnapshotValue(
snapshot,
unsavedToUpsertViewSortsState,
);
const view = await getViewFromCache(viewId);
if (isUndefinedOrNull(view)) {
return;
}
const viewSortsToCreate = unsavedToUpsertViewSorts.filter(
(viewSort) =>
!view.viewSorts.some(
(vf) => vf.fieldMetadataId === viewSort.fieldMetadataId,
),
);
const viewSortsToUpdate = unsavedToUpsertViewSorts.filter((viewSort) =>
view.viewSorts.some((vf) => vf.id === viewSort.id),
);
await createViewSortRecords(viewSortsToCreate, view);
await updateViewSortRecords(viewSortsToUpdate);
await deleteViewSortRecords(unsavedToDeleteViewSortIds);
},
[
createViewSortRecords,
deleteViewSortRecords,
getViewFromCache,
unsavedToDeleteViewSortIdsState,
unsavedToUpsertViewSortsState,
updateViewSortRecords,
],
);
const saveViewFilters = useRecoilCallback(
({ snapshot }) =>
async (viewId: string) => {
const unsavedToDeleteViewFilterIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterIdsState,
);
const unsavedToUpsertViewFilters = getSnapshotValue(
snapshot,
unsavedToUpsertViewFiltersState,
);
const view = await getViewFromCache(viewId);
if (isUndefinedOrNull(view)) {
return;
}
const viewFiltersToCreate = unsavedToUpsertViewFilters.filter(
(viewFilter) =>
!view.viewFilters.some((vf) => vf.id === viewFilter.id),
);
const viewFiltersToUpdate = unsavedToUpsertViewFilters.filter(
(viewFilter) =>
view.viewFilters.some((vf) => vf.id === viewFilter.id),
);
await createViewFilterRecords(viewFiltersToCreate, view);
await updateViewFilterRecords(viewFiltersToUpdate);
await deleteViewFilterRecords(unsavedToDeleteViewFilterIds);
},
[
createViewFilterRecords,
deleteViewFilterRecords,
getViewFromCache,
unsavedToDeleteViewFilterIdsState,
unsavedToUpsertViewFiltersState,
updateViewFilterRecords,
],
);
const saveCurrentViewFilterAndSorts = useRecoilCallback(
({ snapshot }) =>
async () => {
const currentViewId = snapshot
.getLoadable(currentViewIdState)
.getValue();
if (!isDefined(currentViewId)) {
return;
}
await saveViewFilters(currentViewId);
await saveViewSorts(currentViewId);
resetCurrentView();
},
[currentViewIdState, resetCurrentView, saveViewFilters, saveViewSorts],
);
return {
saveCurrentViewFilterAndSorts,
};
};

View File

@ -0,0 +1,15 @@
import { useSetRecoilState } from 'recoil';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
export const useSetRecordCountInCurrentView = (viewBarComponentId?: string) => {
const { entityCountInCurrentViewState } = useViewStates(viewBarComponentId);
const setEntityCountInCurrentView = useSetRecoilState(
entityCountInCurrentViewState,
);
return {
setRecordCountInCurrentView: setEntityCountInCurrentView,
};
};

View File

@ -1,441 +0,0 @@
import { useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewSort } from '@/views/types/ViewSort';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { ViewScopeInternalContext } from '../scopes/scope-internal-context/ViewScopeInternalContext';
import { currentViewFieldsScopedFamilyState } from '../states/currentViewFieldsScopedFamilyState';
import { currentViewFiltersScopedFamilyState } from '../states/currentViewFiltersScopedFamilyState';
import { currentViewSortsScopedFamilyState } from '../states/currentViewSortsScopedFamilyState';
import { getViewScopedStatesFromSnapshot } from '../utils/getViewScopedStatesFromSnapshot';
import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot';
import { useViewFields } from './internal/useViewFields';
import { useViewFilters } from './internal/useViewFilters';
import { useViews } from './internal/useViews';
import { useViewScopedStates } from './internal/useViewScopedStates';
import { useViewSorts } from './internal/useViewSorts';
type UseViewProps = {
viewBarId?: string;
};
export const useViewBar = (props?: UseViewProps) => {
const scopeId = useAvailableScopeIdOrThrow(
ViewScopeInternalContext,
props?.viewBarId,
);
const {
currentViewFiltersState,
currentViewIdState,
currentViewSortsState,
viewEditModeState,
availableFieldDefinitionsState,
availableFilterDefinitionsState,
availableSortDefinitionsState,
entityCountInCurrentViewState,
viewObjectMetadataIdState,
} = useViewScopedStates({
viewScopeId: scopeId,
});
const { persistViewSorts, upsertViewSort, removeViewSort } =
useViewSorts(scopeId);
const { persistViewFilters, upsertViewFilter, removeViewFilter } =
useViewFilters(scopeId);
const { persistViewFields } = useViewFields(scopeId);
const {
createView: internalCreateView,
updateView: internalUpdateView,
deleteView: internalDeleteView,
} = useViews(scopeId);
const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState);
const setAvailableFieldDefinitions = useSetRecoilState(
availableFieldDefinitionsState,
);
const setAvailableSortDefinitions = useSetRecoilState(
availableSortDefinitionsState,
);
const setAvailableFilterDefinitions = useSetRecoilState(
availableFilterDefinitionsState,
);
const setEntityCountInCurrentView = useSetRecoilState(
entityCountInCurrentViewState,
);
const setViewEditMode = useSetRecoilState(viewEditModeState);
const setViewObjectMetadataId = useSetRecoilState(viewObjectMetadataIdState);
const [_, setSearchParams] = useSearchParams();
const changeViewInUrl = useCallback(
(viewId: string) => {
setSearchParams((previousSearchParams) => {
previousSearchParams.set('view', viewId);
return previousSearchParams;
});
},
[setSearchParams],
);
const loadViewFields = useRecoilCallback(
({ snapshot, set }) =>
async (viewFields: ViewField[], currentViewId: string) => {
const {
availableFieldDefinitions,
onViewFieldsChange,
savedViewFields,
isPersistingView,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
const { savedViewFieldsState, currentViewFieldsState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
if (isUndefinedOrNull(availableFieldDefinitions)) {
return;
}
const queriedViewFields = viewFields.filter(isDefined);
if (isPersistingView) {
return;
}
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
set(currentViewFieldsState, queriedViewFields);
set(savedViewFieldsState, queriedViewFields);
}
onViewFieldsChange?.(queriedViewFields);
},
[scopeId],
);
const loadViewFilters = useRecoilCallback(
({ snapshot, set }) =>
async (viewFilters: ViewFilter[], currentViewId: string) => {
const {
availableFilterDefinitions,
savedViewFilters,
onViewFiltersChange,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
const { savedViewFiltersState, currentViewFiltersState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
if (isUndefinedOrNull(availableFilterDefinitions)) {
return;
}
const queriedViewFilters = viewFilters
.map((viewFilter) => {
const availableFilterDefinition = availableFilterDefinitions.find(
(filterDefinition) =>
filterDefinition.fieldMetadataId === viewFilter.fieldMetadataId,
);
if (!availableFilterDefinition) return null;
return {
...viewFilter,
displayValue: viewFilter.displayValue ?? viewFilter.value,
definition: availableFilterDefinition,
};
})
.filter(isDefined);
if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
set(savedViewFiltersState, queriedViewFilters);
set(currentViewFiltersState, queriedViewFilters);
}
onViewFiltersChange?.(queriedViewFilters);
},
[scopeId],
);
const loadViewSorts = useRecoilCallback(
({ snapshot, set }) =>
async (viewSorts: Required<ViewSort>[], currentViewId: string) => {
const { availableSortDefinitions, savedViewSorts, onViewSortsChange } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
const { savedViewSortsState, currentViewSortsState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
if (!availableSortDefinitions || !currentViewId) {
return;
}
const queriedViewSorts = viewSorts
.map((viewSort) => {
const availableSortDefinition = availableSortDefinitions.find(
(sort) => sort.fieldMetadataId === viewSort.fieldMetadataId,
);
if (!availableSortDefinition) return null;
return {
id: viewSort.id,
fieldMetadataId: viewSort.fieldMetadataId,
direction: viewSort.direction,
definition: availableSortDefinition,
};
})
.filter(isDefined);
if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
set(savedViewSortsState, queriedViewSorts);
set(currentViewSortsState, queriedViewSorts);
}
onViewSortsChange?.(queriedViewSorts);
},
[scopeId],
);
const loadView = useRecoilCallback(
({ snapshot }) =>
(viewId: string) => {
setCurrentViewId?.(viewId);
const { currentView, onViewTypeChange, onViewCompactModeChange } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId,
});
if (!currentView) {
return;
}
onViewTypeChange?.(currentView.type);
onViewCompactModeChange?.(currentView.isCompact);
loadViewFields(currentView.viewFields, viewId);
loadViewFilters(currentView.viewFilters, viewId);
loadViewSorts(currentView.viewSorts, viewId);
},
[setCurrentViewId, scopeId, loadViewFields, loadViewFilters, loadViewSorts],
);
const resetViewBar = useRecoilCallback(
({ snapshot, set }) =>
() => {
const {
savedViewFilters,
savedViewSorts,
onViewFiltersChange,
onViewSortsChange,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
});
if (isDefined(savedViewFilters)) {
set(currentViewFiltersState, savedViewFilters);
onViewFiltersChange?.(savedViewFilters);
}
if (isDefined(savedViewSorts)) {
set(currentViewSortsState, savedViewSorts);
onViewSortsChange?.(savedViewSorts);
}
set(viewEditModeState, 'none');
},
[
currentViewFiltersState,
currentViewSortsState,
scopeId,
viewEditModeState,
],
);
const createView = useRecoilCallback(
({ snapshot, set }) =>
async (name: string) => {
const newViewId = v4();
await internalCreateView({ id: newViewId, name });
const { currentViewFields, currentViewFilters, currentViewSorts } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
});
set(
currentViewFieldsScopedFamilyState({ scopeId, familyKey: newViewId }),
currentViewFields,
);
set(
currentViewFiltersScopedFamilyState({
scopeId,
familyKey: newViewId,
}),
currentViewFilters,
);
set(
currentViewSortsScopedFamilyState({
scopeId,
familyKey: newViewId,
}),
currentViewSorts,
);
await persistViewFields(currentViewFields, newViewId);
await persistViewFilters(newViewId);
await persistViewSorts(newViewId);
changeViewInUrl(newViewId);
},
[
changeViewInUrl,
internalCreateView,
persistViewFields,
persistViewFilters,
persistViewSorts,
scopeId,
],
);
const updateCurrentView = async () => {
await persistViewFilters();
await persistViewSorts();
};
const removeView = useRecoilCallback(
({ set, snapshot }) =>
async (viewIdToDelete: string) => {
const { currentViewId } = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
});
const { currentViewIdState, viewsState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId: scopeId,
});
if (currentViewId === viewIdToDelete) {
set(currentViewIdState, undefined);
}
set(viewsState, (previousViews) =>
previousViews.filter((view) => view.id !== viewIdToDelete),
);
internalDeleteView(viewIdToDelete);
if (currentViewId === viewIdToDelete) {
setSearchParams();
}
},
[internalDeleteView, scopeId, setSearchParams],
);
const handleViewNameSubmit = useRecoilCallback(
({ snapshot }) =>
async (name?: string) => {
if (!name) {
return;
}
const { viewEditMode, currentView } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
});
if (!currentView) {
return;
}
if (viewEditMode === 'create' && isNonEmptyString(name)) {
await createView(name);
// Temporary to force refetch
await internalUpdateView({
...currentView,
});
} else {
await internalUpdateView({
...currentView,
name,
});
}
},
[createView, internalUpdateView, scopeId],
);
return {
scopeId,
currentViewId,
setCurrentViewId,
updateCurrentView,
createView,
removeView,
resetViewBar,
handleViewNameSubmit,
setViewEditMode,
setViewObjectMetadataId,
setEntityCountInCurrentView,
setAvailableFieldDefinitions,
setAvailableSortDefinitions,
upsertViewSort,
removeViewSort,
setAvailableFilterDefinitions,
upsertViewFilter,
removeViewFilter,
persistViewFields,
changeViewInUrl,
loadView,
loadViewFields,
loadViewFilters,
loadViewSorts,
};
};

View File

@ -0,0 +1,14 @@
import { useRecoilState } from 'recoil';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
export const useViewBarEditMode = (viewBarComponentId?: string) => {
const { viewEditModeState } = useViewStates(viewBarComponentId);
const [viewEditMode, setViewEditMode] = useRecoilState(viewEditModeState);
return {
viewEditMode,
setViewEditMode,
};
};

View File

@ -1,10 +1,6 @@
import { ReactNode } from 'react';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewSort } from '@/views/types/ViewSort';
import { ViewType } from '@/views/types/ViewType';
import { ViewField } from '../types/ViewField';
import { GraphQLView } from '@/views/types/GraphQLView';
import { ViewScopeInitEffect } from './init-effect/ViewScopeInitEffect';
import { ViewScopeInternalContext } from './scope-internal-context/ViewScopeInternalContext';
@ -12,23 +8,13 @@ import { ViewScopeInternalContext } from './scope-internal-context/ViewScopeInte
type ViewScopeProps = {
children: ReactNode;
viewScopeId: 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 ViewScope = ({
children,
viewScopeId,
onViewSortsChange,
onViewFiltersChange,
onViewFieldsChange,
onViewTypeChange,
onViewCompactModeChange,
onCurrentViewChange,
}: ViewScopeProps) => {
return (
<ViewScopeInternalContext.Provider
@ -38,11 +24,7 @@ export const ViewScope = ({
>
<ViewScopeInitEffect
viewScopeId={viewScopeId}
onViewSortsChange={onViewSortsChange}
onViewFiltersChange={onViewFiltersChange}
onViewFieldsChange={onViewFieldsChange}
onViewTypeChange={onViewTypeChange}
onViewCompactModeChange={onViewCompactModeChange}
onCurrentViewChange={onCurrentViewChange}
/>
{children}
</ViewScopeInternalContext.Provider>

View File

@ -1,64 +1,24 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
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 { useViewStates } from '@/views/hooks/internal/useViewStates';
import { GraphQLView } from '@/views/types/GraphQLView';
type ViewScopeInitEffectProps = {
viewScopeId: 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 ViewScopeInitEffect = ({
onViewSortsChange,
onViewFiltersChange,
onViewFieldsChange,
onViewTypeChange,
onViewCompactModeChange,
onCurrentViewChange,
}: ViewScopeInitEffectProps) => {
const {
onViewFieldsChangeState,
onViewFiltersChangeState,
onViewSortsChangeState,
onViewTypeChangeState,
onViewCompactModeChangeState,
} = useViewScopedStates();
const { onCurrentViewChangeState } = useViewStates();
const setOnViewSortsChange = useSetRecoilState(onViewSortsChangeState);
const setOnViewFiltersChange = useSetRecoilState(onViewFiltersChangeState);
const setOnViewFieldsChange = useSetRecoilState(onViewFieldsChangeState);
const setOnViewTypeChange = useSetRecoilState(onViewTypeChangeState);
const setOnViewCompactModeChange = useSetRecoilState(
onViewCompactModeChangeState,
);
const setOnCurrentViewChange = useSetRecoilState(onCurrentViewChangeState);
useEffect(() => {
setOnViewSortsChange(() => onViewSortsChange);
setOnViewFiltersChange(() => onViewFiltersChange);
setOnViewFieldsChange(() => onViewFieldsChange);
setOnViewTypeChange(() => onViewTypeChange);
setOnViewCompactModeChange(() => onViewCompactModeChange);
}, [
onViewCompactModeChange,
onViewFieldsChange,
onViewFiltersChange,
onViewSortsChange,
onViewTypeChange,
setOnViewCompactModeChange,
setOnViewFieldsChange,
setOnViewFiltersChange,
setOnViewSortsChange,
setOnViewTypeChange,
]);
setOnCurrentViewChange(() => onCurrentViewChange);
}, [onCurrentViewChange, setOnCurrentViewChange]);
return <></>;
};

View File

@ -2,9 +2,9 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const availableFieldDefinitionsScopedState = createComponentState<
export const availableFieldDefinitionsComponentState = createComponentState<
ColumnDefinition<FieldMetadata>[]
>({
key: 'availableFieldDefinitionsScopedState',
key: 'availableFieldDefinitionsComponentState',
defaultValue: [],
});

View File

@ -1,9 +1,9 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const availableFilterDefinitionsScopedState = createComponentState<
export const availableFilterDefinitionsComponentState = createComponentState<
FilterDefinition[]
>({
key: 'availableFilterDefinitionsScopedState',
key: 'availableFilterDefinitionsComponentState',
defaultValue: [],
});

View File

@ -1,9 +1,9 @@
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const availableSortDefinitionsScopedState = createComponentState<
export const availableSortDefinitionsComponentState = createComponentState<
SortDefinition[]
>({
key: 'availableSortDefinitionsScopedState',
key: 'availableSortDefinitionsComponentState',
defaultValue: [],
});

View File

@ -1,11 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
import { ViewField } from '../types/ViewField';
export const currentViewFieldsScopedFamilyState = createComponentFamilyState<
ViewField[],
string
>({
key: 'currentViewFieldsScopedFamilyState',
defaultValue: [],
});

View File

@ -1,11 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
import { ViewFilter } from '../types/ViewFilter';
export const currentViewFiltersScopedFamilyState = createComponentFamilyState<
ViewFilter[],
string
>({
key: 'currentViewFiltersScopedFamilyState',
defaultValue: [],
});

View File

@ -1,8 +1,8 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const currentViewIdScopedState = createComponentState<
export const currentViewIdComponentState = createComponentState<
string | undefined
>({
key: 'currentViewIdScopedState',
key: 'currentViewIdComponentState',
defaultValue: undefined,
});

View File

@ -1,11 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
import { ViewSort } from '../types/ViewSort';
export const currentViewSortsScopedFamilyState = createComponentFamilyState<
ViewSort[],
string
>({
key: 'currentViewSortsScopedFamilyState',
defaultValue: [],
});

View File

@ -0,0 +1,7 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const entityCountInCurrentViewComponentState =
createComponentState<number>({
key: 'entityCountInCurrentViewComponentState',
defaultValue: 0,
});

View File

@ -1,8 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const entityCountInCurrentViewScopedState = createComponentState<number>(
{
key: 'entityCountInCurrentViewScopedState',
defaultValue: 0,
},
);

View File

@ -0,0 +1,7 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const isCurrentViewKeyIndexComponentState =
createComponentState<boolean>({
key: 'isCurrentViewKeyIndexComponentState',
defaultValue: true,
});

View File

@ -0,0 +1,7 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const isPersistingViewFieldsComponentState =
createComponentState<boolean>({
key: 'isPersistingViewFieldsComponentState',
defaultValue: false,
});

View File

@ -1,6 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const isPersistingViewScopedState = createComponentState<boolean>({
key: 'isPersistingViewScopedState',
defaultValue: false,
});

View File

@ -1,6 +1,6 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const isViewBarExpandedScopedState = createComponentState<boolean>({
key: 'isViewBarExpandedScopedState',
export const isViewBarExpandedComponentState = createComponentState<boolean>({
key: 'isViewBarExpandedComponentState',
defaultValue: true,
});

View File

@ -1,6 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
export const noneScopedFamilyState = createComponentFamilyState<any, string>({
key: 'noneScopedFamilyState',
defaultValue: null,
});

View File

@ -0,0 +1,9 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { GraphQLView } from '@/views/types/GraphQLView';
export const onCurrentViewChangeComponentState = createComponentState<
((view: GraphQLView | undefined) => void | Promise<void>) | undefined
>({
key: 'onCurrentViewChangeComponentState',
defaultValue: undefined,
});

View File

@ -1,8 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const onViewCompactModeChangeScopeState = createComponentState<
((isCompactModeActive: boolean) => void | Promise<void>) | undefined
>({
key: 'onViewCompactModeChangeScopeState',
defaultValue: undefined,
});

View File

@ -1,10 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewField } from '../types/ViewField';
export const onViewFieldsChangeScopedState = createComponentState<
((fields: ViewField[]) => void | Promise<void>) | undefined
>({
key: 'onViewFieldsChangeScopedState',
defaultValue: undefined,
});

View File

@ -1,9 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewFilter } from '@/views/types/ViewFilter';
export const onViewFiltersChangeScopedState = createComponentState<
((filters: ViewFilter[]) => void | Promise<void>) | undefined
>({
key: 'onViewFiltersChangeScopedState',
defaultValue: undefined,
});

View File

@ -1,9 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewSort } from '@/views/types/ViewSort';
export const onViewSortsChangeScopedState = createComponentState<
((sorts: ViewSort[]) => void | Promise<void>) | undefined
>({
key: 'onViewSortsChangeScopedState',
defaultValue: undefined,
});

View File

@ -1,9 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewType } from '@/views/types/ViewType';
export const onViewTypeChangeScopedState = createComponentState<
((viewType: ViewType) => void | Promise<void>) | undefined
>({
key: 'onViewTypeChangeScopedState',
defaultValue: undefined,
});

View File

@ -1,11 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
import { ViewField } from '../types/ViewField';
export const savedViewFieldsScopedFamilyState = createComponentFamilyState<
ViewField[],
string
>({
key: 'savedViewFieldsScopedFamilyState',
defaultValue: [],
});

View File

@ -1,11 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
import { ViewFilter } from '../types/ViewFilter';
export const savedViewFiltersScopedFamilyState = createComponentFamilyState<
ViewFilter[],
string
>({
key: 'savedViewFiltersScopedFamilyState',
defaultValue: [],
});

View File

@ -1,11 +0,0 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
import { ViewSort } from '../types/ViewSort';
export const savedViewSortsScopedFamilyState = createComponentFamilyState<
ViewSort[],
string
>({
key: 'savedViewSortsScopedFamilyState',
defaultValue: [],
});

View File

@ -0,0 +1,21 @@
import { selectorFamily } from 'recoil';
import { unsavedToDeleteViewFilterIdsComponentState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentState';
import { unsavedToDeleteViewSortIdsComponentState } from '@/views/states/unsavedToDeleteViewSortIdsComponentState';
import { unsavedToUpsertViewFiltersComponentState } from '@/views/states/unsavedToUpsertViewFiltersComponentState';
import { unsavedToUpsertViewSortsComponentState } from '@/views/states/unsavedToUpsertViewSortsComponentState';
export const canPersistViewComponentSelector = selectorFamily({
key: 'canPersistViewComponentSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
return (
get(unsavedToUpsertViewFiltersComponentState({ scopeId })).length > 0 ||
get(unsavedToUpsertViewSortsComponentState({ scopeId })).length > 0 ||
get(unsavedToDeleteViewFilterIdsComponentState({ scopeId })).length >
0 ||
get(unsavedToDeleteViewSortIdsComponentState({ scopeId })).length > 0
);
},
});

View File

@ -1,32 +0,0 @@
import { selectorFamily } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { currentViewFiltersScopedFamilyState } from '../currentViewFiltersScopedFamilyState';
import { savedViewFiltersScopedFamilyState } from '../savedViewFiltersScopedFamilyState';
export const canPersistViewFiltersScopedFamilySelector = selectorFamily({
key: 'canPersistFiltersScopedFamilySelector',
get:
({ viewScopeId, viewId }: { viewScopeId: string; viewId?: string }) =>
({ get }) => {
if (!viewId) {
return;
}
return !isDeeplyEqual(
get(
savedViewFiltersScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId,
}),
),
get(
currentViewFiltersScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId,
}),
),
);
},
});

View File

@ -1,31 +0,0 @@
import { selectorFamily } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { currentViewSortsScopedFamilyState } from '../currentViewSortsScopedFamilyState';
import { savedViewSortsScopedFamilyState } from '../savedViewSortsScopedFamilyState';
export const canPersistViewSortsScopedFamilySelector = selectorFamily({
key: 'canPersistSortsScopedFamilySelector',
get:
({ viewScopeId, viewId }: { viewScopeId: string; viewId?: string }) =>
({ get }) => {
if (!viewId) {
return;
}
return !isDeeplyEqual(
get(
savedViewSortsScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId,
}),
),
get(
currentViewSortsScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId,
}),
),
);
},
});

View File

@ -0,0 +1,22 @@
import { selectorFamily } from 'recoil';
import { unsavedToDeleteViewFilterIdsComponentState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentState';
import { unsavedToDeleteViewSortIdsComponentState } from '@/views/states/unsavedToDeleteViewSortIdsComponentState';
import { unsavedToUpsertViewFiltersComponentState } from '@/views/states/unsavedToUpsertViewFiltersComponentState';
import { unsavedToUpsertViewSortsComponentState } from '@/views/states/unsavedToUpsertViewSortsComponentState';
export const canResetViewComponentSelector = selectorFamily({
key: 'canResetViewComponentSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
return (
get(unsavedToUpsertViewFiltersComponentState({ scopeId })).length ===
0 &&
get(unsavedToUpsertViewSortsComponentState({ scopeId })).length === 0 &&
get(unsavedToDeleteViewFilterIdsComponentState({ scopeId })).length ===
0 &&
get(unsavedToDeleteViewSortIdsComponentState({ scopeId })).length === 0
);
},
});

View File

@ -1,21 +0,0 @@
import { createComponentReadOnlySelector } from '@/ui/utilities/state/component-state/utils/createComponentReadOnlySelector';
import { GraphQLView } from '@/views/types/GraphQLView';
import { currentViewIdScopedState } from '../currentViewIdScopedState';
import { viewsByIdScopedSelector } from './viewsByIdScopedSelector';
export const currentViewComponentSelector = createComponentReadOnlySelector<
GraphQLView | undefined
>({
key: 'currentViewScopedSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
const currentViewId = get(currentViewIdScopedState({ scopeId: scopeId }));
return currentViewId
? get(viewsByIdScopedSelector(scopeId))[currentViewId]
: undefined;
},
});

View File

@ -1,32 +0,0 @@
import { selectorFamily } from 'recoil';
import { ViewField } from '@/views/types/ViewField';
import { savedViewFieldsScopedFamilyState } from '../savedViewFieldsScopedFamilyState';
export const savedViewFieldByKeyScopedFamilySelector = selectorFamily({
key: 'savedViewFieldByKeyScopedFamilySelector',
get:
({
viewScopeId,
viewId,
}: {
viewScopeId: string;
viewId: string | undefined;
}) =>
({ get }) => {
if (viewId === undefined) {
return undefined;
}
return get(
savedViewFieldsScopedFamilyState({
scopeId: viewScopeId,
familyKey: viewId,
}),
).reduce<Record<string, ViewField>>(
(result, column) => ({ ...result, [column.fieldMetadataId]: column }),
{},
);
},
});

View File

@ -1,25 +0,0 @@
import { selectorFamily } from 'recoil';
import { ViewFilter } from '@/views/types/ViewFilter';
import { savedViewFiltersScopedFamilyState } from '../savedViewFiltersScopedFamilyState';
export const savedViewFiltersByKeyScopedFamilySelector = selectorFamily({
key: 'savedViewFiltersByKeyScopedFamilySelector',
get:
({ scopeId, viewId }: { scopeId: string; viewId: string | undefined }) =>
({ get }) => {
if (viewId === undefined) {
return undefined;
}
return get(
savedViewFiltersScopedFamilyState({
scopeId: scopeId,
familyKey: viewId,
}),
).reduce<Record<string, ViewFilter>>(
(result, filter) => ({ ...result, [filter.fieldMetadataId]: filter }),
{},
);
},
});

View File

@ -1,25 +0,0 @@
import { selectorFamily } from 'recoil';
import { ViewSort } from '@/views/types/ViewSort';
import { savedViewSortsScopedFamilyState } from '../savedViewSortsScopedFamilyState';
export const savedViewSortsByKeyScopedFamilySelector = selectorFamily({
key: 'savedViewSortsByKeyScopedFamilySelector',
get:
({ scopeId, viewId }: { scopeId: string; viewId: string | undefined }) =>
({ get }) => {
if (viewId === undefined) {
return undefined;
}
return get(
savedViewSortsScopedFamilyState({
scopeId: scopeId,
familyKey: viewId,
}),
).reduce<Record<string, ViewSort>>(
(result, sort) => ({ ...result, [sort.fieldMetadataId]: sort }),
{},
);
},
});

View File

@ -1,16 +0,0 @@
import { selectorFamily } from 'recoil';
import { savedViewSortsScopedFamilyState } from '../savedViewSortsScopedFamilyState';
export const savedViewSortsFamilySelector = selectorFamily({
key: 'savedViewSortsFamilySelector',
get:
({ scopeId, viewId }: { scopeId: string; viewId: string }) =>
({ get }) =>
get(
savedViewSortsScopedFamilyState({
scopeId: scopeId,
familyKey: viewId,
}),
),
});

View File

@ -1,18 +0,0 @@
import { selectorFamily } from 'recoil';
import { GraphQLView } from '@/views/types/GraphQLView';
import { viewsScopedState } from '../viewsScopedState';
export const viewsByIdScopedSelector = selectorFamily<
Record<string, GraphQLView>,
string
>({
key: 'viewsByIdScopedSelector',
get:
(scopeId) =>
({ get }) =>
get(viewsScopedState({ scopeId: scopeId })).reduce<
Record<string, GraphQLView>
>((result, view) => ({ ...result, [view.id]: view }), {}),
});

View File

@ -0,0 +1,8 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const unsavedToDeleteViewFilterIdsComponentState = createComponentState<
string[]
>({
key: 'unsavedToDeleteViewFilterIdsComponentState',
defaultValue: [],
});

View File

@ -0,0 +1,8 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const unsavedToDeleteViewSortIdsComponentState = createComponentState<
string[]
>({
key: 'unsavedToDeleteViewSortIdsComponentState',
defaultValue: [],
});

View File

@ -0,0 +1,10 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewFilter } from '../types/ViewFilter';
export const unsavedToUpsertViewFiltersComponentState = createComponentState<
ViewFilter[]
>({
key: 'unsavedToUpsertViewFiltersComponentState',
defaultValue: [],
});

View File

@ -0,0 +1,10 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewSort } from '../types/ViewSort';
export const unsavedToUpsertViewSortsComponentState = createComponentState<
ViewSort[]
>({
key: 'unsavedToUpsertViewSortsComponentState',
defaultValue: [],
});

View File

@ -1,8 +1,8 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const viewEditModeScopedState = createComponentState<
export const viewEditModeComponentState = createComponentState<
'none' | 'edit' | 'create'
>({
key: 'viewEditModeScopedState',
key: 'viewEditModeComponentState',
defaultValue: 'none',
});

View File

@ -1,8 +1,8 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const viewObjectMetadataIdScopeState = createComponentState<
export const viewObjectMetadataIdComponentState = createComponentState<
string | undefined
>({
key: 'viewObjectMetadataIdScopeState',
key: 'viewObjectMetadataIdComponentState',
defaultValue: undefined,
});

View File

@ -1,8 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewType } from '../types/ViewType';
export const viewTypeScopedState = createComponentState<ViewType>({
key: 'viewTypeScopedState',
defaultValue: ViewType.Table,
});

View File

@ -1,7 +0,0 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { GraphQLView } from '@/views/types/GraphQLView';
export const viewsScopedState = createComponentState<GraphQLView[]>({
key: 'viewsScopedState',
defaultValue: [],
});

View File

@ -3,6 +3,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
export type ViewField = {
__typename: 'ViewField';
id: string;
fieldMetadataId: string;
position: number;

View File

@ -1,12 +1,13 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { ViewFilterOperand } from './ViewFilterOperand';
export type ViewFilter = {
__typename: 'ViewFilter';
id: string;
fieldMetadataId: string;
operand: ViewFilterOperand;
value: string;
displayValue: string;
definition: FilterDefinition;
createdAt?: string;
updatedAt?: string;
viewId?: string;
};

View File

@ -1,9 +1,8 @@
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { SortDirection } from '@/object-record/object-sort-dropdown/types/SortDirection';
export type ViewSort = {
__typename: 'ViewSort';
id: string;
fieldMetadataId: string;
direction: SortDirection;
definition: SortDefinition;
};

View File

@ -12,7 +12,7 @@ import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
const baseDefinition = {
fieldMetadataId: 'fieldMetadataId',
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
label: 'label',
iconName: 'iconName',
};
@ -21,20 +21,22 @@ describe('mapViewSortsToSorts', () => {
it('should map each ViewSort object to a corresponding Sort object', () => {
const viewSorts: ViewSort[] = [
{
__typename: 'ViewSort',
id: 'id',
fieldMetadataId: 'fieldMetadataId',
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
direction: 'asc',
definition: baseDefinition,
},
];
const expectedSorts: Sort[] = [
{
fieldMetadataId: 'fieldMetadataId',
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
direction: 'asc',
definition: baseDefinition,
},
];
expect(mapViewSortsToSorts(viewSorts)).toEqual(expectedSorts);
expect(mapViewSortsToSorts(viewSorts, [baseDefinition])).toEqual(
expectedSorts,
);
});
});
@ -42,20 +44,17 @@ describe('mapViewFiltersToFilters', () => {
it('should map each ViewFilter object to a corresponding Filter object', () => {
const viewFilters: ViewFilter[] = [
{
__typename: 'ViewFilter',
id: 'id',
fieldMetadataId: '1',
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
value: 'testValue',
displayValue: 'Test Display Value',
operand: ViewFilterOperand.Is,
definition: {
...baseDefinition,
type: 'FULL_NAME',
},
},
];
const expectedFilters: Filter[] = [
{
fieldMetadataId: '1',
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
value: 'testValue',
displayValue: 'Test Display Value',
operand: ViewFilterOperand.Is,
@ -65,7 +64,14 @@ describe('mapViewFiltersToFilters', () => {
},
},
];
expect(mapViewFiltersToFilters(viewFilters)).toEqual(expectedFilters);
expect(
mapViewFiltersToFilters(viewFilters, [
{
...baseDefinition,
type: 'FULL_NAME',
},
]),
).toEqual(expectedFilters);
});
});
@ -73,6 +79,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
it('should map visible ViewFields to ColumnDefinitions and filter out missing fieldMetadata', () => {
const viewFields: ViewField[] = [
{
__typename: 'ViewField',
id: '1',
fieldMetadataId: '1',
position: 1,
@ -92,6 +99,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
},
},
{
__typename: 'ViewField',
id: '2',
fieldMetadataId: '2',
position: 2,
@ -111,6 +119,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
},
},
{
__typename: 'ViewField',
id: '3',
fieldMetadataId: '3',
position: 3,
@ -209,13 +218,16 @@ describe('mapColumnDefinitionsToViewFields', () => {
const expectedViewFields = [
{
__typename: 'ViewField',
id: 'custom-id-1',
fieldMetadataId: 1,
position: 1,
isVisible: true,
definition: columnDefinitions[0],
size: undefined,
},
{
__typename: 'ViewField',
id: '',
fieldMetadataId: 2,
position: 2,

View File

@ -0,0 +1,34 @@
import { ViewFilter } from '@/views/types/ViewFilter';
export const combinedViewFilters = (
viewFilter: ViewFilter[],
toUpsertViewFilters: ViewFilter[],
toDeleteViewFilterIds: string[],
): ViewFilter[] => {
const toCreateViewFilters = toUpsertViewFilters.filter(
(toUpsertViewFilter) =>
!viewFilter.some((viewFilter) => viewFilter.id === toUpsertViewFilter.id),
);
const toUpdateViewFilters = toUpsertViewFilters.filter((toUpsertViewFilter) =>
viewFilter.some((viewFilter) => viewFilter.id === toUpsertViewFilter.id),
);
const combinedViewFilters = viewFilter
.filter((viewFilter) => !toDeleteViewFilterIds.includes(viewFilter.id))
.map((viewFilter) => {
const toUpdateViewFilter = toUpdateViewFilters.find(
(toUpdateViewFilter) => toUpdateViewFilter.id === viewFilter.id,
);
return toUpdateViewFilter ?? viewFilter;
})
.concat(toCreateViewFilters);
return Object.values(
combinedViewFilters.reduce(
(acc, obj) => ({ ...acc, [obj.fieldMetadataId]: obj }),
{},
),
);
};

View File

@ -0,0 +1,34 @@
import { ViewSort } from '@/views/types/ViewSort';
export const combinedViewSorts = (
viewSort: ViewSort[],
toUpsertViewSorts: ViewSort[],
toDeleteViewSortIds: string[],
): ViewSort[] => {
const toCreateViewSorts = toUpsertViewSorts.filter(
(toUpsertViewSort) =>
!viewSort.some((viewSort) => viewSort.id === toUpsertViewSort.id),
);
const toUpdateViewSorts = toUpsertViewSorts.filter((toUpsertViewSort) =>
viewSort.some((viewSort) => viewSort.id === toUpsertViewSort.id),
);
const combinedViewSorts = viewSort
.filter((viewSort) => !toDeleteViewSortIds.includes(viewSort.id))
.map((viewSort) => {
const toUpdateViewSort = toUpdateViewSorts.find(
(toUpdateViewSort) => toUpdateViewSort.id === viewSort.id,
);
return toUpdateViewSort ?? viewSort;
})
.concat(toCreateViewSorts);
return Object.values(
combinedViewSorts.reduce(
(acc, obj) => ({ ...acc, [obj.fieldMetadataId]: obj }),
{},
),
);
};

View File

@ -1,115 +0,0 @@
import { Snapshot } from 'recoil';
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { UNDEFINED_FAMILY_ITEM_ID } from '../constants';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
import { getViewScopedStates } from './internal/getViewScopedStates';
export const getViewScopedStateValuesFromSnapshot = ({
snapshot,
viewScopeId,
viewId,
}: {
snapshot: Snapshot;
viewScopeId: string;
viewId?: string;
}) => {
const currentViewId = getSnapshotValue(
snapshot,
getScopedStateDeprecated(currentViewIdScopedState, viewScopeId),
);
const familyItemId = viewId ?? currentViewId ?? UNDEFINED_FAMILY_ITEM_ID;
const {
availableFieldDefinitionsState,
availableFilterDefinitionsState,
availableSortDefinitionsState,
canPersistFiltersSelector,
canPersistSortsSelector,
currentViewFieldsState,
currentViewFiltersState,
currentViewIdState,
currentViewSelector,
currentViewSortsState,
entityCountInCurrentViewState,
isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState,
onViewFiltersChangeState,
onViewSortsChangeState,
onViewTypeChangeState,
onViewCompactModeChangeState,
savedViewFieldsByKeySelector,
savedViewFieldsState,
savedViewFiltersByKeySelector,
savedViewFiltersState,
savedViewSortsByKeySelector,
savedViewSortsState,
viewEditModeState,
viewObjectMetadataIdState,
viewTypeState,
viewsState,
} = getViewScopedStates({
viewScopeId,
viewId: familyItemId,
});
return {
availableFieldDefinitions: getSnapshotValue(
snapshot,
availableFieldDefinitionsState,
),
availableFilterDefinitions: getSnapshotValue(
snapshot,
availableFilterDefinitionsState,
),
availableSortDefinitions: getSnapshotValue(
snapshot,
availableSortDefinitionsState,
),
canPersistFilters: getSnapshotValue(snapshot, canPersistFiltersSelector),
canPersistSorts: getSnapshotValue(snapshot, canPersistSortsSelector),
currentViewFields: getSnapshotValue(snapshot, currentViewFieldsState),
currentViewFilters: getSnapshotValue(snapshot, currentViewFiltersState),
currentViewId: getSnapshotValue(snapshot, currentViewIdState),
currentView: getSnapshotValue(snapshot, currentViewSelector),
currentViewSorts: getSnapshotValue(snapshot, currentViewSortsState),
entityCountInCurrentView: getSnapshotValue(
snapshot,
entityCountInCurrentViewState,
),
isViewBarExpanded: getSnapshotValue(snapshot, isViewBarExpandedState),
isPersistingView: getSnapshotValue(snapshot, isPersistingViewState),
onViewFieldsChange: getSnapshotValue(snapshot, onViewFieldsChangeState),
onViewFiltersChange: getSnapshotValue(snapshot, onViewFiltersChangeState),
onViewSortsChange: getSnapshotValue(snapshot, onViewSortsChangeState),
onViewTypeChange: getSnapshotValue(snapshot, onViewTypeChangeState),
onViewCompactModeChange: getSnapshotValue(
snapshot,
onViewCompactModeChangeState,
),
savedViewFieldsByKey: getSnapshotValue(
snapshot,
savedViewFieldsByKeySelector,
),
savedViewFields: getSnapshotValue(snapshot, savedViewFieldsState),
savedViewFiltersByKey: getSnapshotValue(
snapshot,
savedViewFiltersByKeySelector,
),
savedViewFilters: getSnapshotValue(snapshot, savedViewFiltersState),
savedViewSortsByKey: getSnapshotValue(
snapshot,
savedViewSortsByKeySelector,
),
savedViewSorts: getSnapshotValue(snapshot, savedViewSortsState),
viewEditMode: getSnapshotValue(snapshot, viewEditModeState),
viewObjectMetadataId: getSnapshotValue(snapshot, viewObjectMetadataIdState),
viewType: getSnapshotValue(snapshot, viewTypeState),
views: getSnapshotValue(snapshot, viewsState),
};
};

View File

@ -1,87 +0,0 @@
import { Snapshot } from 'recoil';
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { UNDEFINED_FAMILY_ITEM_ID } from '../constants';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
import { getViewScopedStates } from './internal/getViewScopedStates';
export const getViewScopedStatesFromSnapshot = ({
snapshot,
viewScopeId,
viewId,
}: {
snapshot: Snapshot;
viewScopeId: string;
viewId?: string;
}) => {
const currentViewId = getSnapshotValue(
snapshot,
getScopedStateDeprecated(currentViewIdScopedState, viewScopeId),
);
const usedViewId = viewId ?? currentViewId ?? UNDEFINED_FAMILY_ITEM_ID;
const {
availableFieldDefinitionsState,
availableFilterDefinitionsState,
availableSortDefinitionsState,
canPersistFiltersSelector,
canPersistSortsSelector,
currentViewFieldsState,
currentViewFiltersState,
currentViewIdState,
currentViewSelector,
currentViewSortsState,
entityCountInCurrentViewState,
isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState,
onViewFiltersChangeState,
onViewSortsChangeState,
savedViewFieldsByKeySelector,
savedViewFieldsState,
savedViewFiltersByKeySelector,
savedViewFiltersState,
savedViewSortsByKeySelector,
savedViewSortsState,
viewEditModeState,
viewObjectMetadataIdState,
viewTypeState,
viewsState,
} = getViewScopedStates({
viewScopeId,
viewId: usedViewId,
});
return {
availableFieldDefinitionsState,
availableFilterDefinitionsState,
availableSortDefinitionsState,
canPersistFiltersReadOnlyState: canPersistFiltersSelector,
canPersistSortsReadOnlyState: canPersistSortsSelector,
currentViewFieldsState,
currentViewFiltersState,
currentViewIdState,
currentViewSelector,
currentViewSortsState,
entityCountInCurrentViewState,
isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState,
onViewFiltersChangeState,
onViewSortsChangeState,
savedViewFieldsByKeyReadOnlyState: savedViewFieldsByKeySelector,
savedViewFieldsState,
savedViewFiltersByKeyReadOnlyState: savedViewFiltersByKeySelector,
savedViewFiltersState,
savedViewSortsByKeyReadOnlyState: savedViewSortsByKeySelector,
savedViewSortsState,
viewEditModeState,
viewObjectMetadataIdState,
viewTypeState,
viewsState,
};
};

View File

@ -1,225 +0,0 @@
import { getScopedFamilyStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedFamilyStateDeprecated';
import { getScopedSelectorDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedSelectorDeprecated';
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
import { currentViewIdScopedState } from '@/views/states/currentViewIdScopedState';
import { isPersistingViewScopedState } from '@/views/states/isPersistingViewScopedState';
import { onViewCompactModeChangeScopeState } from '@/views/states/onViewCompactModeChangeScopeState';
import { onViewTypeChangeScopedState } from '@/views/states/onViewTypeChangeScopedState';
import { currentViewComponentSelector } from '@/views/states/selectors/currentViewComponentSelector';
import { savedViewFieldByKeyScopedFamilySelector } from '@/views/states/selectors/savedViewFieldByKeyScopedFamilySelector';
import { availableFieldDefinitionsScopedState } from '../../states/availableFieldDefinitionsScopedState';
import { availableFilterDefinitionsScopedState } from '../../states/availableFilterDefinitionsScopedState';
import { availableSortDefinitionsScopedState } from '../../states/availableSortDefinitionsScopedState';
import { currentViewFieldsScopedFamilyState } from '../../states/currentViewFieldsScopedFamilyState';
import { currentViewFiltersScopedFamilyState } from '../../states/currentViewFiltersScopedFamilyState';
import { currentViewSortsScopedFamilyState } from '../../states/currentViewSortsScopedFamilyState';
import { entityCountInCurrentViewScopedState } from '../../states/entityCountInCurrentViewScopedState';
import { isViewBarExpandedScopedState } from '../../states/isViewBarExpandedScopedState';
import { onViewFieldsChangeScopedState } from '../../states/onViewFieldsChangeScopedState';
import { onViewFiltersChangeScopedState } from '../../states/onViewFiltersChangeScopedState';
import { onViewSortsChangeScopedState } from '../../states/onViewSortsChangeScopedState';
import { savedViewFieldsScopedFamilyState } from '../../states/savedViewFieldsScopedFamilyState';
import { savedViewFiltersScopedFamilyState } from '../../states/savedViewFiltersScopedFamilyState';
import { savedViewSortsScopedFamilyState } from '../../states/savedViewSortsScopedFamilyState';
import { canPersistViewFiltersScopedFamilySelector } from '../../states/selectors/canPersistViewFiltersScopedFamilySelector';
import { canPersistViewSortsScopedFamilySelector } from '../../states/selectors/canPersistViewSortsScopedFamilySelector';
import { savedViewFiltersByKeyScopedFamilySelector } from '../../states/selectors/savedViewFiltersByKeyScopedFamilySelector';
import { savedViewSortsByKeyScopedFamilySelector } from '../../states/selectors/savedViewSortsByKeyScopedFamilySelector';
import { viewEditModeScopedState } from '../../states/viewEditModeScopedState';
import { viewObjectMetadataIdScopeState } from '../../states/viewObjectMetadataIdScopeState';
import { viewsScopedState } from '../../states/viewsScopedState';
import { viewTypeScopedState } from '../../states/viewTypeScopedState';
export const getViewScopedStates = ({
viewScopeId,
viewId,
}: {
viewScopeId: string;
viewId: string;
}) => {
const viewEditModeState = getScopedStateDeprecated(
viewEditModeScopedState,
viewScopeId,
);
const viewsState = getScopedStateDeprecated(viewsScopedState, viewScopeId);
const viewObjectMetadataIdState = getScopedStateDeprecated(
viewObjectMetadataIdScopeState,
viewScopeId,
);
const viewTypeState = getScopedStateDeprecated(
viewTypeScopedState,
viewScopeId,
);
const entityCountInCurrentViewState = getScopedStateDeprecated(
entityCountInCurrentViewScopedState,
viewScopeId,
);
const isViewBarExpandedState = getScopedStateDeprecated(
isViewBarExpandedScopedState,
viewScopeId,
);
const isPersistingViewState = getScopedStateDeprecated(
isPersistingViewScopedState,
viewScopeId,
);
// ViewSorts
const currentViewSortsState = getScopedFamilyStateDeprecated(
currentViewSortsScopedFamilyState,
viewScopeId,
viewId,
);
const savedViewSortsState = getScopedFamilyStateDeprecated(
savedViewSortsScopedFamilyState,
viewScopeId,
viewId,
);
const savedViewSortsByKeySelector = savedViewSortsByKeyScopedFamilySelector({
scopeId: viewScopeId,
viewId: viewId,
});
const availableSortDefinitionsState = getScopedStateDeprecated(
availableSortDefinitionsScopedState,
viewScopeId,
);
const canPersistSortsSelector = canPersistViewSortsScopedFamilySelector({
viewScopeId: viewScopeId,
viewId: viewId,
});
// ViewFilters
const currentViewFiltersState = getScopedFamilyStateDeprecated(
currentViewFiltersScopedFamilyState,
viewScopeId,
viewId,
);
const savedViewFiltersState = getScopedFamilyStateDeprecated(
savedViewFiltersScopedFamilyState,
viewScopeId,
viewId,
);
const savedViewFiltersByKeySelector =
savedViewFiltersByKeyScopedFamilySelector({
scopeId: viewScopeId,
viewId: viewId,
});
const availableFilterDefinitionsState = getScopedStateDeprecated(
availableFilterDefinitionsScopedState,
viewScopeId,
);
const canPersistFiltersSelector = canPersistViewFiltersScopedFamilySelector({
viewScopeId: viewScopeId,
viewId: viewId,
});
// ViewFields
const availableFieldDefinitionsState = getScopedStateDeprecated(
availableFieldDefinitionsScopedState,
viewScopeId,
);
const currentViewFieldsState = getScopedFamilyStateDeprecated(
currentViewFieldsScopedFamilyState,
viewScopeId,
viewId,
);
const savedViewFieldsState = getScopedFamilyStateDeprecated(
savedViewFieldsScopedFamilyState,
viewScopeId,
viewId,
);
const savedViewFieldsByKeySelector = savedViewFieldByKeyScopedFamilySelector({
viewScopeId,
viewId,
});
// ViewChangeHandlers
const onViewSortsChangeState = getScopedStateDeprecated(
onViewSortsChangeScopedState,
viewScopeId,
);
const onViewFiltersChangeState = getScopedStateDeprecated(
onViewFiltersChangeScopedState,
viewScopeId,
);
const onViewFieldsChangeState = getScopedStateDeprecated(
onViewFieldsChangeScopedState,
viewScopeId,
);
const onViewTypeChangeState = getScopedStateDeprecated(
onViewTypeChangeScopedState,
viewScopeId,
);
const onViewCompactModeChangeState = getScopedStateDeprecated(
onViewCompactModeChangeScopeState,
viewScopeId,
);
const currentViewIdState = getScopedStateDeprecated(
currentViewIdScopedState,
viewScopeId,
);
const currentViewSelector = getScopedSelectorDeprecated(
currentViewComponentSelector,
viewScopeId,
);
return {
currentViewIdState,
currentViewSelector,
isViewBarExpandedState,
isPersistingViewState,
viewsState,
viewEditModeState,
viewObjectMetadataIdState,
viewTypeState,
entityCountInCurrentViewState,
availableSortDefinitionsState,
currentViewSortsState,
savedViewSortsState,
savedViewSortsByKeySelector,
canPersistSortsSelector,
availableFilterDefinitionsState,
currentViewFiltersState,
savedViewFiltersState,
savedViewFiltersByKeySelector,
canPersistFiltersSelector,
availableFieldDefinitionsState,
currentViewFieldsState,
savedViewFieldsByKeySelector,
savedViewFieldsState,
onViewSortsChangeState,
onViewFiltersChangeState,
onViewFieldsChangeState,
onViewTypeChangeState,
onViewCompactModeChangeState,
};
};

View File

@ -1,5 +1,3 @@
import { v4 } from 'uuid';
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ViewField } from '@/views/types/ViewField';
@ -9,7 +7,8 @@ export const mapBoardFieldDefinitionsToViewFields = (
): ViewField[] => {
return fieldsDefinitions.map(
(fieldDefinition): ViewField => ({
id: fieldDefinition.viewFieldId || v4(),
__typename: 'ViewField',
id: fieldDefinition.viewFieldId || '',
fieldMetadataId: fieldDefinition.fieldMetadataId,
size: 0,
position: fieldDefinition.position,

View File

@ -7,6 +7,7 @@ export const mapColumnDefinitionsToViewFields = (
columnDefinitions: ColumnDefinition<FieldMetadata>[],
): ViewField[] => {
return columnDefinitions.map((columnDefinition) => ({
__typename: 'ViewField',
id: columnDefinition.viewFieldId || '',
fieldMetadataId: columnDefinition.fieldMetadataId,
position: columnDefinition.position,

View File

@ -1,17 +1,29 @@
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { isDefined } from '~/utils/isDefined';
import { ViewFilter } from '../types/ViewFilter';
export const mapViewFiltersToFilters = (
viewFilters: ViewFilter[],
availableFilterDefinitions: FilterDefinition[],
): Filter[] => {
return viewFilters.map((viewFilter) => {
return {
fieldMetadataId: viewFilter.fieldMetadataId,
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
definition: viewFilter.definition,
};
});
return viewFilters
.map((viewFilter) => {
const availableFilterDefinition = availableFilterDefinitions.find(
(filterDefinition) =>
filterDefinition.fieldMetadataId === viewFilter.fieldMetadataId,
);
if (!availableFilterDefinition) return null;
return {
fieldMetadataId: viewFilter.fieldMetadataId,
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
definition: availableFilterDefinition,
};
})
.filter(isDefined);
};

View File

@ -1,13 +1,26 @@
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { isDefined } from '~/utils/isDefined';
import { ViewSort } from '../types/ViewSort';
export const mapViewSortsToSorts = (viewSorts: ViewSort[]): Sort[] => {
return viewSorts.map((viewSort) => {
return {
fieldMetadataId: viewSort.fieldMetadataId,
direction: viewSort.direction,
definition: viewSort.definition,
};
});
export const mapViewSortsToSorts = (
viewSorts: ViewSort[],
availableSortDefinitions: SortDefinition[],
): Sort[] => {
return viewSorts
.map((viewSort) => {
const availableSortDefinition = availableSortDefinitions.find(
(sortDefinition) =>
sortDefinition.fieldMetadataId === viewSort.fieldMetadataId,
);
if (!availableSortDefinition) return null;
return {
fieldMetadataId: viewSort.fieldMetadataId,
direction: viewSort.direction,
definition: availableSortDefinition,
};
})
.filter(isDefined);
};