mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-24 06:48:42 +03:00
parent
886acd1cec
commit
8212606043
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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: {
|
||||
|
@ -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')}
|
||||
}
|
||||
`;
|
||||
};
|
@ -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,
|
||||
});
|
||||
};
|
||||
};
|
@ -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,
|
||||
});
|
||||
};
|
||||
};
|
@ -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
|
||||
|
@ -109,7 +109,6 @@ export const usePersistField = () => {
|
||||
fieldIsFullName
|
||||
) {
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
set(
|
||||
entityFieldsFamilySelector({ entityId, fieldName }),
|
||||
valueToPersist,
|
||||
|
@ -30,6 +30,11 @@ const initializeValue = (
|
||||
currencyCode: 'USD',
|
||||
};
|
||||
}
|
||||
|
||||
if (!fieldValue) {
|
||||
return { amount: null, currencyCode: 'USD' };
|
||||
}
|
||||
|
||||
return {
|
||||
amount: convertCurrencyMicrosToCurrency(fieldValue.amountMicros),
|
||||
currencyCode: fieldValue.currencyCode,
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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 =
|
||||
|
@ -52,8 +52,10 @@ export const useFilter = (props?: UseFilterProps) => {
|
||||
setObjectFilterDropdownSearchInput('');
|
||||
setObjectFilterDropdownSelectedEntityId(null);
|
||||
setSelectedFilter(undefined);
|
||||
setFilterDefinitionUsedInDropdown(null);
|
||||
setSelectedOperandInDropdown(null);
|
||||
}, [
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setObjectFilterDropdownSearchInput,
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
setSelectedFilter,
|
||||
|
@ -31,7 +31,6 @@ export const TableOptionsDropdownContent = ({
|
||||
|
||||
const viewEditMode = useRecoilValue(viewEditModeState);
|
||||
const currentView = useRecoilValue(currentViewSelector);
|
||||
|
||||
const { closeDropdown } = useDropdown();
|
||||
|
||||
const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -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],
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
@ -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 }), {}),
|
||||
});
|
||||
|
@ -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: [],
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user