diff --git a/front/src/modules/favorites/hooks/useFavorites.ts b/front/src/modules/favorites/hooks/useFavorites.ts index 81496621b..35baacffa 100644 --- a/front/src/modules/favorites/hooks/useFavorites.ts +++ b/front/src/modules/favorites/hooks/useFavorites.ts @@ -1,10 +1,8 @@ -import { useState } from 'react'; import { useApolloClient } from '@apollo/client'; import { OnDragEndResponder } from '@hello-pangea/dnd'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { Company } from '@/companies/types/Company'; import { Favorite } from '@/favorites/types/Favorite'; import { mapFavorites } from '@/favorites/utils/mapFavorites'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; @@ -35,55 +33,7 @@ export const useFavorites = ({ const apolloClient = useApolloClient(); - const [allCompanies, setAllCompanies] = useState< - Record - >({}); - const [allPeople, setAllPeople] = useState< - Record - >({}); - - // This is only temporary and will be refactored once we have main identifiers - const { loading: companiesLoading } = useFindManyObjectRecords({ - objectNamePlural: 'companies', - onCompleted: async ( - data: PaginatedObjectTypeResults>, - ) => { - 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({ - skip: companiesLoading || peopleLoading, objectNamePlural: 'favorites', onCompleted: useRecoilCallback( ({ snapshot, set }) => @@ -92,17 +42,13 @@ export const useFavorites = ({ const queriedFavorites = mapFavorites( data.edges.map((edge) => edge.node), - { - ...allCompanies, - ...allPeople, - }, ); if (!isDeeplyEqual(favorites, 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 = { ...additionalData, ...createdFavorite, }; - const newFavoritesMapped = mapFavorites([newFavorite], { - ...allCompanies, - ...allPeople, - }); + const newFavoritesMapped = mapFavorites([newFavorite]); if (createdFavorite) { set(favoritesState, [...favorites, ...newFavoritesMapped]); } }, [ - allCompanies, - allPeople, apolloClient, createOneMutation, currentWorkspaceMember, diff --git a/front/src/modules/favorites/utils/mapFavorites.ts b/front/src/modules/favorites/utils/mapFavorites.ts index 3a26d4be6..76607c5cc 100644 --- a/front/src/modules/favorites/utils/mapFavorites.ts +++ b/front/src/modules/favorites/utils/mapFavorites.ts @@ -1,48 +1,30 @@ -import { Company } from '@/companies/types/Company'; -import { Person } from '@/people/types/Person'; import { getLogoUrlFromDomainName } from '~/utils'; import { assertNotNull } from '~/utils/assert'; import { isDefined } from '~/utils/isDefined'; -export const mapFavorites = ( - favorites: any, - recordsDict: { - [key: string]: { - firstName?: Person['name']['firstName']; - lastName?: Person['name']['lastName']; - avatarUrl?: Person['avatarUrl']; - name?: Company['name']; - domainName?: Company['domainName']; - }; - }, -) => { +export const mapFavorites = (favorites: any) => { return favorites .map((favorite: any) => { - const recordInformation = - isDefined(favorite?.person) && - isDefined(recordsDict[favorite.person.id]) - ? { - id: favorite.person.id, - labelIdentifier: - recordsDict[favorite.person.id].firstName + - ' ' + - recordsDict[favorite.person.id].lastName, - avatarUrl: recordsDict[favorite.person.id].avatarUrl, - avatarType: 'rounded', - link: `/object/personV2/${favorite.person.id}`, - } - : isDefined(favorite?.company) && - isDefined(recordsDict[favorite.company.id]) - ? { - id: favorite.company.id, - labelIdentifier: recordsDict[favorite.company.id].name, - avatarUrl: getLogoUrlFromDomainName( - recordsDict[favorite.company.id].domainName ?? '', - ), - avatarType: 'squared', - link: `/object/companyV2/${favorite.company.id}`, - } - : undefined; + const recordInformation = isDefined(favorite?.person) + ? { + id: favorite.person.id, + labelIdentifier: + favorite.person.name.firstName + + ' ' + + favorite.person.name.lastName, + avatarUrl: favorite.person.avatarUrl, + avatarType: 'rounded', + link: `/object/person/${favorite.person.id}`, + } + : isDefined(favorite?.company) + ? { + id: favorite.company.id, + labelIdentifier: favorite.company.name, + avatarUrl: getLogoUrlFromDomainName(favorite.company.domainName), + avatarType: 'squared', + link: `/object/company/${favorite.company.id}`, + } + : undefined; return { ...recordInformation, diff --git a/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx b/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx index 1d45807bb..deb4e028c 100644 --- a/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx +++ b/front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx @@ -42,7 +42,8 @@ export const ObjectMetadataItemsRelationPickerEffect = () => { ) { return { id: record.id, - name: record.name.firstName + ' ' + record.name.lastName, + name: + (record.name?.firstName ?? '') + ' ' + (record.name?.lastName ?? ''), avatarUrl: record.avatarUrl, avatarType: 'rounded', record: record, @@ -52,7 +53,7 @@ export const ObjectMetadataItemsRelationPickerEffect = () => { if (['opportunity'].includes(objectMetadataItemSingularName)) { return { id: record.id, - name: record.company.name, + name: record?.company?.name, avatarUrl: record.avatarUrl, avatarType: 'rounded', record: record, diff --git a/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts b/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts index b399ca204..a18c11adf 100644 --- a/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts +++ b/front/src/modules/settings/data-model/hooks/useRelationFieldPreview.ts @@ -20,10 +20,15 @@ export const useRelationFieldPreview = ({ skip: skipDefaultValue || !relationObjectMetadataItem, }); + const mockValueName = capitalize( + relationObjectMetadataItem?.nameSingular ?? '', + ); + return { relationObjectMetadataItem, 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, }, }; }; diff --git a/front/src/modules/ui/object/record-table/components/RecordTable.tsx b/front/src/modules/ui/object/record-table/components/RecordTable.tsx index 43c675140..40aea0f14 100644 --- a/front/src/modules/ui/object/record-table/components/RecordTable.tsx +++ b/front/src/modules/ui/object/record-table/components/RecordTable.tsx @@ -1,17 +1,12 @@ import { useRef } from 'react'; 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 { 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 { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext'; -import { useRecordTable } from '../hooks/useRecordTable'; -import { TableHotkeyScope } from '../types/TableHotkeyScope'; import { RecordTableBody } from './RecordTableBody'; import { RecordTableHeader } from './RecordTableHeader'; @@ -82,37 +77,7 @@ type RecordTableProps = { export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => { const tableBodyRef = useRef(null); - const { - 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(); - }, - }); + const { resetTableRowSelection, setRowSelectedState } = useRecordTable(); return ( @@ -130,6 +95,7 @@ export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => { onDragSelectionChange={setRowSelectedState} /> + diff --git a/front/src/modules/ui/object/record-table/components/RecordTableInternalEffect.tsx b/front/src/modules/ui/object/record-table/components/RecordTableInternalEffect.tsx new file mode 100644 index 000000000..4a212475c --- /dev/null +++ b/front/src/modules/ui/object/record-table/components/RecordTableInternalEffect.tsx @@ -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; +}; + +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 <>; +}; diff --git a/front/src/modules/ui/object/record-table/hooks/useRecordTable.ts b/front/src/modules/ui/object/record-table/hooks/useRecordTable.ts index 076c46e63..b910b5122 100644 --- a/front/src/modules/ui/object/record-table/hooks/useRecordTable.ts +++ b/front/src/modules/ui/object/record-table/hooks/useRecordTable.ts @@ -18,7 +18,6 @@ import { ColumnDefinition } from '../types/ColumnDefinition'; import { TableHotkeyScope } from '../types/TableHotkeyScope'; import { useDisableSoftFocus } from './internal/useDisableSoftFocus'; -import { useGetIsSomeCellInEditMode } from './internal/useGetIsSomeCellInEditMode'; import { useLeaveTableFocus } from './internal/useLeaveTableFocus'; import { useRecordTableScopedStates } from './internal/useRecordTableScopedStates'; import { useResetTableRowSelection } from './internal/useResetTableRowSelection'; @@ -99,8 +98,6 @@ export const useRecordTable = (props?: useRecordTableProps) => { const leaveTableFocus = useLeaveTableFocus(); - const getIsSomeCellInEditMode = useGetIsSomeCellInEditMode(); - const setRowSelectedState = useSetRowSelectedState(); const resetTableRowSelection = useResetTableRowSelection(); @@ -308,7 +305,6 @@ export const useRecordTable = (props?: useRecordTableProps) => { setRecordTableData, setTableColumns, leaveTableFocus, - getIsSomeCellInEditMode, setRowSelectedState, resetTableRowSelection, upsertRecordTableItem, diff --git a/front/src/modules/ui/object/record-table/record-table-cell/components/RecordTableCellContainer.tsx b/front/src/modules/ui/object/record-table/record-table-cell/components/RecordTableCellContainer.tsx index 0d94a332a..540c19875 100644 --- a/front/src/modules/ui/object/record-table/record-table-cell/components/RecordTableCellContainer.tsx +++ b/front/src/modules/ui/object/record-table/record-table-cell/components/RecordTableCellContainer.tsx @@ -5,11 +5,11 @@ import { IconArrowUpRight } from '@/ui/display/icon'; import { useGetButtonIcon } from '@/ui/object/field/hooks/useGetButtonIcon'; import { useIsFieldEmpty } from '@/ui/object/field/hooks/useIsFieldEmpty'; 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 { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; import { ColumnIndexContext } from '../../contexts/ColumnIndexContext'; -import { useRecordTable } from '../../hooks/useRecordTable'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode'; import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell'; @@ -57,7 +57,7 @@ export const TableCellContainer = ({ }: TableCellContainerProps) => { const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); - const { getIsSomeCellInEditMode } = useRecordTable(); + const getIsSomeCellInEditMode = useGetIsSomeCellInEditMode(); const [isHovered, setIsHovered] = useState(false); diff --git a/front/src/modules/views/components/ViewBarEffect.tsx b/front/src/modules/views/components/ViewBarEffect.tsx index d97941824..bb2929aa3 100644 --- a/front/src/modules/views/components/ViewBarEffect.tsx +++ b/front/src/modules/views/components/ViewBarEffect.tsx @@ -5,12 +5,12 @@ import { useRecoilCallback, useRecoilValue } from 'recoil'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; 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 { View } from '../types/View'; import { ViewField } from '../types/ViewField'; import { ViewFilter } from '../types/ViewFilter'; import { ViewSort } from '../types/ViewSort'; @@ -18,12 +18,7 @@ import { getViewScopedStatesFromSnapshot } from '../utils/getViewScopedStatesFro import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot'; export const ViewBarEffect = () => { - const { - scopeId: viewScopeId, - currentViewId, - loadView, - changeViewInUrl, - } = useView(); + const { scopeId: viewScopeId, loadView, changeViewInUrl } = useView(); const [searchParams] = useSearchParams(); const currentViewIdFromUrl = searchParams.get('view'); @@ -34,6 +29,7 @@ export const ViewBarEffect = () => { const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState); useFindManyObjectRecords({ + skip: !viewObjectMetadataId, objectNamePlural: 'views', filter: { type: { eq: viewType }, @@ -41,177 +37,190 @@ export const ViewBarEffect = () => { }, onCompleted: useRecoilCallback( ({ snapshot, set }) => - async (data: PaginatedObjectTypeResults) => { + async (data: PaginatedObjectTypeResults) => { const nextViews = data.edges.map((view) => ({ id: view.node.id, name: view.node.name, objectMetadataId: view.node.objectMetadataId, })); - const { viewsState } = getViewScopedStatesFromSnapshot({ - snapshot, - viewScopeId, - }); + const { viewsState, currentViewIdState } = + getViewScopedStatesFromSnapshot({ + snapshot, + viewScopeId, + }); const views = getSnapshotValue(snapshot, viewsState); 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); }, ), }); - useFindManyObjectRecords({ - skip: !currentViewId, - objectNamePlural: 'viewFields', - filter: { viewId: { eq: currentViewId } }, - onCompleted: useRecoilCallback( - ({ snapshot, set }) => - async (data: PaginatedObjectTypeResults) => { - const { - availableFieldDefinitions, - onViewFieldsChange, - savedViewFields, - currentViewId, - } = getViewScopedStateValuesFromSnapshot({ + const updateViewFields = useRecoilCallback( + ({ snapshot, set }) => + async ( + data: PaginatedObjectTypeResults, + currentViewId: string, + ) => { + const { + availableFieldDefinitions, + onViewFieldsChange, + savedViewFields, + isPersistingView, + } = getViewScopedStateValuesFromSnapshot({ + snapshot, + viewScopeId, + viewId: currentViewId, + }); + + const { savedViewFieldsState, currentViewFieldsState } = + getViewScopedStatesFromSnapshot({ snapshot, viewScopeId, + viewId: currentViewId, }); - const { savedViewFieldsState, currentViewFieldsState } = - getViewScopedStatesFromSnapshot({ - snapshot, - viewScopeId, - }); + if (!availableFieldDefinitions) { + return; + } - if (!availableFieldDefinitions || !currentViewId) { - return; - } + const queriedViewFields = data.edges + .map((viewField) => viewField.node) + .filter(assertNotNull); - 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], - ), - }); + if (!isDeeplyEqual(savedViewFields, queriedViewFields)) { + set(currentViewFieldsState, queriedViewFields); + set(savedViewFieldsState, queriedViewFields); + onViewFieldsChange?.(queriedViewFields); + } + }, + [viewScopeId], + ); - useFindManyObjectRecords({ - skip: !currentViewId, - objectNamePlural: 'viewFilters', - filter: { viewId: { eq: currentViewId } }, - onCompleted: useRecoilCallback( - ({ snapshot, set }) => - async (data: PaginatedObjectTypeResults>) => { - const { - availableFilterDefinitions, - savedViewFilters, - onViewFiltersChange, - currentViewId, - } = getViewScopedStateValuesFromSnapshot({ + const updateViewFilters = useRecoilCallback( + ({ snapshot, set }) => + async ( + data: PaginatedObjectTypeResults>, + currentViewId: string, + ) => { + const { + availableFilterDefinitions, + savedViewFilters, + onViewFiltersChange, + } = getViewScopedStateValuesFromSnapshot({ + snapshot, + viewScopeId, + viewId: currentViewId, + }); + + const { savedViewFiltersState, currentViewFiltersState } = + getViewScopedStatesFromSnapshot({ snapshot, viewScopeId, + viewId: currentViewId, }); - const { savedViewFiltersState, currentViewFiltersState } = - getViewScopedStatesFromSnapshot({ - snapshot, - viewScopeId, - }); + if (!availableFilterDefinitions) { + return; + } - if (!availableFilterDefinitions || !currentViewId) { - return; - } + const queriedViewFilters = data.edges + .map(({ node }) => { + const availableFilterDefinition = availableFilterDefinitions.find( + (filterDefinition) => + filterDefinition.fieldMetadataId === node.fieldMetadataId, + ); - const queriedViewFilters = data.edges - .map(({ node }) => { - const availableFilterDefinition = availableFilterDefinitions.find( - (filterDefinition) => - filterDefinition.fieldMetadataId === node.fieldMetadataId, - ); + if (!availableFilterDefinition) return null; - if (!availableFilterDefinition) return null; + return { + ...node, + displayValue: node.displayValue ?? node.value, + definition: availableFilterDefinition, + }; + }) + .filter(assertNotNull); - return { - ...node, - displayValue: node.displayValue ?? node.value, - definition: availableFilterDefinition, - }; - }) - .filter(assertNotNull); + if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) { + set(savedViewFiltersState, queriedViewFilters); + set(currentViewFiltersState, queriedViewFilters); + onViewFiltersChange?.(queriedViewFilters); + } + }, + [viewScopeId], + ); - if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) { - set(savedViewFiltersState, queriedViewFilters); - set(currentViewFiltersState, queriedViewFilters); - onViewFiltersChange?.(queriedViewFilters); - } - }, - [viewScopeId], - ), - }); - - useFindManyObjectRecords({ - skip: !currentViewId, - objectNamePlural: 'viewSorts', - filter: { viewId: { eq: currentViewId } }, - onCompleted: useRecoilCallback( - ({ snapshot, set }) => - async (data: PaginatedObjectTypeResults>) => { - const { - availableSortDefinitions, - savedViewSorts, - onViewSortsChange, - currentViewId, - } = getViewScopedStateValuesFromSnapshot({ + const updateViewSorts = useRecoilCallback( + ({ snapshot, set }) => + async ( + data: PaginatedObjectTypeResults>, + currentViewId: string, + ) => { + const { availableSortDefinitions, savedViewSorts, onViewSortsChange } = + getViewScopedStateValuesFromSnapshot({ snapshot, viewScopeId, + viewId: currentViewId, }); - const { savedViewSortsState, currentViewSortsState } = - getViewScopedStatesFromSnapshot({ - snapshot, - viewScopeId, - }); + const { savedViewSortsState, currentViewSortsState } = + getViewScopedStatesFromSnapshot({ + snapshot, + viewScopeId, + viewId: currentViewId, + }); - if (!availableSortDefinitions || !currentViewId) { - return; - } + if (!availableSortDefinitions || !currentViewId) { + return; + } - const queriedViewSorts = data.edges - .map(({ node }) => { - const availableSortDefinition = availableSortDefinitions.find( - (sort) => sort.fieldMetadataId === node.fieldMetadataId, - ); + const queriedViewSorts = data.edges + .map(({ node }) => { + const availableSortDefinition = availableSortDefinitions.find( + (sort) => sort.fieldMetadataId === node.fieldMetadataId, + ); - if (!availableSortDefinition) return null; + if (!availableSortDefinition) return null; - return { - id: node.id, - fieldMetadataId: node.fieldMetadataId, - direction: node.direction, - definition: availableSortDefinition, - }; - }) - .filter(assertNotNull); + 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], - ), - }); + if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) { + set(savedViewSortsState, queriedViewSorts); + set(currentViewSortsState, queriedViewSorts); + onViewSortsChange?.(queriedViewSorts); + } + }, + [viewScopeId], + ); useEffect(() => { if (!currentViewIdFromUrl) return; diff --git a/front/src/modules/views/hooks/internal/useViewFields.ts b/front/src/modules/views/hooks/internal/useViewFields.ts index fcc41b137..c0e6f0fca 100644 --- a/front/src/modules/views/hooks/internal/useViewFields.ts +++ b/front/src/modules/views/hooks/internal/useViewFields.ts @@ -3,18 +3,18 @@ import { useRecoilCallback } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ViewField } from '@/views/types/ViewField'; +import { getViewScopedStatesFromSnapshot } from '@/views/utils/getViewScopedStatesFromSnapshot'; import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot'; export const useViewFields = (viewScopeId: string) => { - const { updateOneMutation, createOneMutation, findManyQuery } = - useObjectMetadataItem({ - objectNameSingular: 'viewField', - }); + const { updateOneMutation, createOneMutation } = useObjectMetadataItem({ + objectNameSingular: 'viewField', + }); const apolloClient = useApolloClient(); const persistViewFields = useRecoilCallback( - ({ snapshot }) => + ({ snapshot, set }) => async (viewFieldsToPersist: ViewField[], viewId?: string) => { const { viewObjectMetadataId, currentViewId, savedViewFieldsByKey } = getViewScopedStateValuesFromSnapshot({ @@ -23,6 +23,12 @@ export const useViewFields = (viewScopeId: string) => { viewId, }); + const { isPersistingViewState } = getViewScopedStatesFromSnapshot({ + snapshot, + viewScopeId, + viewId, + }); + const viewIdToPersist = viewId ?? currentViewId; if (!currentViewId || !savedViewFieldsByKey || !viewObjectMetadataId) { @@ -47,7 +53,6 @@ export const useViewFields = (viewScopeId: string) => { position: viewField.position, }, }, - refetchQueries: [findManyQuery], }), ), ); @@ -90,17 +95,13 @@ export const useViewFields = (viewScopeId: string) => { .isVisible !== viewFieldToPersit.isVisible), ); + set(isPersistingViewState, true); await _createViewFields(viewFieldsToCreate); await _updateViewFields(viewFieldsToUpdate); + set(isPersistingViewState, false); }, - [ - apolloClient, - createOneMutation, - findManyQuery, - updateOneMutation, - viewScopeId, - ], + [apolloClient, createOneMutation, updateOneMutation, viewScopeId], ); return { persistViewFields }; diff --git a/front/src/modules/views/hooks/internal/useViewFilters.ts b/front/src/modules/views/hooks/internal/useViewFilters.ts index 37dcb2fca..96a837c52 100644 --- a/front/src/modules/views/hooks/internal/useViewFilters.ts +++ b/front/src/modules/views/hooks/internal/useViewFilters.ts @@ -11,14 +11,10 @@ import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScope import { useViewScopedStates } from './useViewScopedStates'; export const useViewFilters = (viewScopeId: string) => { - const { - updateOneMutation, - createOneMutation, - deleteOneMutation, - findManyQuery, - } = useObjectMetadataItem({ - objectNameSingular: 'viewFilter', - }); + const { updateOneMutation, createOneMutation, deleteOneMutation } = + useObjectMetadataItem({ + objectNameSingular: 'viewFilter', + }); const apolloClient = useApolloClient(); const { currentViewFiltersState } = useViewScopedStates({ @@ -60,7 +56,6 @@ export const useViewFilters = (viewScopeId: string) => { operand: viewFilter.operand, }, }, - refetchQueries: [findManyQuery], }), ), ); @@ -139,7 +134,6 @@ export const useViewFilters = (viewScopeId: string) => { apolloClient, createOneMutation, deleteOneMutation, - findManyQuery, updateOneMutation, viewScopeId, ], diff --git a/front/src/modules/views/hooks/internal/useViewScopedStates.ts b/front/src/modules/views/hooks/internal/useViewScopedStates.ts index eee7338db..4a7ce8faf 100644 --- a/front/src/modules/views/hooks/internal/useViewScopedStates.ts +++ b/front/src/modules/views/hooks/internal/useViewScopedStates.ts @@ -36,6 +36,7 @@ export const useViewScopedStates = (args?: { customViewScopeId?: string }) => { currentViewSortsState, entityCountInCurrentViewState, isViewBarExpandedState, + isPersistingViewState, onViewFieldsChangeState, onViewFiltersChangeState, onViewSortsChangeState, @@ -67,6 +68,7 @@ export const useViewScopedStates = (args?: { customViewScopeId?: string }) => { currentViewSortsState, entityCountInCurrentViewState, isViewBarExpandedState, + isPersistingViewState, onViewFieldsChangeState, onViewFiltersChangeState, onViewSortsChangeState, diff --git a/front/src/modules/views/hooks/internal/useViewSorts.ts b/front/src/modules/views/hooks/internal/useViewSorts.ts index 3abed2791..f7e7f1fe9 100644 --- a/front/src/modules/views/hooks/internal/useViewSorts.ts +++ b/front/src/modules/views/hooks/internal/useViewSorts.ts @@ -11,14 +11,10 @@ import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScope import { useViewScopedStates } from './useViewScopedStates'; export const useViewSorts = (viewScopeId: string) => { - const { - updateOneMutation, - createOneMutation, - deleteOneMutation, - findManyQuery, - } = useObjectMetadataItem({ - objectNameSingular: 'viewSort', - }); + const { updateOneMutation, createOneMutation, deleteOneMutation } = + useObjectMetadataItem({ + objectNameSingular: 'viewSort', + }); const apolloClient = useApolloClient(); const { currentViewSortsState } = useViewScopedStates({ @@ -59,7 +55,6 @@ export const useViewSorts = (viewScopeId: string) => { direction: viewSort.direction, }, }, - refetchQueries: [findManyQuery], }), ), ); @@ -132,7 +127,6 @@ export const useViewSorts = (viewScopeId: string) => { apolloClient, createOneMutation, deleteOneMutation, - findManyQuery, updateOneMutation, viewScopeId, ], diff --git a/front/src/modules/views/scopes/ViewScope.tsx b/front/src/modules/views/scopes/ViewScope.tsx index 9e58f29e3..287d95d7b 100644 --- a/front/src/modules/views/scopes/ViewScope.tsx +++ b/front/src/modules/views/scopes/ViewScope.tsx @@ -1,7 +1,7 @@ import { ReactNode } from 'react'; -import { Filter } from '@/ui/object/object-filter-dropdown/types/Filter'; -import { Sort } from '@/ui/object/object-sort-dropdown/types/Sort'; +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewSort } from '@/views/types/ViewSort'; import { ViewField } from '../types/ViewField'; @@ -11,8 +11,8 @@ import { ViewScopeInternalContext } from './scope-internal-context/ViewScopeInte type ViewScopeProps = { children: ReactNode; viewScopeId: string; - onViewSortsChange?: (sorts: Sort[]) => void | Promise; - onViewFiltersChange?: (filters: Filter[]) => void | Promise; + onViewSortsChange?: (sorts: ViewSort[]) => void | Promise; + onViewFiltersChange?: (filters: ViewFilter[]) => void | Promise; onViewFieldsChange?: (fields: ViewField[]) => void | Promise; }; diff --git a/front/src/modules/views/scopes/init-effect/ViewScopeInitEffect.tsx b/front/src/modules/views/scopes/init-effect/ViewScopeInitEffect.tsx index 9aaca1330..9199b4d7e 100644 --- a/front/src/modules/views/scopes/init-effect/ViewScopeInitEffect.tsx +++ b/front/src/modules/views/scopes/init-effect/ViewScopeInitEffect.tsx @@ -1,15 +1,15 @@ import { useEffect } from 'react'; 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 { ViewField } from '@/views/types/ViewField'; +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewSort } from '@/views/types/ViewSort'; type ViewScopeInitEffectProps = { viewScopeId: string; - onViewSortsChange?: (sorts: Sort[]) => void | Promise; - onViewFiltersChange?: (filters: Filter[]) => void | Promise; + onViewSortsChange?: (sorts: ViewSort[]) => void | Promise; + onViewFiltersChange?: (filters: ViewFilter[]) => void | Promise; onViewFieldsChange?: (fields: ViewField[]) => void | Promise; }; diff --git a/front/src/modules/views/states/isPersistingViewScopedState.ts b/front/src/modules/views/states/isPersistingViewScopedState.ts new file mode 100644 index 000000000..aadf01e8f --- /dev/null +++ b/front/src/modules/views/states/isPersistingViewScopedState.ts @@ -0,0 +1,6 @@ +import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; + +export const isPersistingViewScopedState = createScopedState({ + key: 'isPersistingViewScopedState', + defaultValue: false, +}); diff --git a/front/src/modules/views/states/onViewFiltersChangeScopedState.ts b/front/src/modules/views/states/onViewFiltersChangeScopedState.ts index 251aa1c1d..0d4b30ab0 100644 --- a/front/src/modules/views/states/onViewFiltersChangeScopedState.ts +++ b/front/src/modules/views/states/onViewFiltersChangeScopedState.ts @@ -1,8 +1,8 @@ -import { Filter } from '@/ui/object/object-filter-dropdown/types/Filter'; import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; +import { ViewFilter } from '@/views/types/ViewFilter'; export const onViewFiltersChangeScopedState = createScopedState< - ((filters: Filter[]) => void | Promise) | undefined + ((filters: ViewFilter[]) => void | Promise) | undefined >({ key: 'onViewFiltersChangeScopedState', defaultValue: undefined, diff --git a/front/src/modules/views/states/onViewSortsChangeScopedState.ts b/front/src/modules/views/states/onViewSortsChangeScopedState.ts index 24e4df4a2..a76a2da1a 100644 --- a/front/src/modules/views/states/onViewSortsChangeScopedState.ts +++ b/front/src/modules/views/states/onViewSortsChangeScopedState.ts @@ -1,8 +1,8 @@ -import { Sort } from '@/ui/object/object-sort-dropdown/types/Sort'; import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; +import { ViewSort } from '@/views/types/ViewSort'; export const onViewSortsChangeScopedState = createScopedState< - ((sorts: Sort[]) => void | Promise) | undefined + ((sorts: ViewSort[]) => void | Promise) | undefined >({ key: 'onViewSortsChangeScopedState', defaultValue: undefined, diff --git a/front/src/modules/views/types/GraphQLView.ts b/front/src/modules/views/types/GraphQLView.ts new file mode 100644 index 000000000..ae37e52ab --- /dev/null +++ b/front/src/modules/views/types/GraphQLView.ts @@ -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; + viewFilters: PaginatedObjectTypeResults; + viewSorts: PaginatedObjectTypeResults; +}; diff --git a/front/src/modules/views/types/View.ts b/front/src/modules/views/types/View.ts index c2e312219..e6506c2fa 100644 --- a/front/src/modules/views/types/View.ts +++ b/front/src/modules/views/types/View.ts @@ -1 +1,5 @@ -export type View = { id: string; name: string; objectMetadataId: string }; +export type View = { + id: string; + name: string; + objectMetadataId: string; +}; diff --git a/front/src/modules/views/types/ViewFilter.ts b/front/src/modules/views/types/ViewFilter.ts index 49e78ee1f..75216c673 100644 --- a/front/src/modules/views/types/ViewFilter.ts +++ b/front/src/modules/views/types/ViewFilter.ts @@ -3,7 +3,7 @@ import { FilterDefinition } from '@/ui/object/object-filter-dropdown/types/Filte import { ViewFilterOperand } from './ViewFilterOperand'; export type ViewFilter = { - id?: string; + id: string; fieldMetadataId: string; operand: ViewFilterOperand; value: string; diff --git a/front/src/modules/views/types/ViewSort.ts b/front/src/modules/views/types/ViewSort.ts index e2b2581d2..58df58d45 100644 --- a/front/src/modules/views/types/ViewSort.ts +++ b/front/src/modules/views/types/ViewSort.ts @@ -2,7 +2,7 @@ import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefin import { SortDirection } from '@/ui/object/object-sort-dropdown/types/SortDirection'; export type ViewSort = { - id?: string; + id: string; fieldMetadataId: string; direction: SortDirection; definition: SortDefinition; diff --git a/front/src/modules/views/utils/getViewScopedStateValuesFromSnapshot.ts b/front/src/modules/views/utils/getViewScopedStateValuesFromSnapshot.ts index fae6f6aea..b17cdbef6 100644 --- a/front/src/modules/views/utils/getViewScopedStateValuesFromSnapshot.ts +++ b/front/src/modules/views/utils/getViewScopedStateValuesFromSnapshot.ts @@ -37,6 +37,7 @@ export const getViewScopedStateValuesFromSnapshot = ({ currentViewSortsState, entityCountInCurrentViewState, isViewBarExpandedState, + isPersistingViewState, onViewFieldsChangeState, onViewFiltersChangeState, onViewSortsChangeState, @@ -80,6 +81,7 @@ export const getViewScopedStateValuesFromSnapshot = ({ entityCountInCurrentViewState, ), isViewBarExpanded: getSnapshotValue(snapshot, isViewBarExpandedState), + isPersistingView: getSnapshotValue(snapshot, isPersistingViewState), onViewFieldsChange: getSnapshotValue(snapshot, onViewFieldsChangeState), onViewFiltersChange: getSnapshotValue(snapshot, onViewFiltersChangeState), onViewSortsChange: getSnapshotValue(snapshot, onViewSortsChangeState), diff --git a/front/src/modules/views/utils/getViewScopedStatesFromSnapshot.ts b/front/src/modules/views/utils/getViewScopedStatesFromSnapshot.ts index 576f5f62e..7c1b7e75f 100644 --- a/front/src/modules/views/utils/getViewScopedStatesFromSnapshot.ts +++ b/front/src/modules/views/utils/getViewScopedStatesFromSnapshot.ts @@ -37,6 +37,7 @@ export const getViewScopedStatesFromSnapshot = ({ currentViewSortsState, entityCountInCurrentViewState, isViewBarExpandedState, + isPersistingViewState, onViewFieldsChangeState, onViewFiltersChangeState, onViewSortsChangeState, @@ -68,6 +69,7 @@ export const getViewScopedStatesFromSnapshot = ({ currentViewSortsState, entityCountInCurrentViewState, isViewBarExpandedState, + isPersistingViewState, onViewFieldsChangeState, onViewFiltersChangeState, onViewSortsChangeState, diff --git a/front/src/modules/views/utils/internal/getViewScopedStates.ts b/front/src/modules/views/utils/internal/getViewScopedStates.ts index 8ce086845..7234cc10e 100644 --- a/front/src/modules/views/utils/internal/getViewScopedStates.ts +++ b/front/src/modules/views/utils/internal/getViewScopedStates.ts @@ -2,6 +2,7 @@ import { getScopedFamilyState } from '@/ui/utilities/recoil-scope/utils/getScope import { getScopedSelector } from '@/ui/utilities/recoil-scope/utils/getScopedSelector'; import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState'; import { currentViewIdScopedState } from '@/views/states/currentViewIdScopedState'; +import { isPersistingViewScopedState } from '@/views/states/isPersistingViewScopedState'; import { currentViewScopedSelector } from '@/views/states/selectors/currentViewScopedSelector'; import { availableFieldDefinitionsScopedState } from '../../states/availableFieldDefinitionsScopedState'; @@ -59,6 +60,11 @@ export const getViewScopedStates = ({ viewScopeId, ); + const isPersistingViewState = getScopedState( + isPersistingViewScopedState, + viewScopeId, + ); + // ViewSorts const currentViewSortsState = getScopedFamilyState( currentViewSortsScopedFamilyState, @@ -170,6 +176,7 @@ export const getViewScopedStates = ({ currentViewSelector, isViewBarExpandedState, + isPersistingViewState, viewsState, viewEditModeState, diff --git a/front/src/utils/string/capitalize.ts b/front/src/utils/string/capitalize.ts index 6db86f0b9..3e3d732af 100644 --- a/front/src/utils/string/capitalize.ts +++ b/front/src/utils/string/capitalize.ts @@ -1,3 +1,6 @@ +import { isNonEmptyString } from '@sniptt/guards'; + export const capitalize = (stringToCapitalize: string) => { + if (!isNonEmptyString(stringToCapitalize)) return ''; return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1); };