* wip

* Fix viewsé
This commit is contained in:
Charles Bochet 2023-11-24 16:32:59 +01:00 committed by GitHub
parent 886acd1cec
commit 8212606043
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 428 additions and 299 deletions

View File

@ -3,11 +3,12 @@ import { useRecoilValue } from 'recoil';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { useGenerateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation';
import { useGenerateCacheFragment } from '@/object-record/utils/useGenerateCacheFragment';
import { useGenerateDeleteOneObjectMutation } from '@/object-record/utils/useGenerateDeleteOneObjectMutation';
import { useGenerateFindManyCustomObjectsQuery } from '@/object-record/utils/useGenerateFindManyCustomObjectsQuery';
import { useGenerateFindOneCustomObjectQuery } from '@/object-record/utils/useGenerateFindOneCustomObjectQuery';
import { useGenerateUpdateOneObjectMutation } from '@/object-record/utils/useGenerateUpdateOneObjectMutation';
import { useGetRecordFromCache } from '@/object-record/utils/useGetRecordFromCache';
import { useModifyRecordFromCache } from '@/object-record/utils/useModifyRecordFromCache';
import { isDefined } from '~/utils/isDefined';
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
@ -37,7 +38,11 @@ export const useObjectMetadataItem = (
const objectNotFoundInMetadata = !isDefined(objectMetadataItem);
const cacheFragment = useGenerateCacheFragment({
const getRecordFromCache = useGetRecordFromCache({
objectMetadataItem,
});
const modifyRecordFromCache = useModifyRecordFromCache({
objectMetadataItem,
});
@ -74,7 +79,8 @@ export const useObjectMetadataItem = (
basePathToShowPage,
objectMetadataItem,
objectNotFoundInMetadata,
cacheFragment,
getRecordFromCache,
modifyRecordFromCache,
findManyQuery,
findOneQuery,
createOneMutation,

View File

@ -41,7 +41,7 @@ export const useFindManyObjectRecords = <
skip?: boolean;
}) => {
const findManyQueryStateIdentifier =
objectNamePlural + JSON.stringify(filter);
objectNamePlural + JSON.stringify(filter) + JSON.stringify(orderBy) + limit;
const [lastCursor, setLastCursor] = useRecoilState(
cursorFamilyState(findManyQueryStateIdentifier),

View File

@ -1,4 +1,4 @@
import { useApolloClient, useMutation } from '@apollo/client';
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@ -12,14 +12,12 @@ export const useUpdateOneObjectRecord = <T>({
objectMetadataItem: foundObjectMetadataItem,
objectNotFoundInMetadata,
updateOneMutation,
cacheFragment,
getRecordFromCache,
findManyQuery,
} = useObjectMetadataItem({
objectNameSingular,
});
const { cache } = useApolloClient();
// TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(updateOneMutation);
@ -36,14 +34,7 @@ export const useUpdateOneObjectRecord = <T>({
return null;
}
const cachedRecordId = cache.identify({
__typename: capitalize(foundObjectMetadataItem?.nameSingular ?? ''),
id: idToUpdate,
});
const cachedRecord = cache.readFragment({
id: cachedRecordId,
fragment: cacheFragment,
});
const cachedRecord = getRecordFromCache(idToUpdate);
const updatedObject = await mutate({
variables: {

View File

@ -1,29 +0,0 @@
import { gql } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const useGenerateCacheFragment = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
return gql`
fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} {
id
${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery(field))
.join('\n')}
}
`;
};

View File

@ -0,0 +1,43 @@
import { gql, useApolloClient } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const useGetRecordFromCache = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const apolloClient = useApolloClient();
return (recordId: string) => {
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const cacheReadFragment = gql`
fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} {
id
${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery(field))
.join('\n')}
}
`;
const cache = apolloClient.cache;
const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem?.nameSingular ?? ''),
id: recordId,
});
return cache.readFragment({
id: cachedRecordId,
fragment: cacheReadFragment,
});
};
};

View File

@ -0,0 +1,31 @@
import { useApolloClient } from '@apollo/client';
import { Modifiers } from '@apollo/client/cache';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const useModifyRecordFromCache = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
}) => {
const apolloClient = useApolloClient();
return (recordId: string, fieldModifiers: Modifiers) => {
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const cache = apolloClient.cache;
const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem?.nameSingular ?? ''),
id: recordId,
});
cache.modify({
id: cachedRecordId,
fields: fieldModifiers,
});
};
};

View File

@ -142,7 +142,7 @@ export const PeopleCard = ({
isHovered={isHovered}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={() => navigate(`/person/${person.id}`)}
onClick={() => navigate(`/object/person/${person.id}`)}
hasBottomBorder={hasBottomBorder}
>
<Avatar

View File

@ -109,7 +109,6 @@ export const usePersistField = () => {
fieldIsFullName
) {
const fieldName = fieldDefinition.metadata.fieldName;
set(
entityFieldsFamilySelector({ entityId, fieldName }),
valueToPersist,

View File

@ -30,6 +30,11 @@ const initializeValue = (
currencyCode: 'USD',
};
}
if (!fieldValue) {
return { amount: null, currencyCode: 'USD' };
}
return {
amount: convertCurrencyMicrosToCurrency(fieldValue.amountMicros),
currencyCode: fieldValue.currencyCode,

View File

@ -1,6 +1,7 @@
import { IconPlus } from '@/ui/display/icon';
import { LightButton } from '@/ui/input/button/components/LightButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter';
import { ObjectFilterDropdownId } from '../constants/ObjectFilterDropdownId';
@ -9,7 +10,10 @@ export const AddObjectFilterFromDetailsButton = () => {
dropdownScopeId: ObjectFilterDropdownId,
});
const { resetFilter } = useFilter();
const handleClick = () => {
resetFilter();
toggleDropdown();
};

View File

@ -5,27 +5,15 @@ import { ObjectFilterDropdownId } from '../constants/ObjectFilterDropdownId';
import { useFilter } from '../hooks/useFilter';
export const MultipleFiltersButton = () => {
const {
setFilterDefinitionUsedInDropdown,
setIsObjectFilterDropdownOperandSelectUnfolded,
setObjectFilterDropdownSearchInput,
setSelectedOperandInDropdown,
} = useFilter();
const { resetFilter } = useFilter();
const { isDropdownOpen, toggleDropdown } = useDropdown({
dropdownScopeId: ObjectFilterDropdownId,
});
const resetState = () => {
setIsObjectFilterDropdownOperandSelectUnfolded(false);
setFilterDefinitionUsedInDropdown(null);
setSelectedOperandInDropdown(null);
setObjectFilterDropdownSearchInput('');
};
const handleClick = () => {
toggleDropdown();
resetState();
resetFilter();
};
return (

View File

@ -42,7 +42,6 @@ export const ObjectFilterDropdownEntitySearchSelect = ({
}
setObjectFilterDropdownSelectedEntityId(selectedEntity.id);
closeDropdown();
selectFilter?.({
displayValue: selectedEntity.name,
@ -52,6 +51,7 @@ export const ObjectFilterDropdownEntitySearchSelect = ({
displayAvatarUrl: selectedEntity.avatarUrl,
definition: filterDefinitionUsedInDropdown,
});
closeDropdown();
};
const isAllEntitySelectShown =

View File

@ -52,8 +52,10 @@ export const useFilter = (props?: UseFilterProps) => {
setObjectFilterDropdownSearchInput('');
setObjectFilterDropdownSelectedEntityId(null);
setSelectedFilter(undefined);
setFilterDefinitionUsedInDropdown(null);
setSelectedOperandInDropdown(null);
}, [
setFilterDefinitionUsedInDropdown,
setObjectFilterDropdownSearchInput,
setObjectFilterDropdownSelectedEntityId,
setSelectedFilter,

View File

@ -31,7 +31,6 @@ export const TableOptionsDropdownContent = ({
const viewEditMode = useRecoilValue(viewEditModeState);
const currentView = useRecoilValue(currentViewSelector);
const { closeDropdown } = useDropdown();
const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>(

View File

@ -6,19 +6,21 @@ import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjec
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { GraphQLView } from '@/views/types/GraphQLView';
import { assertNotNull } from '~/utils/assert';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
import { useView } from '../hooks/useView';
import { ViewField } from '../types/ViewField';
import { ViewFilter } from '../types/ViewFilter';
import { ViewSort } from '../types/ViewSort';
import { getViewScopedStatesFromSnapshot } from '../utils/getViewScopedStatesFromSnapshot';
import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot';
export const ViewBarEffect = () => {
const { scopeId: viewScopeId, loadView, changeViewInUrl } = useView();
const {
scopeId: viewScopeId,
loadView,
changeViewInUrl,
loadViewFields,
loadViewFilters,
loadViewSorts,
} = useView();
const [searchParams] = useSearchParams();
const currentViewIdFromUrl = searchParams.get('view');
@ -38,11 +40,7 @@ export const ViewBarEffect = () => {
onCompleted: useRecoilCallback(
({ snapshot, set }) =>
async (data: PaginatedObjectTypeResults<GraphQLView>) => {
const nextViews = data.edges.map((view) => ({
id: view.node.id,
name: view.node.name,
objectMetadataId: view.node.objectMetadataId,
}));
const nextViews = data.edges.map(({ node }) => node);
const { viewsState, currentViewIdState } =
getViewScopedStatesFromSnapshot({
@ -52,7 +50,9 @@ export const ViewBarEffect = () => {
const views = getSnapshotValue(snapshot, viewsState);
if (!isDeeplyEqual(views, nextViews)) set(viewsState, nextViews);
if (!isDeeplyEqual(views, nextViews)) {
set(viewsState, nextViews);
}
const currentView =
data.edges
@ -66,9 +66,9 @@ export const ViewBarEffect = () => {
set(currentViewIdState, currentView.id);
if (currentView?.viewFields) {
updateViewFields(currentView.viewFields, currentView.id);
updateViewFilters(currentView.viewFilters, currentView.id);
updateViewSorts(currentView.viewSorts, currentView.id);
loadViewFields(currentView.viewFields, currentView.id);
loadViewFilters(currentView.viewFilters, currentView.id);
loadViewSorts(currentView.viewSorts, currentView.id);
}
if (!nextViews.length) return;
@ -77,154 +77,6 @@ export const ViewBarEffect = () => {
),
});
const updateViewFields = useRecoilCallback(
({ snapshot, set }) =>
async (
data: PaginatedObjectTypeResults<ViewField>,
currentViewId: string,
) => {
const {
availableFieldDefinitions,
onViewFieldsChange,
savedViewFields,
isPersistingView,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
viewId: currentViewId,
});
const { savedViewFieldsState, currentViewFieldsState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId,
viewId: currentViewId,
});
if (!availableFieldDefinitions) {
return;
}
const queriedViewFields = data.edges
.map((viewField) => viewField.node)
.filter(assertNotNull);
if (isPersistingView) {
return;
}
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
set(currentViewFieldsState, queriedViewFields);
set(savedViewFieldsState, queriedViewFields);
onViewFieldsChange?.(queriedViewFields);
}
},
[viewScopeId],
);
const updateViewFilters = useRecoilCallback(
({ snapshot, set }) =>
async (
data: PaginatedObjectTypeResults<Required<ViewFilter>>,
currentViewId: string,
) => {
const {
availableFilterDefinitions,
savedViewFilters,
onViewFiltersChange,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
viewId: currentViewId,
});
const { savedViewFiltersState, currentViewFiltersState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId,
viewId: currentViewId,
});
if (!availableFilterDefinitions) {
return;
}
const queriedViewFilters = data.edges
.map(({ node }) => {
const availableFilterDefinition = availableFilterDefinitions.find(
(filterDefinition) =>
filterDefinition.fieldMetadataId === node.fieldMetadataId,
);
if (!availableFilterDefinition) return null;
return {
...node,
displayValue: node.displayValue ?? node.value,
definition: availableFilterDefinition,
};
})
.filter(assertNotNull);
if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
set(savedViewFiltersState, queriedViewFilters);
set(currentViewFiltersState, queriedViewFilters);
onViewFiltersChange?.(queriedViewFilters);
}
},
[viewScopeId],
);
const updateViewSorts = useRecoilCallback(
({ snapshot, set }) =>
async (
data: PaginatedObjectTypeResults<Required<ViewSort>>,
currentViewId: string,
) => {
const { availableSortDefinitions, savedViewSorts, onViewSortsChange } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
viewId: currentViewId,
});
const { savedViewSortsState, currentViewSortsState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId,
viewId: currentViewId,
});
if (!availableSortDefinitions || !currentViewId) {
return;
}
const queriedViewSorts = data.edges
.map(({ node }) => {
const availableSortDefinition = availableSortDefinitions.find(
(sort) => sort.fieldMetadataId === node.fieldMetadataId,
);
if (!availableSortDefinition) return null;
return {
id: node.id,
fieldMetadataId: node.fieldMetadataId,
direction: node.direction,
definition: availableSortDefinition,
};
})
.filter(assertNotNull);
if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
set(savedViewSortsState, queriedViewSorts);
set(currentViewSortsState, queriedViewSorts);
onViewSortsChange?.(queriedViewSorts);
}
},
[viewScopeId],
);
useEffect(() => {
if (!currentViewIdFromUrl) return;

View File

@ -79,7 +79,7 @@ export const ViewsDropdownButton = ({
entityCountInCurrentViewState,
);
const { setViewEditMode } = useView();
const { setViewEditMode, setCurrentViewId, loadView } = useView();
const {
isDropdownOpen: isViewsDropdownOpen,
@ -95,10 +95,10 @@ export const ViewsDropdownButton = ({
const handleViewSelect = useRecoilCallback(
() => async (viewId: string) => {
changeViewInUrl(viewId);
loadView(viewId);
closeViewsDropdown();
},
[changeViewInUrl, closeViewsDropdown],
[changeViewInUrl, closeViewsDropdown, loadView],
);
const handleAddViewButtonClick = () => {
@ -114,6 +114,7 @@ export const ViewsDropdownButton = ({
) => {
event.stopPropagation();
changeViewInUrl(viewId);
setCurrentViewId(viewId);
setViewEditMode('edit');
onViewEditModeChange?.();
closeViewsDropdown();

View File

@ -1,5 +1,4 @@
import { useApolloClient } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@ -12,7 +11,7 @@ export const useViewFields = (viewScopeId: string) => {
objectNameSingular: 'viewField',
});
const { findManyQuery: findManyViewsQuery } = useObjectMetadataItem({
const { modifyRecordFromCache } = useObjectMetadataItem({
objectNameSingular: 'view',
});
@ -21,14 +20,23 @@ export const useViewFields = (viewScopeId: string) => {
const persistViewFields = useRecoilCallback(
({ snapshot, set }) =>
async (viewFieldsToPersist: ViewField[], viewId?: string) => {
const { viewObjectMetadataId, currentViewId, savedViewFieldsByKey } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
viewId,
});
const {
viewObjectMetadataId,
currentViewId,
savedViewFieldsByKey,
onViewFieldsChange,
views,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
viewId,
});
const { isPersistingViewState } = getViewScopedStatesFromSnapshot({
const {
isPersistingViewState,
currentViewFieldsState,
savedViewFieldsState,
} = getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId,
viewId,
@ -58,9 +66,6 @@ export const useViewFields = (viewScopeId: string) => {
position: viewField.position,
},
},
// TODO: implement optimistic response
refetchQueries: [getOperationName(findManyViewsQuery) ?? ''],
awaitRefetchQueries: true,
}),
),
);
@ -83,9 +88,6 @@ export const useViewFields = (viewScopeId: string) => {
position: viewField.position,
},
},
// TODO: implement optimistic response
refetchQueries: [getOperationName(findManyViewsQuery) ?? ''],
awaitRefetchQueries: true,
}),
),
);
@ -113,13 +115,38 @@ export const useViewFields = (viewScopeId: string) => {
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: () => ({
edges: viewFieldsToPersist.map((viewField) => ({
node: viewField,
cursor: '',
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
}),
});
onViewFieldsChange?.(viewFieldsToPersist);
},
[
viewScopeId,
modifyRecordFromCache,
apolloClient,
createOneMutation,
updateOneMutation,
viewScopeId,
findManyViewsQuery,
],
);

View File

@ -15,6 +15,11 @@ export const useViewFilters = (viewScopeId: string) => {
useObjectMetadataItem({
objectNameSingular: 'viewFilter',
});
const { modifyRecordFromCache } = useObjectMetadataItem({
objectNameSingular: 'view',
});
const apolloClient = useApolloClient();
const { currentViewFiltersState } = useViewScopedStates({
@ -24,11 +29,15 @@ export const useViewFilters = (viewScopeId: string) => {
const persistViewFilters = useRecoilCallback(
({ snapshot, set }) =>
async (viewId?: string) => {
const { currentViewId, currentViewFilters, savedViewFiltersByKey } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
const {
currentViewId,
currentViewFilters,
savedViewFiltersByKey,
views,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
});
if (!currentViewId) {
return;
@ -129,11 +138,34 @@ export const useViewFilters = (viewScopeId: string) => {
}),
currentViewFilters,
);
const existingViewId = viewId ?? currentViewId;
const existingView = views.find((view) => view.id === existingViewId);
if (!existingView) {
return;
}
modifyRecordFromCache(existingViewId, {
viewFilters: () => ({
edges: currentViewFilters.map((viewFilter) => ({
node: viewFilter,
cursor: '',
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
}),
});
},
[
apolloClient,
createOneMutation,
deleteOneMutation,
modifyRecordFromCache,
updateOneMutation,
viewScopeId,
],

View File

@ -15,6 +15,10 @@ export const useViewSorts = (viewScopeId: string) => {
useObjectMetadataItem({
objectNameSingular: 'viewSort',
});
const { modifyRecordFromCache } = useObjectMetadataItem({
objectNameSingular: 'view',
});
const apolloClient = useApolloClient();
const { currentViewSortsState } = useViewScopedStates({
@ -24,7 +28,7 @@ export const useViewSorts = (viewScopeId: string) => {
const persistViewSorts = useRecoilCallback(
({ snapshot, set }) =>
async (viewId?: string) => {
const { currentViewId, currentViewSorts, savedViewSortsByKey } =
const { currentViewId, currentViewSorts, savedViewSortsByKey, views } =
getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId,
@ -122,11 +126,33 @@ export const useViewSorts = (viewScopeId: string) => {
}),
currentViewSorts,
);
const existingViewId = viewId ?? currentViewId;
const existingView = views.find((view) => view.id === existingViewId);
if (!existingView) {
return;
}
modifyRecordFromCache(existingViewId, {
viewSorts: () => ({
edges: currentViewSorts.map((viewSort) => ({
node: viewSort,
cursor: '',
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
}),
});
},
[
apolloClient,
createOneMutation,
deleteOneMutation,
modifyRecordFromCache,
updateOneMutation,
viewScopeId,
],

View File

@ -2,7 +2,7 @@ import { useApolloClient } from '@apollo/client';
import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { View } from '@/views/types/View';
import { GraphQLView } from '@/views/types/GraphQLView';
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
export const useViews = (scopeId: string) => {
@ -18,7 +18,7 @@ export const useViews = (scopeId: string) => {
const createView = useRecoilCallback(
({ snapshot }) =>
async (view: Pick<View, 'id' | 'name'>) => {
async (view: Pick<GraphQLView, 'id' | 'name'>) => {
const { viewObjectMetadataId, viewType } =
getViewScopedStateValuesFromSnapshot({
snapshot,
@ -32,7 +32,8 @@ export const useViews = (scopeId: string) => {
mutation: createOneMutation,
variables: {
input: {
...view,
id: view.id,
name: view.name,
objectMetadataId: viewObjectMetadataId,
type: viewType,
},
@ -43,13 +44,14 @@ export const useViews = (scopeId: string) => {
[scopeId, apolloClient, createOneMutation, findManyQuery],
);
const updateView = async (view: View) => {
const updateView = async (view: GraphQLView) => {
await apolloClient.mutate({
mutation: updateOneMutation,
variables: {
idToUpdate: view.id,
input: {
...view,
id: view.id,
name: view.name,
},
},
refetchQueries: [findManyQuery],

View File

@ -3,7 +3,13 @@ import { useSearchParams } from 'react-router-dom';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
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 { assertNotNull } from '~/utils/assert';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { ViewScopeInternalContext } from '../scopes/scope-internal-context/ViewScopeInternalContext';
import { currentViewFieldsScopedFamilyState } from '../states/currentViewFieldsScopedFamilyState';
@ -85,29 +91,174 @@ export const useView = (props?: UseViewProps) => {
[setSearchParams],
);
const loadViewFields = useRecoilCallback(
({ snapshot, set }) =>
async (
data: PaginatedObjectTypeResults<ViewField>,
currentViewId: string,
) => {
const {
availableFieldDefinitions,
onViewFieldsChange,
savedViewFields,
isPersistingView,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
const { savedViewFieldsState, currentViewFieldsState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
if (!availableFieldDefinitions) {
return;
}
const queriedViewFields = data.edges
.map((viewField) => viewField.node)
.filter(assertNotNull);
if (isPersistingView) {
return;
}
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
set(currentViewFieldsState, queriedViewFields);
set(savedViewFieldsState, queriedViewFields);
onViewFieldsChange?.(queriedViewFields);
}
},
[scopeId],
);
const loadViewFilters = useRecoilCallback(
({ snapshot, set }) =>
async (
data: PaginatedObjectTypeResults<Required<ViewFilter>>,
currentViewId: string,
) => {
const {
availableFilterDefinitions,
savedViewFilters,
onViewFiltersChange,
} = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
const { savedViewFiltersState, currentViewFiltersState } =
getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId: currentViewId,
});
if (!availableFilterDefinitions) {
return;
}
const queriedViewFilters = data.edges
.map(({ node }) => {
const availableFilterDefinition = availableFilterDefinitions.find(
(filterDefinition) =>
filterDefinition.fieldMetadataId === node.fieldMetadataId,
);
if (!availableFilterDefinition) return null;
return {
...node,
displayValue: node.displayValue ?? node.value,
definition: availableFilterDefinition,
};
})
.filter(assertNotNull);
if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
set(savedViewFiltersState, queriedViewFilters);
set(currentViewFiltersState, queriedViewFilters);
}
onViewFiltersChange?.(queriedViewFilters);
},
[scopeId],
);
const loadViewSorts = useRecoilCallback(
({ snapshot, set }) =>
async (
data: PaginatedObjectTypeResults<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 = data.edges
.map(({ node }) => {
const availableSortDefinition = availableSortDefinitions.find(
(sort) => sort.fieldMetadataId === node.fieldMetadataId,
);
if (!availableSortDefinition) return null;
return {
id: node.id,
fieldMetadataId: node.fieldMetadataId,
direction: node.direction,
definition: availableSortDefinition,
};
})
.filter(assertNotNull);
if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
set(savedViewSortsState, queriedViewSorts);
set(currentViewSortsState, queriedViewSorts);
}
onViewSortsChange?.(queriedViewSorts);
},
[scopeId],
);
const loadView = useRecoilCallback(
({ snapshot }) =>
(viewId: string) => {
setCurrentViewId?.(viewId);
const {
currentViewFields,
onViewFieldsChange,
currentViewFilters,
onViewFiltersChange,
currentViewSorts,
onViewSortsChange,
} = getViewScopedStateValuesFromSnapshot({
const { currentView } = getViewScopedStateValuesFromSnapshot({
snapshot,
viewScopeId: scopeId,
viewId,
});
onViewFieldsChange?.(currentViewFields);
onViewFiltersChange?.(currentViewFilters);
onViewSortsChange?.(currentViewSorts);
if (!currentView) {
return;
}
loadViewFields(currentView.viewFields, viewId);
loadViewFilters(currentView.viewFilters, viewId);
loadViewSorts(currentView.viewSorts, viewId);
},
[setCurrentViewId, scopeId],
[setCurrentViewId, scopeId, loadViewFields, loadViewFilters, loadViewSorts],
);
const resetViewBar = useRecoilCallback(
@ -246,12 +397,12 @@ export const useView = (props?: UseViewProps) => {
if (viewEditMode === 'create' && name) {
await createView(name);
} else {
await internalUpdateView({
...currentView,
name,
});
}
await internalUpdateView({
...currentView,
name,
});
},
[createView, internalUpdateView, scopeId],
);
@ -284,5 +435,8 @@ export const useView = (props?: UseViewProps) => {
persistViewFields,
changeViewInUrl,
loadView,
loadViewFields,
loadViewFilters,
loadViewSorts,
};
};

View File

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

View File

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

View File

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