feat: view groups (#7176)

Fix #4244 and #4356

This pull request introduces the new "view groups" capability, enabling
the reordering, hiding, and showing of columns in Kanban mode. The core
enhancement includes the addition of a new entity named `ViewGroup`,
which manages column behaviors and interactions.

#### Key Changes:
1. **ViewGroup Entity**:  
The newly added `ViewGroup` entity is responsible for handling the
organization and state of columns.
This includes:
   - The ability to reorder columns.
- The option to hide or show specific columns based on user preferences.

#### Conclusion:
This PR adds a significant new feature that enhances the flexibility of
Kanban views through the `ViewGroup` entity.
We'll later add the view group logic to table view too.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Jérémy M
2024-10-24 15:38:52 +02:00
committed by GitHub
parent 68a060a046
commit e8d96cfd10
61 changed files with 1408 additions and 508 deletions

View File

@ -1,27 +0,0 @@
import { expect } from '@storybook/test';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '../computeRecordBoardColumnDefinitionsFromObjectMetadata';
describe('computeRecordBoardColumnDefinitionsFromObjectMetadata', () => {
it('should correctly compute', () => {
const objectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'opportunity',
);
const stageField = objectMetadataItem?.fields.find(
(field) => field.name === 'stage',
);
if (!objectMetadataItem) {
throw new Error('Object metadata item not found');
}
const res = computeRecordBoardColumnDefinitionsFromObjectMetadata(
objectMetadataItem,
stageField?.id,
() => null,
);
expect(res.length).toEqual(stageField?.options?.length);
});
});

View File

@ -1,71 +0,0 @@
import { IconSettings } from 'twenty-ui';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import {
RecordBoardColumnDefinition,
RecordBoardColumnDefinitionNoValue,
RecordBoardColumnDefinitionType,
RecordBoardColumnDefinitionValue,
} from '@/object-record/record-board/types/RecordBoardColumnDefinition';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const computeRecordBoardColumnDefinitionsFromObjectMetadata = (
objectMetadataItem: ObjectMetadataItem,
kanbanFieldMetadataId: string,
navigateToSelectSettings: () => void,
): RecordBoardColumnDefinition[] => {
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) =>
field.id === kanbanFieldMetadataId &&
field.type === FieldMetadataType.Select,
);
if (!selectFieldMetadataItem) {
return [];
}
if (!selectFieldMetadataItem.options) {
throw new Error(
`Select Field ${objectMetadataItem.nameSingular} has no options`,
);
}
const valueColumns = selectFieldMetadataItem.options.map(
(selectOption) =>
({
id: selectOption.id,
type: RecordBoardColumnDefinitionType.Value,
title: selectOption.label,
value: selectOption.value,
color: selectOption.color,
position: selectOption.position,
actions: [
{
id: 'edit',
label: 'Edit from Settings',
icon: IconSettings,
position: 0,
callback: navigateToSelectSettings,
},
],
}) satisfies RecordBoardColumnDefinitionValue,
);
const noValueColumn = {
id: 'no-value',
title: 'No Value',
type: RecordBoardColumnDefinitionType.NoValue,
value: null,
actions: [],
position:
selectFieldMetadataItem.options
.map((option) => option.position)
.reduce((a, b) => Math.max(a, b), 0) + 1,
} satisfies RecordBoardColumnDefinitionNoValue;
if (selectFieldMetadataItem.isNullable === true) {
return [...valueColumns, noValueColumn];
}
return valueColumns;
};