Files
twenty/front/src/modules/views/hooks/useTableViewFields.ts
Aditya Pimpalkar 321488ad3c feat: Column title menus (#1616)
* view field index to float

* gql codegen and package.json

* list implementation

* db call

* reposition logic

* lint fix

* edge case fix

* review changes

* handleColumnMove refactor

* dropdown recoil scope

* rename props

* Update server/src/database/migrations/20230727124244_add_view_fields_table/migration.sql

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2023-09-19 15:27:02 -07:00

183 lines
5.7 KiB
TypeScript

import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { availableTableColumnsScopedState } from '@/ui/table/states/availableTableColumnsScopedState';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { savedTableColumnsFamilyState } from '@/ui/table/states/savedTableColumnsFamilyState';
import { savedTableColumnsByKeyFamilySelector } from '@/ui/table/states/selectors/savedTableColumnsByKeyFamilySelector';
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
import type { ColumnDefinition } from '@/ui/table/types/ColumnDefinition';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import {
SortOrder,
useCreateViewFieldsMutation,
useGetViewFieldsQuery,
useUpdateViewFieldMutation,
} from '~/generated/graphql';
import { assertNotNull } from '~/utils/assert';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
const toViewFieldInput = (
objectId: 'company' | 'person',
fieldDefinition: ColumnDefinition<ViewFieldMetadata>,
) => ({
key: fieldDefinition.key,
name: fieldDefinition.name,
index: fieldDefinition.index,
isVisible: fieldDefinition.isVisible ?? true,
objectId,
size: fieldDefinition.size,
});
export const useTableViewFields = ({
objectId,
columnDefinitions,
skipFetch,
}: {
objectId: 'company' | 'person';
columnDefinitions: ColumnDefinition<ViewFieldMetadata>[];
skipFetch?: boolean;
}) => {
const currentViewId = useRecoilScopedValue(
currentViewIdScopedState,
TableRecoilScopeContext,
);
const [availableTableColumns, setAvailableTableColumns] =
useRecoilScopedState(
availableTableColumnsScopedState,
TableRecoilScopeContext,
);
const [tableColumns, setTableColumns] = useRecoilScopedState(
tableColumnsScopedState,
TableRecoilScopeContext,
);
const setSavedTableColumns = useSetRecoilState(
savedTableColumnsFamilyState(currentViewId),
);
const savedTableColumnsByKey = useRecoilValue(
savedTableColumnsByKeyFamilySelector(currentViewId),
);
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const createViewFields = useCallback(
(
columns: ColumnDefinition<ViewFieldMetadata>[],
viewId = currentViewId,
) => {
if (!viewId || !columns.length) return;
return createViewFieldsMutation({
variables: {
data: columns.map((column) => ({
...toViewFieldInput(objectId, column),
viewId,
})),
},
});
},
[createViewFieldsMutation, currentViewId, objectId],
);
const updateViewFields = useCallback(
(columns: ColumnDefinition<ViewFieldMetadata>[]) => {
if (!currentViewId || !columns.length) return;
return Promise.all(
columns.map((column) =>
updateViewFieldMutation({
variables: {
data: {
isVisible: column.isVisible,
size: column.size,
index: column.index,
},
where: {
viewId_key: { key: column.key, viewId: currentViewId },
},
},
}),
),
);
},
[currentViewId, updateViewFieldMutation],
);
const { refetch } = useGetViewFieldsQuery({
skip: !currentViewId || skipFetch,
variables: {
orderBy: { index: SortOrder.Asc },
where: {
viewId: { equals: currentViewId },
},
},
onCompleted: async (data) => {
if (!data.viewFields.length) {
// Populate if empty
await createViewFields(columnDefinitions);
return refetch();
}
const nextColumns = data.viewFields
.map<ColumnDefinition<ViewFieldMetadata> | null>((viewField) => {
const columnDefinition = columnDefinitions.find(
({ key }) => viewField.key === key,
);
return columnDefinition
? {
...columnDefinition,
key: viewField.key,
name: viewField.name,
index: viewField.index,
size: viewField.size ?? columnDefinition.size,
isVisible: viewField.isVisible,
}
: null;
})
.filter<ColumnDefinition<ViewFieldMetadata>>(assertNotNull);
if (!isDeeplyEqual(tableColumns, nextColumns)) {
setSavedTableColumns(nextColumns);
setTableColumns(nextColumns);
}
if (!availableTableColumns.length) {
setAvailableTableColumns(columnDefinitions);
}
},
});
const persistColumns = useCallback(async () => {
if (!currentViewId) return;
const viewFieldsToCreate = tableColumns.filter(
(column) => !savedTableColumnsByKey[column.key],
);
await createViewFields(viewFieldsToCreate);
const viewFieldsToUpdate = tableColumns.filter(
(column) =>
savedTableColumnsByKey[column.key] &&
(savedTableColumnsByKey[column.key].size !== column.size ||
savedTableColumnsByKey[column.key].isVisible !== column.isVisible),
);
await updateViewFields(viewFieldsToUpdate);
return refetch();
}, [
createViewFields,
currentViewId,
refetch,
savedTableColumnsByKey,
tableColumns,
updateViewFields,
]);
return { createViewFields, persistColumns, updateViewFields };
};