Clean and re-organize post table refactoring (#1000)
* Clean and re-organize post table refactoring * Fix tests
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { overlayBackground } from '@/ui/themes/effects';
|
||||
import { overlayBackground } from '@/ui/theme/constants/effects';
|
||||
|
||||
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
||||
align-items: center;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { isNonTextWritingKey } from '@/ui/hotkey/utils/isNonTextWritingKey';
|
||||
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';
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { isViewFieldChip } from '../../types/guards/isViewFieldChip';
|
||||
import { isViewFieldDate } from '../../types/guards/isViewFieldDate';
|
||||
import { isViewFieldDoubleText } from '../../types/guards/isViewFieldDoubleText';
|
||||
import { isViewFieldDoubleTextChip } from '../../types/guards/isViewFieldDoubleTextChip';
|
||||
import { isViewFieldNumber } from '../../types/guards/isViewFieldNumber';
|
||||
import { isViewFieldPhone } from '../../types/guards/isViewFieldPhone';
|
||||
import { isViewFieldRelation } from '../../types/guards/isViewFieldRelation';
|
||||
import { isViewFieldText } from '../../types/guards/isViewFieldText';
|
||||
import { isViewFieldURL } from '../../types/guards/isViewFieldURL';
|
||||
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 { 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 = {
|
||||
viewField: ViewFieldDefinition<ViewFieldMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableCell({ viewField: fieldDefinition }: OwnProps) {
|
||||
if (isViewFieldText(fieldDefinition)) {
|
||||
return <GenericEditableTextCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldRelation(fieldDefinition)) {
|
||||
return <GenericEditableRelationCell fieldDefinition={fieldDefinition} />;
|
||||
} else if (isViewFieldDoubleTextChip(fieldDefinition)) {
|
||||
return <GenericEditableDoubleTextChipCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldDoubleText(fieldDefinition)) {
|
||||
return <GenericEditableDoubleTextCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldPhone(fieldDefinition)) {
|
||||
return <GenericEditablePhoneCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldURL(fieldDefinition)) {
|
||||
return <GenericEditableURLCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldDate(fieldDefinition)) {
|
||||
return <GenericEditableDateCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldNumber(fieldDefinition)) {
|
||||
return <GenericEditableNumberCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldChip(fieldDefinition)) {
|
||||
return <GenericEditableChipCell viewField={fieldDefinition} />;
|
||||
} else {
|
||||
console.warn(
|
||||
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`,
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { EditableCellText } from '../../types/EditableCellText';
|
||||
|
||||
const meta: Meta<typeof EditableCellText> = {
|
||||
title: 'UI/EditableCell/EditableCellText',
|
||||
component: EditableCellText,
|
||||
decorators: [ComponentDecorator, CellPositionDecorator],
|
||||
args: {
|
||||
value: 'Content',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EditableCellText>;
|
||||
|
||||
export const DisplayMode: Story = {
|
||||
render: EditableCellText,
|
||||
};
|
||||
|
||||
export const SoftFocusMode: Story = {
|
||||
...DisplayMode,
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const content = await canvas.findByText('Content');
|
||||
|
||||
await userEvent.click(content);
|
||||
await userEvent.keyboard('{esc}');
|
||||
|
||||
await sleep(10);
|
||||
|
||||
await step('Has soft focus mode', () => {
|
||||
expect(canvas.getByTestId('editable-cell-soft-focus-mode')).toBeDefined();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const EditMode: Story = {
|
||||
...DisplayMode,
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const click = () => userEvent.click(canvas.getByText('Content'));
|
||||
|
||||
await step('Click once', click);
|
||||
|
||||
await step('Has edit mode', () => {
|
||||
expect(
|
||||
canvas.getByTestId('editable-cell-edit-mode-container'),
|
||||
).toBeDefined();
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode';
|
||||
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
|
||||
import { useSetSoftFocusPosition } from '../../hooks/useSetSoftFocusPosition';
|
||||
import { isSoftFocusActiveState } from '../../states/isSoftFocusActiveState';
|
||||
|
||||
@ -2,12 +2,12 @@ import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
|
||||
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
|
||||
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { useListenClickOutside } from '../../../hooks/useListenClickOutside';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
import { useEditableCell } from '../hooks/useEditableCell';
|
||||
import { useEditableCell } from '../../hooks/useEditableCell';
|
||||
|
||||
const EditableCellDateEditModeContainer = styled.div`
|
||||
margin-top: -1px;
|
||||
@ -19,10 +19,7 @@ export type EditableDateProps = {
|
||||
onSubmit: (date: Date) => void;
|
||||
};
|
||||
|
||||
export function EditableCellDateEditMode({
|
||||
value,
|
||||
onSubmit,
|
||||
}: EditableDateProps) {
|
||||
export function DateCellEdit({ value, onSubmit }: EditableDateProps) {
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
|
||||
function handleDateChange(newDate: Date) {
|
||||
@ -55,7 +52,7 @@ export function EditableCellDateEditMode({
|
||||
|
||||
return (
|
||||
<EditableCellDateEditModeContainer ref={containerRef}>
|
||||
<InplaceInputDate onChange={handleDateChange} value={value} />
|
||||
<DateInputEdit onChange={handleDateChange} value={value} />
|
||||
</EditableCellDateEditModeContainer>
|
||||
);
|
||||
}
|
||||
@ -2,13 +2,14 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
import { useMoveSoftFocus } from '@/ui/table/hooks/useMoveSoftFocus';
|
||||
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
import { useEditableCell } from '../hooks/useEditableCell';
|
||||
import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers';
|
||||
import { useEditableCell } from '../../hooks/useEditableCell';
|
||||
import { useRegisterCloseCellHandlers } from '../../hooks/useRegisterCloseCellHandlers';
|
||||
|
||||
import { StyledInput } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
firstValue: string;
|
||||
@ -35,7 +36,7 @@ const StyledContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export function EditableCellDoubleTextEditMode({
|
||||
export function DoubleTextCellEdit({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
@ -0,0 +1,31 @@
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode';
|
||||
import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldChipMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function GenericEditableChipCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableChipCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={
|
||||
<GenericEditableChipCellDisplayMode fieldDefinition={viewField} />
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
type OwnProps = {
|
||||
fieldDefinition: ViewFieldDefinition<ViewFieldChipMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableChipCellDisplayMode({
|
||||
fieldDefinition,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const content = useRecoilValue<any | null>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: fieldDefinition.metadata.contentFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const chipUrl = useRecoilValue<any | null>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: fieldDefinition.metadata.urlFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
switch (fieldDefinition.metadata.relationType) {
|
||||
case Entity.Company: {
|
||||
return (
|
||||
<CompanyChip
|
||||
id={currentRowEntityId ?? ''}
|
||||
name={content ?? ''}
|
||||
pictureUrl={getLogoUrlFromDomainName(chipUrl)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableChipCellEditMode`,
|
||||
);
|
||||
return <> </>;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldChipMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableChipCellEditMode({ viewField }: 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: viewField.metadata.contentFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newText: string) {
|
||||
if (newText === fieldValue) return;
|
||||
|
||||
setFieldValue(newText);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, newText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDateMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function GenericEditableDateCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableDateCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={<DateInputDisplay value={fieldValue} />}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { DateCellEdit } from './DateCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDateMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableDateCellEditMode({ viewField }: 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: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newDate: Date) {
|
||||
const fieldValueDate = fieldValue
|
||||
? DateTime.fromISO(fieldValue).toJSDate()
|
||||
: null;
|
||||
|
||||
const newDateISO = DateTime.fromJSDate(newDate).toISO();
|
||||
|
||||
if (newDate === fieldValueDate || !newDateISO) return;
|
||||
|
||||
setFieldValue(newDateISO);
|
||||
|
||||
if (currentRowEntityId && updateField && newDateISO) {
|
||||
updateField(currentRowEntityId, viewField, newDateISO);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DateCellEdit
|
||||
value={DateTime.fromISO(fieldValue).toJSDate()}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDoubleTextMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableDoubleTextCell({ viewField }: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const firstValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.firstValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const secondValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.secondValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const displayName = `${firstValue ?? ''} ${secondValue ?? ''}`;
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
<GenericEditableDoubleTextCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={<TextInputDisplay>{displayName}</TextInputDisplay>}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { DoubleTextCellEdit } from './DoubleTextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDoubleTextMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableDoubleTextCellEditMode({ viewField }: 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: viewField.metadata.firstValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const [secondValue, setSecondValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.firstValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newFirstValue: string, newSecondValue: string) {
|
||||
if (newFirstValue === firstValue && newSecondValue === secondValue) return;
|
||||
|
||||
setFirstValue(newFirstValue);
|
||||
setSecondValue(newSecondValue);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, {
|
||||
firstValue: newFirstValue,
|
||||
secondValue: newSecondValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DoubleTextCellEdit
|
||||
firstValuePlaceholder={viewField.metadata.firstValuePlaceholder}
|
||||
secondValuePlaceholder={viewField.metadata.secondValuePlaceholder}
|
||||
firstValue={firstValue ?? ''}
|
||||
secondValue={secondValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableDoubleTextChipCellDisplayMode } from './GenericEditableDoubleTextChipCellDisplayMode';
|
||||
import { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableDoubleTextChipCell({ viewField }: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editHotkeyScope={{ scope: TableHotkeyScope.CellDoubleTextInput }}
|
||||
editModeContent={
|
||||
<GenericEditableDoubleTextChipCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={
|
||||
<GenericEditableDoubleTextChipCellDisplayMode viewField={viewField} />
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
import { PersonChip } from '@/people/components/PersonChip';
|
||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableDoubleTextChipCellDisplayMode({
|
||||
viewField,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const [firstValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.firstValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const [secondValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.secondValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const [avatarUrlValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.avatarUrlFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const displayName = `${firstValue} ${secondValue}`;
|
||||
|
||||
switch (viewField.metadata.entityType) {
|
||||
case Entity.Company: {
|
||||
return <CompanyChip id={currentRowEntityId ?? ''} name={displayName} />;
|
||||
}
|
||||
case Entity.Person: {
|
||||
return (
|
||||
<PersonChip
|
||||
id={currentRowEntityId ?? ''}
|
||||
name={displayName}
|
||||
pictureUrl={avatarUrlValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`Unknown relation type: "${viewField.metadata.entityType}" in GenericEditableDoubleTextChipCellDisplayMode`,
|
||||
);
|
||||
return <> </>;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { DoubleTextCellEdit } from './DoubleTextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableDoubleTextChipCellEditMode({
|
||||
viewField,
|
||||
}: 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: viewField.metadata.firstValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const [secondValue, setSecondValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.secondValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function 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, viewField, {
|
||||
firstValue: firstValueChanged ? newFirstValue : firstValue,
|
||||
secondValue: secondValueChanged ? newSecondValue : secondValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DoubleTextCellEdit
|
||||
firstValuePlaceholder={viewField.metadata.firstValuePlaceholder}
|
||||
secondValuePlaceholder={viewField.metadata.secondValuePlaceholder}
|
||||
firstValue={firstValue ?? ''}
|
||||
secondValue={secondValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldNumberMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableNumberCellEditMode } from './GenericEditableNumberCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldNumberMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function GenericEditableNumberCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableNumberCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={<>{fieldValue}</>}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldNumberMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldNumberMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableNumberCellEditMode({ viewField }: 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: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newText: string) {
|
||||
if (newText === fieldValue) return;
|
||||
|
||||
try {
|
||||
const numberValue = parseInt(newText);
|
||||
|
||||
if (isNaN(numberValue)) {
|
||||
throw new Error('Not a number');
|
||||
}
|
||||
|
||||
// TODO: find a way to store this better in DB
|
||||
if (numberValue > 2000000000) {
|
||||
throw new Error('Number too big');
|
||||
}
|
||||
|
||||
console.log({ numberValue });
|
||||
|
||||
setFieldValue(numberValue.toString());
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, numberValue);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`In GenericEditableNumberCellEditMode, Invalid number: ${newText}, ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit autoFocus value={fieldValue ?? ''} onSubmit={handleSubmit} />
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldPhoneMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldPhoneMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function GenericEditablePhoneCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditablePhoneCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={<PhoneInputDisplay value={fieldValue} />}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldPhoneMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldPhoneMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditablePhoneCellEditMode({ viewField }: 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: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newText: string) {
|
||||
if (newText === fieldValue) return;
|
||||
|
||||
setFieldValue(newText);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, newText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldRelationMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode';
|
||||
import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function GenericEditableRelationCell({
|
||||
fieldDefinition,
|
||||
editModeHorizontalAlign,
|
||||
placeholder,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
|
||||
editModeContent={
|
||||
<GenericEditableRelationCellEditMode viewField={fieldDefinition} />
|
||||
}
|
||||
nonEditModeContent={
|
||||
<GenericEditableRelationCellDisplayMode
|
||||
fieldDefinition={fieldDefinition}
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldRelationMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { UserChip } from '@/users/components/UserChip';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
type OwnProps = {
|
||||
fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function GenericEditableRelationCellDisplayMode({
|
||||
fieldDefinition,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
// TODO: type value with generic getter
|
||||
const fieldValue = useRecoilValue<any | null>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
switch (fieldDefinition.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: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||
);
|
||||
return <> </>;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CompanyPickerCell } from '@/companies/components/CompanyPickerCell';
|
||||
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/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldRelationMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { UserPicker } from '@/users/components/UserPicker';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldRelationMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableRelationCellEditMode({ viewField }: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
|
||||
const [fieldValueEntity] = useRecoilState<any | null>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateEntityField = useUpdateEntityField();
|
||||
|
||||
function handleEntitySubmit(newFieldEntity: EntityForSelect | null) {
|
||||
if (
|
||||
newFieldEntity?.id !== fieldValueEntity?.id &&
|
||||
currentRowEntityId &&
|
||||
updateEntityField
|
||||
) {
|
||||
updateEntityField(currentRowEntityId, viewField, newFieldEntity);
|
||||
}
|
||||
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
switch (viewField.metadata.relationType) {
|
||||
case Entity.Company: {
|
||||
return (
|
||||
<CompanyPickerCell
|
||||
companyId={fieldValueEntity?.id ?? null}
|
||||
onSubmit={handleEntitySubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case Entity.User: {
|
||||
return (
|
||||
<UserPicker
|
||||
userId={fieldValueEntity?.id ?? null}
|
||||
onSubmit={handleEntitySubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`Unknown relation type: "${viewField.metadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldTextMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function GenericEditableTextCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableTextCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={<TextInputDisplay>{fieldValue}</TextInputDisplay>}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldTextMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableTextCellEditMode({ viewField }: 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: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newText: string) {
|
||||
if (newText === fieldValue) return;
|
||||
|
||||
setFieldValue(newText);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, newText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { InplaceInputURLDisplayMode } from '@/ui/input/url/components/URLTextInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldURLMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function GenericEditableURLCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
|
||||
nonEditModeContent={<InplaceInputURLDisplayMode value={fieldValue} />}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldURLMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableURLCellEditMode({ viewField }: 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: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newText: string) {
|
||||
if (newText === fieldValue) return;
|
||||
|
||||
setFieldValue(newText);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, newText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { textInputStyle } from '@/ui/theme/constants/effects';
|
||||
|
||||
import { useRegisterCloseCellHandlers } from '../../hooks/useRegisterCloseCellHandlers';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
autoFocus?: boolean;
|
||||
value: string;
|
||||
onSubmit: (newText: string) => void;
|
||||
};
|
||||
|
||||
export function TextCellEdit({
|
||||
placeholder,
|
||||
autoFocus,
|
||||
value,
|
||||
onSubmit,
|
||||
}: OwnProps) {
|
||||
const [internalText, setInternalText] = useState(value);
|
||||
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
function handleSubmit() {
|
||||
onSubmit(internalText);
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setInternalText(value);
|
||||
}
|
||||
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setInternalText(event.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setInternalText(value);
|
||||
}, [value]);
|
||||
|
||||
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
|
||||
|
||||
return (
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
ref={wrapperRef}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
autoFocus={autoFocus}
|
||||
value={internalText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import { InplaceInputDateDisplayMode } from '@/ui/display/component/InplaceInputDateDisplayMode';
|
||||
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
import { EditableCell } from '../components/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 onSubmit={onChange} value={value} />
|
||||
}
|
||||
nonEditModeContent={<InplaceInputDateDisplayMode value={value} />}
|
||||
editHotkeyScope={{ scope: TableHotkeyScope.CellDateEditMode }}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
import { CellSkeleton } from '../components/CellSkeleton';
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
import { EditableCellDoubleTextEditMode } from './EditableCellDoubleTextEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
firstValuePlaceholder: string;
|
||||
secondValuePlaceholder: string;
|
||||
nonEditModeContent: ReactElement;
|
||||
onSubmit?: (firstValue: string, secondValue: string) => void;
|
||||
onCancel?: () => void;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
export function EditableCellDoubleText({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
secondValuePlaceholder,
|
||||
|
||||
onSubmit,
|
||||
onCancel,
|
||||
nonEditModeContent,
|
||||
loading,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editHotkeyScope={{ scope: TableHotkeyScope.CellDoubleTextInput }}
|
||||
editModeContent={
|
||||
<EditableCellDoubleTextEditMode
|
||||
firstValue={firstValue}
|
||||
secondValue={secondValue}
|
||||
firstValuePlaceholder={firstValuePlaceholder}
|
||||
secondValuePlaceholder={secondValuePlaceholder}
|
||||
onSubmit={onSubmit}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={loading ? <CellSkeleton /> : nonEditModeContent}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
onSubmit?: (newText: string) => void;
|
||||
};
|
||||
|
||||
export function EditableCellPhone({ value, placeholder, onSubmit }: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
<InplaceInputTextCellEditMode
|
||||
autoFocus
|
||||
placeholder={placeholder || ''}
|
||||
value={value}
|
||||
onSubmit={(newText) => onSubmit?.(newText)}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={<InplaceInputPhoneDisplayMode value={value} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const EditableCellRelationCreateButton = styled.button`
|
||||
align-items: center;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-family: 'Inter';
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
height: 31px;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
padding-top: ${({ theme }) => theme.spacing(1)};
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
`;
|
||||
@ -1,42 +0,0 @@
|
||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { CellSkeleton } from '../components/CellSkeleton';
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
loading?: boolean;
|
||||
onSubmit?: (newText: string) => void;
|
||||
};
|
||||
|
||||
export function EditableCellText({
|
||||
value,
|
||||
placeholder,
|
||||
editModeHorizontalAlign,
|
||||
loading,
|
||||
onSubmit,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={value}
|
||||
onSubmit={(newText) => onSubmit?.(newText)}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
loading ? (
|
||||
<CellSkeleton />
|
||||
) : (
|
||||
<InplaceInputTextDisplayMode>{value}</InplaceInputTextDisplayMode>
|
||||
)
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { RawLink } from '../../../link/components/RawLink';
|
||||
import { CellSkeleton } from '../components/CellSkeleton';
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
url: string;
|
||||
onChange?: (newURL: string) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
loading?: boolean;
|
||||
onSubmit?: (newURL: string) => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
export function EditableCellURL({
|
||||
url,
|
||||
placeholder,
|
||||
editModeHorizontalAlign,
|
||||
loading,
|
||||
onSubmit,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
value={url}
|
||||
onSubmit={(newURL) => onSubmit?.(newURL)}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
loading ? (
|
||||
<CellSkeleton />
|
||||
) : (
|
||||
<RawLink onClick={(e) => e.stopPropagation()} href={url ?? ''}>
|
||||
{url}
|
||||
</RawLink>
|
||||
)
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { InplaceInputTextCellEditMode } from '../../../inplace-input/components/InplaceInputTextCellEditMode';
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
export type EditableChipProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
ChipComponent: React.ReactNode;
|
||||
activityCount?: number;
|
||||
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
rightEndContents?: ReactNode[];
|
||||
onSubmit?: (newValue: string) => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
const NoEditModeContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const RightContainer = styled.div`
|
||||
margin-left: ${(props) => props.theme.spacing(1)};
|
||||
`;
|
||||
|
||||
// TODO: move right end content in EditableCell
|
||||
export function EditableCellChip({
|
||||
value,
|
||||
placeholder,
|
||||
editModeHorizontalAlign,
|
||||
ChipComponent,
|
||||
rightEndContents,
|
||||
onSubmit,
|
||||
}: EditableChipProps) {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value);
|
||||
}, [value]);
|
||||
|
||||
const handleRightEndContentClick = (
|
||||
event: React.MouseEvent<HTMLDivElement>,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={inputValue}
|
||||
onSubmit={(newValue) => onSubmit?.(newValue)}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
<NoEditModeContainer>
|
||||
{ChipComponent}
|
||||
<RightContainer>
|
||||
{rightEndContents &&
|
||||
rightEndContents.length > 0 &&
|
||||
rightEndContents.map((content, index) => (
|
||||
<div key={index} onClick={handleRightEndContentClick}>
|
||||
{content}
|
||||
</div>
|
||||
))}
|
||||
</RightContainer>
|
||||
</NoEditModeContainer>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user