Improve Performances of FE by reducing first print queries (#2623)

This commit is contained in:
Charles Bochet
2023-11-21 22:47:49 +01:00
committed by GitHub
parent c74bde28b8
commit 77733f2bc8
26 changed files with 304 additions and 331 deletions

View File

@ -1,10 +1,8 @@
import { useState } from 'react';
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { OnDragEndResponder } from '@hello-pangea/dnd'; import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Company } from '@/companies/types/Company';
import { Favorite } from '@/favorites/types/Favorite'; import { Favorite } from '@/favorites/types/Favorite';
import { mapFavorites } from '@/favorites/utils/mapFavorites'; import { mapFavorites } from '@/favorites/utils/mapFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@ -35,55 +33,7 @@ export const useFavorites = ({
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const [allCompanies, setAllCompanies] = useState<
Record<string, { name: string; domainName?: string }>
>({});
const [allPeople, setAllPeople] = useState<
Record<string, { firstName: string; lastName: string; avatarUrl?: string }>
>({});
// This is only temporary and will be refactored once we have main identifiers
const { loading: companiesLoading } = useFindManyObjectRecords({
objectNamePlural: 'companies',
onCompleted: async (
data: PaginatedObjectTypeResults<Required<Company>>,
) => {
setAllCompanies(
data.edges.reduce(
(acc, { node: company }) => ({
...acc,
[company.id]: {
name: company.name,
domainName: company.domainName,
},
}),
{},
),
);
},
});
const { loading: peopleLoading } = useFindManyObjectRecords({
objectNamePlural: 'people',
onCompleted: async (data) => {
setAllPeople(
data.edges.reduce(
(acc, { node: person }) => ({
...acc,
[person.id]: {
firstName: person.firstName,
lastName: person.lastName,
avatarUrl: person.avatarUrl,
},
}),
{},
),
);
},
});
useFindManyObjectRecords({ useFindManyObjectRecords({
skip: companiesLoading || peopleLoading,
objectNamePlural: 'favorites', objectNamePlural: 'favorites',
onCompleted: useRecoilCallback( onCompleted: useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
@ -92,17 +42,13 @@ export const useFavorites = ({
const queriedFavorites = mapFavorites( const queriedFavorites = mapFavorites(
data.edges.map((edge) => edge.node), data.edges.map((edge) => edge.node),
{
...allCompanies,
...allPeople,
},
); );
if (!isDeeplyEqual(favorites, queriedFavorites)) { if (!isDeeplyEqual(favorites, queriedFavorites)) {
set(favoritesState, queriedFavorites); set(favoritesState, queriedFavorites);
} }
}, },
[allCompanies, allPeople], [],
), ),
}); });
@ -125,25 +71,20 @@ export const useFavorites = ({
}, },
}); });
const createdFavorite = result?.data?.createFavoriteV2; const createdFavorite = result?.data?.createFavorite;
const newFavorite = { const newFavorite = {
...additionalData, ...additionalData,
...createdFavorite, ...createdFavorite,
}; };
const newFavoritesMapped = mapFavorites([newFavorite], { const newFavoritesMapped = mapFavorites([newFavorite]);
...allCompanies,
...allPeople,
});
if (createdFavorite) { if (createdFavorite) {
set(favoritesState, [...favorites, ...newFavoritesMapped]); set(favoritesState, [...favorites, ...newFavoritesMapped]);
} }
}, },
[ [
allCompanies,
allPeople,
apolloClient, apolloClient,
createOneMutation, createOneMutation,
currentWorkspaceMember, currentWorkspaceMember,

View File

@ -1,48 +1,30 @@
import { Company } from '@/companies/types/Company';
import { Person } from '@/people/types/Person';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const mapFavorites = ( export const mapFavorites = (favorites: any) => {
favorites: any,
recordsDict: {
[key: string]: {
firstName?: Person['name']['firstName'];
lastName?: Person['name']['lastName'];
avatarUrl?: Person['avatarUrl'];
name?: Company['name'];
domainName?: Company['domainName'];
};
},
) => {
return favorites return favorites
.map((favorite: any) => { .map((favorite: any) => {
const recordInformation = const recordInformation = isDefined(favorite?.person)
isDefined(favorite?.person) && ? {
isDefined(recordsDict[favorite.person.id]) id: favorite.person.id,
? { labelIdentifier:
id: favorite.person.id, favorite.person.name.firstName +
labelIdentifier: ' ' +
recordsDict[favorite.person.id].firstName + favorite.person.name.lastName,
' ' + avatarUrl: favorite.person.avatarUrl,
recordsDict[favorite.person.id].lastName, avatarType: 'rounded',
avatarUrl: recordsDict[favorite.person.id].avatarUrl, link: `/object/person/${favorite.person.id}`,
avatarType: 'rounded', }
link: `/object/personV2/${favorite.person.id}`, : isDefined(favorite?.company)
} ? {
: isDefined(favorite?.company) && id: favorite.company.id,
isDefined(recordsDict[favorite.company.id]) labelIdentifier: favorite.company.name,
? { avatarUrl: getLogoUrlFromDomainName(favorite.company.domainName),
id: favorite.company.id, avatarType: 'squared',
labelIdentifier: recordsDict[favorite.company.id].name, link: `/object/company/${favorite.company.id}`,
avatarUrl: getLogoUrlFromDomainName( }
recordsDict[favorite.company.id].domainName ?? '', : undefined;
),
avatarType: 'squared',
link: `/object/companyV2/${favorite.company.id}`,
}
: undefined;
return { return {
...recordInformation, ...recordInformation,

View File

@ -42,7 +42,8 @@ export const ObjectMetadataItemsRelationPickerEffect = () => {
) { ) {
return { return {
id: record.id, id: record.id,
name: record.name.firstName + ' ' + record.name.lastName, name:
(record.name?.firstName ?? '') + ' ' + (record.name?.lastName ?? ''),
avatarUrl: record.avatarUrl, avatarUrl: record.avatarUrl,
avatarType: 'rounded', avatarType: 'rounded',
record: record, record: record,
@ -52,7 +53,7 @@ export const ObjectMetadataItemsRelationPickerEffect = () => {
if (['opportunity'].includes(objectMetadataItemSingularName)) { if (['opportunity'].includes(objectMetadataItemSingularName)) {
return { return {
id: record.id, id: record.id,
name: record.company.name, name: record?.company?.name,
avatarUrl: record.avatarUrl, avatarUrl: record.avatarUrl,
avatarType: 'rounded', avatarType: 'rounded',
record: record, record: record,

View File

@ -20,10 +20,15 @@ export const useRelationFieldPreview = ({
skip: skipDefaultValue || !relationObjectMetadataItem, skip: skipDefaultValue || !relationObjectMetadataItem,
}); });
const mockValueName = capitalize(
relationObjectMetadataItem?.nameSingular ?? '',
);
return { return {
relationObjectMetadataItem, relationObjectMetadataItem,
defaultValue: relationObjects?.[0] ?? { defaultValue: relationObjects?.[0] ?? {
name: capitalize(relationObjectMetadataItem?.nameSingular ?? ''), company: { name: mockValueName }, // Temporary mock for opportunities, this needs to be replaced once labelIdentifiers are implemented
name: mockValueName,
}, },
}; };
}; };

View File

@ -1,17 +1,12 @@
import { useRef } from 'react'; import { useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { RecordTableInternalEffect } from '@/ui/object/record-table/components/RecordTableInternalEffect';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
useListenClickOutside,
useListenClickOutsideByClassName,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext'; import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
import { useRecordTable } from '../hooks/useRecordTable';
import { TableHotkeyScope } from '../types/TableHotkeyScope';
import { RecordTableBody } from './RecordTableBody'; import { RecordTableBody } from './RecordTableBody';
import { RecordTableHeader } from './RecordTableHeader'; import { RecordTableHeader } from './RecordTableHeader';
@ -82,37 +77,7 @@ type RecordTableProps = {
export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => { export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => {
const tableBodyRef = useRef<HTMLDivElement>(null); const tableBodyRef = useRef<HTMLDivElement>(null);
const { const { resetTableRowSelection, setRowSelectedState } = useRecordTable();
leaveTableFocus,
setRowSelectedState,
resetTableRowSelection,
useMapKeyboardToSoftFocus,
} = useRecordTable();
useMapKeyboardToSoftFocus();
useListenClickOutside({
refs: [tableBodyRef],
callback: () => {
leaveTableFocus();
},
});
useScopedHotkeys(
'escape',
() => {
resetTableRowSelection();
},
TableHotkeyScope.Table,
);
useListenClickOutsideByClassName({
classNames: ['entity-table-cell'],
excludeClassNames: ['action-bar', 'context-menu'],
callback: () => {
resetTableRowSelection();
},
});
return ( return (
<ScrollWrapper> <ScrollWrapper>
@ -130,6 +95,7 @@ export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => {
onDragSelectionChange={setRowSelectedState} onDragSelectionChange={setRowSelectedState}
/> />
</div> </div>
<RecordTableInternalEffect tableBodyRef={tableBodyRef} />
</StyledTableContainer> </StyledTableContainer>
</StyledTableWithHeader> </StyledTableWithHeader>
</EntityUpdateMutationContext.Provider> </EntityUpdateMutationContext.Provider>

View File

@ -0,0 +1,45 @@
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
import { TableHotkeyScope } from '@/ui/object/record-table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
useListenClickOutside,
useListenClickOutsideByClassName,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
type RecordTableInternalEffectProps = {
tableBodyRef: React.RefObject<HTMLDivElement>;
};
export const RecordTableInternalEffect = ({
tableBodyRef,
}: RecordTableInternalEffectProps) => {
const { leaveTableFocus, resetTableRowSelection, useMapKeyboardToSoftFocus } =
useRecordTable();
useMapKeyboardToSoftFocus();
useListenClickOutside({
refs: [tableBodyRef],
callback: () => {
leaveTableFocus();
},
});
useScopedHotkeys(
'escape',
() => {
resetTableRowSelection();
},
TableHotkeyScope.Table,
);
useListenClickOutsideByClassName({
classNames: ['entity-table-cell'],
excludeClassNames: ['action-bar', 'context-menu'],
callback: () => {
resetTableRowSelection();
},
});
return <></>;
};

View File

@ -18,7 +18,6 @@ import { ColumnDefinition } from '../types/ColumnDefinition';
import { TableHotkeyScope } from '../types/TableHotkeyScope'; import { TableHotkeyScope } from '../types/TableHotkeyScope';
import { useDisableSoftFocus } from './internal/useDisableSoftFocus'; import { useDisableSoftFocus } from './internal/useDisableSoftFocus';
import { useGetIsSomeCellInEditMode } from './internal/useGetIsSomeCellInEditMode';
import { useLeaveTableFocus } from './internal/useLeaveTableFocus'; import { useLeaveTableFocus } from './internal/useLeaveTableFocus';
import { useRecordTableScopedStates } from './internal/useRecordTableScopedStates'; import { useRecordTableScopedStates } from './internal/useRecordTableScopedStates';
import { useResetTableRowSelection } from './internal/useResetTableRowSelection'; import { useResetTableRowSelection } from './internal/useResetTableRowSelection';
@ -99,8 +98,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const leaveTableFocus = useLeaveTableFocus(); const leaveTableFocus = useLeaveTableFocus();
const getIsSomeCellInEditMode = useGetIsSomeCellInEditMode();
const setRowSelectedState = useSetRowSelectedState(); const setRowSelectedState = useSetRowSelectedState();
const resetTableRowSelection = useResetTableRowSelection(); const resetTableRowSelection = useResetTableRowSelection();
@ -308,7 +305,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setRecordTableData, setRecordTableData,
setTableColumns, setTableColumns,
leaveTableFocus, leaveTableFocus,
getIsSomeCellInEditMode,
setRowSelectedState, setRowSelectedState,
resetTableRowSelection, resetTableRowSelection,
upsertRecordTableItem, upsertRecordTableItem,

View File

@ -5,11 +5,11 @@ import { IconArrowUpRight } from '@/ui/display/icon';
import { useGetButtonIcon } from '@/ui/object/field/hooks/useGetButtonIcon'; import { useGetButtonIcon } from '@/ui/object/field/hooks/useGetButtonIcon';
import { useIsFieldEmpty } from '@/ui/object/field/hooks/useIsFieldEmpty'; import { useIsFieldEmpty } from '@/ui/object/field/hooks/useIsFieldEmpty';
import { useIsFieldInputOnly } from '@/ui/object/field/hooks/useIsFieldInputOnly'; import { useIsFieldInputOnly } from '@/ui/object/field/hooks/useIsFieldInputOnly';
import { useGetIsSomeCellInEditMode } from '@/ui/object/record-table/hooks/internal/useGetIsSomeCellInEditMode';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { ColumnIndexContext } from '../../contexts/ColumnIndexContext'; import { ColumnIndexContext } from '../../contexts/ColumnIndexContext';
import { useRecordTable } from '../../hooks/useRecordTable';
import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode'; import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode';
import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell'; import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell';
@ -57,7 +57,7 @@ export const TableCellContainer = ({
}: TableCellContainerProps) => { }: TableCellContainerProps) => {
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const { getIsSomeCellInEditMode } = useRecordTable(); const getIsSomeCellInEditMode = useGetIsSomeCellInEditMode();
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);

View File

@ -5,12 +5,12 @@ import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults'; import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { GraphQLView } from '@/views/types/GraphQLView';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates'; import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
import { useView } from '../hooks/useView'; import { useView } from '../hooks/useView';
import { View } from '../types/View';
import { ViewField } from '../types/ViewField'; import { ViewField } from '../types/ViewField';
import { ViewFilter } from '../types/ViewFilter'; import { ViewFilter } from '../types/ViewFilter';
import { ViewSort } from '../types/ViewSort'; import { ViewSort } from '../types/ViewSort';
@ -18,12 +18,7 @@ import { getViewScopedStatesFromSnapshot } from '../utils/getViewScopedStatesFro
import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot'; import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot';
export const ViewBarEffect = () => { export const ViewBarEffect = () => {
const { const { scopeId: viewScopeId, loadView, changeViewInUrl } = useView();
scopeId: viewScopeId,
currentViewId,
loadView,
changeViewInUrl,
} = useView();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const currentViewIdFromUrl = searchParams.get('view'); const currentViewIdFromUrl = searchParams.get('view');
@ -34,6 +29,7 @@ export const ViewBarEffect = () => {
const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState); const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState);
useFindManyObjectRecords({ useFindManyObjectRecords({
skip: !viewObjectMetadataId,
objectNamePlural: 'views', objectNamePlural: 'views',
filter: { filter: {
type: { eq: viewType }, type: { eq: viewType },
@ -41,177 +37,190 @@ export const ViewBarEffect = () => {
}, },
onCompleted: useRecoilCallback( onCompleted: useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
async (data: PaginatedObjectTypeResults<View>) => { async (data: PaginatedObjectTypeResults<GraphQLView>) => {
const nextViews = data.edges.map((view) => ({ const nextViews = data.edges.map((view) => ({
id: view.node.id, id: view.node.id,
name: view.node.name, name: view.node.name,
objectMetadataId: view.node.objectMetadataId, objectMetadataId: view.node.objectMetadataId,
})); }));
const { viewsState } = getViewScopedStatesFromSnapshot({ const { viewsState, currentViewIdState } =
snapshot, getViewScopedStatesFromSnapshot({
viewScopeId, snapshot,
}); viewScopeId,
});
const views = getSnapshotValue(snapshot, viewsState); const views = getSnapshotValue(snapshot, viewsState);
if (!isDeeplyEqual(views, nextViews)) set(viewsState, nextViews); if (!isDeeplyEqual(views, nextViews)) set(viewsState, nextViews);
if (!nextViews.length) return; const currentView =
data.edges
.map((view) => view.node)
.find((view) => view.id === currentViewIdFromUrl) ??
data.edges[0].node;
set(currentViewIdState, currentView.id);
if (currentView?.viewFields) {
updateViewFields(currentView.viewFields, currentView.id);
updateViewFilters(currentView.viewFilters, currentView.id);
updateViewSorts(currentView.viewSorts, currentView.id);
}
if (!nextViews.length) return;
if (!currentViewIdFromUrl) return changeViewInUrl(nextViews[0].id); if (!currentViewIdFromUrl) return changeViewInUrl(nextViews[0].id);
}, },
), ),
}); });
useFindManyObjectRecords({ const updateViewFields = useRecoilCallback(
skip: !currentViewId, ({ snapshot, set }) =>
objectNamePlural: 'viewFields', async (
filter: { viewId: { eq: currentViewId } }, data: PaginatedObjectTypeResults<ViewField>,
onCompleted: useRecoilCallback( currentViewId: string,
({ snapshot, set }) => ) => {
async (data: PaginatedObjectTypeResults<ViewField>) => { const {
const { availableFieldDefinitions,
availableFieldDefinitions, onViewFieldsChange,
onViewFieldsChange, savedViewFields,
savedViewFields, isPersistingView,
currentViewId, } = getViewScopedStateValuesFromSnapshot({
} = getViewScopedStateValuesFromSnapshot({ snapshot,
viewScopeId,
viewId: currentViewId,
});
const { savedViewFieldsState, currentViewFieldsState } =
getViewScopedStatesFromSnapshot({
snapshot, snapshot,
viewScopeId, viewScopeId,
viewId: currentViewId,
}); });
const { savedViewFieldsState, currentViewFieldsState } = if (!availableFieldDefinitions) {
getViewScopedStatesFromSnapshot({ return;
snapshot, }
viewScopeId,
});
if (!availableFieldDefinitions || !currentViewId) { const queriedViewFields = data.edges
return; .map((viewField) => viewField.node)
} .filter(assertNotNull);
const queriedViewFields = data.edges if (isPersistingView) {
.map((viewField) => viewField.node) return;
.filter(assertNotNull); }
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) { if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
set(currentViewFieldsState, queriedViewFields); set(currentViewFieldsState, queriedViewFields);
set(savedViewFieldsState, queriedViewFields); set(savedViewFieldsState, queriedViewFields);
onViewFieldsChange?.(queriedViewFields); onViewFieldsChange?.(queriedViewFields);
} }
}, },
[viewScopeId], [viewScopeId],
), );
});
useFindManyObjectRecords({ const updateViewFilters = useRecoilCallback(
skip: !currentViewId, ({ snapshot, set }) =>
objectNamePlural: 'viewFilters', async (
filter: { viewId: { eq: currentViewId } }, data: PaginatedObjectTypeResults<Required<ViewFilter>>,
onCompleted: useRecoilCallback( currentViewId: string,
({ snapshot, set }) => ) => {
async (data: PaginatedObjectTypeResults<Required<ViewFilter>>) => { const {
const { availableFilterDefinitions,
availableFilterDefinitions, savedViewFilters,
savedViewFilters, onViewFiltersChange,
onViewFiltersChange, } = getViewScopedStateValuesFromSnapshot({
currentViewId, snapshot,
} = getViewScopedStateValuesFromSnapshot({ viewScopeId,
viewId: currentViewId,
});
const { savedViewFiltersState, currentViewFiltersState } =
getViewScopedStatesFromSnapshot({
snapshot, snapshot,
viewScopeId, viewScopeId,
viewId: currentViewId,
}); });
const { savedViewFiltersState, currentViewFiltersState } = if (!availableFilterDefinitions) {
getViewScopedStatesFromSnapshot({ return;
snapshot, }
viewScopeId,
});
if (!availableFilterDefinitions || !currentViewId) { const queriedViewFilters = data.edges
return; .map(({ node }) => {
} const availableFilterDefinition = availableFilterDefinitions.find(
(filterDefinition) =>
filterDefinition.fieldMetadataId === node.fieldMetadataId,
);
const queriedViewFilters = data.edges if (!availableFilterDefinition) return null;
.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);
return { if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
...node, set(savedViewFiltersState, queriedViewFilters);
displayValue: node.displayValue ?? node.value, set(currentViewFiltersState, queriedViewFilters);
definition: availableFilterDefinition, onViewFiltersChange?.(queriedViewFilters);
}; }
}) },
.filter(assertNotNull); [viewScopeId],
);
if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) { const updateViewSorts = useRecoilCallback(
set(savedViewFiltersState, queriedViewFilters); ({ snapshot, set }) =>
set(currentViewFiltersState, queriedViewFilters); async (
onViewFiltersChange?.(queriedViewFilters); data: PaginatedObjectTypeResults<Required<ViewSort>>,
} currentViewId: string,
}, ) => {
[viewScopeId], const { availableSortDefinitions, savedViewSorts, onViewSortsChange } =
), getViewScopedStateValuesFromSnapshot({
});
useFindManyObjectRecords({
skip: !currentViewId,
objectNamePlural: 'viewSorts',
filter: { viewId: { eq: currentViewId } },
onCompleted: useRecoilCallback(
({ snapshot, set }) =>
async (data: PaginatedObjectTypeResults<Required<ViewSort>>) => {
const {
availableSortDefinitions,
savedViewSorts,
onViewSortsChange,
currentViewId,
} = getViewScopedStateValuesFromSnapshot({
snapshot, snapshot,
viewScopeId, viewScopeId,
viewId: currentViewId,
}); });
const { savedViewSortsState, currentViewSortsState } = const { savedViewSortsState, currentViewSortsState } =
getViewScopedStatesFromSnapshot({ getViewScopedStatesFromSnapshot({
snapshot, snapshot,
viewScopeId, viewScopeId,
}); viewId: currentViewId,
});
if (!availableSortDefinitions || !currentViewId) { if (!availableSortDefinitions || !currentViewId) {
return; return;
} }
const queriedViewSorts = data.edges const queriedViewSorts = data.edges
.map(({ node }) => { .map(({ node }) => {
const availableSortDefinition = availableSortDefinitions.find( const availableSortDefinition = availableSortDefinitions.find(
(sort) => sort.fieldMetadataId === node.fieldMetadataId, (sort) => sort.fieldMetadataId === node.fieldMetadataId,
); );
if (!availableSortDefinition) return null; if (!availableSortDefinition) return null;
return { return {
id: node.id, id: node.id,
fieldMetadataId: node.fieldMetadataId, fieldMetadataId: node.fieldMetadataId,
direction: node.direction, direction: node.direction,
definition: availableSortDefinition, definition: availableSortDefinition,
}; };
}) })
.filter(assertNotNull); .filter(assertNotNull);
if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) { if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
set(savedViewSortsState, queriedViewSorts); set(savedViewSortsState, queriedViewSorts);
set(currentViewSortsState, queriedViewSorts); set(currentViewSortsState, queriedViewSorts);
onViewSortsChange?.(queriedViewSorts); onViewSortsChange?.(queriedViewSorts);
} }
}, },
[viewScopeId], [viewScopeId],
), );
});
useEffect(() => { useEffect(() => {
if (!currentViewIdFromUrl) return; if (!currentViewIdFromUrl) return;

View File

@ -3,18 +3,18 @@ import { useRecoilCallback } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ViewField } from '@/views/types/ViewField'; import { ViewField } from '@/views/types/ViewField';
import { getViewScopedStatesFromSnapshot } from '@/views/utils/getViewScopedStatesFromSnapshot';
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot'; import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
export const useViewFields = (viewScopeId: string) => { export const useViewFields = (viewScopeId: string) => {
const { updateOneMutation, createOneMutation, findManyQuery } = const { updateOneMutation, createOneMutation } = useObjectMetadataItem({
useObjectMetadataItem({ objectNameSingular: 'viewField',
objectNameSingular: 'viewField', });
});
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const persistViewFields = useRecoilCallback( const persistViewFields = useRecoilCallback(
({ snapshot }) => ({ snapshot, set }) =>
async (viewFieldsToPersist: ViewField[], viewId?: string) => { async (viewFieldsToPersist: ViewField[], viewId?: string) => {
const { viewObjectMetadataId, currentViewId, savedViewFieldsByKey } = const { viewObjectMetadataId, currentViewId, savedViewFieldsByKey } =
getViewScopedStateValuesFromSnapshot({ getViewScopedStateValuesFromSnapshot({
@ -23,6 +23,12 @@ export const useViewFields = (viewScopeId: string) => {
viewId, viewId,
}); });
const { isPersistingViewState } = getViewScopedStatesFromSnapshot({
snapshot,
viewScopeId,
viewId,
});
const viewIdToPersist = viewId ?? currentViewId; const viewIdToPersist = viewId ?? currentViewId;
if (!currentViewId || !savedViewFieldsByKey || !viewObjectMetadataId) { if (!currentViewId || !savedViewFieldsByKey || !viewObjectMetadataId) {
@ -47,7 +53,6 @@ export const useViewFields = (viewScopeId: string) => {
position: viewField.position, position: viewField.position,
}, },
}, },
refetchQueries: [findManyQuery],
}), }),
), ),
); );
@ -90,17 +95,13 @@ export const useViewFields = (viewScopeId: string) => {
.isVisible !== viewFieldToPersit.isVisible), .isVisible !== viewFieldToPersit.isVisible),
); );
set(isPersistingViewState, true);
await _createViewFields(viewFieldsToCreate); await _createViewFields(viewFieldsToCreate);
await _updateViewFields(viewFieldsToUpdate); await _updateViewFields(viewFieldsToUpdate);
set(isPersistingViewState, false);
}, },
[ [apolloClient, createOneMutation, updateOneMutation, viewScopeId],
apolloClient,
createOneMutation,
findManyQuery,
updateOneMutation,
viewScopeId,
],
); );
return { persistViewFields }; return { persistViewFields };

View File

@ -11,14 +11,10 @@ import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScope
import { useViewScopedStates } from './useViewScopedStates'; import { useViewScopedStates } from './useViewScopedStates';
export const useViewFilters = (viewScopeId: string) => { export const useViewFilters = (viewScopeId: string) => {
const { const { updateOneMutation, createOneMutation, deleteOneMutation } =
updateOneMutation, useObjectMetadataItem({
createOneMutation, objectNameSingular: 'viewFilter',
deleteOneMutation, });
findManyQuery,
} = useObjectMetadataItem({
objectNameSingular: 'viewFilter',
});
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const { currentViewFiltersState } = useViewScopedStates({ const { currentViewFiltersState } = useViewScopedStates({
@ -60,7 +56,6 @@ export const useViewFilters = (viewScopeId: string) => {
operand: viewFilter.operand, operand: viewFilter.operand,
}, },
}, },
refetchQueries: [findManyQuery],
}), }),
), ),
); );
@ -139,7 +134,6 @@ export const useViewFilters = (viewScopeId: string) => {
apolloClient, apolloClient,
createOneMutation, createOneMutation,
deleteOneMutation, deleteOneMutation,
findManyQuery,
updateOneMutation, updateOneMutation,
viewScopeId, viewScopeId,
], ],

View File

@ -36,6 +36,7 @@ export const useViewScopedStates = (args?: { customViewScopeId?: string }) => {
currentViewSortsState, currentViewSortsState,
entityCountInCurrentViewState, entityCountInCurrentViewState,
isViewBarExpandedState, isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState, onViewFieldsChangeState,
onViewFiltersChangeState, onViewFiltersChangeState,
onViewSortsChangeState, onViewSortsChangeState,
@ -67,6 +68,7 @@ export const useViewScopedStates = (args?: { customViewScopeId?: string }) => {
currentViewSortsState, currentViewSortsState,
entityCountInCurrentViewState, entityCountInCurrentViewState,
isViewBarExpandedState, isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState, onViewFieldsChangeState,
onViewFiltersChangeState, onViewFiltersChangeState,
onViewSortsChangeState, onViewSortsChangeState,

View File

@ -11,14 +11,10 @@ import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScope
import { useViewScopedStates } from './useViewScopedStates'; import { useViewScopedStates } from './useViewScopedStates';
export const useViewSorts = (viewScopeId: string) => { export const useViewSorts = (viewScopeId: string) => {
const { const { updateOneMutation, createOneMutation, deleteOneMutation } =
updateOneMutation, useObjectMetadataItem({
createOneMutation, objectNameSingular: 'viewSort',
deleteOneMutation, });
findManyQuery,
} = useObjectMetadataItem({
objectNameSingular: 'viewSort',
});
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const { currentViewSortsState } = useViewScopedStates({ const { currentViewSortsState } = useViewScopedStates({
@ -59,7 +55,6 @@ export const useViewSorts = (viewScopeId: string) => {
direction: viewSort.direction, direction: viewSort.direction,
}, },
}, },
refetchQueries: [findManyQuery],
}), }),
), ),
); );
@ -132,7 +127,6 @@ export const useViewSorts = (viewScopeId: string) => {
apolloClient, apolloClient,
createOneMutation, createOneMutation,
deleteOneMutation, deleteOneMutation,
findManyQuery,
updateOneMutation, updateOneMutation,
viewScopeId, viewScopeId,
], ],

View File

@ -1,7 +1,7 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Filter } from '@/ui/object/object-filter-dropdown/types/Filter'; import { ViewFilter } from '@/views/types/ViewFilter';
import { Sort } from '@/ui/object/object-sort-dropdown/types/Sort'; import { ViewSort } from '@/views/types/ViewSort';
import { ViewField } from '../types/ViewField'; import { ViewField } from '../types/ViewField';
@ -11,8 +11,8 @@ import { ViewScopeInternalContext } from './scope-internal-context/ViewScopeInte
type ViewScopeProps = { type ViewScopeProps = {
children: ReactNode; children: ReactNode;
viewScopeId: string; viewScopeId: string;
onViewSortsChange?: (sorts: Sort[]) => void | Promise<void>; onViewSortsChange?: (sorts: ViewSort[]) => void | Promise<void>;
onViewFiltersChange?: (filters: Filter[]) => void | Promise<void>; onViewFiltersChange?: (filters: ViewFilter[]) => void | Promise<void>;
onViewFieldsChange?: (fields: ViewField[]) => void | Promise<void>; onViewFieldsChange?: (fields: ViewField[]) => void | Promise<void>;
}; };

View File

@ -1,15 +1,15 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { Filter } from '@/ui/object/object-filter-dropdown/types/Filter';
import { Sort } from '@/ui/object/object-sort-dropdown/types/Sort';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'; import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { ViewField } from '@/views/types/ViewField'; import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewSort } from '@/views/types/ViewSort';
type ViewScopeInitEffectProps = { type ViewScopeInitEffectProps = {
viewScopeId: string; viewScopeId: string;
onViewSortsChange?: (sorts: Sort[]) => void | Promise<void>; onViewSortsChange?: (sorts: ViewSort[]) => void | Promise<void>;
onViewFiltersChange?: (filters: Filter[]) => void | Promise<void>; onViewFiltersChange?: (filters: ViewFilter[]) => void | Promise<void>;
onViewFieldsChange?: (fields: ViewField[]) => void | Promise<void>; onViewFieldsChange?: (fields: ViewField[]) => void | Promise<void>;
}; };

View File

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

View File

@ -1,8 +1,8 @@
import { Filter } from '@/ui/object/object-filter-dropdown/types/Filter';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { ViewFilter } from '@/views/types/ViewFilter';
export const onViewFiltersChangeScopedState = createScopedState< export const onViewFiltersChangeScopedState = createScopedState<
((filters: Filter[]) => void | Promise<void>) | undefined ((filters: ViewFilter[]) => void | Promise<void>) | undefined
>({ >({
key: 'onViewFiltersChangeScopedState', key: 'onViewFiltersChangeScopedState',
defaultValue: undefined, defaultValue: undefined,

View File

@ -1,8 +1,8 @@
import { Sort } from '@/ui/object/object-sort-dropdown/types/Sort';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { ViewSort } from '@/views/types/ViewSort';
export const onViewSortsChangeScopedState = createScopedState< export const onViewSortsChangeScopedState = createScopedState<
((sorts: Sort[]) => void | Promise<void>) | undefined ((sorts: ViewSort[]) => void | Promise<void>) | undefined
>({ >({
key: 'onViewSortsChangeScopedState', key: 'onViewSortsChangeScopedState',
defaultValue: undefined, defaultValue: undefined,

View File

@ -0,0 +1,13 @@
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewSort } from '@/views/types/ViewSort';
export type GraphQLView = {
id: string;
name: string;
objectMetadataId: string;
viewFields: PaginatedObjectTypeResults<ViewField>;
viewFilters: PaginatedObjectTypeResults<ViewFilter>;
viewSorts: PaginatedObjectTypeResults<ViewSort>;
};

View File

@ -1 +1,5 @@
export type View = { id: string; name: string; objectMetadataId: string }; export type View = {
id: string;
name: string;
objectMetadataId: string;
};

View File

@ -3,7 +3,7 @@ import { FilterDefinition } from '@/ui/object/object-filter-dropdown/types/Filte
import { ViewFilterOperand } from './ViewFilterOperand'; import { ViewFilterOperand } from './ViewFilterOperand';
export type ViewFilter = { export type ViewFilter = {
id?: string; id: string;
fieldMetadataId: string; fieldMetadataId: string;
operand: ViewFilterOperand; operand: ViewFilterOperand;
value: string; value: string;

View File

@ -2,7 +2,7 @@ import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefin
import { SortDirection } from '@/ui/object/object-sort-dropdown/types/SortDirection'; import { SortDirection } from '@/ui/object/object-sort-dropdown/types/SortDirection';
export type ViewSort = { export type ViewSort = {
id?: string; id: string;
fieldMetadataId: string; fieldMetadataId: string;
direction: SortDirection; direction: SortDirection;
definition: SortDefinition; definition: SortDefinition;

View File

@ -37,6 +37,7 @@ export const getViewScopedStateValuesFromSnapshot = ({
currentViewSortsState, currentViewSortsState,
entityCountInCurrentViewState, entityCountInCurrentViewState,
isViewBarExpandedState, isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState, onViewFieldsChangeState,
onViewFiltersChangeState, onViewFiltersChangeState,
onViewSortsChangeState, onViewSortsChangeState,
@ -80,6 +81,7 @@ export const getViewScopedStateValuesFromSnapshot = ({
entityCountInCurrentViewState, entityCountInCurrentViewState,
), ),
isViewBarExpanded: getSnapshotValue(snapshot, isViewBarExpandedState), isViewBarExpanded: getSnapshotValue(snapshot, isViewBarExpandedState),
isPersistingView: getSnapshotValue(snapshot, isPersistingViewState),
onViewFieldsChange: getSnapshotValue(snapshot, onViewFieldsChangeState), onViewFieldsChange: getSnapshotValue(snapshot, onViewFieldsChangeState),
onViewFiltersChange: getSnapshotValue(snapshot, onViewFiltersChangeState), onViewFiltersChange: getSnapshotValue(snapshot, onViewFiltersChangeState),
onViewSortsChange: getSnapshotValue(snapshot, onViewSortsChangeState), onViewSortsChange: getSnapshotValue(snapshot, onViewSortsChangeState),

View File

@ -37,6 +37,7 @@ export const getViewScopedStatesFromSnapshot = ({
currentViewSortsState, currentViewSortsState,
entityCountInCurrentViewState, entityCountInCurrentViewState,
isViewBarExpandedState, isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState, onViewFieldsChangeState,
onViewFiltersChangeState, onViewFiltersChangeState,
onViewSortsChangeState, onViewSortsChangeState,
@ -68,6 +69,7 @@ export const getViewScopedStatesFromSnapshot = ({
currentViewSortsState, currentViewSortsState,
entityCountInCurrentViewState, entityCountInCurrentViewState,
isViewBarExpandedState, isViewBarExpandedState,
isPersistingViewState,
onViewFieldsChangeState, onViewFieldsChangeState,
onViewFiltersChangeState, onViewFiltersChangeState,
onViewSortsChangeState, onViewSortsChangeState,

View File

@ -2,6 +2,7 @@ import { getScopedFamilyState } from '@/ui/utilities/recoil-scope/utils/getScope
import { getScopedSelector } from '@/ui/utilities/recoil-scope/utils/getScopedSelector'; import { getScopedSelector } from '@/ui/utilities/recoil-scope/utils/getScopedSelector';
import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState'; import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState';
import { currentViewIdScopedState } from '@/views/states/currentViewIdScopedState'; import { currentViewIdScopedState } from '@/views/states/currentViewIdScopedState';
import { isPersistingViewScopedState } from '@/views/states/isPersistingViewScopedState';
import { currentViewScopedSelector } from '@/views/states/selectors/currentViewScopedSelector'; import { currentViewScopedSelector } from '@/views/states/selectors/currentViewScopedSelector';
import { availableFieldDefinitionsScopedState } from '../../states/availableFieldDefinitionsScopedState'; import { availableFieldDefinitionsScopedState } from '../../states/availableFieldDefinitionsScopedState';
@ -59,6 +60,11 @@ export const getViewScopedStates = ({
viewScopeId, viewScopeId,
); );
const isPersistingViewState = getScopedState(
isPersistingViewScopedState,
viewScopeId,
);
// ViewSorts // ViewSorts
const currentViewSortsState = getScopedFamilyState( const currentViewSortsState = getScopedFamilyState(
currentViewSortsScopedFamilyState, currentViewSortsScopedFamilyState,
@ -170,6 +176,7 @@ export const getViewScopedStates = ({
currentViewSelector, currentViewSelector,
isViewBarExpandedState, isViewBarExpandedState,
isPersistingViewState,
viewsState, viewsState,
viewEditModeState, viewEditModeState,

View File

@ -1,3 +1,6 @@
import { isNonEmptyString } from '@sniptt/guards';
export const capitalize = (stringToCapitalize: string) => { export const capitalize = (stringToCapitalize: string) => {
if (!isNonEmptyString(stringToCapitalize)) return '';
return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1); return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1);
}; };