FieldDisplay & FieldInput (#1708)

* Removed view field duplicate types

* wip

* wip 2

* wip 3

* Unified state for fields

* Renaming

* Wip

* Post merge

* Post post merge

* wip

* Delete unused file

* Boolean and Probability

* Finished InlineCell

* Renamed EditableCell to TableCell

* Finished double texts

* Finished MoneyField

* Fixed bug inline cell click outside

* Fixed hotkey scope

* Final fixes

* Phone

* Fix url and number input validation

* Fix

* Fix position

* wip refactor activity editor

* Fixed activity editor

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-09-27 18:18:02 +02:00
committed by GitHub
parent d9feabbc63
commit cbadcba188
290 changed files with 3152 additions and 4481 deletions

View File

@ -1,24 +0,0 @@
import { useEditableCell } from '../hooks/useEditableCell';
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
import { EditableCellDisplayContainer } from './EditableCellDisplayContainer';
export const EditableCellDisplayMode = ({
children,
isHovered,
}: React.PropsWithChildren<unknown> & { isHovered?: boolean }) => {
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const { openEditableCell } = useEditableCell();
const handleClick = () => {
setSoftFocusOnCurrentCell();
openEditableCell();
};
return (
<EditableCellDisplayContainer isHovered={isHovered} onClick={handleClick}>
{children}
</EditableCellDisplayContainer>
);
};

View File

@ -1,68 +0,0 @@
import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewFieldBoolean';
import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip';
import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate';
import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip';
import { isViewFieldEmail } from '@/ui/editable-field/types/guards/isViewFieldEmail';
import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney';
import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber';
import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone';
import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation';
import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldText';
import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL';
import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { ColumnDefinition } from '../../types/ColumnDefinition';
import { GenericEditableBooleanCell } from '../type/components/GenericEditableBooleanCell';
import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell';
import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell';
import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell';
import { GenericEditableDoubleTextChipCell } from '../type/components/GenericEditableDoubleTextChipCell';
import { GenericEditableEmailCell } from '../type/components/GenericEditableEmailCell';
import { GenericEditableMoneyCell } from '../type/components/GenericEditableMoneyCell';
import { GenericEditableNumberCell } from '../type/components/GenericEditableNumberCell';
import { GenericEditablePhoneCell } from '../type/components/GenericEditablePhoneCell';
import { GenericEditableRelationCell } from '../type/components/GenericEditableRelationCell';
import { GenericEditableTextCell } from '../type/components/GenericEditableTextCell';
import { GenericEditableURLCell } from '../type/components/GenericEditableURLCell';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldMetadata>;
};
export const GenericEditableCell = ({ columnDefinition }: OwnProps) => {
if (isViewFieldEmail(columnDefinition)) {
return <GenericEditableEmailCell columnDefinition={columnDefinition} />;
} else if (isViewFieldText(columnDefinition)) {
return <GenericEditableTextCell columnDefinition={columnDefinition} />;
} else if (isViewFieldRelation(columnDefinition)) {
return <GenericEditableRelationCell columnDefinition={columnDefinition} />;
} else if (isViewFieldDoubleTextChip(columnDefinition)) {
return (
<GenericEditableDoubleTextChipCell columnDefinition={columnDefinition} />
);
} else if (isViewFieldDoubleText(columnDefinition)) {
return (
<GenericEditableDoubleTextCell columnDefinition={columnDefinition} />
);
} else if (isViewFieldPhone(columnDefinition)) {
return <GenericEditablePhoneCell columnDefinition={columnDefinition} />;
} else if (isViewFieldURL(columnDefinition)) {
return <GenericEditableURLCell columnDefinition={columnDefinition} />;
} else if (isViewFieldDate(columnDefinition)) {
return <GenericEditableDateCell columnDefinition={columnDefinition} />;
} else if (isViewFieldNumber(columnDefinition)) {
return <GenericEditableNumberCell columnDefinition={columnDefinition} />;
} else if (isViewFieldBoolean(columnDefinition)) {
return <GenericEditableBooleanCell columnDefinition={columnDefinition} />;
} else if (isViewFieldChip(columnDefinition)) {
return <GenericEditableChipCell columnDefinition={columnDefinition} />;
} else if (isViewFieldMoney(columnDefinition)) {
return <GenericEditableMoneyCell columnDefinition={columnDefinition} />;
} else {
console.warn(
`Unknown field metadata type: ${columnDefinition.metadata.type} in GenericEditableCell`,
);
return <></>;
}
};

View File

@ -0,0 +1,74 @@
import { useContext } from 'react';
import { FieldDisplay } from '@/ui/field/components/FieldDisplay';
import { FieldInput } from '@/ui/field/components/FieldInput';
import { FieldContext } from '@/ui/field/contexts/FieldContext';
import { FieldInputEvent } from '@/ui/field/types/FieldInputEvent';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
import { useTableCell } from '../hooks/useTableCell';
import { TableCellContainer } from './TableCellContainer';
export const TableCell = ({
customHotkeyScope,
}: {
customHotkeyScope: HotkeyScope;
}) => {
const { fieldDefinition } = useContext(FieldContext);
const { closeTableCell } = useTableCell();
const { moveLeft, moveRight, moveDown } = useMoveSoftFocus();
const handleEnter: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
moveDown();
};
const handleSubmit: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
};
const handleCancel = () => {
closeTableCell();
};
const handleEscape = () => {
closeTableCell();
};
const handleTab: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
moveRight();
};
const handleShiftTab: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
moveLeft();
};
return (
<TableCellContainer
editHotkeyScope={customHotkeyScope}
editModeContent={
<FieldInput
onCancel={handleCancel}
onClickOutside={handleCancel}
onEnter={handleEnter}
onEscape={handleEscape}
onShiftTab={handleShiftTab}
onSubmit={handleSubmit}
onTab={handleTab}
/>
}
nonEditModeContent={<FieldDisplay />}
useEditButton={fieldDefinition.useEditButton}
></TableCellContainer>
);
};

View File

@ -1,19 +1,20 @@
import { ReactElement, useState } from 'react';
import styled from '@emotion/styled';
import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
import { useEditableCell } from '../hooks/useEditableCell';
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode';
import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell';
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
import { useTableCell } from '../hooks/useTableCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { EditableCellEditButton } from './EditableCellEditButton';
import { EditableCellEditMode } from './EditableCellEditMode';
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
import { TableCellDisplayMode } from './TableCellDisplayMode';
import { TableCellEditButton } from './TableCellEditButton';
import { TableCellEditMode } from './TableCellEditMode';
import { TableCellSoftFocusMode } from './TableCellSoftFocusMode';
const StyledCellBaseContainer = styled.div`
align-items: center;
@ -43,7 +44,7 @@ const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export const EditableCell = ({
export const TableCellContainer = ({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
editModeContent,
@ -53,16 +54,16 @@ export const EditableCell = ({
maxContentWidth,
useEditButton,
}: EditableCellProps) => {
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const [isHovered, setIsHovered] = useState(false);
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell();
const { openEditableCell } = useEditableCell();
const { openTableCell } = useTableCell();
const handlePenClick = () => {
setSoftFocusOnCurrentCell();
openEditableCell();
setSoftFocusOnCurrentTableCell();
openTableCell();
};
const handleContainerMouseEnter = () => {
@ -73,9 +74,15 @@ export const EditableCell = ({
setIsHovered(false);
};
const showEditButton = useEditButton && isHovered && !isCurrentCellInEditMode;
const editModeContentOnly = useIsFieldInputOnly();
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
const showEditButton =
useEditButton &&
isHovered &&
!isCurrentTableCellInEditMode &&
!editModeContentOnly;
const hasSoftFocus = useIsSoftFocusOnCurrentTableCell();
return (
<CellHotkeyScopeContext.Provider
@ -85,32 +92,28 @@ export const EditableCell = ({
onMouseEnter={handleContainerMouseEnter}
onMouseLeave={handleContainerMouseLeave}
>
{isCurrentCellInEditMode ? (
<EditableCellEditMode
{isCurrentTableCellInEditMode ? (
<TableCellEditMode
maxContentWidth={maxContentWidth}
transparent={transparent}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{editModeContent}
</EditableCellEditMode>
</TableCellEditMode>
) : hasSoftFocus ? (
<>
{showEditButton && (
<EditableCellEditButton onClick={handlePenClick} />
)}
<EditableCellSoftFocusMode>
{nonEditModeContent}
</EditableCellSoftFocusMode>
{showEditButton && <TableCellEditButton onClick={handlePenClick} />}
<TableCellSoftFocusMode>
{editModeContentOnly ? editModeContent : nonEditModeContent}
</TableCellSoftFocusMode>
</>
) : (
<>
{showEditButton && (
<EditableCellEditButton onClick={handlePenClick} />
)}
<EditableCellDisplayMode isHovered={isHovered}>
{nonEditModeContent}
</EditableCellDisplayMode>
{showEditButton && <TableCellEditButton onClick={handlePenClick} />}
<TableCellDisplayMode isHovered={isHovered}>
{editModeContentOnly ? editModeContent : nonEditModeContent}
</TableCellDisplayMode>
</>
)}
</StyledCellBaseContainer>

View File

@ -36,7 +36,7 @@ const StyledEditableCellDisplayModeInnerContainer = styled.div`
width: 100%;
`;
export const EditableCellDisplayContainer = ({
export const TableCellDisplayContainer = ({
children,
softFocus,
onClick,

View File

@ -0,0 +1,31 @@
import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly';
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
import { useTableCell } from '../hooks/useTableCell';
import { TableCellDisplayContainer } from './TableCellDisplayContainer';
export const TableCellDisplayMode = ({
children,
isHovered,
}: React.PropsWithChildren<unknown> & { isHovered?: boolean }) => {
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentTableCell();
const isFieldInputOnly = useIsFieldInputOnly();
const { openTableCell } = useTableCell();
const handleClick = () => {
setSoftFocusOnCurrentCell();
if (!isFieldInputOnly) {
openTableCell();
}
};
return (
<TableCellDisplayContainer isHovered={isHovered} onClick={handleClick}>
{children}
</TableCellDisplayContainer>
);
};

View File

@ -13,7 +13,7 @@ type EditableCellEditButtonProps = {
onClick?: () => void;
};
export const EditableCellEditButton = ({
export const TableCellEditButton = ({
onClick,
}: EditableCellEditButtonProps) => (
<StyledEditButtonContainer

View File

@ -37,7 +37,7 @@ export type EditableCellEditModeProps = {
initialValue?: string;
};
export const EditableCellEditMode = ({
export const TableCellEditMode = ({
editModeHorizontalAlign,
editModeVerticalPosition,
children,

View File

@ -1,17 +1,20 @@
import { PropsWithChildren, useEffect, useRef } from 'react';
import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useEditableCell } from '../hooks/useEditableCell';
import { useTableCell } from '../hooks/useTableCell';
import { EditableCellDisplayContainer } from './EditableCellDisplayContainer';
import { TableCellDisplayContainer } from './TableCellDisplayContainer';
type OwnProps = PropsWithChildren<unknown>;
export const EditableCellSoftFocusMode = ({ children }: OwnProps) => {
const { openEditableCell } = useEditableCell();
export const TableCellSoftFocusMode = ({ children }: OwnProps) => {
const { openTableCell } = useTableCell();
const isFieldInputOnly = useIsFieldInputOnly();
const scrollRef = useRef<HTMLDivElement>(null);
@ -19,17 +22,16 @@ export const EditableCellSoftFocusMode = ({ children }: OwnProps) => {
scrollRef.current?.scrollIntoView({ block: 'nearest' });
}, []);
const openEditMode = () => {
openEditableCell();
};
useScopedHotkeys(
'enter',
() => {
openEditMode();
openTableCell();
},
TableHotkeyScope.TableSoftFocus,
[openEditMode],
[openTableCell],
{
enabled: !isFieldInputOnly,
},
);
useScopedHotkeys(
@ -44,26 +46,29 @@ export const EditableCellSoftFocusMode = ({ children }: OwnProps) => {
return;
}
openEditMode();
openTableCell();
},
TableHotkeyScope.TableSoftFocus,
[openEditMode],
[openTableCell],
{
preventDefault: false,
enabled: !isFieldInputOnly,
},
);
const handleClick = () => {
openEditMode();
if (!isFieldInputOnly) {
openTableCell();
}
};
return (
<EditableCellDisplayContainer
<TableCellDisplayContainer
onClick={handleClick}
softFocus
scrollRef={scrollRef}
>
{children}
</EditableCellDisplayContainer>
</TableCellDisplayContainer>
);
};

View File

@ -1,23 +0,0 @@
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { useMoveEditModeToCellPosition } from '../../hooks/useMoveEditModeToCellPosition';
import { isCellInEditModeFamilyState } from '../../states/isCellInEditModeFamilyState';
import { useCurrentCellPosition } from './useCurrentCellPosition';
export const useCurrentCellEditMode = () => {
const moveEditModeToCellPosition = useMoveEditModeToCellPosition();
const currentCellPosition = useCurrentCellPosition();
const [isCurrentCellInEditMode] = useRecoilState(
isCellInEditModeFamilyState(currentCellPosition),
);
const setCurrentCellInEditMode = useCallback(() => {
moveEditModeToCellPosition(currentCellPosition);
}, [currentCellPosition, moveEditModeToCellPosition]);
return { isCurrentCellInEditMode, setCurrentCellInEditMode };
};

View File

@ -2,13 +2,13 @@ import { useContext, useMemo } from 'react';
import { ColumnIndexContext } from '../../contexts/ColumnIndexContext';
import { RowIndexContext } from '../../contexts/RowIndexContext';
import { CellPosition } from '../../types/CellPosition';
import { TableCellPosition } from '../../types/TableCellPosition';
export const useCurrentCellPosition = () => {
export const useCurrentTableCellPosition = () => {
const currentRowNumber = useContext(RowIndexContext);
const currentColumnNumber = useContext(ColumnIndexContext);
const currentCellPosition: CellPosition = useMemo(
const currentTableCellPosition: TableCellPosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
@ -16,5 +16,5 @@ export const useCurrentCellPosition = () => {
[currentColumnNumber, currentRowNumber],
);
return currentCellPosition;
return currentTableCellPosition;
};

View File

@ -0,0 +1,26 @@
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { useMoveEditModeToTableCellPosition } from '../../hooks/useMoveEditModeToCellPosition';
import { isTableCellInEditModeFamilyState } from '../../states/isTableCellInEditModeFamilyState';
import { useCurrentTableCellPosition } from './useCurrentCellPosition';
export const useCurrentTableCellEditMode = () => {
const moveEditModeToTableCellPosition = useMoveEditModeToTableCellPosition();
const currentTableCellPosition = useCurrentTableCellPosition();
const [isCurrentTableCellInEditMode] = useRecoilState(
isTableCellInEditModeFamilyState(currentTableCellPosition),
);
const setCurrentTableCellInEditMode = useCallback(() => {
moveEditModeToTableCellPosition(currentTableCellPosition);
}, [currentTableCellPosition, moveEditModeToTableCellPosition]);
return {
isCurrentTableCellInEditMode,
setCurrentTableCellInEditMode,
};
};

View File

@ -1,15 +0,0 @@
import { useRecoilValue } from 'recoil';
import { isSoftFocusOnCellFamilyState } from '../../states/isSoftFocusOnCellFamilyState';
import { useCurrentCellPosition } from './useCurrentCellPosition';
export const useIsSoftFocusOnCurrentCell = () => {
const currentCellPosition = useCurrentCellPosition();
const isSoftFocusOnCell = useRecoilValue(
isSoftFocusOnCellFamilyState(currentCellPosition),
);
return isSoftFocusOnCell;
};

View File

@ -0,0 +1,15 @@
import { useRecoilValue } from 'recoil';
import { isSoftFocusOnTableCellFamilyState } from '../../states/isSoftFocusOnTableCellFamilyState';
import { useCurrentTableCellPosition } from './useCurrentCellPosition';
export const useIsSoftFocusOnCurrentTableCell = () => {
const currentTableCellPosition = useCurrentTableCellPosition();
const isSoftFocusOnTableCell = useRecoilValue(
isSoftFocusOnTableCellFamilyState(currentTableCellPosition),
);
return isSoftFocusOnTableCell;
};

View File

@ -1,75 +0,0 @@
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
import { useEditableCell } from './useEditableCell';
export const useRegisterCloseCellHandlers = (
wrapperRef: React.RefObject<HTMLDivElement>,
onSubmit?: () => void,
onCancel?: () => void,
) => {
const { closeEditableCell } = useEditableCell();
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
useListenClickOutside({
refs: [wrapperRef],
callback: (event) => {
if (isCurrentCellInEditMode) {
event.stopImmediatePropagation();
onSubmit?.();
closeEditableCell();
}
},
});
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
useScopedHotkeys(
'enter',
() => {
onSubmit?.();
closeEditableCell();
moveDown();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onSubmit, moveDown],
);
useScopedHotkeys(
'esc',
() => {
closeEditableCell();
onCancel?.();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onCancel],
);
useScopedHotkeys(
'tab',
() => {
onSubmit?.();
closeEditableCell();
moveRight();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onSubmit, moveRight],
);
useScopedHotkeys(
'shift+tab',
() => {
onSubmit?.();
closeEditableCell();
moveLeft();
},
TableHotkeyScope.CellEditMode,
[closeEditableCell, onSubmit, moveRight],
);
};

View File

@ -6,24 +6,24 @@ import { useSetSoftFocusPosition } from '../../hooks/useSetSoftFocusPosition';
import { isSoftFocusActiveState } from '../../states/isSoftFocusActiveState';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellPosition } from './useCurrentCellPosition';
import { useCurrentTableCellPosition } from './useCurrentCellPosition';
export const useSetSoftFocusOnCurrentCell = () => {
export const useSetSoftFocusOnCurrentTableCell = () => {
const setSoftFocusPosition = useSetSoftFocusPosition();
const currentCellPosition = useCurrentCellPosition();
const currentTableCellPosition = useCurrentTableCellPosition();
const setHotkeyScope = useSetHotkeyScope();
return useRecoilCallback(
({ set }) =>
() => {
setSoftFocusPosition(currentCellPosition);
setSoftFocusPosition(currentTableCellPosition);
set(isSoftFocusActiveState, true);
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
},
[setHotkeyScope, currentCellPosition, setSoftFocusPosition],
[setHotkeyScope, currentTableCellPosition, setSoftFocusPosition],
);
};

View File

@ -5,34 +5,34 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode';
import { useCloseCurrentTableCellInEditMode } from '../../hooks/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode';
const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export const useEditableCell = () => {
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
export const useTableCell = () => {
const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const closeEditableCell = () => {
const closeTableCell = () => {
setDragSelectionStartEnabled(true);
closeCurrentCellInEditMode();
closeCurrentTableCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
};
const openEditableCell = () => {
const openTableCell = () => {
setDragSelectionStartEnabled(false);
setCurrentCellInEditMode();
setCurrentTableCellInEditMode();
if (customCellHotkeyScope) {
setHotkeyScope(
@ -45,7 +45,7 @@ export const useEditableCell = () => {
};
return {
closeEditableCell,
openEditableCell,
closeTableCell,
openTableCell,
};
};

View File

@ -1,141 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { useRegisterCloseCellHandlers } from '@/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers';
import { useMoveSoftFocus } from '@/ui/table/hooks/useMoveSoftFocus';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
type OwnProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
onChange?: (firstValue: string, secondValue: string) => void;
onSubmit?: (firstValue: string, secondValue: string) => void;
onCancel?: () => void;
};
export const DoubleTextCellEdit = ({
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
onSubmit,
onCancel,
}: OwnProps) => {
const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
useEffect(() => {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
}, [firstValue, secondValue]);
const handleOnChange = (
newFirstValue: string,
newSecondValue: string,
): void => {
setFirstInternalValue(newFirstValue);
setSecondInternalValue(newSecondValue);
};
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
const firstValueInputRef = useRef<HTMLInputElement>(null);
const secondValueInputRef = useRef<HTMLInputElement>(null);
const { closeEditableCell } = useEditableCell();
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
const closeCell = () => {
setFocusPosition('left');
closeEditableCell();
};
const handleCancel = () => {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
onCancel?.();
};
const handleSubmit = () => {
onSubmit?.(firstInternalValue, secondInternalValue);
};
useScopedHotkeys(
Key.Enter,
() => {
closeCell();
moveDown();
handleSubmit();
},
TableHotkeyScope.CellDoubleTextInput,
[closeCell],
);
useScopedHotkeys(
Key.Escape,
() => {
handleCancel();
closeCell();
},
TableHotkeyScope.CellDoubleTextInput,
[closeCell],
);
useScopedHotkeys(
'tab',
() => {
if (focusPosition === 'left') {
setFocusPosition('right');
secondValueInputRef.current?.focus();
} else {
handleSubmit();
closeCell();
moveRight();
}
},
TableHotkeyScope.CellDoubleTextInput,
[closeCell, moveRight, focusPosition],
);
useScopedHotkeys(
'shift+tab',
() => {
if (focusPosition === 'right') {
setFocusPosition('left');
firstValueInputRef.current?.focus();
} else {
handleSubmit();
closeCell();
moveLeft();
}
},
TableHotkeyScope.CellDoubleTextInput,
[closeCell, moveRight, focusPosition],
);
const wrapperRef = useRef(null);
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
return (
<DoubleTextInput
{...{
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
firstValueInputRef,
secondValueInputRef,
}}
onChange={handleOnChange}
containerRef={wrapperRef}
/>
);
};

View File

@ -1,64 +0,0 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { ViewFieldBooleanMetadata } from '@/ui/editable-field/types/ViewField';
import { BooleanInput } from '@/ui/input/components/BooleanInput';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { EditableCellDisplayContainer } from '../../components/EditableCellDisplayContainer';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldBooleanMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
const StyledCellBaseContainer = styled.div`
align-items: center;
box-sizing: border-box;
cursor: pointer;
display: flex;
height: ${({ theme }) => theme.spacing(8)};
position: relative;
user-select: none;
width: 100%;
`;
export const GenericEditableBooleanCell = ({ columnDefinition }: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const [fieldValue, setFieldValue] = useRecoilState<boolean>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
const handleClick = () => {
const newValue = !fieldValue;
try {
setFieldValue(newValue);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, newValue);
}
} catch (error) {
console.warn(
`In GenericEditableBooleanCellEditMode, Invalid value: ${newValue}, ${error}`,
);
}
};
return (
<StyledCellBaseContainer>
<EditableCellDisplayContainer onClick={handleClick}>
<BooleanInput value={fieldValue} />
</EditableCellDisplayContainer>
</StyledCellBaseContainer>
);
};

View File

@ -1,28 +0,0 @@
import { ViewFieldChipMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode';
import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldChipMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string;
};
export const GenericEditableChipCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableChipCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={
<GenericEditableChipCellDisplayMode columnDefinition={columnDefinition} />
}
></EditableCell>
);

View File

@ -1,51 +0,0 @@
import { useRecoilValue } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { ViewFieldChipMetadata } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { getLogoUrlFromDomainName } from '~/utils';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldChipMetadata>;
};
export const GenericEditableChipCellDisplayMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const content = useRecoilValue<any | null>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.contentFieldName,
}),
);
const chipUrl = useRecoilValue<any | null>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.urlFieldName,
}),
);
switch (columnDefinition.metadata.relationType) {
case Entity.Company: {
return (
<CompanyChip
id={currentRowEntityId ?? ''}
name={content ?? ''}
pictureUrl={getLogoUrlFromDomainName(chipUrl)}
/>
);
}
default:
console.warn(
`Unknown relation type: "${columnDefinition.metadata.relationType}" in GenericEditableChipCellEditMode`,
);
return <> </>;
}
};

View File

@ -1,65 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldChipMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { TextInput } from '../../../../input/components/TextInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldChipMetadata>;
};
export const GenericEditableChipCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.contentFieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newText: string) => {
if (newText === fieldValue) return;
setFieldValue(newText);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, newText);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<TextInput
placeholder={columnDefinition.metadata.placeHolder ?? ''}
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onTab={handleTab}
onShiftTab={handleShiftTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,40 +0,0 @@
import { useRecoilValue } from 'recoil';
import { DateDisplay } from '@/ui/content-display/components/DateDisplay';
import { ViewFieldDateMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDateMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditableDateCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableDateCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={<DateDisplay value={fieldValue} />}
></EditableCell>
);
};

View File

@ -1,65 +0,0 @@
import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil';
import { ViewFieldDateMetadata } from '@/ui/editable-field/types/ViewField';
import { DateInput } from '@/ui/input/components/DateInput';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { Nullable } from '~/types/Nullable';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDateMetadata>;
};
export const GenericEditableDateCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
// Wrap this into a hook
const handleSubmit = (newDate: Nullable<Date>) => {
const fieldValueDate = fieldValue
? DateTime.fromISO(fieldValue).toJSDate()
: null;
const newDateISO = newDate ? DateTime.fromJSDate(newDate).toISO() : null;
if (newDate === fieldValueDate || !newDateISO) return;
setFieldValue(newDateISO);
if (currentRowEntityId && updateField && newDateISO) {
updateField(currentRowEntityId, columnDefinition, newDateISO);
}
};
const { handleEnter, handleEscape, handleClickOutside } =
useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<DateInput
value={DateTime.fromISO(fieldValue).toJSDate()}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,48 +0,0 @@
import { useRecoilValue } from 'recoil';
import { DoubleTextDisplay } from '@/ui/content-display/components/DoubleTextDisplay';
import { ViewFieldDoubleTextMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDoubleTextMetadata>;
};
export const GenericEditableDoubleTextCell = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const firstValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.firstValueFieldName,
}),
);
const secondValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.secondValueFieldName,
}),
);
const displayName = `${firstValue ?? ''} ${secondValue ?? ''}`;
return (
<EditableCell
editModeContent={
<GenericEditableDoubleTextCellEditMode
columnDefinition={columnDefinition}
/>
}
nonEditModeContent={<DoubleTextDisplay text={displayName} />}
></EditableCell>
);
};

View File

@ -1,61 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldDoubleTextMetadata } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { DoubleTextCellEdit } from './DoubleTextCellEdit';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDoubleTextMetadata>;
};
export const GenericEditableDoubleTextCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [firstValue, setFirstValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.firstValueFieldName,
}),
);
const [secondValue, setSecondValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.secondValueFieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newFirstValue: string, newSecondValue: string) => {
if (newFirstValue === firstValue && newSecondValue === secondValue) return;
setFirstValue(newFirstValue);
setSecondValue(newSecondValue);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, {
firstValue: newFirstValue,
secondValue: newSecondValue,
});
}
};
return (
<DoubleTextCellEdit
firstValuePlaceholder={columnDefinition.metadata.firstValuePlaceholder}
secondValuePlaceholder={columnDefinition.metadata.secondValuePlaceholder}
firstValue={firstValue ?? ''}
secondValue={secondValue ?? ''}
onSubmit={handleSubmit}
/>
);
};

View File

@ -1,30 +0,0 @@
import { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableDoubleTextChipCellDisplayMode } from './GenericEditableDoubleTextChipCellDisplayMode';
import { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDoubleTextChipMetadata>;
};
export const GenericEditableDoubleTextChipCell = ({
columnDefinition,
}: OwnProps) => (
<EditableCell
editHotkeyScope={{ scope: TableHotkeyScope.CellDoubleTextInput }}
editModeContent={
<GenericEditableDoubleTextChipCellEditMode
columnDefinition={columnDefinition}
/>
}
nonEditModeContent={
<GenericEditableDoubleTextChipCellDisplayMode
columnDefinition={columnDefinition}
/>
}
></EditableCell>
);

View File

@ -1,50 +0,0 @@
import { useRecoilState } from 'recoil';
import { DoubleTextChipDisplay } from '@/ui/content-display/components/DoubleTextChipDisplay';
import { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDoubleTextChipMetadata>;
};
export const GenericEditableDoubleTextChipCellDisplayMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const [firstValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.firstValueFieldName,
}),
);
const [secondValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.secondValueFieldName,
}),
);
const [avatarUrlValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.avatarUrlFieldName,
}),
);
const displayName = [firstValue, secondValue].filter(Boolean).join(' ');
return (
<DoubleTextChipDisplay
entityType={columnDefinition.metadata.entityType}
displayName={displayName}
entityId={currentRowEntityId}
avatarUrlValue={avatarUrlValue}
/>
);
};

View File

@ -1,71 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { DoubleTextCellEdit } from './DoubleTextCellEdit';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldDoubleTextChipMetadata>;
};
export const GenericEditableDoubleTextChipCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [firstValue, setFirstValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.firstValueFieldName,
}),
);
const [secondValue, setSecondValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.secondValueFieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newFirstValue: string, newSecondValue: string) => {
const firstValueChanged = newFirstValue !== firstValue;
const secondValueChanged = newSecondValue !== secondValue;
if (firstValueChanged) {
setFirstValue(newFirstValue);
}
if (secondValueChanged) {
setSecondValue(newSecondValue);
}
if (
currentRowEntityId &&
updateField &&
(firstValueChanged || secondValueChanged)
) {
updateField(currentRowEntityId, columnDefinition, {
firstValue: firstValueChanged ? newFirstValue : firstValue,
secondValue: secondValueChanged ? newSecondValue : secondValue,
});
}
};
return (
<DoubleTextCellEdit
firstValuePlaceholder={columnDefinition.metadata.firstValuePlaceholder}
secondValuePlaceholder={columnDefinition.metadata.secondValuePlaceholder}
firstValue={firstValue ?? ''}
secondValue={secondValue ?? ''}
onSubmit={handleSubmit}
/>
);
};

View File

@ -1,40 +0,0 @@
import { useRecoilValue } from 'recoil';
import { EmailDisplay } from '@/ui/content-display/components/EmailDisplay';
import { ViewFieldEmailMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableEmailCellEditMode } from './GenericEditableEmailCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldEmailMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditableEmailCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableEmailCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={<EmailDisplay value={fieldValue} />}
></EditableCell>
);
};

View File

@ -1,65 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldEmailMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { TextInput } from '../../../../input/components/TextInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldEmailMetadata>;
};
export const GenericEditableEmailCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newEmail: string) => {
if (newEmail === fieldValue) return;
setFieldValue(newEmail);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, newEmail);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<TextInput
placeholder={columnDefinition.metadata.placeHolder ?? ''}
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onTab={handleTab}
onShiftTab={handleShiftTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,40 +0,0 @@
import { useRecoilValue } from 'recoil';
import { MoneyDisplay } from '@/ui/content-display/components/MoneyDisplay';
import { ViewFieldMoneyMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableMoneyCellEditMode } from './GenericEditableMoneyCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldMoneyMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditableMoneyCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<number>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableMoneyCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={<MoneyDisplay value={fieldValue} />}
></EditableCell>
);
};

View File

@ -1,81 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldMoneyMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { TextInput } from '../../../../input/components/TextInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldMoneyMetadata>;
};
export const GenericEditableMoneyCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
// TODO: handle this logic in a number input
const handleSubmit = (newText: string) => {
if (newText === fieldValue) return;
try {
const numberValue = newText !== '' ? parseInt(newText) : null;
if (numberValue && isNaN(numberValue)) {
throw new Error('Not a number');
}
if (numberValue && numberValue > 2000000000) {
throw new Error('Number too big');
}
setFieldValue(numberValue ? numberValue.toString() : '');
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, numberValue);
}
} catch (error) {
console.warn(
`In GenericEditableMoneyCellEditMode, Invalid number: ${newText}, ${error}`,
);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
// TODO: use a number input
return (
<TextInput
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onTab={handleTab}
onShiftTab={handleShiftTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,42 +0,0 @@
import { useRecoilValue } from 'recoil';
import { NumberDisplay } from '@/ui/content-display/components/NumberDisplay';
import { ViewFieldNumberMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableNumberCellEditMode } from './GenericEditableNumberCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldNumberMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditableNumberCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableNumberCellEditMode
columnDefinition={columnDefinition}
/>
}
nonEditModeContent={<NumberDisplay value={fieldValue} />}
></EditableCell>
);
};

View File

@ -1,100 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldNumberMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import {
canBeCastAsPositiveIntegerOrNull,
castAsPositiveIntegerOrNull,
} from '~/utils/cast-as-positive-integer-or-null';
import { TextInput } from '../../../../input/components/TextInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldNumberMetadata>;
};
export const GenericEditableNumberCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newText: string) => {
if (newText === fieldValue) return;
try {
let numberValue = parseInt(newText);
if (isNaN(numberValue)) {
throw new Error('Not a number');
}
if (columnDefinition.metadata.isPositive) {
if (!canBeCastAsPositiveIntegerOrNull(newText)) {
return;
}
const valueCastedAsPositiveNumberOrNull =
castAsPositiveIntegerOrNull(newText);
if (valueCastedAsPositiveNumberOrNull === null) {
throw Error('Not a number');
}
numberValue = valueCastedAsPositiveNumberOrNull;
}
// TODO: find a way to store this better in DB
if (numberValue > 2000000000) {
throw new Error('Number too big');
}
setFieldValue(numberValue.toString());
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, numberValue);
}
} catch (error) {
console.warn(
`In GenericEditableNumberCellEditMode, Invalid number: ${newText}, ${error}`,
);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<TextInput
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onTab={handleTab}
onShiftTab={handleShiftTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,41 +0,0 @@
import { useRecoilValue } from 'recoil';
import { PhoneDisplay } from '@/ui/content-display/components/PhoneDisplay';
import { ViewFieldPhoneMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldPhoneMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditablePhoneCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
useEditButton
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditablePhoneCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={<PhoneDisplay value={fieldValue} />}
></EditableCell>
);
};

View File

@ -1,68 +0,0 @@
import { isPossiblePhoneNumber } from 'libphonenumber-js';
import { useRecoilState } from 'recoil';
import { ViewFieldPhoneMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { PhoneInput } from '../../../../input/components/PhoneInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldPhoneMetadata>;
};
export const GenericEditablePhoneCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newValue: string) => {
if (!isPossiblePhoneNumber(newValue)) return;
if (newValue === fieldValue) return;
setFieldValue(newValue);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, newValue);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<PhoneInput
placeholder={columnDefinition.metadata.placeHolder ?? ''}
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onShiftTab={handleShiftTab}
onTab={handleTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,38 +0,0 @@
import { ViewFieldRelationMetadata } from '@/ui/editable-field/types/ViewField';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode';
import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldRelationMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string;
};
export const GenericEditableRelationCell = ({
columnDefinition,
editModeHorizontalAlign,
placeholder,
}: OwnProps) => (
<EditableCell
maxContentWidth={160}
editModeHorizontalAlign={editModeHorizontalAlign}
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
editModeContent={
<GenericEditableRelationCellEditMode
columnDefinition={columnDefinition}
/>
}
nonEditModeContent={
<GenericEditableRelationCellDisplayMode
columnDefinition={columnDefinition}
editModeHorizontalAlign={editModeHorizontalAlign}
placeholder={placeholder}
/>
}
></EditableCell>
);

View File

@ -1,57 +0,0 @@
import { useRecoilValue } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { ViewFieldRelationMetadata } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { UserChip } from '@/users/components/UserChip';
import { getLogoUrlFromDomainName } from '~/utils';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldRelationMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string;
};
export const GenericEditableRelationCellDisplayMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: type value with generic getter
const fieldValue = useRecoilValue<any | null>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
switch (columnDefinition.metadata.relationType) {
case Entity.Company: {
return (
<CompanyChip
id={fieldValue?.id ?? ''}
name={fieldValue?.name ?? ''}
pictureUrl={getLogoUrlFromDomainName(fieldValue?.domainName)}
/>
);
}
case Entity.User: {
return (
<UserChip
id={fieldValue?.id ?? ''}
name={fieldValue?.displayName ?? ''}
pictureUrl={fieldValue?.avatarUrl ?? ''}
/>
);
}
default:
console.warn(
`Unknown relation type: "${columnDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
);
return <> </>;
}
};

View File

@ -1,120 +0,0 @@
import { useRecoilState } from 'recoil';
import {
CompanyPickerCell,
CompanyPickerSelectedCompany,
} from '@/companies/components/CompanyPickerCell';
import { ViewFieldRelationMetadata } from '@/ui/editable-field/types/ViewField';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { UserPicker } from '@/users/components/UserPicker';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldRelationMetadata>;
};
export const GenericEditableRelationCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const { closeEditableCell } = useEditableCell();
const [fieldValueEntity, setFieldValueEntity] = useRecoilState<any | null>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateEntityField = useUpdateEntityField();
const updateCachedPersonField = (newFieldEntity: EntityForSelect | null) => {
setFieldValueEntity({
avatarUrl: newFieldEntity?.avatarUrl ?? '',
entityType: Entity.Company,
id: newFieldEntity?.id ?? '',
displayName: newFieldEntity?.name ?? '',
});
};
const updateCachedCompanyField = (
newFieldEntity: CompanyPickerSelectedCompany | null,
) => {
setFieldValueEntity({
id: newFieldEntity?.id ?? '',
name: newFieldEntity?.name ?? '',
domainName: newFieldEntity?.domainName ?? '',
});
};
const handleCompanySubmit = (
newFieldEntity: CompanyPickerSelectedCompany | null,
) => {
if (
newFieldEntity?.id !== fieldValueEntity?.id &&
currentRowEntityId &&
updateEntityField
) {
updateCachedCompanyField(newFieldEntity);
updateEntityField<ViewFieldRelationMetadata, EntityForSelect>(
currentRowEntityId,
columnDefinition,
newFieldEntity,
);
}
closeEditableCell();
};
const handlePersonSubmit = (newFieldEntity: EntityForSelect | null) => {
if (
newFieldEntity?.id !== fieldValueEntity?.id &&
currentRowEntityId &&
updateEntityField
) {
updateCachedPersonField(newFieldEntity);
updateEntityField(currentRowEntityId, columnDefinition, newFieldEntity);
}
closeEditableCell();
};
const handleCancel = () => {
closeEditableCell();
};
switch (columnDefinition.metadata.relationType) {
case Entity.Company: {
return (
<CompanyPickerCell
companyId={fieldValueEntity?.id ?? null}
onSubmit={handleCompanySubmit}
onCancel={handleCancel}
width={columnDefinition.size}
createModeEnabled
/>
);
}
case Entity.User: {
return (
<UserPicker
userId={fieldValueEntity?.id ?? null}
onSubmit={handlePersonSubmit}
onCancel={handleCancel}
width={columnDefinition.size}
/>
);
}
default:
console.warn(
`Unknown relation type: "${columnDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
);
return <></>;
}
};

View File

@ -1,40 +0,0 @@
import { useRecoilValue } from 'recoil';
import { TextDisplay } from '@/ui/content-display/components/TextDisplay';
import { ViewFieldTextMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldTextMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditableTextCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableTextCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={<TextDisplay text={fieldValue} />}
></EditableCell>
);
};

View File

@ -1,65 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldTextMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { TextInput } from '../../../../input/components/TextInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldTextMetadata>;
};
export const GenericEditableTextCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newText: string) => {
if (newText === fieldValue) return;
setFieldValue(newText);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, newText);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<TextInput
placeholder={columnDefinition.metadata.placeHolder ?? ''}
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onTab={handleTab}
onShiftTab={handleShiftTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,42 +0,0 @@
import { useRecoilValue } from 'recoil';
import { URLDisplay } from '@/ui/content-display/components/URLDisplay';
import { ViewFieldURLMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { sanitizeURL } from '~/utils';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldURLMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
};
export const GenericEditableURLCell = ({
columnDefinition,
editModeHorizontalAlign,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
return (
<EditableCell
useEditButton
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableURLCellEditMode columnDefinition={columnDefinition} />
}
nonEditModeContent={<URLDisplay value={sanitizeURL(fieldValue)} />}
></EditableCell>
);
};

View File

@ -1,68 +0,0 @@
import { useRecoilState } from 'recoil';
import { ViewFieldURLMetadata } from '@/ui/editable-field/types/ViewField';
import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { isURL } from '~/utils/is-url';
import { TextInput } from '../../../../input/components/TextInput';
import { ColumnDefinition } from '../../../types/ColumnDefinition';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldURLMetadata>;
};
export const GenericEditableURLCellEditMode = ({
columnDefinition,
}: OwnProps) => {
const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: columnDefinition.metadata.fieldName,
}),
);
const updateField = useUpdateEntityField();
const handleSubmit = (newText: string) => {
if (newText === fieldValue) return;
if (newText !== '' && !isURL(newText)) return;
setFieldValue(newText);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, columnDefinition, newText);
}
};
const {
handleEnter,
handleEscape,
handleTab,
handleShiftTab,
handleClickOutside,
} = useCellInputEventHandlers({
onSubmit: handleSubmit,
});
return (
<TextInput
placeholder={columnDefinition.metadata.placeHolder ?? ''}
autoFocus
value={fieldValue ?? ''}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onTab={handleTab}
onShiftTab={handleShiftTab}
hotkeyScope={TableHotkeyScope.CellEditMode}
/>
);
};

View File

@ -1,23 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { PhoneInput } from '@/ui/input/components/PhoneInput';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
const meta: Meta<typeof PhoneInput> = {
title: 'UI/Table/EditableCell/PhoneCellEdit',
component: PhoneInput,
decorators: [ComponentWithRecoilScopeDecorator],
args: {
value: '+33714446494',
autoFocus: true,
},
parameters: {
customRecoilScopeContext: TableRecoilScopeContext,
},
};
export default meta;
type Story = StoryObj<typeof PhoneInput>;
export const Default: Story = {};