mirror of
https://github.com/twentyhq/twenty.git
synced 2024-09-19 20:18:19 +03:00
Create board fields reorder (#2639)
* wip * fields reorder works but fields are not yet persisted * fields are persisted * modify according to comments
This commit is contained in:
parent
532e4342ec
commit
85646a8072
@ -1,6 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { BoardContext } from '@/companies/states/contexts/BoardContext';
|
import { BoardContext } from '@/companies/states/contexts/BoardContext';
|
||||||
|
import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields';
|
||||||
import { BoardOptionsDropdown } from '@/ui/layout/board/components/BoardOptionsDropdown';
|
import { BoardOptionsDropdown } from '@/ui/layout/board/components/BoardOptionsDropdown';
|
||||||
import { BoardOptionsDropdownId } from '@/ui/layout/board/components/constants/BoardOptionsDropdownId';
|
import { BoardOptionsDropdownId } from '@/ui/layout/board/components/constants/BoardOptionsDropdownId';
|
||||||
import {
|
import {
|
||||||
@ -10,6 +11,7 @@ import {
|
|||||||
import { EntityBoardActionBar } from '@/ui/layout/board/components/EntityBoardActionBar';
|
import { EntityBoardActionBar } from '@/ui/layout/board/components/EntityBoardActionBar';
|
||||||
import { EntityBoardContextMenu } from '@/ui/layout/board/components/EntityBoardContextMenu';
|
import { EntityBoardContextMenu } from '@/ui/layout/board/components/EntityBoardContextMenu';
|
||||||
import { ViewBar } from '@/views/components/ViewBar';
|
import { ViewBar } from '@/views/components/ViewBar';
|
||||||
|
import { useViewFields } from '@/views/hooks/internal/useViewFields';
|
||||||
import { ViewScope } from '@/views/scopes/ViewScope';
|
import { ViewScope } from '@/views/scopes/ViewScope';
|
||||||
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
||||||
|
|
||||||
@ -36,6 +38,8 @@ export const CompanyBoard = ({
|
|||||||
}: CompanyBoardProps) => {
|
}: CompanyBoardProps) => {
|
||||||
const viewScopeId = 'company-board-view';
|
const viewScopeId = 'company-board-view';
|
||||||
|
|
||||||
|
const { persistViewFields } = useViewFields(viewScopeId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewScope
|
<ViewScope
|
||||||
viewScopeId={viewScopeId}
|
viewScopeId={viewScopeId}
|
||||||
@ -47,6 +51,9 @@ export const CompanyBoard = ({
|
|||||||
<BoardContext.Provider
|
<BoardContext.Provider
|
||||||
value={{
|
value={{
|
||||||
BoardRecoilScopeContext: CompanyBoardRecoilScopeContext,
|
BoardRecoilScopeContext: CompanyBoardRecoilScopeContext,
|
||||||
|
onFieldsChange: (fields) => {
|
||||||
|
persistViewFields(mapBoardFieldDefinitionsToViewFields(fields));
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ViewBar
|
<ViewBar
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
import { RecoilScopeContext } from '@/types/RecoilScopeContext';
|
import { RecoilScopeContext } from '@/types/RecoilScopeContext';
|
||||||
|
import { BoardFieldDefinition } from '@/ui/layout/board/types/BoardFieldDefinition';
|
||||||
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
|
|
||||||
export const BoardContext = createContext<{
|
export const BoardContext = createContext<{
|
||||||
BoardRecoilScopeContext: RecoilScopeContext;
|
BoardRecoilScopeContext: RecoilScopeContext;
|
||||||
|
onFieldsChange: (fields: BoardFieldDefinition<FieldMetadata>[]) => void;
|
||||||
}>({
|
}>({
|
||||||
BoardRecoilScopeContext: createContext<string | null>(null),
|
BoardRecoilScopeContext: createContext<string | null>(null),
|
||||||
|
onFieldsChange: () => {},
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { BoardFieldDefinition } from '@/ui/layout/board/types/BoardFieldDefinition';
|
||||||
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
|
import { ViewField } from '@/views/types/ViewField';
|
||||||
|
|
||||||
|
export const mapBoardFieldDefinitionsToViewFields = (
|
||||||
|
fieldsDefinitions: BoardFieldDefinition<FieldMetadata>[],
|
||||||
|
): ViewField[] => {
|
||||||
|
return fieldsDefinitions.map(
|
||||||
|
(fieldDefinition): ViewField => ({
|
||||||
|
id: fieldDefinition.viewFieldId || '',
|
||||||
|
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||||
|
size: 0,
|
||||||
|
position: fieldDefinition.position,
|
||||||
|
isVisible: fieldDefinition.isVisible ?? true,
|
||||||
|
definition: fieldDefinition,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import { useContext, useRef, useState } from 'react';
|
import { useCallback, useContext, useRef, useState } from 'react';
|
||||||
|
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
@ -107,10 +108,26 @@ export const BoardOptionsDropdownContent = ({
|
|||||||
setCurrentMenu(menu);
|
setCurrentMenu(menu);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { handleFieldVisibilityChange } = useBoardCardFields();
|
const { handleFieldVisibilityChange, handleFieldsReorder } =
|
||||||
|
useBoardCardFields();
|
||||||
|
|
||||||
const { closeDropdown } = useDropdown();
|
const { closeDropdown } = useDropdown();
|
||||||
|
|
||||||
|
const handleReorderField: OnDragEndResponder = useCallback(
|
||||||
|
(result) => {
|
||||||
|
if (!result.destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reorderFields = [...visibleBoardCardFields];
|
||||||
|
const [removed] = reorderFields.splice(result.source.index - 1, 1);
|
||||||
|
reorderFields.splice(result.destination.index - 1, 0, removed);
|
||||||
|
|
||||||
|
handleFieldsReorder(reorderFields);
|
||||||
|
},
|
||||||
|
[handleFieldsReorder, visibleBoardCardFields],
|
||||||
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
Key.Escape,
|
Key.Escape,
|
||||||
() => {
|
() => {
|
||||||
@ -209,6 +226,7 @@ export const BoardOptionsDropdownContent = ({
|
|||||||
fields={visibleBoardCardFields}
|
fields={visibleBoardCardFields}
|
||||||
onVisibilityChange={handleFieldVisibilityChange}
|
onVisibilityChange={handleFieldVisibilityChange}
|
||||||
isDraggable={true}
|
isDraggable={true}
|
||||||
|
onDragEnd={handleReorderField}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasVisibleFields && hasHiddenFields && <DropdownMenuSeparator />}
|
{hasVisibleFields && hasHiddenFields && <DropdownMenuSeparator />}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { savedBoardCardFieldsFamilyState } from '@/ui/layout/board/states/savedBoardCardFieldsFamilyState';
|
||||||
|
import { BoardFieldDefinition } from '@/ui/layout/board/types/BoardFieldDefinition';
|
||||||
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
@ -7,13 +11,18 @@ import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState
|
|||||||
import { useBoardContext } from './useBoardContext';
|
import { useBoardContext } from './useBoardContext';
|
||||||
|
|
||||||
export const useBoardCardFields = () => {
|
export const useBoardCardFields = () => {
|
||||||
const { BoardRecoilScopeContext } = useBoardContext();
|
const { BoardRecoilScopeContext, onFieldsChange } = useBoardContext();
|
||||||
|
|
||||||
const [, setBoardCardFields] = useRecoilScopedState(
|
const [, setBoardCardFields] = useRecoilScopedState(
|
||||||
boardCardFieldsScopedState,
|
boardCardFieldsScopedState,
|
||||||
BoardRecoilScopeContext,
|
BoardRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [, setSavedBoardCardFields] = useRecoilScopedState(
|
||||||
|
savedBoardCardFieldsFamilyState,
|
||||||
|
BoardRecoilScopeContext,
|
||||||
|
);
|
||||||
|
|
||||||
const handleFieldVisibilityChange = (
|
const handleFieldVisibilityChange = (
|
||||||
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
|
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
|
||||||
) => {
|
) => {
|
||||||
@ -26,5 +35,27 @@ export const useBoardCardFields = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { handleFieldVisibilityChange };
|
const handleFieldsChange = useCallback(
|
||||||
|
async (fields: BoardFieldDefinition<FieldMetadata>[]) => {
|
||||||
|
setSavedBoardCardFields(fields);
|
||||||
|
setBoardCardFields(fields);
|
||||||
|
|
||||||
|
await onFieldsChange?.(fields);
|
||||||
|
},
|
||||||
|
[setBoardCardFields, setSavedBoardCardFields, onFieldsChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFieldsReorder = useCallback(
|
||||||
|
async (fields: BoardFieldDefinition<FieldMetadata>[]) => {
|
||||||
|
const updatedFields = fields.map((column, index) => ({
|
||||||
|
...column,
|
||||||
|
position: index,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await handleFieldsChange(updatedFields);
|
||||||
|
},
|
||||||
|
[handleFieldsChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { handleFieldVisibilityChange, handleFieldsReorder };
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ export const visibleBoardCardFieldsScopedSelector = selectorFamily({
|
|||||||
get:
|
get:
|
||||||
(scopeId: string) =>
|
(scopeId: string) =>
|
||||||
({ get }) =>
|
({ get }) =>
|
||||||
get(boardCardFieldsScopedState(scopeId)).filter(
|
get(boardCardFieldsScopedState(scopeId))
|
||||||
(field) => field.isVisible,
|
.filter((field) => field.isVisible)
|
||||||
),
|
.sort((a, b) => a.position - b.position),
|
||||||
});
|
});
|
||||||
|
@ -5,4 +5,5 @@ export type BoardFieldDefinition<T extends FieldMetadata> =
|
|||||||
FieldDefinition<T> & {
|
FieldDefinition<T> & {
|
||||||
position: number;
|
position: number;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
|
viewFieldId?: string;
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { BoardFieldDefinition } from '@/ui/layout/board/types/BoardFieldDefinition';
|
||||||
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
||||||
|
|
||||||
@ -7,5 +8,7 @@ export type ViewField = {
|
|||||||
position: number;
|
position: number;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
size: number;
|
size: number;
|
||||||
definition: ColumnDefinition<FieldMetadata>;
|
definition:
|
||||||
|
| ColumnDefinition<FieldMetadata>
|
||||||
|
| BoardFieldDefinition<FieldMetadata>;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user