Feat/generic editable cell all types (#987)
* Added generic relation cell * Deactivated debug * Added default warning * Put back display component * Removed unused types * wip * Renamed to view field * Use new view field structure to have chip working * Finished * Added a temp feature flag * Added double text chip cell * Ok * Finished tables * Fixed icon size * Fixed bug on date field * Use icon index * Fix * Fixed naming * Fix * removed file from merge * Fixed tests * Coverage
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
|
||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
||||
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { BoardCardEditableField } from './BoardCardEditableField';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
|
||||
type OwnProps = {
|
||||
value: Date | null;
|
||||
value: Date | string | null;
|
||||
};
|
||||
|
||||
export function InplaceInputDateDisplayMode({ value }: OwnProps) {
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { RawLink } from '@/ui/link/components/RawLink';
|
||||
|
||||
const StyledRawLink = styled(RawLink)`
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
export function InplaceInputURLDisplayMode({ value }: OwnProps) {
|
||||
function handleClick(event: MouseEvent<HTMLElement>) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
const absoluteUrl = value
|
||||
? value.startsWith('http')
|
||||
? value
|
||||
: 'https://' + value
|
||||
: '';
|
||||
|
||||
return (
|
||||
<StyledRawLink href={absoluteUrl} onClick={handleClick}>
|
||||
{value}
|
||||
</StyledRawLink>
|
||||
);
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
||||
|
||||
const DEBUG_HOTKEY_SCOPE = false;
|
||||
const DEBUG_HOTKEY_SCOPE = true;
|
||||
|
||||
export function useScopedHotkeyCallback() {
|
||||
return useRecoilCallback(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
firstValue: string;
|
||||
@ -11,7 +11,7 @@ type OwnProps = {
|
||||
onChange: (firstValue: string, secondValue: string) => void;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
export const StyledDoubleTextContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -30,7 +30,7 @@ export function InplaceInputDoubleText({
|
||||
onChange,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledDoubleTextContainer>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
@ -47,6 +47,6 @@ export function InplaceInputDoubleText({
|
||||
onChange(firstValue, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</StyledDoubleTextContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
import { useRegisterCloseCellHandlers } from '../../table/editable-cell/hooks/useRegisterCloseCellHandlers';
|
||||
|
||||
import { StyledDoubleTextContainer } from './InplaceInputDoubleText';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
${textInputStyle}
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
firstValuePlaceholder?: string;
|
||||
secondValuePlaceholder?: string;
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
onSubmit: (newFirstValue: string, newSecondValue: string) => void;
|
||||
};
|
||||
|
||||
export function InplaceInputDoubleTextCellEditMode({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
secondValuePlaceholder,
|
||||
onSubmit,
|
||||
}: OwnProps) {
|
||||
const [internalFirstValue, setInternalFirstValue] = useState(firstValue);
|
||||
const [internalSecondValue, setInternalSecondValue] = useState(secondValue);
|
||||
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
function handleSubmit() {
|
||||
onSubmit(internalFirstValue, internalSecondValue);
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setInternalFirstValue(firstValue);
|
||||
setInternalSecondValue(secondValue);
|
||||
}
|
||||
|
||||
function handleFirstValueChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setInternalFirstValue(event.target.value);
|
||||
}
|
||||
|
||||
function handleSecondValueChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setInternalSecondValue(event.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setInternalFirstValue(firstValue);
|
||||
}, [firstValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setInternalSecondValue(secondValue);
|
||||
}, [secondValue]);
|
||||
|
||||
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
|
||||
|
||||
return (
|
||||
<StyledDoubleTextContainer ref={wrapperRef}>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
value={internalFirstValue}
|
||||
onChange={handleFirstValueChange}
|
||||
/>
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
placeholder={secondValuePlaceholder}
|
||||
value={internalSecondValue}
|
||||
onChange={handleSecondValueChange}
|
||||
/>
|
||||
</StyledDoubleTextContainer>
|
||||
);
|
||||
}
|
||||
@ -18,7 +18,7 @@ type OwnProps = {
|
||||
onSubmit: (newText: string) => void;
|
||||
};
|
||||
|
||||
export function InplaceInputTextEditMode({
|
||||
export function InplaceInputTextCellEditMode({
|
||||
placeholder,
|
||||
autoFocus,
|
||||
value,
|
||||
@ -11,13 +11,18 @@ const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledIcon = styled.div`
|
||||
display: flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
& > svg {
|
||||
height: ${({ theme }) => theme.icon.size.md}px;
|
||||
width: ${({ theme }) => theme.icon.size.md}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export function ColumnHead({ viewName, viewIcon }: OwnProps) {
|
||||
|
||||
@ -25,9 +25,9 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
|
||||
});
|
||||
}
|
||||
|
||||
const entityFieldMetadata = useContext(ViewFieldContext);
|
||||
const viewField = useContext(ViewFieldContext);
|
||||
|
||||
if (!entityFieldMetadata) {
|
||||
if (!viewField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -37,12 +37,12 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
|
||||
<td
|
||||
onContextMenu={(event) => handleContextMenu(event)}
|
||||
style={{
|
||||
width: entityFieldMetadata.columnSize,
|
||||
minWidth: entityFieldMetadata.columnSize,
|
||||
maxWidth: entityFieldMetadata.columnSize,
|
||||
width: viewField.columnSize,
|
||||
minWidth: viewField.columnSize,
|
||||
maxWidth: viewField.columnSize,
|
||||
}}
|
||||
>
|
||||
<GenericEditableCell fieldDefinition={entityFieldMetadata} />
|
||||
<GenericEditableCell viewField={viewField} />
|
||||
</td>
|
||||
</ColumnIndexContext.Provider>
|
||||
</RecoilScope>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
import { viewFieldsFamilyState } from '../states/viewFieldsState';
|
||||
|
||||
import { ColumnHead } from './ColumnHead';
|
||||
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
||||
|
||||
export function EntityTableHeader() {
|
||||
const viewFields = useRecoilValue(viewFieldsState);
|
||||
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
||||
|
||||
return (
|
||||
<thead>
|
||||
|
||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ViewFieldContext } from '../states/ViewFieldContext';
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
import { viewFieldsFamilyState } from '../states/viewFieldsState';
|
||||
|
||||
import { CheckboxCell } from './CheckboxCell';
|
||||
import { EntityTableCell } from './EntityTableCellV2';
|
||||
@ -13,18 +13,18 @@ const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
`;
|
||||
|
||||
export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
const entityFieldMetadataArray = useRecoilValue(viewFieldsState);
|
||||
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
||||
|
||||
return (
|
||||
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
|
||||
<td>
|
||||
<CheckboxCell />
|
||||
</td>
|
||||
{entityFieldMetadataArray.map((entityFieldMetadata, columnIndex) => {
|
||||
{viewFields.map((viewField, columnIndex) => {
|
||||
return (
|
||||
<ViewFieldContext.Provider
|
||||
value={entityFieldMetadata}
|
||||
key={entityFieldMetadata.columnOrder}
|
||||
value={viewField}
|
||||
key={viewField.columnOrder}
|
||||
>
|
||||
<EntityTableCell cellIndex={columnIndex} />
|
||||
</ViewFieldContext.Provider>
|
||||
|
||||
@ -1,37 +1,54 @@
|
||||
import { ViewFieldDefinition } from '@/ui/table/types/ViewField';
|
||||
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 './GenericEditableChipCell';
|
||||
import { GenericEditableDateCell } from './GenericEditableDateCell';
|
||||
import { GenericEditableDoubleTextCell } from './GenericEditableDoubleTextCell';
|
||||
import { GenericEditableDoubleTextChipCell } from './GenericEditableDoubleTextChipCell';
|
||||
import { GenericEditableNumberCell } from './GenericEditableNumberCell';
|
||||
import { GenericEditablePhoneCell } from './GenericEditablePhoneCell';
|
||||
import { GenericEditableRelationCell } from './GenericEditableRelationCell';
|
||||
import { GenericEditableTextCell } from './GenericEditableTextCell';
|
||||
import { GenericEditableURLCell } from './GenericEditableURLCell';
|
||||
|
||||
type OwnProps = {
|
||||
fieldDefinition: ViewFieldDefinition<unknown>;
|
||||
viewField: ViewFieldDefinition<ViewFieldMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableCell({ fieldDefinition }: OwnProps) {
|
||||
export function GenericEditableCell({ viewField: fieldDefinition }: OwnProps) {
|
||||
if (isViewFieldText(fieldDefinition)) {
|
||||
return (
|
||||
<GenericEditableTextCell
|
||||
viewField={fieldDefinition}
|
||||
editModeHorizontalAlign="left"
|
||||
/>
|
||||
);
|
||||
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}
|
||||
editModeHorizontalAlign="left"
|
||||
/>
|
||||
);
|
||||
return <GenericEditableChipCell viewField={fieldDefinition} />;
|
||||
} else {
|
||||
console.warn(
|
||||
`Unknown field type: ${fieldDefinition.type} in GenericEditableCell`,
|
||||
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`,
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { ViewFieldChipMetadata, ViewFieldDefinition } from '../types/ViewField';
|
||||
|
||||
import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode';
|
||||
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
|
||||
import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldChipMetadata>;
|
||||
@ -14,17 +14,12 @@ type OwnProps = {
|
||||
export function GenericEditableChipCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
placeholder,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableTextCellEditMode
|
||||
fieldName={viewField.metadata.contentFieldName}
|
||||
viewFieldId={viewField.id}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<GenericEditableChipCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={
|
||||
<GenericEditableChipCellDisplayMode fieldDefinition={viewField} />
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
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 '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { InplaceInputDateDisplayMode } from '@/ui/display/component/InplaceInputDateDisplayMode';
|
||||
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 '../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={<InplaceInputDateDisplayMode value={fieldValue} />}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
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 { EditableCellDateEditMode } from '../editable-cell/types/EditableCellDateEditMode';
|
||||
import { ViewFieldDateMetadata, ViewFieldDefinition } from '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<EditableCellDateEditMode
|
||||
value={DateTime.fromISO(fieldValue).toJSDate()}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
||||
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 '../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={
|
||||
<InplaceInputTextDisplayMode>{displayName}</InplaceInputTextDisplayMode>
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { InplaceInputDoubleTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputDoubleTextCellEditMode';
|
||||
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 '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<InplaceInputDoubleTextCellEditMode
|
||||
firstValuePlaceholder={viewField.metadata.firstValuePlaceholder}
|
||||
secondValuePlaceholder={viewField.metadata.secondValuePlaceholder}
|
||||
firstValue={firstValue ?? ''}
|
||||
secondValue={secondValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
|
||||
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
} from '../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,51 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
import { PersonChip } from '@/people/components/PersonChip';
|
||||
import { Entity } from '@/ui/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 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} />;
|
||||
}
|
||||
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 { EditableCellDoubleTextEditMode } from '../editable-cell/types/EditableCellDoubleTextEditMode';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
} from '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<EditableCellDoubleTextEditMode
|
||||
firstValuePlaceholder={viewField.metadata.firstValuePlaceholder}
|
||||
secondValuePlaceholder={viewField.metadata.secondValuePlaceholder}
|
||||
firstValue={firstValue ?? ''}
|
||||
secondValue={secondValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
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 '../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,66 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
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 '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<InplaceInputTextCellEditMode
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
|
||||
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 '../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={<InplaceInputPhoneDisplayMode value={fieldValue} />}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
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 '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -24,9 +24,7 @@ export function GenericEditableRelationCell({
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
|
||||
editModeContent={
|
||||
<GenericEditableRelationCellEditMode
|
||||
viewFieldDefinition={fieldDefinition}
|
||||
/>
|
||||
<GenericEditableRelationCellEditMode viewField={fieldDefinition} />
|
||||
}
|
||||
nonEditModeContent={
|
||||
<GenericEditableRelationCellDisplayMode
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldRelationMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { UserChip } from '@/users/components/UserChip';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
type OwnProps = {
|
||||
@ -21,6 +22,7 @@ export function GenericEditableRelationCellDisplayMode({
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
// TODO: type value with generic getter
|
||||
const fieldValue = useRecoilValue<any | null>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
@ -38,6 +40,14 @@ export function GenericEditableRelationCellDisplayMode({
|
||||
/>
|
||||
);
|
||||
}
|
||||
case Entity.User: {
|
||||
return (
|
||||
<UserChip
|
||||
id={fieldValue?.id ?? ''}
|
||||
name={fieldValue?.displayName ?? ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CompanyPickerCell } from '@/companies/components/CompanyPickerCell';
|
||||
import { useUpdateEntityField } from '@/people/hooks/useUpdateEntityField';
|
||||
import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
|
||||
import { Entity } from '@/ui/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 = {
|
||||
viewFieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
|
||||
viewField: ViewFieldDefinition<ViewFieldRelationMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableRelationCellEditMode({
|
||||
viewFieldDefinition,
|
||||
}: OwnProps) {
|
||||
export function GenericEditableRelationCellEditMode({ viewField }: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
@ -26,7 +25,7 @@ export function GenericEditableRelationCellEditMode({
|
||||
const [fieldValueEntity] = useRecoilState<any | null>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewFieldDefinition.metadata.fieldName,
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -38,11 +37,7 @@ export function GenericEditableRelationCellEditMode({
|
||||
currentRowEntityId &&
|
||||
updateEntityField
|
||||
) {
|
||||
updateEntityField(
|
||||
currentRowEntityId,
|
||||
viewFieldDefinition.id,
|
||||
newFieldEntity,
|
||||
);
|
||||
updateEntityField(currentRowEntityId, viewField, newFieldEntity);
|
||||
}
|
||||
|
||||
closeEditableCell();
|
||||
@ -52,7 +47,7 @@ export function GenericEditableRelationCellEditMode({
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
switch (viewFieldDefinition.metadata.relationType) {
|
||||
switch (viewField.metadata.relationType) {
|
||||
case Entity.Company: {
|
||||
return (
|
||||
<CompanyPickerCell
|
||||
@ -62,9 +57,18 @@ export function GenericEditableRelationCellEditMode({
|
||||
/>
|
||||
);
|
||||
}
|
||||
case Entity.User: {
|
||||
return (
|
||||
<UserPicker
|
||||
userId={fieldValueEntity?.id ?? null}
|
||||
onSubmit={handleEntitySubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`Unknown relation type: "${viewFieldDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||
`Unknown relation type: "${viewField.metadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -12,13 +12,11 @@ import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMo
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldTextMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function GenericEditableTextCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
placeholder,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
@ -33,11 +31,7 @@ export function GenericEditableTextCell({
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableTextCellEditMode
|
||||
fieldName={viewField.metadata.fieldName}
|
||||
viewFieldId={viewField.id}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<GenericEditableTextCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={
|
||||
<InplaceInputTextDisplayMode>{fieldValue}</InplaceInputTextDisplayMode>
|
||||
|
||||
@ -1,28 +1,24 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useUpdateEntityField } from '@/people/hooks/useUpdateEntityField';
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
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 '../types/ViewField';
|
||||
|
||||
type OwnProps = {
|
||||
fieldName: string;
|
||||
viewFieldId: string;
|
||||
placeholder?: string;
|
||||
viewField: ViewFieldDefinition<ViewFieldTextMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableTextCellEditMode({
|
||||
fieldName,
|
||||
viewFieldId,
|
||||
placeholder,
|
||||
}: OwnProps) {
|
||||
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,
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -34,13 +30,13 @@ export function GenericEditableTextCellEditMode({
|
||||
setFieldValue(newText);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewFieldId, newText);
|
||||
updateField(currentRowEntityId, viewField, newText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<InplaceInputTextEditMode
|
||||
placeholder={placeholder ?? ''}
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { InplaceInputURLDisplayMode } from '@/ui/display/component/InplaceInputURLDisplayMode';
|
||||
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 '../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,45 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
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 '../types/ViewField';
|
||||
|
||||
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 (
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { defaultOrderBy } from '../../../people/queries';
|
||||
|
||||
export function GenericEntityTableData({
|
||||
useGetRequest,
|
||||
getRequestResultKey,
|
||||
orderBy = defaultOrderBy,
|
||||
whereFilters,
|
||||
viewFields,
|
||||
filterDefinitionArray,
|
||||
}: {
|
||||
useGetRequest: any;
|
||||
getRequestResultKey: string;
|
||||
orderBy?: any;
|
||||
whereFilters?: any;
|
||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
filterDefinitionArray: FilterDefinition[];
|
||||
}) {
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
useGetRequest({
|
||||
variables: { orderBy, where: whereFilters },
|
||||
onCompleted: (data: any) => {
|
||||
const entities = data[getRequestResultKey] ?? [];
|
||||
|
||||
setEntityTableData(entities, viewFields, filterDefinitionArray);
|
||||
},
|
||||
});
|
||||
|
||||
return <></>;
|
||||
}
|
||||
@ -20,7 +20,7 @@ export function EditableCellDate({
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<EditableCellDateEditMode onChange={onChange} value={value} />
|
||||
<EditableCellDateEditMode onSubmit={onChange} value={value} />
|
||||
}
|
||||
nonEditModeContent={<InplaceInputDateDisplayMode value={value} />}
|
||||
editHotkeyScope={{ scope: TableHotkeyScope.CellDateEditMode }}
|
||||
|
||||
@ -16,17 +16,17 @@ const EditableCellDateEditModeContainer = styled.div`
|
||||
|
||||
export type EditableDateProps = {
|
||||
value: Date;
|
||||
onChange: (date: Date) => void;
|
||||
onSubmit: (date: Date) => void;
|
||||
};
|
||||
|
||||
export function EditableCellDateEditMode({
|
||||
value,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: EditableDateProps) {
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
|
||||
function handleDateChange(newDate: Date) {
|
||||
onChange(newDate);
|
||||
onSubmit(newDate);
|
||||
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ 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/InplaceInputTextEditMode';
|
||||
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
@ -13,7 +13,7 @@ export function EditableCellPhone({ value, placeholder, onSubmit }: OwnProps) {
|
||||
return (
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
<InplaceInputTextCellEditMode
|
||||
autoFocus
|
||||
placeholder={placeholder || ''}
|
||||
value={value}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { CellSkeleton } from '../components/CellSkeleton';
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
@ -23,7 +23,7 @@ export function EditableCellText({
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={value}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||
import { InplaceInputTextCellEditMode } from '@/ui/inplace-input/components/InplaceInputTextCellEditMode';
|
||||
|
||||
import { RawLink } from '../../../link/components/RawLink';
|
||||
import { CellSkeleton } from '../components/CellSkeleton';
|
||||
@ -25,7 +25,7 @@ export function EditableCellURL({
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
value={url}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { InplaceInputTextEditMode } from '../../../inplace-input/components/InplaceInputTextEditMode';
|
||||
import { InplaceInputTextCellEditMode } from '../../../inplace-input/components/InplaceInputTextCellEditMode';
|
||||
import { EditableCell } from '../components/EditableCell';
|
||||
|
||||
export type EditableChipProps = {
|
||||
@ -52,7 +52,7 @@ export function EditableCellChip({
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
<InplaceInputTextCellEditMode
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={inputValue}
|
||||
|
||||
65
front/src/modules/ui/table/hooks/useSetEntityTableData.ts
Normal file
65
front/src/modules/ui/table/hooks/useSetEntityTableData.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { useContextScopeId } from '@/ui/recoil-scope/hooks/useContextScopeId';
|
||||
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
|
||||
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
|
||||
import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
|
||||
import { TableContext } from '@/ui/table/states/TableContext';
|
||||
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
|
||||
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
|
||||
import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
export function useSetEntityTableData() {
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
const tableContextScopeId = useContextScopeId(TableContext);
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends { id: string }>(
|
||||
newEntityArray: T[],
|
||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[],
|
||||
filters: FilterDefinition[],
|
||||
) => {
|
||||
for (const entity of newEntityArray) {
|
||||
const currentEntity = snapshot
|
||||
.getLoadable(tableEntitiesFamilyState(entity.id))
|
||||
.valueOrThrow();
|
||||
|
||||
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
|
||||
set(tableEntitiesFamilyState(entity.id), entity);
|
||||
}
|
||||
}
|
||||
|
||||
const entityIds = newEntityArray.map((entity) => entity.id);
|
||||
|
||||
set(tableRowIdsState, (currentRowIds) => {
|
||||
if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
return currentRowIds;
|
||||
});
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
set(entityTableDimensionsState, {
|
||||
numberOfColumns: viewFields.length,
|
||||
numberOfRows: entityIds.length,
|
||||
});
|
||||
|
||||
set(availableFiltersScopedState(tableContextScopeId), filters);
|
||||
|
||||
set(viewFieldsFamilyState, viewFields);
|
||||
|
||||
set(isFetchingEntityTableDataState, false);
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
228
front/src/modules/ui/table/hooks/useUpdateEntityField.ts
Normal file
228
front/src/modules/ui/table/hooks/useUpdateEntityField.ts
Normal file
@ -0,0 +1,228 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
|
||||
import { isViewFieldChip } from '@/ui/table/types/guards/isViewFieldChip';
|
||||
import { isViewFieldRelation } from '@/ui/table/types/guards/isViewFieldRelation';
|
||||
import { isViewFieldText } from '@/ui/table/types/guards/isViewFieldText';
|
||||
|
||||
import { isViewFieldChipValue } from '../types/guards/isViewFieldChipValue';
|
||||
import { isViewFieldDate } from '../types/guards/isViewFieldDate';
|
||||
import { isViewFieldDateValue } from '../types/guards/isViewFieldDateValue';
|
||||
import { isViewFieldDoubleText } from '../types/guards/isViewFieldDoubleText';
|
||||
import { isViewFieldDoubleTextChip } from '../types/guards/isViewFieldDoubleTextChip';
|
||||
import { isViewFieldDoubleTextChipValue } from '../types/guards/isViewFieldDoubleTextChipValue';
|
||||
import { isViewFieldDoubleTextValue } from '../types/guards/isViewFieldDoubleTextValue';
|
||||
import { isViewFieldNumber } from '../types/guards/isViewFieldNumber';
|
||||
import { isViewFieldNumberValue } from '../types/guards/isViewFieldNumberValue';
|
||||
import { isViewFieldPhone } from '../types/guards/isViewFieldPhone';
|
||||
import { isViewFieldPhoneValue } from '../types/guards/isViewFieldPhoneValue';
|
||||
import { isViewFieldRelationValue } from '../types/guards/isViewFieldRelationValue';
|
||||
import { isViewFieldTextValue } from '../types/guards/isViewFieldTextValue';
|
||||
import { isViewFieldURL } from '../types/guards/isViewFieldURL';
|
||||
import { isViewFieldURLValue } from '../types/guards/isViewFieldURLValue';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldChipValue,
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDateValue,
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
ViewFieldDoubleTextChipValue,
|
||||
ViewFieldDoubleTextMetadata,
|
||||
ViewFieldDoubleTextValue,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldNumberMetadata,
|
||||
ViewFieldNumberValue,
|
||||
ViewFieldPhoneMetadata,
|
||||
ViewFieldPhoneValue,
|
||||
ViewFieldRelationMetadata,
|
||||
ViewFieldRelationValue,
|
||||
ViewFieldTextMetadata,
|
||||
ViewFieldTextValue,
|
||||
ViewFieldURLMetadata,
|
||||
ViewFieldURLValue,
|
||||
} from '../types/ViewField';
|
||||
|
||||
export function useUpdateEntityField() {
|
||||
const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
|
||||
|
||||
const [updateEntity] = useUpdateEntityMutation();
|
||||
|
||||
return function updatePeopleField<
|
||||
MetadataType extends ViewFieldMetadata,
|
||||
ValueType extends MetadataType extends ViewFieldDoubleTextMetadata
|
||||
? ViewFieldDoubleTextValue
|
||||
: MetadataType extends ViewFieldTextMetadata
|
||||
? ViewFieldTextValue
|
||||
: MetadataType extends ViewFieldPhoneMetadata
|
||||
? ViewFieldPhoneValue
|
||||
: MetadataType extends ViewFieldURLMetadata
|
||||
? ViewFieldURLValue
|
||||
: MetadataType extends ViewFieldNumberMetadata
|
||||
? ViewFieldNumberValue
|
||||
: MetadataType extends ViewFieldDateMetadata
|
||||
? ViewFieldDateValue
|
||||
: MetadataType extends ViewFieldChipMetadata
|
||||
? ViewFieldChipValue
|
||||
: MetadataType extends ViewFieldDoubleTextChipMetadata
|
||||
? ViewFieldDoubleTextChipValue
|
||||
: MetadataType extends ViewFieldRelationMetadata
|
||||
? ViewFieldRelationValue
|
||||
: unknown,
|
||||
>(
|
||||
currentEntityId: string,
|
||||
viewField: ViewFieldDefinition<MetadataType>,
|
||||
newFieldValue: ValueType,
|
||||
) {
|
||||
const newFieldValueUnknown = newFieldValue as unknown;
|
||||
// TODO: improve type guards organization, maybe with a common typeguard for all view fields
|
||||
// taking an object of options as parameter ?
|
||||
//
|
||||
// The goal would be to check that the view field value not only is valid,
|
||||
// but also that it is validated against the corresponding view field type
|
||||
|
||||
// Relation
|
||||
if (
|
||||
isViewFieldRelation(viewField) &&
|
||||
isViewFieldRelationValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newSelectedEntity = newFieldValueUnknown;
|
||||
|
||||
const fieldName = viewField.metadata.fieldName;
|
||||
|
||||
if (!newSelectedEntity) {
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: {
|
||||
[fieldName]: {
|
||||
disconnect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: {
|
||||
[fieldName]: {
|
||||
connect: { id: newSelectedEntity.id },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
// Chip
|
||||
} else if (
|
||||
isViewFieldChip(viewField) &&
|
||||
isViewFieldChipValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.contentFieldName]: newContent },
|
||||
},
|
||||
});
|
||||
// Text
|
||||
} else if (
|
||||
isViewFieldText(viewField) &&
|
||||
isViewFieldTextValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.fieldName]: newContent },
|
||||
},
|
||||
});
|
||||
// Double text
|
||||
} else if (
|
||||
isViewFieldDoubleText(viewField) &&
|
||||
isViewFieldDoubleTextValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: {
|
||||
[viewField.metadata.firstValueFieldName]: newContent.firstValue,
|
||||
[viewField.metadata.secondValueFieldName]: newContent.secondValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
// Double Text Chip
|
||||
} else if (
|
||||
isViewFieldDoubleTextChip(viewField) &&
|
||||
isViewFieldDoubleTextChipValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: {
|
||||
[viewField.metadata.firstValueFieldName]: newContent.firstValue,
|
||||
[viewField.metadata.secondValueFieldName]: newContent.secondValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
// Phone
|
||||
} else if (
|
||||
isViewFieldPhone(viewField) &&
|
||||
isViewFieldPhoneValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.fieldName]: newContent },
|
||||
},
|
||||
});
|
||||
// URL
|
||||
} else if (
|
||||
isViewFieldURL(viewField) &&
|
||||
isViewFieldURLValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.fieldName]: newContent },
|
||||
},
|
||||
});
|
||||
// Number
|
||||
} else if (
|
||||
isViewFieldNumber(viewField) &&
|
||||
isViewFieldNumberValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.fieldName]: newContent },
|
||||
},
|
||||
});
|
||||
// Date
|
||||
} else if (
|
||||
isViewFieldDate(viewField) &&
|
||||
isViewFieldDateValue(newFieldValueUnknown)
|
||||
) {
|
||||
const newContent = newFieldValueUnknown;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.fieldName]: newContent },
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { ViewFieldDefinition } from '../types/ViewField';
|
||||
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';
|
||||
|
||||
export const ViewFieldContext =
|
||||
createContext<ViewFieldDefinition<unknown> | null>(null);
|
||||
createContext<ViewFieldDefinition<ViewFieldMetadata> | null>(null);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { ViewFieldDefinition } from '../types/ViewField';
|
||||
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';
|
||||
|
||||
export const viewFieldsState = atom<ViewFieldDefinition<unknown>[]>({
|
||||
key: 'viewFieldsState',
|
||||
export const viewFieldsFamilyState = atom<
|
||||
ViewFieldDefinition<ViewFieldMetadata>[]
|
||||
>({
|
||||
key: 'viewFieldsFamilyState',
|
||||
default: [],
|
||||
});
|
||||
|
||||
@ -1,36 +1,114 @@
|
||||
import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
|
||||
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
|
||||
|
||||
export type ViewFieldType = 'text' | 'relation' | 'chip';
|
||||
export type ViewFieldType =
|
||||
| 'text'
|
||||
| 'relation'
|
||||
| 'chip'
|
||||
| 'double-text-chip'
|
||||
| 'double-text'
|
||||
| 'number'
|
||||
| 'date'
|
||||
| 'phone'
|
||||
| 'url';
|
||||
|
||||
export type ViewFieldTextMetadata = {
|
||||
type: 'text';
|
||||
placeHolder: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldPhoneMetadata = {
|
||||
type: 'phone';
|
||||
placeHolder: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldURLMetadata = {
|
||||
type: 'url';
|
||||
placeHolder: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldDateMetadata = {
|
||||
type: 'date';
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldNumberMetadata = {
|
||||
type: 'number';
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldRelationMetadata = {
|
||||
type: 'relation';
|
||||
relationType: Entity;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldChipMetadata = {
|
||||
type: 'chip';
|
||||
relationType: Entity;
|
||||
contentFieldName: string;
|
||||
urlFieldName: string;
|
||||
placeHolder: string;
|
||||
};
|
||||
|
||||
export type ViewFieldDefinition<
|
||||
T extends
|
||||
| ViewFieldTextMetadata
|
||||
| ViewFieldRelationMetadata
|
||||
| ViewFieldChipMetadata
|
||||
| unknown,
|
||||
> = {
|
||||
export type ViewFieldDoubleTextMetadata = {
|
||||
type: 'double-text';
|
||||
firstValueFieldName: string;
|
||||
firstValuePlaceholder: string;
|
||||
secondValueFieldName: string;
|
||||
secondValuePlaceholder: string;
|
||||
};
|
||||
|
||||
export type ViewFieldDoubleTextChipMetadata = {
|
||||
type: 'double-text-chip';
|
||||
firstValueFieldName: string;
|
||||
firstValuePlaceholder: string;
|
||||
secondValueFieldName: string;
|
||||
secondValuePlaceholder: string;
|
||||
entityType: Entity;
|
||||
};
|
||||
|
||||
export type ViewFieldMetadata = { type: ViewFieldType } & (
|
||||
| ViewFieldTextMetadata
|
||||
| ViewFieldRelationMetadata
|
||||
| ViewFieldChipMetadata
|
||||
| ViewFieldDoubleTextChipMetadata
|
||||
| ViewFieldDoubleTextMetadata
|
||||
| ViewFieldPhoneMetadata
|
||||
| ViewFieldURLMetadata
|
||||
| ViewFieldNumberMetadata
|
||||
| ViewFieldDateMetadata
|
||||
);
|
||||
|
||||
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
|
||||
id: string;
|
||||
columnLabel: string;
|
||||
columnSize: number;
|
||||
columnOrder: number;
|
||||
columnIcon?: JSX.Element;
|
||||
filterIcon?: JSX.Element;
|
||||
type: ViewFieldType;
|
||||
metadata: T;
|
||||
};
|
||||
|
||||
export type ViewFieldTextValue = string;
|
||||
|
||||
export type ViewFieldChipValue = string;
|
||||
export type ViewFieldDateValue = string;
|
||||
export type ViewFieldPhoneValue = string;
|
||||
export type ViewFieldURLValue = string;
|
||||
export type ViewFieldNumberValue = number;
|
||||
|
||||
export type ViewFieldDoubleTextValue = {
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
};
|
||||
|
||||
export type ViewFieldDoubleTextChipValue = {
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
};
|
||||
|
||||
export type ViewFieldRelationValue = EntityForSelect | null;
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { ViewFieldChipMetadata, ViewFieldDefinition } from '../ViewField';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldChip(
|
||||
field: ViewFieldDefinition<unknown>,
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldChipMetadata> {
|
||||
return field.type === 'chip';
|
||||
return field.metadata.type === 'chip';
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldChipValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldChipValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldChipValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'string'
|
||||
);
|
||||
}
|
||||
11
front/src/modules/ui/table/types/guards/isViewFieldDate.ts
Normal file
11
front/src/modules/ui/table/types/guards/isViewFieldDate.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldDate(
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldDateMetadata> {
|
||||
return field.metadata.type === 'date';
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldDateValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldDateValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldDateValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'string'
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextMetadata,
|
||||
ViewFieldMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldDoubleText(
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldDoubleTextMetadata> {
|
||||
return field.metadata.type === 'double-text';
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
ViewFieldMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldDoubleTextChip(
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldDoubleTextChipMetadata> {
|
||||
return field.metadata.type === 'double-text-chip';
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldDoubleTextChipValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldDoubleTextChipValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldDoubleTextChipValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'object'
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldDoubleTextValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldDoubleTextValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldDoubleTextValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'object'
|
||||
);
|
||||
}
|
||||
11
front/src/modules/ui/table/types/guards/isViewFieldNumber.ts
Normal file
11
front/src/modules/ui/table/types/guards/isViewFieldNumber.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldNumberMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldNumber(
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldNumberMetadata> {
|
||||
return field.metadata.type === 'number';
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldNumberValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldNumberValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldNumberValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'number'
|
||||
);
|
||||
}
|
||||
11
front/src/modules/ui/table/types/guards/isViewFieldPhone.ts
Normal file
11
front/src/modules/ui/table/types/guards/isViewFieldPhone.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldPhoneMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldPhone(
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldPhoneMetadata> {
|
||||
return field.metadata.type === 'phone';
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldPhoneValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldPhoneValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldPhoneValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'string'
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
import { ViewFieldDefinition, ViewFieldRelationMetadata } from '../ViewField';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldRelationMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldRelation(
|
||||
field: ViewFieldDefinition<unknown>,
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldRelationMetadata> {
|
||||
return field.type === 'relation';
|
||||
return field.metadata.type === 'relation';
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldRelationValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldRelationValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldRelationValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'object'
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
import { ViewFieldDefinition, ViewFieldTextMetadata } from '../ViewField';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldText(
|
||||
field: ViewFieldDefinition<unknown>,
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldTextMetadata> {
|
||||
return field.type === 'text';
|
||||
return field.metadata.type === 'text';
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldTextValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldTextValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldTextValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'string'
|
||||
);
|
||||
}
|
||||
11
front/src/modules/ui/table/types/guards/isViewFieldURL.ts
Normal file
11
front/src/modules/ui/table/types/guards/isViewFieldURL.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldURLMetadata,
|
||||
} from '../ViewField';
|
||||
|
||||
export function isViewFieldURL(
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
): field is ViewFieldDefinition<ViewFieldURLMetadata> {
|
||||
return field.metadata.type === 'url';
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewFieldURLValue } from '../ViewField';
|
||||
|
||||
// TODO: add yup
|
||||
export function isViewFieldURLValue(
|
||||
fieldValue: unknown,
|
||||
): fieldValue is ViewFieldURLValue {
|
||||
return (
|
||||
fieldValue !== null &&
|
||||
fieldValue !== undefined &&
|
||||
typeof fieldValue === 'string'
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user