From cd0c2203fd92563c951b3f63969632df9daf5964 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 25 Jan 2024 16:40:20 +0100 Subject: [PATCH 1/4] Display columns on Record board --- .../record-board/components/RecordBoard.tsx | 16 ++- .../contexts/RecordBoardColumnContext.ts | 11 +- .../hooks/internal/useRecordBoardStates.ts | 32 +++++ .../internal/useSetRecordBoardColumns.ts | 48 ++++++++ .../record-board/hooks/useRecordBoard.ts | 16 +++ .../components/RecordBoardColumn.tsx | 33 ++++- .../RecordBoardColumnDropdownMenu.tsx | 58 +++++++++ .../components/RecordBoardColumnHeader.tsx | 105 ++++++++++++++++ ...cordBoardColumnFirstFamilyStateScopeMap.ts | 7 ++ ...ecordBoardColumnLastFamilyStateScopeMap.ts | 7 ++ .../recordBoardColumnIdsStateScopeMap.ts | 6 + .../recordBoardColumnsFamilyStateScopeMap.ts | 10 ++ ...ecordBoardColumnsFamilySelectorScopeMap.ts | 115 ++++++++++++++++++ .../types/RecordBoardColumnAction.ts | 9 ++ .../types/RecordBoardColumnDefinition.ts | 4 +- .../RecordIndexBoardContainerEffect.tsx | 45 ++++++- .../components/RecordIndexContainer.tsx | 5 +- ...oardColumnDefinitionsFromObjectMetadata.ts | 39 ++++++ .../utils/getScopeIdFromComponentId.ts | 4 +- .../utils/getScopeIdFromComponentIdStrict.ts | 2 + .../utils/guardRecoilDefaultValue.ts | 8 ++ .../typeorm-seeds/workspace/opportunity.ts | 5 + .../clean-inactive-workspace.job.ts | 5 +- .../opportunity.object-metadata.ts | 21 ++++ 24 files changed, 585 insertions(+), 26 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnIdsStateScopeMap.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnsFamilyStateScopeMap.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnAction.ts create mode 100644 packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts create mode 100644 packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts create mode 100644 packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/guardRecoilDefaultValue.ts diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx index a8aaaa97bc33..196d09593060 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx @@ -1,10 +1,13 @@ import { useRef } from 'react'; import styled from '@emotion/styled'; import { DragDropContext } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 +import { useRecoilValue } from 'recoil'; +import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn'; import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; +import { getScopeIdFromComponentIdStrict } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; export type RecordBoardProps = { @@ -37,9 +40,13 @@ const StyledBoardHeader = styled.div` export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => { const boardRef = useRef(null); + const { getColumnIdsState } = useRecordBoard(recordBoardId); + + const columnIds = useRecoilValue(getColumnIdsState()); + return ( {}} onFieldsChange={() => {}} > @@ -48,11 +55,10 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => { {}}> - {[].map((column) => ( + {columnIds.map((columnId) => ( ))} diff --git a/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts b/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts index bd19cd033595..3a0f167ae3c3 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts @@ -1,11 +1,14 @@ import { createContext } from 'react'; -import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition'; +import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; type RecordBoardColumnContextProps = { - id: string; - columnDefinition: BoardColumnDefinition; + columnDefinition: RecordBoardColumnDefinition; + isColumnFirst: boolean; + isColumnLast: boolean; }; export const RecordBoardColumnContext = - createContext(null); + createContext( + {} as RecordBoardColumnContextProps, + ); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts new file mode 100644 index 000000000000..d0f6358088c7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts @@ -0,0 +1,32 @@ +import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext'; +import { isRecordBoardColumnFirstFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap'; +import { recordBoardColumnIdsStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnIdsStateScopeMap'; +import { recordBoardColumnsFamilySelectorScopeMap } from '@/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; +import { getFamilyState } from '@/ui/utilities/recoil-scope/utils/getFamilyState'; +import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getState } from '@/ui/utilities/recoil-scope/utils/getState'; + +export const useRecordBoardStates = (recordBoardId?: string) => { + const scopeId = useAvailableScopeIdOrThrow( + RecordBoardScopeInternalContext, + getScopeIdFromComponentId(recordBoardId), + ); + + return { + scopeId, + getColumnIdsState: getState(recordBoardColumnIdsStateScopeMap, scopeId), + isColumnLastFamilyState: getFamilyState( + isRecordBoardColumnFirstFamilyStateScopeMap, + scopeId, + ), + isColumnFirstFamilyState: getFamilyState( + isRecordBoardColumnFirstFamilyStateScopeMap, + scopeId, + ), + columnsFamilySelector: getFamilyState( + recordBoardColumnsFamilySelectorScopeMap, + scopeId, + ), + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts new file mode 100644 index 000000000000..14086fa7c147 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts @@ -0,0 +1,48 @@ +import { useRecoilCallback } from 'recoil'; + +import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; + +export const useSetRecordBoardColumns = (recordBoardId?: string) => { + const { scopeId, getColumnIdsState, columnsFamilySelector } = + useRecordBoardStates(recordBoardId); + + const setRecordBoardColumns = useRecoilCallback( + ({ set, snapshot }) => + (columns: RecordBoardColumnDefinition[]) => { + const currentColumns = snapshot + .getLoadable(getColumnIdsState()) + .getValue(); + + const columnIds = columns.map(({ id }) => id); + + if (isDeeplyEqual(currentColumns, columnIds)) { + return; + } + + set( + getColumnIdsState(), + columns.map(({ id }) => id), + ); + + columns.forEach((column) => { + const currentColumn = snapshot + .getLoadable(columnsFamilySelector(column.id)) + .getValue(); + + if (isDeeplyEqual(currentColumn, column)) { + return; + } + + set(columnsFamilySelector(column.id), column); + }); + }, + [columnsFamilySelector, getColumnIdsState], + ); + + return { + scopeId, + setRecordBoardColumns, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts new file mode 100644 index 000000000000..2ddfcfc0418e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts @@ -0,0 +1,16 @@ +import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { useSetRecordBoardColumns } from '@/object-record/record-board/hooks/internal/useSetRecordBoardColumns'; + +export const useRecordBoard = (recordBoardId?: string) => { + const { scopeId, getColumnIdsState, columnsFamilySelector } = + useRecordBoardStates(recordBoardId); + + const { setRecordBoardColumns } = useSetRecordBoardColumns(recordBoardId); + + return { + scopeId, + getColumnIdsState, + columnsFamilySelector, + setRecordBoardColumns, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx index e7f037b7dd57..225d833b71f1 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx @@ -1,10 +1,12 @@ import styled from '@emotion/styled'; import { Droppable } from '@hello-pangea/dnd'; +import { useRecoilValue } from 'recoil'; import { RecordBoardColumnContext } from '@/object-record/record-board/contexts/RecordBoardColumnContext'; +import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { RecordBoardColumnCardsContainer } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer'; +import { RecordBoardColumnHeader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeader'; import { BoardCardIdContext } from '@/object-record/record-board-deprecated/contexts/BoardCardIdContext'; -import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition'; const StyledColumn = styled.div<{ isFirstColumn: boolean }>` background-color: ${({ theme }) => theme.background.primary}; @@ -22,25 +24,44 @@ const StyledColumn = styled.div<{ isFirstColumn: boolean }>` type RecordBoardColumnProps = { recordBoardColumnId: string; - columnDefinition: BoardColumnDefinition; }; export const RecordBoardColumn = ({ recordBoardColumnId, - columnDefinition, }: RecordBoardColumnProps) => { - const isFirstColumn = columnDefinition.position === 0; + const { + isColumnFirstFamilyState, + isColumnLastFamilyState, + columnsFamilySelector, + } = useRecordBoardStates(); + const columnDefinition = useRecoilValue( + columnsFamilySelector(recordBoardColumnId), + ); + + const isColumnFirst = useRecoilValue( + isColumnFirstFamilyState(recordBoardColumnId), + ); + + const isColumnLast = useRecoilValue( + isColumnLastFamilyState(recordBoardColumnId), + ); + + if (!columnDefinition) { + return null; + } return ( {(droppableProvided) => ( - + + diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu.tsx new file mode 100644 index 000000000000..e6bd403109c9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu.tsx @@ -0,0 +1,58 @@ +import { useCallback, useContext, useRef } from 'react'; +import styled from '@emotion/styled'; +import { MenuItem } from 'tsup.ui.index'; + +import { RecordBoardColumnContext } from '@/object-record/record-board/contexts/RecordBoardColumnContext'; +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; + +const StyledMenuContainer = styled.div` + position: absolute; + top: ${({ theme }) => theme.spacing(10)}; + width: 200px; + z-index: 1; +`; + +type RecordBoardColumnDropdownMenuProps = { + onClose: () => void; + onDelete?: (id: string) => void; + stageId: string; +}; + +export const RecordBoardColumnDropdownMenu = ({ + onClose, +}: RecordBoardColumnDropdownMenuProps) => { + const boardColumnMenuRef = useRef(null); + + const closeMenu = useCallback(() => { + onClose(); + }, [onClose]); + + useListenClickOutside({ + refs: [boardColumnMenuRef], + callback: closeMenu, + }); + + const { columnDefinition } = useContext(RecordBoardColumnContext); + + return ( + + + + {columnDefinition.actions.map((action) => ( + { + action.callback(); + closeMenu(); + }} + LeftIcon={action.icon} + text={action.label} + /> + ))} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx new file mode 100644 index 000000000000..da1d5999eb97 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx @@ -0,0 +1,105 @@ +import React, { useContext, useState } from 'react'; +import styled from '@emotion/styled'; + +import { RecordBoardColumnContext } from '@/object-record/record-board/contexts/RecordBoardColumnContext'; +import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu'; +import { BoardColumnHotkeyScope } from '@/object-record/record-board-deprecated/types/BoardColumnHotkeyScope'; +import { IconDotsVertical } from '@/ui/display/icon'; +import { Tag } from '@/ui/display/tag/components/Tag'; +import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; + +const StyledHeader = styled.div` + align-items: center; + cursor: pointer; + display: flex; + flex-direction: row; + height: 24px; + justify-content: left; + margin-bottom: ${({ theme }) => theme.spacing(2)}; + width: 100%; +`; + +const StyledAmount = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + margin-left: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledNumChildren = styled.div` + align-items: center; + background-color: ${({ theme }) => theme.background.tertiary}; + border-radius: ${({ theme }) => theme.border.radius.rounded}; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + height: 20px; + justify-content: center; + line-height: ${({ theme }) => theme.text.lineHeight.lg}; + margin-left: auto; + width: 16px; +`; + +const StyledHeaderActions = styled.div` + display: flex; + margin-left: auto; +`; + +export const RecordBoardColumnHeader = () => { + const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false); + const [isHeaderHovered, setIsHeaderHovered] = useState(false); + + const { columnDefinition } = useContext(RecordBoardColumnContext); + + const { + setHotkeyScopeAndMemorizePreviousScope, + goBackToPreviousHotkeyScope, + } = usePreviousHotkeyScope(); + + const handleBoardColumnMenuOpen = () => { + setIsBoardColumnMenuOpen(true); + setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, { + goto: false, + }); + }; + + const handleBoardColumnMenuClose = () => { + goBackToPreviousHotkeyScope(); + setIsBoardColumnMenuOpen(false); + }; + + const boardColumnTotal = 0; + const cardIds = []; + + return ( + <> + setIsHeaderHovered(true)} + onMouseLeave={() => setIsHeaderHovered(false)} + > + + {!!boardColumnTotal && ${boardColumnTotal}} + {!isHeaderHovered && ( + {cardIds.length} + )} + {isHeaderHovered && ( + + + + )} + + {isBoardColumnMenuOpen && ( + + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap.ts new file mode 100644 index 000000000000..67c3b6a38323 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap.ts @@ -0,0 +1,7 @@ +import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap'; + +export const isRecordBoardColumnFirstFamilyStateScopeMap = + createFamilyStateScopeMap({ + key: 'isRecordBoardColumnFirstFamilyStateScopeMap', + defaultValue: false, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts new file mode 100644 index 000000000000..f12094f9d25e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts @@ -0,0 +1,7 @@ +import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap'; + +export const isRecordBoardColumnLastFamilyStateScopeMap = + createFamilyStateScopeMap({ + key: 'isRecordBoardColumnLastFamilyStateScopeMap', + defaultValue: false, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnIdsStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnIdsStateScopeMap.ts new file mode 100644 index 000000000000..82118caf3485 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnIdsStateScopeMap.ts @@ -0,0 +1,6 @@ +import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap'; + +export const recordBoardColumnIdsStateScopeMap = createStateScopeMap({ + key: 'recordBoardColumnIdsStateScopeMap', + defaultValue: [], +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnsFamilyStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnsFamilyStateScopeMap.ts new file mode 100644 index 000000000000..aca09ce52f72 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardColumnsFamilyStateScopeMap.ts @@ -0,0 +1,10 @@ +import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; +import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap'; + +export const recordBoardColumnsFamilyStateScopeMap = createFamilyStateScopeMap< + RecordBoardColumnDefinition | undefined, + string +>({ + key: 'recordBoardColumnsFamilyStateScopeMap', + defaultValue: undefined, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts new file mode 100644 index 000000000000..aa3a83405ca2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts @@ -0,0 +1,115 @@ +import { isRecordBoardColumnFirstFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap'; +import { isRecordBoardColumnLastFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap'; +import { recordBoardColumnIdsStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnIdsStateScopeMap'; +import { recordBoardColumnsFamilyStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnsFamilyStateScopeMap'; +import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; +import { createFamilySelectorScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilySelectorScopeMap'; +import { guardRecoilDefaultValue } from '@/ui/utilities/recoil-scope/utils/guardRecoilDefaultValue'; +import { assertNotNull } from '~/utils/assert'; + +export const recordBoardColumnsFamilySelectorScopeMap = + createFamilySelectorScopeMap( + { + key: 'recordBoardColumnsFamilySelectorScopeMap', + get: + ({ + scopeId, + familyKey: columnId, + }: { + scopeId: string; + familyKey: string; + }) => + ({ get }) => { + return get( + recordBoardColumnsFamilyStateScopeMap({ + scopeId, + familyKey: columnId, + }), + ); + }, + set: + ({ + scopeId, + familyKey: columnId, + }: { + scopeId: string; + familyKey: string; + }) => + ({ set, get }, newColumn) => { + set( + recordBoardColumnsFamilyStateScopeMap({ + scopeId, + familyKey: columnId, + }), + newColumn, + ); + + if (guardRecoilDefaultValue(newColumn)) return; + + const columnIds = get(recordBoardColumnIdsStateScopeMap({ scopeId })); + + const columns = columnIds + .map((columnId) => { + return get( + recordBoardColumnsFamilyStateScopeMap({ + scopeId, + familyKey: columnId, + }), + ); + }) + .filter(assertNotNull); + + const lastColumn = [...columns].sort( + (a, b) => b.position - a.position, + )[0]; + + const firstColumn = [...columns].sort( + (a, b) => a.position - b.position, + )[0]; + + if (!newColumn) { + return; + } + + if (!lastColumn || newColumn.position > lastColumn.position) { + set( + isRecordBoardColumnLastFamilyStateScopeMap({ + scopeId, + familyKey: columnId, + }), + true, + ); + + if (lastColumn) { + set( + isRecordBoardColumnLastFamilyStateScopeMap({ + scopeId, + familyKey: lastColumn.id, + }), + false, + ); + } + } + + if (!firstColumn || newColumn.position < firstColumn.position) { + set( + isRecordBoardColumnFirstFamilyStateScopeMap({ + scopeId, + familyKey: columnId, + }), + true, + ); + + if (firstColumn) { + set( + isRecordBoardColumnFirstFamilyStateScopeMap({ + scopeId, + familyKey: firstColumn.id, + }), + false, + ); + } + } + }, + }, + ); diff --git a/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnAction.ts b/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnAction.ts new file mode 100644 index 000000000000..f2a5713cb9d4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnAction.ts @@ -0,0 +1,9 @@ +import { IconComponent } from '@/ui/display/icon/types/IconComponent'; + +export type RecordBoardColumnAction = { + id: string; + label: string; + icon: IconComponent; + position: number; + callback: () => void; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnDefinition.ts b/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnDefinition.ts index a2ce4a32fe44..cafdb56e7d73 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnDefinition.ts @@ -1,8 +1,10 @@ +import { RecordBoardColumnAction } from '@/object-record/record-board/types/RecordBoardColumnAction'; import { ThemeColor } from '@/ui/theme/constants/colors'; export type RecordBoardColumnDefinition = { id: string; title: string; position: number; - colorCode?: ThemeColor; + color: ThemeColor; + actions: RecordBoardColumnAction[]; }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx index 95dac6191c08..e6a18ce0994b 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx @@ -1,11 +1,50 @@ +import { useCallback, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; +import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; +import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata'; + type RecordIndexBoardContainerEffectProps = { objectNamePlural: string; recordBoardId: string; viewBarId: string; }; -export const RecordIndexBoardContainerEffect = ( - _props: RecordIndexBoardContainerEffectProps, -) => { +export const RecordIndexBoardContainerEffect = ({ + objectNamePlural, + recordBoardId, +}: RecordIndexBoardContainerEffectProps) => { + const { objectNameSingular } = useObjectNameSingularFromPlural({ + objectNamePlural, + }); + + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular, + }); + + const navigate = useNavigate(); + + const navigateToSelectSettings = useCallback(() => { + navigate(`/settings/objects/${objectNamePlural}`); + }, [navigate, objectNamePlural]); + + const { setRecordBoardColumns } = useRecordBoard(recordBoardId); + + useEffect(() => { + setRecordBoardColumns( + computeRecordBoardColumnDefinitionsFromObjectMetadata( + objectMetadataItem, + navigateToSelectSettings, + ), + ); + }, [ + navigateToSelectSettings, + objectMetadataItem, + objectNameSingular, + setRecordBoardColumns, + ]); + return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 7b4c99b92675..02a522033e9b 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -6,6 +6,7 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer'; +import { RecordIndexBoardContainerEffect } from '@/object-record/record-index/components/RecordIndexBoardContainerEffect'; import { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer'; import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect'; import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect'; @@ -129,9 +130,9 @@ export const RecordIndexContainer = ({ objectNamePlural={objectNamePlural} createRecord={createRecord} /> - diff --git a/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts b/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts new file mode 100644 index 000000000000..d06f01dc252b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts @@ -0,0 +1,39 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; +import { IconPencil } from '@/ui/display/icon'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const computeRecordBoardColumnDefinitionsFromObjectMetadata = ( + objectMetadataItem: ObjectMetadataItem, + navigateToSelectSettings: () => void, +): RecordBoardColumnDefinition[] => { + const selectFieldMetadataItem = objectMetadataItem.fields.find( + (field) => field.type === FieldMetadataType.Select, + ); + + if (!selectFieldMetadataItem) { + return []; + } + + if (!selectFieldMetadataItem.options) { + throw new Error( + `Select Field ${objectMetadataItem.nameSingular} has no options`, + ); + } + + return selectFieldMetadataItem.options.map((selectOption) => ({ + id: selectOption.id, + title: selectOption.label, + color: selectOption.color, + position: selectOption.position, + actions: [ + { + id: 'edit', + label: 'Edit from settings', + icon: IconPencil, + position: 0, + callback: navigateToSelectSettings, + }, + ], + })); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts index 44f75b8631ea..3d8a33e8f3a9 100644 --- a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts @@ -1,2 +1,4 @@ +import { getScopeIdFromComponentIdStrict } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict'; + export const getScopeIdFromComponentId = (componentId?: string) => - componentId ? `${componentId}-scope` : undefined; + componentId ? getScopeIdFromComponentIdStrict(componentId) : undefined; diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts new file mode 100644 index 000000000000..d33b4b310c71 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts @@ -0,0 +1,2 @@ +export const getScopeIdFromComponentIdStrict = (componentId: string) => + `${componentId}-scope`; diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/guardRecoilDefaultValue.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/guardRecoilDefaultValue.ts new file mode 100644 index 000000000000..843496011262 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/guardRecoilDefaultValue.ts @@ -0,0 +1,8 @@ +import { DefaultValue } from 'recoil'; + +export const guardRecoilDefaultValue = ( + candidate: any, +): candidate is DefaultValue => { + if (candidate instanceof DefaultValue) return true; + return false; +}; diff --git a/packages/twenty-server/src/database/typeorm-seeds/workspace/opportunity.ts b/packages/twenty-server/src/database/typeorm-seeds/workspace/opportunity.ts index c6ec0511799c..6a3626e011d1 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/workspace/opportunity.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/workspace/opportunity.ts @@ -16,6 +16,7 @@ export const seedOpportunity = async ( 'amountCurrencyCode', 'closeDate', 'probability', + 'stage', 'pipelineStepId', 'pointOfContactId', 'companyId', @@ -29,6 +30,7 @@ export const seedOpportunity = async ( amountCurrencyCode: 'USD', closeDate: new Date(), probability: 0.5, + stage: 'new', pipelineStepId: '6edf4ead-006a-46e1-9c6d-228f1d0143c9', pointOfContactId: '86083141-1c0e-494c-a1b6-85b1c6fefaa5', companyId: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408', @@ -40,6 +42,7 @@ export const seedOpportunity = async ( amountCurrencyCode: 'USD', closeDate: new Date(), probability: 0.5, + stage: 'meeting', pipelineStepId: 'd8361722-03fb-4e65-bd4f-ec9e52e5ec0a', pointOfContactId: '93c72d2e-f517-42fd-80ae-14173b3b70ae', companyId: '118995f3-5d81-46d6-bf83-f7fd33ea6102', @@ -51,6 +54,7 @@ export const seedOpportunity = async ( amountCurrencyCode: 'USD', closeDate: new Date(), probability: 0.5, + stage: 'proposal', pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02', pointOfContactId: '9b324a88-6784-4449-afdf-dc62cb8702f2', companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4', @@ -62,6 +66,7 @@ export const seedOpportunity = async ( amountCurrencyCode: 'USD', closeDate: new Date(), probability: 0.5, + stage: 'proposal', pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02', pointOfContactId: '98406e26-80f1-4dff-b570-a74942528de3', companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4', diff --git a/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts b/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts index 43167dfde4c5..64bd8bb7b5a0 100644 --- a/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts +++ b/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts @@ -3,10 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { render } from '@react-email/render'; import { In, Repository } from 'typeorm'; -import { - CleanInactiveWorkspaceEmail, - DeleteInactiveWorkspaceEmail, -} from 'twenty-emails'; +import { CleanInactiveWorkspaceEmail } from 'twenty-emails'; import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface'; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts index b2ea3bbc9162..db36e98e664c 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts @@ -55,6 +55,27 @@ export class OpportunityObjectMetadata extends BaseObjectMetadata { }) probability: string; + @FieldMetadata({ + type: FieldMetadataType.SELECT, + label: 'Stage', + description: 'Opportunity stage', + icon: 'IconProgressCheck', + options: [ + { value: 'new', label: 'New', position: 0, color: 'red' }, + { value: 'screening', label: 'Screening', position: 1, color: 'purple' }, + { value: 'meeting', label: 'Meeting', position: 2, color: 'sky' }, + { + value: 'proposal', + label: 'Proposal', + position: 3, + color: 'turquoise', + }, + { value: 'customer', label: 'Customer', position: 4, color: 'yellow' }, + ], + defaultValue: { value: 'new' }, + }) + stage: string; + // Relations @FieldMetadata({ type: FieldMetadataType.RELATION, From 739c760910e79e77b13359ca1e95e844aa949341 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 25 Jan 2024 16:51:51 +0100 Subject: [PATCH 2/4] Fix --- .../clean-inactive-workspace.job.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts b/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts index 64bd8bb7b5a0..43167dfde4c5 100644 --- a/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts +++ b/packages/twenty-server/src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job.ts @@ -3,7 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm'; import { render } from '@react-email/render'; import { In, Repository } from 'typeorm'; -import { CleanInactiveWorkspaceEmail } from 'twenty-emails'; +import { + CleanInactiveWorkspaceEmail, + DeleteInactiveWorkspaceEmail, +} from 'twenty-emails'; import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface'; From aedff245d350a5c31e0675d8db0131e8df62353b Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 25 Jan 2024 18:01:53 +0100 Subject: [PATCH 3/4] Fix according to review --- .../__tests__/useObjectRecordTable.test.tsx | 2 +- .../record-board/components/RecordBoard.tsx | 4 ++-- .../contexts/RecordBoardColumnContext.ts | 4 ++-- .../hooks/internal/useRecordBoardStates.ts | 15 ++++++++------- .../hooks/internal/useSetRecordBoardColumns.ts | 6 +++--- .../components/RecordBoardColumn.tsx | 18 +++++++++--------- ...rstRecordBoardColumnFamilyStateScopeMap.ts} | 4 ++-- ...astRecordBoardColumnFamilyStateScopeMap.ts} | 4 ++-- ...recordBoardColumnsFamilySelectorScopeMap.ts | 12 ++++++------ .../hooks/internal/useRecordTableStates.ts | 4 ++-- .../ui/layout/dropdown/hooks/useDropdown.ts | 4 ++-- .../hooks/useClickOustideListenerStates.ts | 3 +-- .../utils/getScopeIdFromComponentId.ts | 6 ++---- .../utils/getScopeIdFromComponentIdStrict.ts | 2 -- .../getScopeIdOrUndefinedFromComponentId.ts | 4 ++++ 15 files changed, 46 insertions(+), 46 deletions(-) rename packages/twenty-front/src/modules/object-record/record-board/states/{isRecordBoardColumnFirstFamilyStateScopeMap.ts => isFirstRecordBoardColumnFamilyStateScopeMap.ts} (61%) rename packages/twenty-front/src/modules/object-record/record-board/states/{isRecordBoardColumnLastFamilyStateScopeMap.ts => isLastRecordBoardColumnFamilyStateScopeMap.ts} (61%) delete mode 100644 packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts create mode 100644 packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId.ts diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx index 0824b33ee59b..e50f36d50055 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx @@ -22,7 +22,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => { diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx index 196d09593060..27e78a85cfa8 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx @@ -7,7 +7,7 @@ import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoar import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn'; import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; -import { getScopeIdFromComponentIdStrict } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict'; +import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; export type RecordBoardProps = { @@ -46,7 +46,7 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => { return ( {}} onFieldsChange={() => {}} > diff --git a/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts b/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts index 3a0f167ae3c3..7016dcce81d8 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/contexts/RecordBoardColumnContext.ts @@ -4,8 +4,8 @@ import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/ type RecordBoardColumnContextProps = { columnDefinition: RecordBoardColumnDefinition; - isColumnFirst: boolean; - isColumnLast: boolean; + isFirstColumn: boolean; + isLastColumn: boolean; }; export const RecordBoardColumnContext = diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts index d0f6358088c7..090f8f0eda74 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts @@ -1,27 +1,28 @@ import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext'; -import { isRecordBoardColumnFirstFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap'; +import { isFirstRecordBoardColumnFamilyStateScopeMap } from '@/object-record/record-board/states/isFirstRecordBoardColumnFamilyStateScopeMap'; +import { isLastRecordBoardColumnFamilyStateScopeMap } from '@/object-record/record-board/states/isLastRecordBoardColumnFamilyStateScopeMap'; import { recordBoardColumnIdsStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnIdsStateScopeMap'; import { recordBoardColumnsFamilySelectorScopeMap } from '@/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { getFamilyState } from '@/ui/utilities/recoil-scope/utils/getFamilyState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId'; import { getState } from '@/ui/utilities/recoil-scope/utils/getState'; export const useRecordBoardStates = (recordBoardId?: string) => { const scopeId = useAvailableScopeIdOrThrow( RecordBoardScopeInternalContext, - getScopeIdFromComponentId(recordBoardId), + getScopeIdOrUndefinedFromComponentId(recordBoardId), ); return { scopeId, getColumnIdsState: getState(recordBoardColumnIdsStateScopeMap, scopeId), - isColumnLastFamilyState: getFamilyState( - isRecordBoardColumnFirstFamilyStateScopeMap, + isLastColumnFamilyState: getFamilyState( + isFirstRecordBoardColumnFamilyStateScopeMap, scopeId, ), - isColumnFirstFamilyState: getFamilyState( - isRecordBoardColumnFirstFamilyStateScopeMap, + isFirstColumnFamilyState: getFamilyState( + isLastRecordBoardColumnFamilyStateScopeMap, scopeId, ), columnsFamilySelector: getFamilyState( diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts index 14086fa7c147..393f0250bd94 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardColumns.ts @@ -11,19 +11,19 @@ export const useSetRecordBoardColumns = (recordBoardId?: string) => { const setRecordBoardColumns = useRecoilCallback( ({ set, snapshot }) => (columns: RecordBoardColumnDefinition[]) => { - const currentColumns = snapshot + const currentColumnsIds = snapshot .getLoadable(getColumnIdsState()) .getValue(); const columnIds = columns.map(({ id }) => id); - if (isDeeplyEqual(currentColumns, columnIds)) { + if (isDeeplyEqual(currentColumnsIds, columnIds)) { return; } set( getColumnIdsState(), - columns.map(({ id }) => id), + columns.map((column) => column.id), ); columns.forEach((column) => { diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx index 225d833b71f1..97b14a715b9c 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx @@ -30,20 +30,20 @@ export const RecordBoardColumn = ({ recordBoardColumnId, }: RecordBoardColumnProps) => { const { - isColumnFirstFamilyState, - isColumnLastFamilyState, + isFirstColumnFamilyState, + isLastColumnFamilyState, columnsFamilySelector, } = useRecordBoardStates(); const columnDefinition = useRecoilValue( columnsFamilySelector(recordBoardColumnId), ); - const isColumnFirst = useRecoilValue( - isColumnFirstFamilyState(recordBoardColumnId), + const isFirstColumn = useRecoilValue( + isFirstColumnFamilyState(recordBoardColumnId), ); - const isColumnLast = useRecoilValue( - isColumnLastFamilyState(recordBoardColumnId), + const isLastColumn = useRecoilValue( + isLastColumnFamilyState(recordBoardColumnId), ); if (!columnDefinition) { @@ -54,13 +54,13 @@ export const RecordBoardColumn = ({ {(droppableProvided) => ( - + ({ - key: 'isRecordBoardColumnFirstFamilyStateScopeMap', + key: 'isFirstRecordBoardColumnFamilyStateScopeMap', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isLastRecordBoardColumnFamilyStateScopeMap.ts similarity index 61% rename from packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts rename to packages/twenty-front/src/modules/object-record/record-board/states/isLastRecordBoardColumnFamilyStateScopeMap.ts index f12094f9d25e..b29e9ecaeb4d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isLastRecordBoardColumnFamilyStateScopeMap.ts @@ -1,7 +1,7 @@ import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap'; -export const isRecordBoardColumnLastFamilyStateScopeMap = +export const isLastRecordBoardColumnFamilyStateScopeMap = createFamilyStateScopeMap({ - key: 'isRecordBoardColumnLastFamilyStateScopeMap', + key: 'isLastRecordBoardColumnFamilyStateScopeMap', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts index aa3a83405ca2..7f9d28f644e3 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts @@ -1,5 +1,5 @@ -import { isRecordBoardColumnFirstFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardColumnFirstFamilyStateScopeMap'; -import { isRecordBoardColumnLastFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardColumnLastFamilyStateScopeMap'; +import { isFirstRecordBoardColumnFamilyStateScopeMap } from '@/object-record/record-board/states/isFirstRecordBoardColumnFamilyStateScopeMap'; +import { isLastRecordBoardColumnFamilyStateScopeMap } from '@/object-record/record-board/states/isLastRecordBoardColumnFamilyStateScopeMap'; import { recordBoardColumnIdsStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnIdsStateScopeMap'; import { recordBoardColumnsFamilyStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnsFamilyStateScopeMap'; import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; @@ -73,7 +73,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = if (!lastColumn || newColumn.position > lastColumn.position) { set( - isRecordBoardColumnLastFamilyStateScopeMap({ + isLastRecordBoardColumnFamilyStateScopeMap({ scopeId, familyKey: columnId, }), @@ -82,7 +82,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = if (lastColumn) { set( - isRecordBoardColumnLastFamilyStateScopeMap({ + isFirstRecordBoardColumnFamilyStateScopeMap({ scopeId, familyKey: lastColumn.id, }), @@ -93,7 +93,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = if (!firstColumn || newColumn.position < firstColumn.position) { set( - isRecordBoardColumnFirstFamilyStateScopeMap({ + isLastRecordBoardColumnFamilyStateScopeMap({ scopeId, familyKey: columnId, }), @@ -102,7 +102,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = if (firstColumn) { set( - isRecordBoardColumnFirstFamilyStateScopeMap({ + isFirstRecordBoardColumnFamilyStateScopeMap({ scopeId, familyKey: firstColumn.id, }), diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useRecordTableStates.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useRecordTableStates.ts index 717a66b51194..5067ab540cc8 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useRecordTableStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useRecordTableStates.ts @@ -25,14 +25,14 @@ import { tableRowIdsStateScopeMap } from '@/object-record/record-table/states/ta import { tableSortsStateScopeMap } from '@/object-record/record-table/states/tableSortsStateScopeMap'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { getFamilyState } from '@/ui/utilities/recoil-scope/utils/getFamilyState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId'; import { getSelector } from '@/ui/utilities/recoil-scope/utils/getSelector'; import { getState } from '@/ui/utilities/recoil-scope/utils/getState'; export const useRecordTableStates = (recordTableId?: string) => { const scopeId = useAvailableScopeIdOrThrow( RecordTableScopeInternalContext, - getScopeIdFromComponentId(recordTableId), + getScopeIdOrUndefinedFromComponentId(recordTableId), ); return { diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts index a8f2f463013a..00f16c174f85 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts @@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil'; import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId'; export const useDropdown = (dropdownId?: string) => { const { @@ -11,7 +11,7 @@ export const useDropdown = (dropdownId?: string) => { dropdownWidthState, isDropdownOpenState, } = useDropdownStates({ - dropdownScopeId: getScopeIdFromComponentId(dropdownId), + dropdownScopeId: getScopeIdOrUndefinedFromComponentId(dropdownId), }); const { diff --git a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useClickOustideListenerStates.ts b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useClickOustideListenerStates.ts index 238df6465928..50c598ea0bdd 100644 --- a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useClickOustideListenerStates.ts +++ b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useClickOustideListenerStates.ts @@ -5,8 +5,7 @@ import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/get import { getState } from '@/ui/utilities/recoil-scope/utils/getState'; export const useClickOustideListenerStates = (componentId: string) => { - // TODO: improve typing - const scopeId = getScopeIdFromComponentId(componentId) ?? ''; + const scopeId = getScopeIdFromComponentId(componentId); return { scopeId, diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts index 3d8a33e8f3a9..feb67c86dba3 100644 --- a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId.ts @@ -1,4 +1,2 @@ -import { getScopeIdFromComponentIdStrict } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict'; - -export const getScopeIdFromComponentId = (componentId?: string) => - componentId ? getScopeIdFromComponentIdStrict(componentId) : undefined; +export const getScopeIdFromComponentId = (componentId: string) => + `${componentId}-scope`; diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts deleted file mode 100644 index d33b4b310c71..000000000000 --- a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdFromComponentIdStrict.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const getScopeIdFromComponentIdStrict = (componentId: string) => - `${componentId}-scope`; diff --git a/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId.ts b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId.ts new file mode 100644 index 000000000000..3892e6afd38e --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId.ts @@ -0,0 +1,4 @@ +import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; + +export const getScopeIdOrUndefinedFromComponentId = (componentId?: string) => + componentId ? getScopeIdFromComponentId(componentId) : undefined; From 5f3d97a4a9cf9d29cb7b15f8c4a254002dade959 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 25 Jan 2024 18:04:53 +0100 Subject: [PATCH 4/4] Fix --- .../record-board/hooks/internal/useRecordBoardStates.ts | 4 ++-- .../selectors/recordBoardColumnsFamilySelectorScopeMap.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts index 090f8f0eda74..fe2f8ed61f36 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts @@ -17,11 +17,11 @@ export const useRecordBoardStates = (recordBoardId?: string) => { return { scopeId, getColumnIdsState: getState(recordBoardColumnIdsStateScopeMap, scopeId), - isLastColumnFamilyState: getFamilyState( + isFirstColumnFamilyState: getFamilyState( isFirstRecordBoardColumnFamilyStateScopeMap, scopeId, ), - isFirstColumnFamilyState: getFamilyState( + isLastColumnFamilyState: getFamilyState( isLastRecordBoardColumnFamilyStateScopeMap, scopeId, ), diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts index 7f9d28f644e3..46d5c6123452 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap.ts @@ -82,7 +82,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = if (lastColumn) { set( - isFirstRecordBoardColumnFamilyStateScopeMap({ + isLastRecordBoardColumnFamilyStateScopeMap({ scopeId, familyKey: lastColumn.id, }), @@ -93,7 +93,7 @@ export const recordBoardColumnsFamilySelectorScopeMap = if (!firstColumn || newColumn.position < firstColumn.position) { set( - isLastRecordBoardColumnFamilyStateScopeMap({ + isFirstRecordBoardColumnFamilyStateScopeMap({ scopeId, familyKey: columnId, }),