Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,155 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
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 { updateOneRecordMutation, createOneRecordMutation } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: 'viewField',
|
||||
});
|
||||
|
||||
const { modifyRecordFromCache } = useObjectMetadataItem({
|
||||
objectNameSingular: 'view',
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const persistViewFields = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (viewFieldsToPersist: ViewField[], viewId?: string) => {
|
||||
const {
|
||||
viewObjectMetadataId,
|
||||
currentViewId,
|
||||
savedViewFieldsByKey,
|
||||
onViewFieldsChange,
|
||||
views,
|
||||
} = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
viewId,
|
||||
});
|
||||
|
||||
const {
|
||||
isPersistingViewState,
|
||||
currentViewFieldsState,
|
||||
savedViewFieldsState,
|
||||
} = getViewScopedStatesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
viewId,
|
||||
});
|
||||
|
||||
const viewIdToPersist = viewId ?? currentViewId;
|
||||
|
||||
if (!currentViewId || !savedViewFieldsByKey || !viewObjectMetadataId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _createViewFields = (viewFieldsToCreate: ViewField[]) => {
|
||||
if (!viewFieldsToCreate.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
viewFieldsToCreate.map((viewField) =>
|
||||
apolloClient.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
fieldMetadataId: viewField.fieldMetadataId,
|
||||
viewId: viewIdToPersist,
|
||||
isVisible: viewField.isVisible,
|
||||
size: viewField.size,
|
||||
position: viewField.position,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const _updateViewFields = (viewFieldsToUpdate: ViewField[]) => {
|
||||
if (!viewFieldsToUpdate.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
viewFieldsToUpdate.map((viewField) =>
|
||||
apolloClient.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate: viewField.id,
|
||||
input: {
|
||||
isVisible: viewField.isVisible,
|
||||
size: viewField.size,
|
||||
position: viewField.position,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const viewFieldsToCreate = viewFieldsToPersist.filter(
|
||||
(viewField) => !savedViewFieldsByKey[viewField.fieldMetadataId],
|
||||
);
|
||||
|
||||
const viewFieldsToUpdate = viewFieldsToPersist.filter(
|
||||
(viewFieldToPersit) =>
|
||||
savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId] &&
|
||||
(savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId].size !==
|
||||
viewFieldToPersit.size ||
|
||||
savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId]
|
||||
.position !== viewFieldToPersit.position ||
|
||||
savedViewFieldsByKey[viewFieldToPersit.fieldMetadataId]
|
||||
.isVisible !== viewFieldToPersit.isVisible),
|
||||
);
|
||||
|
||||
set(isPersistingViewState, true);
|
||||
|
||||
await _createViewFields(viewFieldsToCreate);
|
||||
|
||||
await _updateViewFields(viewFieldsToUpdate);
|
||||
|
||||
set(isPersistingViewState, false);
|
||||
set(currentViewFieldsState, viewFieldsToPersist);
|
||||
set(savedViewFieldsState, viewFieldsToPersist);
|
||||
|
||||
const existingView = views.find((view) => view.id === viewIdToPersist);
|
||||
|
||||
if (!existingView) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifyRecordFromCache(viewIdToPersist ?? '', {
|
||||
viewFields: () => ({
|
||||
edges: viewFieldsToPersist.map((viewField) => ({
|
||||
node: viewField,
|
||||
cursor: '',
|
||||
})),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
onViewFieldsChange?.(viewFieldsToPersist);
|
||||
},
|
||||
[
|
||||
viewScopeId,
|
||||
modifyRecordFromCache,
|
||||
apolloClient,
|
||||
createOneRecordMutation,
|
||||
updateOneRecordMutation,
|
||||
],
|
||||
);
|
||||
|
||||
return { persistViewFields };
|
||||
};
|
||||
@ -0,0 +1,252 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { produce } from 'immer';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { savedViewFiltersScopedFamilyState } from '@/views/states/savedViewFiltersScopedFamilyState';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
|
||||
|
||||
import { useViewScopedStates } from './useViewScopedStates';
|
||||
|
||||
export const useViewFilters = (viewScopeId: string) => {
|
||||
const {
|
||||
updateOneRecordMutation,
|
||||
createOneRecordMutation,
|
||||
deleteOneRecordMutation,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: 'viewFilter',
|
||||
});
|
||||
|
||||
const { modifyRecordFromCache } = useObjectMetadataItem({
|
||||
objectNameSingular: 'view',
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { currentViewFiltersState } = useViewScopedStates({
|
||||
viewScopeId: viewScopeId,
|
||||
});
|
||||
|
||||
const persistViewFilters = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (viewId?: string) => {
|
||||
const {
|
||||
currentViewId,
|
||||
currentViewFilters,
|
||||
savedViewFiltersByKey,
|
||||
views,
|
||||
} = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
if (!currentViewFilters) {
|
||||
return;
|
||||
}
|
||||
if (!savedViewFiltersByKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createViewFilters = (viewFiltersToCreate: ViewFilter[]) => {
|
||||
if (!viewFiltersToCreate.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewFiltersToCreate.map((viewFilter) =>
|
||||
apolloClient.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
fieldMetadataId: viewFilter.fieldMetadataId,
|
||||
viewId: viewId ?? currentViewId,
|
||||
value: viewFilter.value,
|
||||
displayValue: viewFilter.displayValue,
|
||||
operand: viewFilter.operand,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const updateViewFilters = (viewFiltersToUpdate: ViewFilter[]) => {
|
||||
if (!viewFiltersToUpdate.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewFiltersToUpdate.map((viewFilter) =>
|
||||
apolloClient.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate: viewFilter.id,
|
||||
input: {
|
||||
value: viewFilter.value,
|
||||
displayValue: viewFilter.displayValue,
|
||||
operand: viewFilter.operand,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const deleteViewFilters = (viewFilterIdsToDelete: string[]) => {
|
||||
if (!viewFilterIdsToDelete.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewFilterIdsToDelete.map((viewFilterId) =>
|
||||
apolloClient.mutate({
|
||||
mutation: deleteOneRecordMutation,
|
||||
variables: {
|
||||
idToDelete: viewFilterId,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const filtersToCreate = currentViewFilters.filter(
|
||||
(filter) => !savedViewFiltersByKey[filter.fieldMetadataId],
|
||||
);
|
||||
await createViewFilters(filtersToCreate);
|
||||
|
||||
const filtersToUpdate = currentViewFilters.filter(
|
||||
(filter) =>
|
||||
savedViewFiltersByKey[filter.fieldMetadataId] &&
|
||||
(savedViewFiltersByKey[filter.fieldMetadataId].operand !==
|
||||
filter.operand ||
|
||||
savedViewFiltersByKey[filter.fieldMetadataId].value !==
|
||||
filter.value),
|
||||
);
|
||||
await updateViewFilters(filtersToUpdate);
|
||||
|
||||
const filterKeys = currentViewFilters.map(
|
||||
(filter) => filter.fieldMetadataId,
|
||||
);
|
||||
const filterKeysToDelete = Object.keys(savedViewFiltersByKey).filter(
|
||||
(previousFilterKey) => !filterKeys.includes(previousFilterKey),
|
||||
);
|
||||
const filterIdsToDelete = filterKeysToDelete.map(
|
||||
(filterKeyToDelete) =>
|
||||
savedViewFiltersByKey[filterKeyToDelete].id ?? '',
|
||||
);
|
||||
await deleteViewFilters(filterIdsToDelete);
|
||||
set(
|
||||
savedViewFiltersScopedFamilyState({
|
||||
scopeId: viewScopeId,
|
||||
familyKey: viewId ?? currentViewId,
|
||||
}),
|
||||
currentViewFilters,
|
||||
);
|
||||
|
||||
const existingViewId = viewId ?? currentViewId;
|
||||
const existingView = views.find((view) => view.id === existingViewId);
|
||||
|
||||
if (!existingView) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifyRecordFromCache(existingViewId, {
|
||||
viewFilters: () => ({
|
||||
edges: currentViewFilters.map((viewFilter) => ({
|
||||
node: viewFilter,
|
||||
cursor: '',
|
||||
})),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
createOneRecordMutation,
|
||||
deleteOneRecordMutation,
|
||||
modifyRecordFromCache,
|
||||
updateOneRecordMutation,
|
||||
viewScopeId,
|
||||
],
|
||||
);
|
||||
|
||||
const upsertViewFilter = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(filterToUpsert: Filter) => {
|
||||
const { currentViewId, savedViewFiltersByKey, onViewFiltersChange } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!savedViewFiltersByKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingSavedFilterId =
|
||||
savedViewFiltersByKey[filterToUpsert.fieldMetadataId]?.id;
|
||||
|
||||
set(currentViewFiltersState, (filters) => {
|
||||
const newViewFilters = produce(filters, (filtersDraft) => {
|
||||
const existingFilterIndex = filtersDraft.findIndex(
|
||||
(filter) =>
|
||||
filter.fieldMetadataId === filterToUpsert.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (existingFilterIndex === -1 && filterToUpsert.value !== '') {
|
||||
filtersDraft.push({
|
||||
...filterToUpsert,
|
||||
id: existingSavedFilterId,
|
||||
});
|
||||
return filtersDraft;
|
||||
}
|
||||
|
||||
if (filterToUpsert.value === '') {
|
||||
filtersDraft.splice(existingFilterIndex, 1);
|
||||
return filtersDraft;
|
||||
}
|
||||
|
||||
filtersDraft[existingFilterIndex] = {
|
||||
...filterToUpsert,
|
||||
id: existingSavedFilterId,
|
||||
};
|
||||
});
|
||||
onViewFiltersChange?.(newViewFilters);
|
||||
return newViewFilters;
|
||||
});
|
||||
},
|
||||
[currentViewFiltersState, viewScopeId],
|
||||
);
|
||||
|
||||
const removeViewFilter = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(fieldMetadataId: string) => {
|
||||
const { currentViewId, currentViewFilters, onViewFiltersChange } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newViewFilters = currentViewFilters.filter((filter) => {
|
||||
return filter.fieldMetadataId !== fieldMetadataId;
|
||||
});
|
||||
set(currentViewFiltersState, newViewFilters);
|
||||
onViewFiltersChange?.(newViewFilters);
|
||||
},
|
||||
[currentViewFiltersState, viewScopeId],
|
||||
);
|
||||
|
||||
return { persistViewFilters, removeViewFilter, upsertViewFilter };
|
||||
};
|
||||
@ -0,0 +1,86 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState';
|
||||
|
||||
import { UNDEFINED_FAMILY_ITEM_ID } from '../../constants';
|
||||
import { ViewScopeInternalContext } from '../../scopes/scope-internal-context/ViewScopeInternalContext';
|
||||
import { currentViewIdScopedState } from '../../states/currentViewIdScopedState';
|
||||
import { getViewScopedStates } from '../../utils/internal/getViewScopedStates';
|
||||
|
||||
export const useViewScopedStates = (args?: { viewScopeId?: string }) => {
|
||||
const { viewScopeId } = args ?? {};
|
||||
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
ViewScopeInternalContext,
|
||||
viewScopeId,
|
||||
);
|
||||
|
||||
// View
|
||||
const [currentViewId] = useRecoilState(
|
||||
getScopedState(currentViewIdScopedState, scopeId),
|
||||
);
|
||||
|
||||
const viewId = currentViewId ?? UNDEFINED_FAMILY_ITEM_ID;
|
||||
|
||||
const {
|
||||
availableFieldDefinitionsState,
|
||||
availableFilterDefinitionsState,
|
||||
availableSortDefinitionsState,
|
||||
canPersistFiltersSelector,
|
||||
canPersistSortsSelector,
|
||||
currentViewFieldsState,
|
||||
currentViewFiltersState,
|
||||
currentViewIdState,
|
||||
currentViewSelector,
|
||||
currentViewSortsState,
|
||||
entityCountInCurrentViewState,
|
||||
isViewBarExpandedState,
|
||||
isPersistingViewState,
|
||||
onViewFieldsChangeState,
|
||||
onViewFiltersChangeState,
|
||||
onViewSortsChangeState,
|
||||
savedViewFieldsByKeySelector,
|
||||
savedViewFieldsState,
|
||||
savedViewFiltersByKeySelector,
|
||||
savedViewFiltersState,
|
||||
savedViewSortsByKeySelector,
|
||||
savedViewSortsState,
|
||||
viewEditModeState,
|
||||
viewObjectMetadataIdState,
|
||||
viewTypeState,
|
||||
viewsState,
|
||||
} = getViewScopedStates({
|
||||
viewScopeId: scopeId,
|
||||
viewId,
|
||||
});
|
||||
|
||||
return {
|
||||
availableFieldDefinitionsState,
|
||||
availableFilterDefinitionsState,
|
||||
availableSortDefinitionsState,
|
||||
canPersistFiltersSelector,
|
||||
canPersistSortsSelector,
|
||||
currentViewFieldsState,
|
||||
currentViewFiltersState,
|
||||
currentViewIdState,
|
||||
currentViewSelector,
|
||||
currentViewSortsState,
|
||||
entityCountInCurrentViewState,
|
||||
isViewBarExpandedState,
|
||||
isPersistingViewState,
|
||||
onViewFieldsChangeState,
|
||||
onViewFiltersChangeState,
|
||||
onViewSortsChangeState,
|
||||
savedViewFieldsByKeySelector,
|
||||
savedViewFieldsState,
|
||||
savedViewFiltersByKeySelector,
|
||||
savedViewFiltersState,
|
||||
savedViewSortsByKeySelector,
|
||||
savedViewSortsState,
|
||||
viewEditModeState,
|
||||
viewObjectMetadataIdState,
|
||||
viewTypeState,
|
||||
viewsState,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,230 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { produce } from 'immer';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
|
||||
import { savedViewSortsScopedFamilyState } from '@/views/states/savedViewSortsScopedFamilyState';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
|
||||
|
||||
import { useViewScopedStates } from './useViewScopedStates';
|
||||
|
||||
export const useViewSorts = (viewScopeId: string) => {
|
||||
const {
|
||||
updateOneRecordMutation,
|
||||
createOneRecordMutation,
|
||||
deleteOneRecordMutation,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: 'viewSort',
|
||||
});
|
||||
|
||||
const { modifyRecordFromCache } = useObjectMetadataItem({
|
||||
objectNameSingular: 'view',
|
||||
});
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { currentViewSortsState } = useViewScopedStates({
|
||||
viewScopeId: viewScopeId,
|
||||
});
|
||||
|
||||
const persistViewSorts = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (viewId?: string) => {
|
||||
const { currentViewId, currentViewSorts, savedViewSortsByKey, views } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentViewSorts) {
|
||||
return;
|
||||
}
|
||||
if (!savedViewSortsByKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createViewSorts = (viewSortsToCreate: ViewSort[]) => {
|
||||
if (!viewSortsToCreate.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewSortsToCreate.map((viewSort) =>
|
||||
apolloClient.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
fieldMetadataId: viewSort.fieldMetadataId,
|
||||
viewId: viewId ?? currentViewId,
|
||||
direction: viewSort.direction,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const updateViewSorts = (viewSortsToUpdate: ViewSort[]) => {
|
||||
if (!viewSortsToUpdate.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewSortsToUpdate.map((viewSort) =>
|
||||
apolloClient.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate: viewSort.id,
|
||||
input: {
|
||||
direction: viewSort.direction,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const deleteViewSorts = (viewSortIdsToDelete: string[]) => {
|
||||
if (!viewSortIdsToDelete.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewSortIdsToDelete.map((viewSortId) =>
|
||||
apolloClient.mutate({
|
||||
mutation: deleteOneRecordMutation,
|
||||
variables: {
|
||||
idToDelete: viewSortId,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const sortsToCreate = currentViewSorts.filter(
|
||||
(sort) => !savedViewSortsByKey[sort.fieldMetadataId],
|
||||
);
|
||||
|
||||
await createViewSorts(sortsToCreate);
|
||||
|
||||
const sortsToUpdate = currentViewSorts.filter(
|
||||
(sort) =>
|
||||
savedViewSortsByKey[sort.fieldMetadataId] &&
|
||||
savedViewSortsByKey[sort.fieldMetadataId].direction !==
|
||||
sort.direction,
|
||||
);
|
||||
await updateViewSorts(sortsToUpdate);
|
||||
|
||||
const sortKeys = currentViewSorts.map((sort) => sort.fieldMetadataId);
|
||||
const sortKeysToDelete = Object.keys(savedViewSortsByKey).filter(
|
||||
(previousSortKey) => !sortKeys.includes(previousSortKey),
|
||||
);
|
||||
const sortIdsToDelete = sortKeysToDelete.map(
|
||||
(sortKeyToDelete) => savedViewSortsByKey[sortKeyToDelete].id ?? '',
|
||||
);
|
||||
await deleteViewSorts(sortIdsToDelete);
|
||||
set(
|
||||
savedViewSortsScopedFamilyState({
|
||||
scopeId: viewScopeId,
|
||||
familyKey: viewId ?? currentViewId,
|
||||
}),
|
||||
currentViewSorts,
|
||||
);
|
||||
const existingViewId = viewId ?? currentViewId;
|
||||
const existingView = views.find((view) => view.id === existingViewId);
|
||||
|
||||
if (!existingView) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifyRecordFromCache(existingViewId, {
|
||||
viewSorts: () => ({
|
||||
edges: currentViewSorts.map((viewSort) => ({
|
||||
node: viewSort,
|
||||
cursor: '',
|
||||
})),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
createOneRecordMutation,
|
||||
deleteOneRecordMutation,
|
||||
modifyRecordFromCache,
|
||||
updateOneRecordMutation,
|
||||
viewScopeId,
|
||||
],
|
||||
);
|
||||
|
||||
const upsertViewSort = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(sortToUpsert: Sort) => {
|
||||
const { currentViewId, onViewSortsChange, savedViewSortsByKey } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!savedViewSortsByKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingSavedSortId =
|
||||
savedViewSortsByKey[sortToUpsert.fieldMetadataId]?.id;
|
||||
|
||||
set(currentViewSortsState, (sorts) => {
|
||||
const newViewSorts = produce(sorts, (sortsDraft) => {
|
||||
const existingSortIndex = sortsDraft.findIndex(
|
||||
(sort) => sort.fieldMetadataId === sortToUpsert.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (existingSortIndex === -1) {
|
||||
sortsDraft.push({ ...sortToUpsert, id: existingSavedSortId });
|
||||
return sortsDraft;
|
||||
}
|
||||
|
||||
sortsDraft[existingSortIndex] = {
|
||||
...sortToUpsert,
|
||||
id: existingSavedSortId,
|
||||
};
|
||||
});
|
||||
onViewSortsChange?.(newViewSorts);
|
||||
return newViewSorts;
|
||||
});
|
||||
},
|
||||
[currentViewSortsState, viewScopeId],
|
||||
);
|
||||
|
||||
const removeViewSort = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(fieldMetadataId: string) => {
|
||||
const { currentViewId, onViewSortsChange, currentViewSorts } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newViewSorts = currentViewSorts.filter((filter) => {
|
||||
return filter.fieldMetadataId !== fieldMetadataId;
|
||||
});
|
||||
set(currentViewSortsState, newViewSorts);
|
||||
onViewSortsChange?.(newViewSorts);
|
||||
},
|
||||
[currentViewSortsState, viewScopeId],
|
||||
);
|
||||
|
||||
return { persistViewSorts, upsertViewSort, removeViewSort };
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { getViewScopedStateValuesFromSnapshot } from '@/views/utils/getViewScopedStateValuesFromSnapshot';
|
||||
|
||||
export const useViews = (scopeId: string) => {
|
||||
const {
|
||||
updateOneRecordMutation: updateOneMutation,
|
||||
createOneRecordMutation: createOneMutation,
|
||||
deleteOneRecordMutation: deleteOneMutation,
|
||||
findManyRecordsQuery: findManyQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: 'view',
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createView = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (view: Pick<GraphQLView, 'id' | 'name'>) => {
|
||||
const { viewObjectMetadataId, viewType } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
if (!viewObjectMetadataId || !viewType) {
|
||||
return;
|
||||
}
|
||||
await apolloClient.mutate({
|
||||
mutation: createOneMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
objectMetadataId: viewObjectMetadataId,
|
||||
type: viewType,
|
||||
},
|
||||
},
|
||||
refetchQueries: [findManyQuery],
|
||||
});
|
||||
},
|
||||
[scopeId, apolloClient, createOneMutation, findManyQuery],
|
||||
);
|
||||
|
||||
const updateView = async (view: GraphQLView) => {
|
||||
await apolloClient.mutate({
|
||||
mutation: updateOneMutation,
|
||||
variables: {
|
||||
idToUpdate: view.id,
|
||||
input: {
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
},
|
||||
},
|
||||
refetchQueries: [findManyQuery],
|
||||
});
|
||||
};
|
||||
|
||||
const deleteView = async (viewId: string) => {
|
||||
await apolloClient.mutate({
|
||||
mutation: deleteOneMutation,
|
||||
variables: {
|
||||
idToDelete: viewId,
|
||||
},
|
||||
refetchQueries: [findManyQuery],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createView,
|
||||
deleteView,
|
||||
isFetchingViews: false,
|
||||
updateView,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
export const useMoveViewColumns = () => {
|
||||
const handleColumnMove = <T extends { position: number }>(
|
||||
direction: 'left' | 'right',
|
||||
currentArrayindex: number,
|
||||
targetArray: T[],
|
||||
) => {
|
||||
const targetArrayIndex =
|
||||
direction === 'left' ? currentArrayindex - 1 : currentArrayindex + 1;
|
||||
const targetArraySize = targetArray.length - 1;
|
||||
if (
|
||||
currentArrayindex >= 0 &&
|
||||
targetArrayIndex >= 0 &&
|
||||
currentArrayindex <= targetArraySize &&
|
||||
targetArrayIndex <= targetArraySize
|
||||
) {
|
||||
const currentEntity = targetArray[currentArrayindex];
|
||||
const targetEntity = targetArray[targetArrayIndex];
|
||||
const newArray = [...targetArray];
|
||||
|
||||
newArray[currentArrayindex] = {
|
||||
...targetEntity,
|
||||
index: currentEntity.position,
|
||||
};
|
||||
newArray[targetArrayIndex] = {
|
||||
...currentEntity,
|
||||
index: targetEntity.position,
|
||||
};
|
||||
|
||||
return newArray.map((column, index) => ({
|
||||
...column,
|
||||
position: index,
|
||||
}));
|
||||
}
|
||||
|
||||
return targetArray;
|
||||
};
|
||||
|
||||
return { handleColumnMove };
|
||||
};
|
||||
447
packages/twenty-front/src/modules/views/hooks/useViewBar.ts
Normal file
447
packages/twenty-front/src/modules/views/hooks/useViewBar.ts
Normal file
@ -0,0 +1,447 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
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';
|
||||
import { currentViewFiltersScopedFamilyState } from '../states/currentViewFiltersScopedFamilyState';
|
||||
import { currentViewSortsScopedFamilyState } from '../states/currentViewSortsScopedFamilyState';
|
||||
import { getViewScopedStatesFromSnapshot } from '../utils/getViewScopedStatesFromSnapshot';
|
||||
import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot';
|
||||
|
||||
import { useViewFields } from './internal/useViewFields';
|
||||
import { useViewFilters } from './internal/useViewFilters';
|
||||
import { useViews } from './internal/useViews';
|
||||
import { useViewScopedStates } from './internal/useViewScopedStates';
|
||||
import { useViewSorts } from './internal/useViewSorts';
|
||||
|
||||
type UseViewProps = {
|
||||
viewBarId?: string;
|
||||
};
|
||||
|
||||
export const useViewBar = (props?: UseViewProps) => {
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
ViewScopeInternalContext,
|
||||
props?.viewBarId,
|
||||
);
|
||||
|
||||
const {
|
||||
currentViewFiltersState,
|
||||
currentViewIdState,
|
||||
currentViewSortsState,
|
||||
viewEditModeState,
|
||||
availableFieldDefinitionsState,
|
||||
availableFilterDefinitionsState,
|
||||
availableSortDefinitionsState,
|
||||
entityCountInCurrentViewState,
|
||||
viewObjectMetadataIdState,
|
||||
viewTypeState,
|
||||
} = useViewScopedStates({
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
const { persistViewSorts, upsertViewSort, removeViewSort } =
|
||||
useViewSorts(scopeId);
|
||||
const { persistViewFilters, upsertViewFilter, removeViewFilter } =
|
||||
useViewFilters(scopeId);
|
||||
const { persistViewFields } = useViewFields(scopeId);
|
||||
const {
|
||||
createView: internalCreateView,
|
||||
updateView: internalUpdateView,
|
||||
deleteView: internalDeleteView,
|
||||
} = useViews(scopeId);
|
||||
|
||||
const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState);
|
||||
|
||||
const setAvailableFieldDefinitions = useSetRecoilState(
|
||||
availableFieldDefinitionsState,
|
||||
);
|
||||
|
||||
const setAvailableSortDefinitions = useSetRecoilState(
|
||||
availableSortDefinitionsState,
|
||||
);
|
||||
|
||||
const setAvailableFilterDefinitions = useSetRecoilState(
|
||||
availableFilterDefinitionsState,
|
||||
);
|
||||
|
||||
const setEntityCountInCurrentView = useSetRecoilState(
|
||||
entityCountInCurrentViewState,
|
||||
);
|
||||
|
||||
const setViewEditMode = useSetRecoilState(viewEditModeState);
|
||||
const setViewObjectMetadataId = useSetRecoilState(viewObjectMetadataIdState);
|
||||
const setViewType = useSetRecoilState(viewTypeState);
|
||||
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
|
||||
const changeViewInUrl = useCallback(
|
||||
(viewId: string) => {
|
||||
setSearchParams({ view: viewId });
|
||||
},
|
||||
[setSearchParams],
|
||||
);
|
||||
|
||||
const loadViewFields = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (
|
||||
data: PaginatedRecordTypeResults<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: PaginatedRecordTypeResults<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: PaginatedRecordTypeResults<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 { currentView } = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
viewId,
|
||||
});
|
||||
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadViewFields(currentView.viewFields, viewId);
|
||||
loadViewFilters(currentView.viewFilters, viewId);
|
||||
loadViewSorts(currentView.viewSorts, viewId);
|
||||
},
|
||||
[setCurrentViewId, scopeId, loadViewFields, loadViewFilters, loadViewSorts],
|
||||
);
|
||||
|
||||
const resetViewBar = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const {
|
||||
savedViewFilters,
|
||||
savedViewSorts,
|
||||
onViewFiltersChange,
|
||||
onViewSortsChange,
|
||||
} = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
if (savedViewFilters) {
|
||||
set(currentViewFiltersState, savedViewFilters);
|
||||
onViewFiltersChange?.(savedViewFilters);
|
||||
}
|
||||
if (savedViewSorts) {
|
||||
set(currentViewSortsState, savedViewSorts);
|
||||
onViewSortsChange?.(savedViewSorts);
|
||||
}
|
||||
|
||||
set(viewEditModeState, 'none');
|
||||
},
|
||||
[
|
||||
currentViewFiltersState,
|
||||
currentViewSortsState,
|
||||
scopeId,
|
||||
viewEditModeState,
|
||||
],
|
||||
);
|
||||
|
||||
const createView = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (name: string) => {
|
||||
const newViewId = v4();
|
||||
await internalCreateView({ id: newViewId, name });
|
||||
|
||||
const { currentViewFields, currentViewFilters, currentViewSorts } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
set(
|
||||
currentViewFieldsScopedFamilyState({ scopeId, familyKey: newViewId }),
|
||||
currentViewFields,
|
||||
);
|
||||
|
||||
set(
|
||||
currentViewFiltersScopedFamilyState({
|
||||
scopeId,
|
||||
familyKey: newViewId,
|
||||
}),
|
||||
currentViewFilters,
|
||||
);
|
||||
|
||||
set(
|
||||
currentViewSortsScopedFamilyState({
|
||||
scopeId,
|
||||
familyKey: newViewId,
|
||||
}),
|
||||
currentViewSorts,
|
||||
);
|
||||
|
||||
await persistViewFields(currentViewFields, newViewId);
|
||||
await persistViewFilters(newViewId);
|
||||
await persistViewSorts(newViewId);
|
||||
|
||||
changeViewInUrl(newViewId);
|
||||
},
|
||||
[
|
||||
changeViewInUrl,
|
||||
internalCreateView,
|
||||
persistViewFields,
|
||||
persistViewFilters,
|
||||
persistViewSorts,
|
||||
scopeId,
|
||||
],
|
||||
);
|
||||
|
||||
const updateCurrentView = async () => {
|
||||
await persistViewFilters();
|
||||
await persistViewSorts();
|
||||
};
|
||||
|
||||
const removeView = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (viewIdToDelete: string) => {
|
||||
const { currentViewId } = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
const { currentViewIdState, viewsState } =
|
||||
getViewScopedStatesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
if (currentViewId === viewIdToDelete) {
|
||||
set(currentViewIdState, undefined);
|
||||
}
|
||||
|
||||
set(viewsState, (previousViews) =>
|
||||
previousViews.filter((view) => view.id !== viewIdToDelete),
|
||||
);
|
||||
|
||||
internalDeleteView(viewIdToDelete);
|
||||
|
||||
if (currentViewId === viewIdToDelete) {
|
||||
setSearchParams();
|
||||
}
|
||||
},
|
||||
[internalDeleteView, scopeId, setSearchParams],
|
||||
);
|
||||
|
||||
const handleViewNameSubmit = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (name?: string) => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { viewEditMode, currentView } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId: scopeId,
|
||||
});
|
||||
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewEditMode === 'create' && name) {
|
||||
await createView(name);
|
||||
|
||||
// Temporary to force refetch
|
||||
await internalUpdateView({
|
||||
...currentView,
|
||||
});
|
||||
} else {
|
||||
await internalUpdateView({
|
||||
...currentView,
|
||||
name,
|
||||
});
|
||||
}
|
||||
},
|
||||
[createView, internalUpdateView, scopeId],
|
||||
);
|
||||
|
||||
return {
|
||||
scopeId,
|
||||
currentViewId,
|
||||
|
||||
setCurrentViewId,
|
||||
updateCurrentView,
|
||||
createView,
|
||||
removeView,
|
||||
resetViewBar,
|
||||
handleViewNameSubmit,
|
||||
|
||||
setViewEditMode,
|
||||
setViewObjectMetadataId,
|
||||
setViewType,
|
||||
setEntityCountInCurrentView,
|
||||
setAvailableFieldDefinitions,
|
||||
|
||||
setAvailableSortDefinitions,
|
||||
upsertViewSort,
|
||||
removeViewSort,
|
||||
|
||||
setAvailableFilterDefinitions,
|
||||
upsertViewFilter,
|
||||
removeViewFilter,
|
||||
|
||||
persistViewFields,
|
||||
changeViewInUrl,
|
||||
loadView,
|
||||
loadViewFields,
|
||||
loadViewFilters,
|
||||
loadViewSorts,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user