Display columns on Record Board (#3626)
* Display columns on Record board * Fix * Fix according to review * Fix
This commit is contained in:
@ -22,7 +22,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => {
|
|||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<ObjectNamePluralSetter>
|
<ObjectNamePluralSetter>
|
||||||
<RecordTableScope
|
<RecordTableScope
|
||||||
recordTableScopeId={getScopeIdFromComponentId(recordTableId) ?? ''}
|
recordTableScopeId={getScopeIdFromComponentId(recordTableId)}
|
||||||
onColumnsChange={onColumnsChange}
|
onColumnsChange={onColumnsChange}
|
||||||
>
|
>
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
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 { 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 { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn';
|
||||||
import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope';
|
import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
|
||||||
export type RecordBoardProps = {
|
export type RecordBoardProps = {
|
||||||
@ -37,9 +40,13 @@ const StyledBoardHeader = styled.div`
|
|||||||
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||||
const boardRef = useRef<HTMLDivElement>(null);
|
const boardRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { getColumnIdsState } = useRecordBoard(recordBoardId);
|
||||||
|
|
||||||
|
const columnIds = useRecoilValue(getColumnIdsState());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordBoardScope
|
<RecordBoardScope
|
||||||
recordBoardScopeId={recordBoardId}
|
recordBoardScopeId={getScopeIdFromComponentId(recordBoardId)}
|
||||||
onColumnsChange={() => {}}
|
onColumnsChange={() => {}}
|
||||||
onFieldsChange={() => {}}
|
onFieldsChange={() => {}}
|
||||||
>
|
>
|
||||||
@ -48,11 +55,10 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
|||||||
<ScrollWrapper>
|
<ScrollWrapper>
|
||||||
<StyledContainer ref={boardRef}>
|
<StyledContainer ref={boardRef}>
|
||||||
<DragDropContext onDragEnd={() => {}}>
|
<DragDropContext onDragEnd={() => {}}>
|
||||||
{[].map((column) => (
|
{columnIds.map((columnId) => (
|
||||||
<RecordBoardColumn
|
<RecordBoardColumn
|
||||||
key={'a'}
|
key={columnId}
|
||||||
recordBoardColumnId={'a'}
|
recordBoardColumnId={columnId}
|
||||||
columnDefinition={column}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { createContext } from 'react';
|
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 = {
|
type RecordBoardColumnContextProps = {
|
||||||
id: string;
|
columnDefinition: RecordBoardColumnDefinition;
|
||||||
columnDefinition: BoardColumnDefinition;
|
isFirstColumn: boolean;
|
||||||
|
isLastColumn: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordBoardColumnContext =
|
export const RecordBoardColumnContext =
|
||||||
createContext<RecordBoardColumnContextProps | null>(null);
|
createContext<RecordBoardColumnContextProps>(
|
||||||
|
{} as RecordBoardColumnContextProps,
|
||||||
|
);
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
|
||||||
|
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 { 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,
|
||||||
|
getScopeIdOrUndefinedFromComponentId(recordBoardId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
scopeId,
|
||||||
|
getColumnIdsState: getState(recordBoardColumnIdsStateScopeMap, scopeId),
|
||||||
|
isFirstColumnFamilyState: getFamilyState(
|
||||||
|
isFirstRecordBoardColumnFamilyStateScopeMap,
|
||||||
|
scopeId,
|
||||||
|
),
|
||||||
|
isLastColumnFamilyState: getFamilyState(
|
||||||
|
isLastRecordBoardColumnFamilyStateScopeMap,
|
||||||
|
scopeId,
|
||||||
|
),
|
||||||
|
columnsFamilySelector: getFamilyState(
|
||||||
|
recordBoardColumnsFamilySelectorScopeMap,
|
||||||
|
scopeId,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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 currentColumnsIds = snapshot
|
||||||
|
.getLoadable(getColumnIdsState())
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const columnIds = columns.map(({ id }) => id);
|
||||||
|
|
||||||
|
if (isDeeplyEqual(currentColumnsIds, columnIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(
|
||||||
|
getColumnIdsState(),
|
||||||
|
columns.map((column) => column.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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,10 +1,12 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Droppable } from '@hello-pangea/dnd';
|
import { Droppable } from '@hello-pangea/dnd';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/contexts/RecordBoardColumnContext';
|
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 { 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 { 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 }>`
|
const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
@ -22,25 +24,44 @@ const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
|||||||
|
|
||||||
type RecordBoardColumnProps = {
|
type RecordBoardColumnProps = {
|
||||||
recordBoardColumnId: string;
|
recordBoardColumnId: string;
|
||||||
columnDefinition: BoardColumnDefinition;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordBoardColumn = ({
|
export const RecordBoardColumn = ({
|
||||||
recordBoardColumnId,
|
recordBoardColumnId,
|
||||||
columnDefinition,
|
|
||||||
}: RecordBoardColumnProps) => {
|
}: RecordBoardColumnProps) => {
|
||||||
const isFirstColumn = columnDefinition.position === 0;
|
const {
|
||||||
|
isFirstColumnFamilyState,
|
||||||
|
isLastColumnFamilyState,
|
||||||
|
columnsFamilySelector,
|
||||||
|
} = useRecordBoardStates();
|
||||||
|
const columnDefinition = useRecoilValue(
|
||||||
|
columnsFamilySelector(recordBoardColumnId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstColumn = useRecoilValue(
|
||||||
|
isFirstColumnFamilyState(recordBoardColumnId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isLastColumn = useRecoilValue(
|
||||||
|
isLastColumnFamilyState(recordBoardColumnId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!columnDefinition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordBoardColumnContext.Provider
|
<RecordBoardColumnContext.Provider
|
||||||
value={{
|
value={{
|
||||||
id: recordBoardColumnId,
|
|
||||||
columnDefinition: columnDefinition,
|
columnDefinition: columnDefinition,
|
||||||
|
isFirstColumn: isFirstColumn,
|
||||||
|
isLastColumn: isLastColumn,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Droppable droppableId={recordBoardColumnId}>
|
<Droppable droppableId={recordBoardColumnId}>
|
||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||||
|
<RecordBoardColumnHeader />
|
||||||
<RecordBoardColumnCardsContainer
|
<RecordBoardColumnCardsContainer
|
||||||
droppableProvided={droppableProvided}
|
droppableProvided={droppableProvided}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const closeMenu = useCallback(() => {
|
||||||
|
onClose();
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
|
useListenClickOutside({
|
||||||
|
refs: [boardColumnMenuRef],
|
||||||
|
callback: closeMenu,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||||
|
<DropdownMenu data-select-disable>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{columnDefinition.actions.map((action) => (
|
||||||
|
<MenuItem
|
||||||
|
key={action.id}
|
||||||
|
onClick={() => {
|
||||||
|
action.callback();
|
||||||
|
closeMenu();
|
||||||
|
}}
|
||||||
|
LeftIcon={action.icon}
|
||||||
|
text={action.label}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
</StyledMenuContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 (
|
||||||
|
<>
|
||||||
|
<StyledHeader
|
||||||
|
onMouseEnter={() => setIsHeaderHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHeaderHovered(false)}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
onClick={handleBoardColumnMenuOpen}
|
||||||
|
color={columnDefinition.color}
|
||||||
|
text={columnDefinition.title}
|
||||||
|
/>
|
||||||
|
{!!boardColumnTotal && <StyledAmount>${boardColumnTotal}</StyledAmount>}
|
||||||
|
{!isHeaderHovered && (
|
||||||
|
<StyledNumChildren>{cardIds.length}</StyledNumChildren>
|
||||||
|
)}
|
||||||
|
{isHeaderHovered && (
|
||||||
|
<StyledHeaderActions>
|
||||||
|
<LightIconButton
|
||||||
|
accent="tertiary"
|
||||||
|
Icon={IconDotsVertical}
|
||||||
|
onClick={handleBoardColumnMenuOpen}
|
||||||
|
/>
|
||||||
|
</StyledHeaderActions>
|
||||||
|
)}
|
||||||
|
</StyledHeader>
|
||||||
|
{isBoardColumnMenuOpen && (
|
||||||
|
<RecordBoardColumnDropdownMenu
|
||||||
|
onClose={handleBoardColumnMenuClose}
|
||||||
|
stageId={columnDefinition.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap';
|
||||||
|
|
||||||
|
export const isFirstRecordBoardColumnFamilyStateScopeMap =
|
||||||
|
createFamilyStateScopeMap<boolean, string>({
|
||||||
|
key: 'isFirstRecordBoardColumnFamilyStateScopeMap',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { createFamilyStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createFamilyStateScopeMap';
|
||||||
|
|
||||||
|
export const isLastRecordBoardColumnFamilyStateScopeMap =
|
||||||
|
createFamilyStateScopeMap<boolean, string>({
|
||||||
|
key: 'isLastRecordBoardColumnFamilyStateScopeMap',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||||
|
|
||||||
|
export const recordBoardColumnIdsStateScopeMap = createStateScopeMap<string[]>({
|
||||||
|
key: 'recordBoardColumnIdsStateScopeMap',
|
||||||
|
defaultValue: [],
|
||||||
|
});
|
||||||
@ -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,
|
||||||
|
});
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
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';
|
||||||
|
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<RecordBoardColumnDefinition | undefined, string>(
|
||||||
|
{
|
||||||
|
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(
|
||||||
|
isLastRecordBoardColumnFamilyStateScopeMap({
|
||||||
|
scopeId,
|
||||||
|
familyKey: columnId,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastColumn) {
|
||||||
|
set(
|
||||||
|
isLastRecordBoardColumnFamilyStateScopeMap({
|
||||||
|
scopeId,
|
||||||
|
familyKey: lastColumn.id,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstColumn || newColumn.position < firstColumn.position) {
|
||||||
|
set(
|
||||||
|
isFirstRecordBoardColumnFamilyStateScopeMap({
|
||||||
|
scopeId,
|
||||||
|
familyKey: columnId,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (firstColumn) {
|
||||||
|
set(
|
||||||
|
isFirstRecordBoardColumnFamilyStateScopeMap({
|
||||||
|
scopeId,
|
||||||
|
familyKey: firstColumn.id,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -1,8 +1,10 @@
|
|||||||
|
import { RecordBoardColumnAction } from '@/object-record/record-board/types/RecordBoardColumnAction';
|
||||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
export type RecordBoardColumnDefinition = {
|
export type RecordBoardColumnDefinition = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
position: number;
|
position: number;
|
||||||
colorCode?: ThemeColor;
|
color: ThemeColor;
|
||||||
|
actions: RecordBoardColumnAction[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 = {
|
type RecordIndexBoardContainerEffectProps = {
|
||||||
objectNamePlural: string;
|
objectNamePlural: string;
|
||||||
recordBoardId: string;
|
recordBoardId: string;
|
||||||
viewBarId: string;
|
viewBarId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordIndexBoardContainerEffect = (
|
export const RecordIndexBoardContainerEffect = ({
|
||||||
_props: RecordIndexBoardContainerEffectProps,
|
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 <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer';
|
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 { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer';
|
||||||
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
|
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
|
||||||
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
|
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
|
||||||
@ -129,9 +130,9 @@ export const RecordIndexContainer = ({
|
|||||||
objectNamePlural={objectNamePlural}
|
objectNamePlural={objectNamePlural}
|
||||||
createRecord={createRecord}
|
createRecord={createRecord}
|
||||||
/>
|
/>
|
||||||
<RecordIndexTableContainerEffect
|
<RecordIndexBoardContainerEffect
|
||||||
objectNamePlural={objectNamePlural}
|
objectNamePlural={objectNamePlural}
|
||||||
recordTableId={recordIndexId}
|
recordBoardId={recordIndexId}
|
||||||
viewBarId={recordIndexId}
|
viewBarId={recordIndexId}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -25,14 +25,14 @@ import { tableRowIdsStateScopeMap } from '@/object-record/record-table/states/ta
|
|||||||
import { tableSortsStateScopeMap } from '@/object-record/record-table/states/tableSortsStateScopeMap';
|
import { tableSortsStateScopeMap } from '@/object-record/record-table/states/tableSortsStateScopeMap';
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { getFamilyState } from '@/ui/utilities/recoil-scope/utils/getFamilyState';
|
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 { getSelector } from '@/ui/utilities/recoil-scope/utils/getSelector';
|
||||||
import { getState } from '@/ui/utilities/recoil-scope/utils/getState';
|
import { getState } from '@/ui/utilities/recoil-scope/utils/getState';
|
||||||
|
|
||||||
export const useRecordTableStates = (recordTableId?: string) => {
|
export const useRecordTableStates = (recordTableId?: string) => {
|
||||||
const scopeId = useAvailableScopeIdOrThrow(
|
const scopeId = useAvailableScopeIdOrThrow(
|
||||||
RecordTableScopeInternalContext,
|
RecordTableScopeInternalContext,
|
||||||
getScopeIdFromComponentId(recordTableId),
|
getScopeIdOrUndefinedFromComponentId(recordTableId),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
};
|
||||||
@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates';
|
import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdownStates';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
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) => {
|
export const useDropdown = (dropdownId?: string) => {
|
||||||
const {
|
const {
|
||||||
@ -11,7 +11,7 @@ export const useDropdown = (dropdownId?: string) => {
|
|||||||
dropdownWidthState,
|
dropdownWidthState,
|
||||||
isDropdownOpenState,
|
isDropdownOpenState,
|
||||||
} = useDropdownStates({
|
} = useDropdownStates({
|
||||||
dropdownScopeId: getScopeIdFromComponentId(dropdownId),
|
dropdownScopeId: getScopeIdOrUndefinedFromComponentId(dropdownId),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/get
|
|||||||
import { getState } from '@/ui/utilities/recoil-scope/utils/getState';
|
import { getState } from '@/ui/utilities/recoil-scope/utils/getState';
|
||||||
|
|
||||||
export const useClickOustideListenerStates = (componentId: string) => {
|
export const useClickOustideListenerStates = (componentId: string) => {
|
||||||
// TODO: improve typing
|
const scopeId = getScopeIdFromComponentId(componentId);
|
||||||
const scopeId = getScopeIdFromComponentId(componentId) ?? '';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scopeId,
|
scopeId,
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export const getScopeIdFromComponentId = (componentId?: string) =>
|
export const getScopeIdFromComponentId = (componentId: string) =>
|
||||||
componentId ? `${componentId}-scope` : undefined;
|
`${componentId}-scope`;
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
|
|
||||||
|
export const getScopeIdOrUndefinedFromComponentId = (componentId?: string) =>
|
||||||
|
componentId ? getScopeIdFromComponentId(componentId) : undefined;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { DefaultValue } from 'recoil';
|
||||||
|
|
||||||
|
export const guardRecoilDefaultValue = (
|
||||||
|
candidate: any,
|
||||||
|
): candidate is DefaultValue => {
|
||||||
|
if (candidate instanceof DefaultValue) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
@ -16,6 +16,7 @@ export const seedOpportunity = async (
|
|||||||
'amountCurrencyCode',
|
'amountCurrencyCode',
|
||||||
'closeDate',
|
'closeDate',
|
||||||
'probability',
|
'probability',
|
||||||
|
'stage',
|
||||||
'pipelineStepId',
|
'pipelineStepId',
|
||||||
'pointOfContactId',
|
'pointOfContactId',
|
||||||
'companyId',
|
'companyId',
|
||||||
@ -29,6 +30,7 @@ export const seedOpportunity = async (
|
|||||||
amountCurrencyCode: 'USD',
|
amountCurrencyCode: 'USD',
|
||||||
closeDate: new Date(),
|
closeDate: new Date(),
|
||||||
probability: 0.5,
|
probability: 0.5,
|
||||||
|
stage: 'new',
|
||||||
pipelineStepId: '6edf4ead-006a-46e1-9c6d-228f1d0143c9',
|
pipelineStepId: '6edf4ead-006a-46e1-9c6d-228f1d0143c9',
|
||||||
pointOfContactId: '86083141-1c0e-494c-a1b6-85b1c6fefaa5',
|
pointOfContactId: '86083141-1c0e-494c-a1b6-85b1c6fefaa5',
|
||||||
companyId: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
companyId: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||||
@ -40,6 +42,7 @@ export const seedOpportunity = async (
|
|||||||
amountCurrencyCode: 'USD',
|
amountCurrencyCode: 'USD',
|
||||||
closeDate: new Date(),
|
closeDate: new Date(),
|
||||||
probability: 0.5,
|
probability: 0.5,
|
||||||
|
stage: 'meeting',
|
||||||
pipelineStepId: 'd8361722-03fb-4e65-bd4f-ec9e52e5ec0a',
|
pipelineStepId: 'd8361722-03fb-4e65-bd4f-ec9e52e5ec0a',
|
||||||
pointOfContactId: '93c72d2e-f517-42fd-80ae-14173b3b70ae',
|
pointOfContactId: '93c72d2e-f517-42fd-80ae-14173b3b70ae',
|
||||||
companyId: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
companyId: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
||||||
@ -51,6 +54,7 @@ export const seedOpportunity = async (
|
|||||||
amountCurrencyCode: 'USD',
|
amountCurrencyCode: 'USD',
|
||||||
closeDate: new Date(),
|
closeDate: new Date(),
|
||||||
probability: 0.5,
|
probability: 0.5,
|
||||||
|
stage: 'proposal',
|
||||||
pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02',
|
pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02',
|
||||||
pointOfContactId: '9b324a88-6784-4449-afdf-dc62cb8702f2',
|
pointOfContactId: '9b324a88-6784-4449-afdf-dc62cb8702f2',
|
||||||
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||||
@ -62,6 +66,7 @@ export const seedOpportunity = async (
|
|||||||
amountCurrencyCode: 'USD',
|
amountCurrencyCode: 'USD',
|
||||||
closeDate: new Date(),
|
closeDate: new Date(),
|
||||||
probability: 0.5,
|
probability: 0.5,
|
||||||
|
stage: 'proposal',
|
||||||
pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02',
|
pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02',
|
||||||
pointOfContactId: '98406e26-80f1-4dff-b570-a74942528de3',
|
pointOfContactId: '98406e26-80f1-4dff-b570-a74942528de3',
|
||||||
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||||
|
|||||||
@ -55,6 +55,27 @@ export class OpportunityObjectMetadata extends BaseObjectMetadata {
|
|||||||
})
|
})
|
||||||
probability: string;
|
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
|
// Relations
|
||||||
@FieldMetadata({
|
@FieldMetadata({
|
||||||
type: FieldMetadataType.RELATION,
|
type: FieldMetadataType.RELATION,
|
||||||
|
|||||||
Reference in New Issue
Block a user