Refactor/inplace input (#541)

* wip

* Changed all other components

* Removed console log

* Console.log

* lint

* Removed internal state

* Fix

* Lint
This commit is contained in:
Lucas Bordeau
2023-07-09 01:45:52 +02:00
committed by GitHub
parent b3d0061e0d
commit e03d5ed8a7
47 changed files with 680 additions and 326 deletions

View File

@ -1,6 +1,6 @@
import { CellCommentChip } from '@/comments/components/CellCommentChip'; import { CellCommentChip } from '@/comments/components/CellCommentChip';
import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer'; import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer';
import EditableChip from '@/ui/components/editable-cell/types/EditableChip'; import { EditableCellChip } from '@/ui/components/editable-cell/types/EditableChip';
import { getLogoUrlFromDomainName } from '@/utils/utils'; import { getLogoUrlFromDomainName } from '@/utils/utils';
import { import {
CommentableType, CommentableType,
@ -34,7 +34,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
} }
return ( return (
<EditableChip <EditableCellChip
value={company.name || ''} value={company.name || ''}
placeholder="Name" placeholder="Name"
picture={getLogoUrlFromDomainName(company.domainName)} picture={getLogoUrlFromDomainName(company.domainName)}

View File

@ -5,6 +5,8 @@ export enum InternalHotkeysScope {
Table = 'table', Table = 'table',
TableSoftFocus = 'table-soft-focus', TableSoftFocus = 'table-soft-focus',
CellEditMode = 'cell-edit-mode', CellEditMode = 'cell-edit-mode',
CellDateEditMode = 'cell-date-edit-mode',
BoardCardFieldEditMode = 'board-card-field-edit-mode',
RightDrawer = 'right-drawer', RightDrawer = 'right-drawer',
TableHeaderDropdownButton = 'table-header-dropdown-button', TableHeaderDropdownButton = 'table-header-dropdown-button',
RelationPicker = 'relation-picker', RelationPicker = 'relation-picker',

View File

@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { CellCommentChip } from '@/comments/components/CellCommentChip'; import { CellCommentChip } from '@/comments/components/CellCommentChip';
import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer'; import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer';
import { EditableDoubleText } from '@/ui/components/editable-cell/types/EditableDoubleText'; import { EditableCellDoubleText } from '@/ui/components/editable-cell/types/EditableCellDoubleText';
import { CommentableType, Person } from '~/generated/graphql'; import { CommentableType, Person } from '~/generated/graphql';
import { PersonChip } from './PersonChip'; import { PersonChip } from './PersonChip';
@ -52,7 +52,7 @@ export function EditablePeopleFullName({ person, onChange }: OwnProps) {
} }
return ( return (
<EditableDoubleText <EditableCellDoubleText
firstValue={firstNameValue} firstValue={firstNameValue}
secondValue={lastNameValue} secondValue={lastNameValue}
firstValuePlaceholder="First name" firstValuePlaceholder="First name"

View File

@ -9,14 +9,14 @@ import {
} 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 } 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 { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { BoardColumn } from '@/ui/components/board/BoardColumn'; import { BoardColumn } from '@/ui/board/components/BoardColumn';
import { Company, PipelineProgress } from '~/generated/graphql'; import { Company, PipelineProgress } from '~/generated/graphql';
import { import {
Column, Column,
getOptimisticlyUpdatedBoard, getOptimisticlyUpdatedBoard,
StyledBoard, StyledBoard,
} from '../../ui/components/board/Board'; } from '../../ui/board/components/Board';
import { boardColumnsState } from '../states/boardColumnsState'; import { boardColumnsState } from '../states/boardColumnsState';
import { boardItemsState } from '../states/boardItemsState'; import { boardItemsState } from '../states/boardItemsState';
import { selectedBoardItemsState } from '../states/selectedBoardItemsState'; import { selectedBoardItemsState } from '../states/selectedBoardItemsState';
@ -81,6 +81,10 @@ export function Board({
); );
const [isInitialBoardLoaded, setIsInitialBoardLoaded] = useState(false); const [isInitialBoardLoaded, setIsInitialBoardLoaded] = useState(false);
useEffect(() => {
setBoardItems(initialItems);
}, [initialItems, setBoardItems]);
useEffect(() => { useEffect(() => {
if (isInitialBoardLoaded) return; if (isInitialBoardLoaded) return;
setBoard(initialBoard); setBoard(initialBoard);

View File

@ -2,11 +2,8 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconCurrencyDollar } from '@tabler/icons-react'; import { IconCurrencyDollar } from '@tabler/icons-react';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope'; import { BoardCardEditableFieldDate } from '@/ui/board-card-field-inputs/components/BoardCardEditableFieldDate';
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate'; import { BoardCardEditableFieldText } from '@/ui/board-card-field-inputs/components/BoardCardEditableFieldText';
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
import { CellContext } from '@/ui/tables/states/CellContext';
import { RowContext } from '@/ui/tables/states/RowContext';
import { Company, PipelineProgress } from '../../../generated/graphql'; import { Company, PipelineProgress } from '../../../generated/graphql';
import { Checkbox } from '../../ui/components/form/Checkbox'; import { Checkbox } from '../../ui/components/form/Checkbox';
@ -72,17 +69,6 @@ type PipelineProgressProp = Pick<
'id' | 'amount' | 'closeDate' 'id' | 'amount' | 'closeDate'
>; >;
// TODO: Remove when refactoring EditableCell into EditableField
function HackScope({ children }: { children: React.ReactNode }) {
return (
<RecoilScope>
<RecoilScope SpecificContext={RowContext}>
<RecoilScope SpecificContext={CellContext}>{children}</RecoilScope>
</RecoilScope>
</RecoilScope>
);
}
export function CompanyBoardCard({ export function CompanyBoardCard({
company, company,
pipelineProgress, pipelineProgress,
@ -112,32 +98,28 @@ export function CompanyBoardCard({
<StyledBoardCardBody> <StyledBoardCardBody>
<span> <span>
<IconCurrencyDollar size={theme.icon.size.md} /> <IconCurrencyDollar size={theme.icon.size.md} />
<HackScope> <BoardCardEditableFieldText
<EditableText value={pipelineProgress.amount?.toString() || ''}
content={pipelineProgress.amount?.toString() || ''} placeholder="Opportunity amount"
placeholder="Opportunity amount" onChange={(value) =>
changeHandler={(value) => onCardUpdate({
onCardUpdate({ ...pipelineProgress,
...pipelineProgress, amount: parseInt(value),
amount: parseInt(value), })
}) }
} />
/>
</HackScope>
</span> </span>
<span> <span>
<IconCalendarEvent size={theme.icon.size.md} /> <IconCalendarEvent size={theme.icon.size.md} />
<HackScope> <BoardCardEditableFieldDate
<EditableDate value={new Date(pipelineProgress.closeDate || Date.now())}
value={new Date(pipelineProgress.closeDate || Date.now())} onChange={(value) => {
changeHandler={(value) => { onCardUpdate({
onCardUpdate({ ...pipelineProgress,
...pipelineProgress, closeDate: value.toISOString(),
closeDate: value.toISOString(), });
}); }}
}} />
/>
</HackScope>
</span> </span>
</StyledBoardCardBody> </StyledBoardCardBody>
</StyledBoardCard> </StyledBoardCard>

View File

@ -3,8 +3,8 @@ import { useRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { Column } from '@/ui/components/board/Board'; import { Column } from '@/ui/board/components/Board';
import { NewButton as UINewButton } from '@/ui/components/board/NewButton'; import { NewButton as UINewButton } from '@/ui/board/components/NewButton';
import { import {
Company, Company,
PipelineProgressableType, PipelineProgressableType,

View File

@ -1,4 +1,4 @@
import { Column } from '@/ui/components/board/Board'; import { Column } from '@/ui/board/components/Board';
import { mockedCompaniesData } from '~/testing/mock-data/companies'; import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { CompanyProgressDict } from '../Board'; import { CompanyProgressDict } from '../Board';

View File

@ -4,7 +4,7 @@ import {
useGetCompaniesQuery, useGetCompaniesQuery,
useGetPipelinesQuery, useGetPipelinesQuery,
} from '../../../generated/graphql'; } from '../../../generated/graphql';
import { Column } from '../../ui/components/board/Board'; import { Column } from '../../ui/board/components/Board';
type ItemCompany = Pick<Company, 'id' | 'name' | 'domainName'>; type ItemCompany = Pick<Company, 'id' | 'name' | 'domainName'>;
type ItemPipelineProgress = Pick< type ItemPipelineProgress = Pick<

View File

@ -1,6 +1,6 @@
import { atom } from 'recoil'; import { atom } from 'recoil';
import { Column } from '@/ui/components/board/Board'; import { Column } from '@/ui/board/components/Board';
export const boardColumnsState = atom<Column[]>({ export const boardColumnsState = atom<Column[]>({
key: 'boardColumnsState', key: 'boardColumnsState',

View File

@ -0,0 +1,26 @@
import { BoardCardEditableField } from '@/ui/board-card-field/components/BoardCardEditableField';
import { InplaceInputDateDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputDateDisplayMode';
import { BoardCardEditableFieldDateEditMode } from './BoardCardEditableFieldDateEditMode';
type OwnProps = {
value: Date;
onChange: (newValue: Date) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
export function BoardCardEditableFieldDate({
value,
onChange,
editModeHorizontalAlign,
}: OwnProps) {
return (
<BoardCardEditableField
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<BoardCardEditableFieldDateEditMode value={value} onChange={onChange} />
}
nonEditModeContent={<InplaceInputDateDisplayMode value={value} />}
></BoardCardEditableField>
);
}

View File

@ -0,0 +1,21 @@
import { useBoardCardField } from '@/ui/board-card-field/hooks/useBoardCardField';
import { InplaceInputDateEditMode } from '@/ui/inplace-inputs/components/InplaceInputDateEditMode';
type OwnProps = {
value: Date;
onChange: (newValue: Date) => void;
};
export function BoardCardEditableFieldDateEditMode({
value,
onChange,
}: OwnProps) {
const { closeBoardCardField } = useBoardCardField();
function handleDateChange(newDate: Date) {
onChange(newDate);
closeBoardCardField();
}
return <InplaceInputDateEditMode value={value} onChange={handleDateChange} />;
}

View File

@ -0,0 +1,38 @@
import { ChangeEvent } from 'react';
import { BoardCardEditableField } from '@/ui/board-card-field/components/BoardCardEditableField';
import { InplaceInputTextDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputTextDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
type OwnProps = {
placeholder?: string;
value: string;
onChange: (newValue: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
export function BoardCardEditableFieldText({
value,
placeholder,
onChange,
editModeHorizontalAlign,
}: OwnProps) {
return (
<BoardCardEditableField
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<InplaceInputTextEditMode
placeholder={placeholder || ''}
autoFocus
value={value}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value);
}}
/>
}
nonEditModeContent={
<InplaceInputTextDisplayMode>{value}</InplaceInputTextDisplayMode>
}
></BoardCardEditableField>
);
}

View File

@ -0,0 +1,24 @@
import { ReactElement } from 'react';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
import { BoardCardEditableFieldInternal } from './BoardCardEditableFieldInternal';
type OwnProps = {
editModeContent: ReactElement;
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeysScope?: HotkeysScopeStackItem;
};
export function BoardCardEditableField(props: OwnProps) {
return (
<RecoilScope SpecificContext={BoardCardFieldContext}>
<BoardCardEditableFieldInternal {...props} />
</RecoilScope>
);
}

View File

@ -0,0 +1,32 @@
import styled from '@emotion/styled';
export const BoardCardFieldDisplayModeOuterContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
export const BoardCardFieldDisplayModeInnerContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
width: 100%;
`;
export function BoardCardEditableFieldDisplayMode({
children,
}: React.PropsWithChildren<unknown>) {
return (
<BoardCardFieldDisplayModeOuterContainer>
<BoardCardFieldDisplayModeInnerContainer>
{children}
</BoardCardFieldDisplayModeInnerContainer>
</BoardCardFieldDisplayModeOuterContainer>
);
}

View File

@ -0,0 +1,78 @@
import { ReactElement, useRef } from 'react';
import styled from '@emotion/styled';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { overlayBackground } from '@/ui/themes/effects';
import { useBoardCardField } from '../hooks/useBoardCardField';
export const BoardCardFieldEditModeContainer = styled.div<OwnProps>`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
left: ${(props) =>
props.editModeHorizontalAlign === 'right' ? 'auto' : '0'};
margin-left: -2px;
min-height: 100%;
min-width: calc(100% + 20px);
position: absolute;
right: ${(props) =>
props.editModeHorizontalAlign === 'right' ? '0' : 'auto'};
top: ${(props) => (props.editModeVerticalPosition === 'over' ? '0' : '100%')};
z-index: 1;
${overlayBackground}
`;
type OwnProps = {
children: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
onOutsideClick?: () => void;
};
export function BoardCardEditableFieldEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
}: OwnProps) {
const wrapperRef = useRef(null);
const { closeBoardCardField } = useBoardCardField();
useListenClickOutsideArrayOfRef([wrapperRef], () => {
closeBoardCardField();
});
useScopedHotkeys(
'enter',
() => {
closeBoardCardField();
},
InternalHotkeysScope.BoardCardFieldEditMode,
[closeBoardCardField],
);
useScopedHotkeys(
'esc',
() => {
closeBoardCardField();
},
InternalHotkeysScope.BoardCardFieldEditMode,
[closeBoardCardField],
);
return (
<BoardCardFieldEditModeContainer
data-testid="editable-cell-edit-mode-container"
ref={wrapperRef}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{children}
</BoardCardFieldEditModeContainer>
);
}

View File

@ -0,0 +1,71 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useBoardCardField } from '../hooks/useBoardCardField';
import { BoardCardEditableFieldDisplayMode } from './BoardCardEditableFieldDisplayMode';
import { BoardCardEditableFieldEditMode } from './BoardCardEditableFieldEditMode';
export const BoardCardFieldContainer = styled.div`
align-items: center;
box-sizing: border-box;
cursor: pointer;
display: flex;
height: 32px;
position: relative;
user-select: none;
width: 100%;
`;
type OwnProps = {
editModeContent: ReactElement;
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeysScope?: HotkeysScopeStackItem;
};
export function BoardCardEditableFieldInternal({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
editModeContent,
nonEditModeContent,
editHotkeysScope,
}: OwnProps) {
const { openBoardCardField, isBoardCardFieldInEditMode } =
useBoardCardField();
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
function handleOnClick() {
if (!isBoardCardFieldInEditMode) {
openBoardCardField();
addToHotkeysScopeStack(
editHotkeysScope ?? {
scope: InternalHotkeysScope.BoardCardFieldEditMode,
},
);
}
}
return (
<BoardCardFieldContainer onClick={handleOnClick}>
{isBoardCardFieldInEditMode ? (
<BoardCardEditableFieldEditMode
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{editModeContent}
</BoardCardEditableFieldEditMode>
) : (
<BoardCardEditableFieldDisplayMode>
{nonEditModeContent}
</BoardCardEditableFieldDisplayMode>
)}
</BoardCardFieldContainer>
);
}

View File

@ -0,0 +1,26 @@
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
import { isBoardCardFieldInEditModeScopedState } from '../states/isBoardCardFieldInEditModeScopedState';
export function useBoardCardField() {
const [isBoardCardFieldInEditMode, setIsBoardCardFieldInEditMode] =
useRecoilScopedState(
isBoardCardFieldInEditModeScopedState,
BoardCardFieldContext,
);
function openBoardCardField() {
setIsBoardCardFieldInEditMode(true);
}
function closeBoardCardField() {
setIsBoardCardFieldInEditMode(false);
}
return {
isBoardCardFieldInEditMode,
openBoardCardField,
closeBoardCardField,
};
}

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const BoardCardFieldContext = createContext<string | null>(null);

View File

@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
export const isBoardCardFieldInEditModeScopedState = atomFamily<
boolean,
string
>({
key: 'isBoardCardFieldInEditModeScopedState',
default: false,
});

View File

@ -1,14 +1,14 @@
import { DropResult } from '@hello-pangea/dnd'; import { DropResult } from '@hello-pangea/dnd';
import { BoardItemKey, getOptimisticlyUpdatedBoard } from '../Board'; import { getOptimisticlyUpdatedBoard } from '../Board';
describe('getOptimisticlyUpdatedBoard', () => { describe('getOptimisticlyUpdatedBoard', () => {
it('should return a new board with the updated cell', () => { it('should return a new board with the updated cell', () => {
const initialColumn1: BoardItemKey[] = ['item-1', 'item-2', 'item-3']; const initialColumn1: string[] = ['item-1', 'item-2', 'item-3'];
const initialColumn2: BoardItemKey[] = ['item-4', 'item-5']; const initialColumn2: string[] = ['item-4', 'item-5'];
const finalColumn1: BoardItemKey[] = ['item-2', 'item-3']; const finalColumn1: string[] = ['item-2', 'item-3'];
const finalColumn2: BoardItemKey[] = ['item-4', 'item-1', 'item-5']; const finalColumn2: string[] = ['item-4', 'item-1', 'item-5'];
const dropResult = { const dropResult = {
source: { source: {

View File

@ -1,14 +1,13 @@
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems'; import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useEditableCell } from './hooks/useCloseEditableCell'; import { useEditableCell } from './hooks/useCloseEditableCell';
import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode'; import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode';
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell'; import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
import { useSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell'; import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode'; import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { EditableCellEditMode } from './EditableCellEditMode'; import { EditableCellEditMode } from './EditableCellEditMode';
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode'; import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
@ -41,14 +40,12 @@ export function EditableCell({
}: OwnProps) { }: OwnProps) {
const { isCurrentCellInEditMode } = useCurrentCellEditMode(); const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const setSoftFocusOnCurrentCell = useSoftFocusOnCurrentCell(); const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const { openEditableCell } = useEditableCell(); const { openEditableCell } = useEditableCell();
const hasSoftFocus = useIsSoftFocusOnCurrentCell(); const hasSoftFocus = useIsSoftFocusOnCurrentCell();
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
// TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate // TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate
// Maybe we could build a switchEditableCell to handle the case where we go from one cell to another. // Maybe we could build a switchEditableCell to handle the case where we go from one cell to another.
// See https://github.com/twentyhq/twenty/issues/446 // See https://github.com/twentyhq/twenty/issues/446
@ -58,8 +55,7 @@ export function EditableCell({
} }
if (hasSoftFocus) { if (hasSoftFocus) {
openEditableCell(); openEditableCell(
addToHotkeysScopeStack(
editHotkeysScope ?? { editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode, scope: InternalHotkeysScope.CellEditMode,
}, },

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems'; import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
@ -14,20 +13,18 @@ export function EditableCellSoftFocusMode({
editHotkeysScope, editHotkeysScope,
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScopeStackItem }>) { }: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScopeStackItem }>) {
const { closeEditableCell, openEditableCell } = useEditableCell(); const { closeEditableCell, openEditableCell } = useEditableCell();
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
useScopedHotkeys( useScopedHotkeys(
'enter', 'enter',
() => { () => {
openEditableCell(); openEditableCell(
addToHotkeysScopeStack(
editHotkeysScope ?? { editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode, scope: InternalHotkeysScope.CellEditMode,
}, },
); );
}, },
InternalHotkeysScope.TableSoftFocus, InternalHotkeysScope.TableSoftFocus,
[closeEditableCell], [closeEditableCell, editHotkeysScope],
); );
useScopedHotkeys( useScopedHotkeys(
@ -42,15 +39,14 @@ export function EditableCellSoftFocusMode({
return; return;
} }
openEditableCell(); openEditableCell(
addToHotkeysScopeStack(
editHotkeysScope ?? { editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode, scope: InternalHotkeysScope.CellEditMode,
}, },
); );
}, },
InternalHotkeysScope.TableSoftFocus, InternalHotkeysScope.TableSoftFocus,
[openEditableCell, addToHotkeysScopeStack, editHotkeysScope], [openEditableCell, editHotkeysScope],
{ {
preventDefault: false, preventDefault: false,
}, },

View File

@ -1,6 +1,8 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { useRemoveHighestHotkeysScopeStackItem } from '@/hotkeys/hooks/useRemoveHighestHotkeysScopeStackItem'; import { useRemoveHighestHotkeysScopeStackItem } from '@/hotkeys/hooks/useRemoveHighestHotkeysScopeStackItem';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { useCloseCurrentCellInEditMode } from '@/ui/tables/hooks/useClearCellInEditMode'; import { useCloseCurrentCellInEditMode } from '@/ui/tables/hooks/useClearCellInEditMode';
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState'; import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState'; import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
@ -10,6 +12,8 @@ import { useCurrentCellEditMode } from './useCurrentCellEditMode';
export function useEditableCell() { export function useEditableCell() {
const { setCurrentCellInEditMode } = useCurrentCellEditMode(); const { setCurrentCellInEditMode } = useCurrentCellEditMode();
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode(); const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const removeHighestHotkeysScopedStackItem = const removeHighestHotkeysScopedStackItem =
@ -22,7 +26,7 @@ export function useEditableCell() {
const openEditableCell = useRecoilCallback( const openEditableCell = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
() => { (hotkeysScopeStackItem: HotkeysScopeStackItem) => {
const isSomeInputInEditMode = snapshot const isSomeInputInEditMode = snapshot
.getLoadable(isSomeInputInEditModeState) .getLoadable(isSomeInputInEditModeState)
.valueOrThrow(); .valueOrThrow();
@ -32,9 +36,11 @@ export function useEditableCell() {
set(isSoftFocusActiveState, false); set(isSoftFocusActiveState, false);
setCurrentCellInEditMode(); setCurrentCellInEditMode();
addToHotkeysScopeStack(hotkeysScopeStackItem);
} }
}, },
[setCurrentCellInEditMode], [setCurrentCellInEditMode, addToHotkeysScopeStack],
); );
return { return {

View File

@ -12,7 +12,7 @@ import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveStat
import { RowContext } from '@/ui/tables/states/RowContext'; import { RowContext } from '@/ui/tables/states/RowContext';
import { CellPosition } from '@/ui/tables/types/CellPosition'; import { CellPosition } from '@/ui/tables/types/CellPosition';
export function useSoftFocusOnCurrentCell() { export function useSetSoftFocusOnCurrentCell() {
const setSoftFocusPosition = useSetSoftFocusPosition(); const setSoftFocusPosition = useSetSoftFocusPosition();
const [currentRowNumber] = useRecoilScopedState( const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState, currentRowNumberScopedState,

View File

@ -0,0 +1,29 @@
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { InplaceInputDateDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputDateDisplayMode';
import { EditableCell } from '../EditableCell';
import { EditableCellDateEditMode } from './EditableCellDateEditMode';
export type EditableDateProps = {
value: Date;
onChange: (date: Date) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
export function EditableCellDate({
value,
onChange,
editModeHorizontalAlign,
}: EditableDateProps) {
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<EditableCellDateEditMode onChange={onChange} value={value} />
}
nonEditModeContent={<InplaceInputDateDisplayMode value={value} />}
editHotkeysScope={{ scope: InternalHotkeysScope.CellDateEditMode }}
></EditableCell>
);
}

View File

@ -0,0 +1,22 @@
import { InplaceInputDateEditMode } from '@/ui/inplace-inputs/components/InplaceInputDateEditMode';
import { useEditableCell } from '../hooks/useCloseEditableCell';
export type EditableDateProps = {
value: Date;
onChange: (date: Date) => void;
};
export function EditableCellDateEditMode({
value,
onChange,
}: EditableDateProps) {
const { closeEditableCell } = useEditableCell();
function handleDateChange(newDate: Date) {
onChange(newDate);
closeEditableCell();
}
return <InplaceInputDateEditMode onChange={handleDateChange} value={value} />;
}

View File

@ -4,7 +4,7 @@ import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysSc
import { EditableCell } from '../EditableCell'; import { EditableCell } from '../EditableCell';
import { EditableDoubleTextEditMode } from './EditableDoubleTextEditMode'; import { EditableCellDoubleTextEditMode } from './EditableCellDoubleTextEditMode';
type OwnProps = { type OwnProps = {
firstValue: string; firstValue: string;
@ -15,7 +15,7 @@ type OwnProps = {
onChange: (firstValue: string, secondValue: string) => void; onChange: (firstValue: string, secondValue: string) => void;
}; };
export function EditableDoubleText({ export function EditableCellDoubleText({
firstValue, firstValue,
secondValue, secondValue,
firstValuePlaceholder, firstValuePlaceholder,
@ -27,7 +27,7 @@ export function EditableDoubleText({
<EditableCell <EditableCell
editHotkeysScope={{ scope: InternalHotkeysScope.CellDoubleTextInput }} editHotkeysScope={{ scope: InternalHotkeysScope.CellDoubleTextInput }}
editModeContent={ editModeContent={
<EditableDoubleTextEditMode <EditableCellDoubleTextEditMode
firstValue={firstValue} firstValue={firstValue}
secondValue={secondValue} secondValue={secondValue}
firstValuePlaceholder={firstValuePlaceholder} firstValuePlaceholder={firstValuePlaceholder}

View File

@ -4,8 +4,8 @@ import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus'; import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
import { textInputStyle } from '@/ui/themes/effects';
import { useEditableCell } from '../hooks/useCloseEditableCell'; import { useEditableCell } from '../hooks/useCloseEditableCell';
@ -28,15 +28,7 @@ const StyledContainer = styled.div`
} }
`; `;
const StyledEditInplaceInput = styled.input` export function EditableCellDoubleTextEditMode({
height: 18px;
margin: 0;
width: 45%;
${textInputStyle}
`;
export function EditableDoubleTextEditMode({
firstValue, firstValue,
secondValue, secondValue,
firstValuePlaceholder, firstValuePlaceholder,
@ -77,7 +69,7 @@ export function EditableDoubleTextEditMode({
useScopedHotkeys( useScopedHotkeys(
'tab', 'tab',
async (keyboardEvent, hotkeyEvent) => { () => {
if (focusPosition === 'left') { if (focusPosition === 'left') {
setFocusPosition('right'); setFocusPosition('right');
secondValueInputRef.current?.focus(); secondValueInputRef.current?.focus();
@ -107,7 +99,7 @@ export function EditableDoubleTextEditMode({
return ( return (
<StyledContainer> <StyledContainer>
<StyledEditInplaceInput <InplaceInputTextEditMode
autoFocus autoFocus
placeholder={firstValuePlaceholder} placeholder={firstValuePlaceholder}
ref={firstValueInputRef} ref={firstValueInputRef}
@ -116,7 +108,7 @@ export function EditableDoubleTextEditMode({
onChange(event.target.value, secondValue); onChange(event.target.value, secondValue);
}} }}
/> />
<StyledEditInplaceInput <InplaceInputTextEditMode
placeholder={secondValuePlaceholder} placeholder={secondValuePlaceholder}
ref={secondValueInputRef} ref={secondValueInputRef}
value={secondValue} value={secondValue}

View File

@ -0,0 +1,39 @@
import { ChangeEvent, useRef, useState } from 'react';
import { InplaceInputPhoneDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputPhoneDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
import { EditableCell } from '../EditableCell';
type OwnProps = {
placeholder?: string;
value: string;
changeHandler: (updated: string) => void;
};
export function EditableCellPhone({
value,
placeholder,
changeHandler,
}: OwnProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(value);
return (
<EditableCell
editModeContent={
<InplaceInputTextEditMode
autoFocus
placeholder={placeholder || ''}
ref={inputRef}
value={inputValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
changeHandler(event.target.value);
}}
/>
}
nonEditModeContent={<InplaceInputPhoneDisplayMode value={inputValue} />}
/>
);
}

View File

@ -1,6 +1,6 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
export const EditableRelationCreateButton = styled.button` export const EditableCellRelationCreateButton = styled.button`
align-items: center; align-items: center;
background: none; background: none;
border: none; border: none;

View File

@ -0,0 +1,39 @@
import { ChangeEvent } from 'react';
import { InplaceInputTextDisplayMode } from '@/ui/inplace-inputs/components/InplaceInputTextDisplayMode';
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
import { EditableCell } from '../EditableCell';
type OwnProps = {
placeholder?: string;
value: string;
onChange: (newValue: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
export function EditableCellText({
value,
placeholder,
onChange,
editModeHorizontalAlign,
}: OwnProps) {
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<InplaceInputTextEditMode
placeholder={placeholder || ''}
autoFocus
value={value}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value);
}}
/>
}
nonEditModeContent={
<InplaceInputTextDisplayMode>{value}</InplaceInputTextDisplayMode>
}
></EditableCell>
);
}

View File

@ -39,7 +39,8 @@ const RightContainer = styled.div`
margin-left: ${(props) => props.theme.spacing(1)}; margin-left: ${(props) => props.theme.spacing(1)};
`; `;
function EditableChip({ // TODO: move right end content in EditableCell
export function EditableCellChip({
value, value,
placeholder, placeholder,
changeHandler, changeHandler,
@ -89,5 +90,3 @@ function EditableChip({
/> />
); );
} }
export default EditableChip;

View File

@ -1,81 +0,0 @@
import { forwardRef, useState } from 'react';
import styled from '@emotion/styled';
import { humanReadableDate } from '@/utils/utils';
import DatePicker from '../../form/DatePicker';
import { EditableCell } from '../EditableCell';
export type EditableDateProps = {
value: Date;
changeHandler: (date: Date) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
margin: 0px ${({ theme }) => theme.spacing(2)};
`;
export type StyledCalendarContainerProps = {
editModeHorizontalAlign?: 'left' | 'right';
};
const StyledCalendarContainer = styled.div<StyledCalendarContainerProps>`
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: 8px;
box-shadow: ${({ theme }) => theme.boxShadow.strong};
left: -10px;
position: absolute;
top: 10px;
z-index: 1;
`;
export function EditableDate({
value,
changeHandler,
editModeHorizontalAlign,
}: EditableDateProps) {
const [inputValue, setInputValue] = useState(value);
type DivProps = React.HTMLProps<HTMLDivElement>;
const DateDisplay = forwardRef<HTMLDivElement, DivProps>(
({ value, onClick }, ref) => (
<div onClick={onClick} ref={ref}>
{value && humanReadableDate(new Date(value as string))}
</div>
),
);
type DatePickerContainerProps = {
children: React.ReactNode;
};
const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
return <StyledCalendarContainer>{children}</StyledCalendarContainer>;
};
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<StyledContainer>
<DatePicker
date={inputValue}
onChangeHandler={(date: Date) => {
changeHandler(date);
setInputValue(date);
}}
customInput={<DateDisplay />}
customCalendarContainer={DatePickerContainer}
/>
</StyledContainer>
}
nonEditModeContent={
<div>{inputValue && humanReadableDate(inputValue)}</div>
}
></EditableCell>
);
}

View File

@ -1,70 +0,0 @@
import { ChangeEvent, MouseEvent, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js';
import { textInputStyle } from '@/ui/themes/effects';
import { RawLink } from '../../links/RawLink';
import { EditableCell } from '../EditableCell';
type OwnProps = {
placeholder?: string;
value: string;
changeHandler: (updated: string) => void;
};
const StyledRawLink = styled(RawLink)`
overflow: hidden;
a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`;
// TODO: refactor
const StyledEditInplaceInput = styled.input`
margin: 0;
width: 100%;
${textInputStyle}
`;
export function EditablePhone({ value, placeholder, changeHandler }: OwnProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(value);
return (
<EditableCell
editModeContent={
<StyledEditInplaceInput
autoFocus
placeholder={placeholder || ''}
ref={inputRef}
value={inputValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
changeHandler(event.target.value);
}}
/>
}
nonEditModeContent={
<>
{isValidPhoneNumber(inputValue) ? (
<StyledRawLink
href={parsePhoneNumber(inputValue, 'FR')?.getURI()}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{parsePhoneNumber(inputValue, 'FR')?.formatInternational() ||
inputValue}
</StyledRawLink>
) : (
<StyledRawLink href="#">{inputValue}</StyledRawLink>
)}
</>
}
/>
);
}

View File

@ -1,56 +0,0 @@
import { ChangeEvent, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { textInputStyle } from '@/ui/themes/effects';
import { EditableCell } from '../EditableCell';
type OwnProps = {
placeholder?: string;
content: string;
changeHandler: (updated: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
// TODO: refactor
const StyledInplaceInput = styled.input`
margin: 0;
width: 100%;
${textInputStyle}
`;
const StyledNoEditText = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
export function EditableText({
content,
placeholder,
changeHandler,
editModeHorizontalAlign,
}: OwnProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState(content);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<StyledInplaceInput
placeholder={placeholder || ''}
autoFocus
ref={inputRef}
value={inputValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
changeHandler(event.target.value);
}}
/>
}
nonEditModeContent={<StyledNoEditText>{inputValue}</StyledNoEditText>}
></EditableCell>
);
}

View File

@ -0,0 +1,9 @@
import { humanReadableDate } from '@/utils/utils';
type OwnProps = {
value: Date;
};
export function InplaceInputDateDisplayMode({ value }: OwnProps) {
return <div>{value && humanReadableDate(value)}</div>;
}

View File

@ -0,0 +1,62 @@
import { forwardRef } from 'react';
import styled from '@emotion/styled';
import DatePicker from '@/ui/components/form/DatePicker';
import { humanReadableDate } from '@/utils/utils';
const StyledContainer = styled.div`
align-items: center;
display: flex;
margin: 0px ${({ theme }) => theme.spacing(2)};
`;
export type StyledCalendarContainerProps = {
editModeHorizontalAlign?: 'left' | 'right';
};
const StyledCalendarContainer = styled.div<StyledCalendarContainerProps>`
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: 8px;
box-shadow: ${({ theme }) => theme.boxShadow.strong};
left: -10px;
position: absolute;
top: 10px;
z-index: 1;
`;
type DivProps = React.HTMLProps<HTMLDivElement>;
const DateDisplay = forwardRef<HTMLDivElement, DivProps>(
({ value, onClick }, ref) => (
<div onClick={onClick} ref={ref}>
{value && humanReadableDate(new Date(value as string))}
</div>
),
);
type DatePickerContainerProps = {
children: React.ReactNode;
};
const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
return <StyledCalendarContainer>{children}</StyledCalendarContainer>;
};
type OwnProps = {
value: Date;
onChange: (newDate: Date) => void;
};
export function InplaceInputDateEditMode({ onChange, value }: OwnProps) {
return (
<StyledContainer>
<DatePicker
date={value}
onChangeHandler={onChange}
customInput={<DateDisplay />}
customCalendarContainer={DatePickerContainer}
/>
</StyledContainer>
);
}

View File

@ -0,0 +1,34 @@
import { MouseEvent } from 'react';
import styled from '@emotion/styled';
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js';
import { RawLink } from '@/ui/components/links/RawLink';
const StyledRawLink = styled(RawLink)`
overflow: hidden;
a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`;
type OwnProps = {
value: string;
};
export function InplaceInputPhoneDisplayMode({ value }: OwnProps) {
return isValidPhoneNumber(value) ? (
<StyledRawLink
href={parsePhoneNumber(value, 'FR')?.getURI()}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{parsePhoneNumber(value, 'FR')?.formatInternational() || value}
</StyledRawLink>
) : (
<StyledRawLink href="#">{value}</StyledRawLink>
);
}

View File

@ -0,0 +1,8 @@
import styled from '@emotion/styled';
export const InplaceInputTextDisplayMode = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;

View File

@ -0,0 +1,9 @@
import styled from '@emotion/styled';
import { textInputStyle } from '@/ui/themes/effects';
export const InplaceInputTextEditMode = styled.input`
margin: 0;
width: 100%;
${textInputStyle}
`;

View File

@ -13,6 +13,7 @@ export function useCloseCurrentCellInEditMode() {
set(isCellInEditModeFamilyState(currentCellInEditModePosition), false); set(isCellInEditModeFamilyState(currentCellInEditModePosition), false);
// TODO: find a way to remove this
await new Promise((resolve) => setTimeout(resolve, 20)); await new Promise((resolve) => setTimeout(resolve, 20));
set(isSomeInputInEditModeState, false); set(isSomeInputInEditModeState, false);

View File

@ -3,8 +3,8 @@ import { createColumnHelper } from '@tanstack/react-table';
import { CompanyAccountOwnerCell } from '@/companies/components/CompanyAccountOwnerCell'; import { CompanyAccountOwnerCell } from '@/companies/components/CompanyAccountOwnerCell';
import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell'; import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell';
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate'; import { EditableCellDate } from '@/ui/components/editable-cell/types/EditableCellDate';
import { EditableText } from '@/ui/components/editable-cell/types/EditableText'; import { EditableCellText } from '@/ui/components/editable-cell/types/EditableCellText';
import { ColumnHead } from '@/ui/components/table/ColumnHead'; import { ColumnHead } from '@/ui/components/table/ColumnHead';
import { import {
IconBuildingSkyscraper, IconBuildingSkyscraper,
@ -44,10 +44,10 @@ export const useCompaniesColumns = () => {
<ColumnHead viewName="URL" viewIcon={<IconLink size={16} />} /> <ColumnHead viewName="URL" viewIcon={<IconLink size={16} />} />
), ),
cell: (props) => ( cell: (props) => (
<EditableText <EditableCellText
content={props.row.original.domainName || ''} value={props.row.original.domainName || ''}
placeholder="Domain name" placeholder="Domain name"
changeHandler={(value) => { onChange={(value) => {
const company = { ...props.row.original }; const company = { ...props.row.original };
company.domainName = value; company.domainName = value;
updateCompany({ updateCompany({
@ -66,10 +66,10 @@ export const useCompaniesColumns = () => {
<ColumnHead viewName="Employees" viewIcon={<IconUsers size={16} />} /> <ColumnHead viewName="Employees" viewIcon={<IconUsers size={16} />} />
), ),
cell: (props) => ( cell: (props) => (
<EditableText <EditableCellText
content={props.row.original.employees?.toString() || ''} value={props.row.original.employees?.toString() || ''}
placeholder="Employees" placeholder="Employees"
changeHandler={(value) => { onChange={(value) => {
const company = { ...props.row.original }; const company = { ...props.row.original };
updateCompany({ updateCompany({
@ -89,10 +89,10 @@ export const useCompaniesColumns = () => {
<ColumnHead viewName="Address" viewIcon={<IconMap size={16} />} /> <ColumnHead viewName="Address" viewIcon={<IconMap size={16} />} />
), ),
cell: (props) => ( cell: (props) => (
<EditableText <EditableCellText
content={props.row.original.address || ''} value={props.row.original.address || ''}
placeholder="Address" placeholder="Address"
changeHandler={(value) => { onChange={(value) => {
const company = { ...props.row.original }; const company = { ...props.row.original };
company.address = value; company.address = value;
updateCompany({ updateCompany({
@ -114,13 +114,13 @@ export const useCompaniesColumns = () => {
/> />
), ),
cell: (props) => ( cell: (props) => (
<EditableDate <EditableCellDate
value={ value={
props.row.original.createdAt props.row.original.createdAt
? new Date(props.row.original.createdAt) ? new Date(props.row.original.createdAt)
: new Date() : new Date()
} }
changeHandler={(value: Date) => { onChange={(value: Date) => {
const company = { ...props.row.original }; const company = { ...props.row.original };
company.createdAt = value.toISOString(); company.createdAt = value.toISOString();
updateCompany({ updateCompany({

View File

@ -1,8 +1,10 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { BoardActionBarButtonDeletePipelineProgress } from '@/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress'; import { BoardActionBarButtonDeletePipelineProgress } from '@/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress';
import { EntityBoardActionBar } from '@/pipeline-progress/components/EntityBoardActionBar'; import { EntityBoardActionBar } from '@/pipeline-progress/components/EntityBoardActionBar';
import { GET_PIPELINES } from '@/pipeline-progress/queries';
import { IconTargetArrow } from '@/ui/icons/index'; import { IconTargetArrow } from '@/ui/icons/index';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'; import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
@ -23,6 +25,7 @@ export function Opportunities() {
const pipelineId = pipelines.data?.findManyPipeline[0].id; const pipelineId = pipelines.data?.findManyPipeline[0].id;
const { initialBoard, items } = useBoard(pipelineId || ''); const { initialBoard, items } = useBoard(pipelineId || '');
const columns = useMemo( const columns = useMemo(
() => () =>
initialBoard?.map(({ id, colorCode, title }) => ({ initialBoard?.map(({ id, colorCode, title }) => ({
@ -40,12 +43,13 @@ export function Opportunities() {
async ( async (
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>, pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>,
) => { ) => {
updatePipelineProgress({ await updatePipelineProgress({
variables: { variables: {
id: pipelineProgress.id, id: pipelineProgress.id,
amount: pipelineProgress.amount, amount: pipelineProgress.amount,
closeDate: pipelineProgress.closeDate || null, closeDate: pipelineProgress.closeDate || null,
}, },
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
}); });
}, },
[updatePipelineProgress], [updatePipelineProgress],

View File

@ -3,9 +3,9 @@ import { createColumnHelper } from '@tanstack/react-table';
import { EditablePeopleFullName } from '@/people/components/EditablePeopleFullName'; import { EditablePeopleFullName } from '@/people/components/EditablePeopleFullName';
import { PeopleCompanyCell } from '@/people/components/PeopleCompanyCell'; import { PeopleCompanyCell } from '@/people/components/PeopleCompanyCell';
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate'; import { EditableCellDate } from '@/ui/components/editable-cell/types/EditableCellDate';
import { EditablePhone } from '@/ui/components/editable-cell/types/EditablePhone'; import { EditableCellPhone } from '@/ui/components/editable-cell/types/EditableCellPhone';
import { EditableText } from '@/ui/components/editable-cell/types/EditableText'; import { EditableCellText } from '@/ui/components/editable-cell/types/EditableCellText';
import { ColumnHead } from '@/ui/components/table/ColumnHead'; import { ColumnHead } from '@/ui/components/table/ColumnHead';
import { import {
IconBuildingSkyscraper, IconBuildingSkyscraper,
@ -55,10 +55,10 @@ export const usePeopleColumns = () => {
<ColumnHead viewName="Email" viewIcon={<IconMail size={16} />} /> <ColumnHead viewName="Email" viewIcon={<IconMail size={16} />} />
), ),
cell: (props) => ( cell: (props) => (
<EditableText <EditableCellText
placeholder="Email" placeholder="Email"
content={props.row.original.email || ''} value={props.row.original.email || ''}
changeHandler={async (value: string) => { onChange={async (value: string) => {
const person = props.row.original; const person = props.row.original;
await updatePerson({ await updatePerson({
variables: { variables: {
@ -87,7 +87,7 @@ export const usePeopleColumns = () => {
<ColumnHead viewName="Phone" viewIcon={<IconPhone size={16} />} /> <ColumnHead viewName="Phone" viewIcon={<IconPhone size={16} />} />
), ),
cell: (props) => ( cell: (props) => (
<EditablePhone <EditableCellPhone
placeholder="Phone" placeholder="Phone"
value={props.row.original.phone || ''} value={props.row.original.phone || ''}
changeHandler={async (value: string) => { changeHandler={async (value: string) => {
@ -112,13 +112,13 @@ export const usePeopleColumns = () => {
/> />
), ),
cell: (props) => ( cell: (props) => (
<EditableDate <EditableCellDate
value={ value={
props.row.original.createdAt props.row.original.createdAt
? new Date(props.row.original.createdAt) ? new Date(props.row.original.createdAt)
: new Date() : new Date()
} }
changeHandler={async (value: Date) => { onChange={async (value: Date) => {
const person = { ...props.row.original }; const person = { ...props.row.original };
await updatePerson({ await updatePerson({
variables: { variables: {
@ -137,11 +137,11 @@ export const usePeopleColumns = () => {
<ColumnHead viewName="City" viewIcon={<IconMap size={16} />} /> <ColumnHead viewName="City" viewIcon={<IconMap size={16} />} />
), ),
cell: (props) => ( cell: (props) => (
<EditableText <EditableCellText
editModeHorizontalAlign="right" editModeHorizontalAlign="right"
placeholder="City" placeholder="City"
content={props.row.original.city || ''} value={props.row.original.city || ''}
changeHandler={async (value: string) => { onChange={async (value: string) => {
const person = { ...props.row.original }; const person = { ...props.row.original };
await updatePerson({ await updatePerson({
variables: { variables: {