Add FieldDefinition (#1162)

* add fieldDefinition

* update naming

* use a unique contextProvider for editable fields

* remove EntityUpdateMutationHookContext.Provider usage in CompanyBoardCard

* add fieldDefinitionState

* remove unnecessary refetchQueries to avoid re-render

* add FieldMetadata

* add type guards and update useUpdateGenericEntityField

* restore refetchQueries
This commit is contained in:
Weiko
2023-08-10 11:26:27 -07:00
committed by GitHub
parent 80a562d90d
commit 07a8f68ef1
39 changed files with 644 additions and 309 deletions

View File

@ -1,32 +1,31 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
} from '@/ui/editable-field/types/ViewField';
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { parseDate } from '~/utils/date-utils';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldDateMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
import { GenericEditableDateFieldEditMode } from './GenericEditableDateFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldDateMetadata>;
};
export function GenericEditableDateField({ viewField }: OwnProps) {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
export function GenericEditableDateField() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<FieldDateMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
@ -37,10 +36,8 @@ export function GenericEditableDateField({ viewField }: OwnProps) {
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={viewField.columnIcon}
editModeContent={
<GenericEditableDateFieldEditMode viewField={viewField} />
}
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableDateFieldEditMode />}
displayModeContent={<DateInputDisplay value={internalDateValue} />}
isDisplayModeContentEmpty={!fieldValue}
/>

View File

@ -1,28 +1,26 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
} from '@/ui/editable-field/types/ViewField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldDateMetadata } from '../types/FieldMetadata';
import { EditableFieldEditModeDate } from '../variants/components/EditableFieldEditModeDate';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldDateMetadata>;
};
export function GenericEditableDateFieldEditMode({ viewField }: OwnProps) {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
export function GenericEditableDateFieldEditMode() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<FieldDateMetadata>;
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
@ -34,7 +32,11 @@ export function GenericEditableDateFieldEditMode({ viewField }: OwnProps) {
setFieldValue(newDateISO);
if (currentEditableFieldEntityId && updateField) {
updateField(currentEditableFieldEntityId, viewField, newDateISO);
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
newDateISO,
);
}
}

View File

@ -1,34 +1,31 @@
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { useContext } from 'react';
import { isViewFieldDate } from '../types/guards/isViewFieldDate';
import { isViewFieldNumber } from '../types/guards/isViewFieldNumber';
import { isViewFieldProbability } from '../types/guards/isViewFieldProbability';
import { isViewFieldRelation } from '../types/guards/isViewFieldRelation';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldNumber } from '../types/guards/isFieldNumber';
import { isFieldProbability } from '../types/guards/isFieldProbability';
import { isFieldRelation } from '../types/guards/isFieldRelation';
import { GenericEditableDateField } from './GenericEditableDateField';
import { GenericEditableNumberField } from './GenericEditableNumberField';
import { GenericEditableRelationField } from './GenericEditableRelationField';
import { ProbabilityEditableField } from './ProbabilityEditableField';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldMetadata>;
};
export function GenericEditableField() {
const currentEditableField = useContext(EditableFieldContext);
const fieldDefinition = currentEditableField.fieldDefinition;
export function GenericEditableField({ viewField: fieldDefinition }: OwnProps) {
if (isViewFieldDate(fieldDefinition)) {
return <GenericEditableDateField viewField={fieldDefinition} />;
} else if (isViewFieldNumber(fieldDefinition)) {
return <GenericEditableNumberField viewField={fieldDefinition} />;
} else if (isViewFieldRelation(fieldDefinition)) {
return <GenericEditableRelationField viewField={fieldDefinition} />;
} else if (isViewFieldProbability(fieldDefinition)) {
return <ProbabilityEditableField viewField={fieldDefinition} />;
if (isFieldRelation(fieldDefinition)) {
return <GenericEditableRelationField />;
} else if (isFieldDate(fieldDefinition)) {
return <GenericEditableDateField />;
} else if (isFieldNumber(fieldDefinition)) {
return <GenericEditableNumberField />;
} else if (isFieldProbability(fieldDefinition)) {
return <ProbabilityEditableField />;
} else {
console.warn(
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableField`,
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`,
);
return <></>;
}

View File

@ -1,40 +1,37 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldNumberMetadata,
} from '@/ui/editable-field/types/ViewField';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldNumberMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
import { GenericEditableNumberFieldEditMode } from './GenericEditableNumberFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldNumberMetadata>;
};
export function GenericEditableNumberField({ viewField }: OwnProps) {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
export function GenericEditableNumberField() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<FieldNumberMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={viewField.columnIcon}
editModeContent={
<GenericEditableNumberFieldEditMode viewField={viewField} />
}
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableNumberFieldEditMode />}
displayModeContent={fieldValue}
isDisplayModeContentEmpty={!fieldValue}
/>

View File

@ -1,10 +1,6 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldNumberMetadata,
} from '@/ui/editable-field/types/ViewField';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import {
canBeCastAsIntegerOrNull,
@ -13,21 +9,24 @@ import {
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldNumberMetadata } from '../types/FieldMetadata';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldNumberMetadata>;
};
export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
export function GenericEditableNumberFieldEditMode() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<FieldNumberMetadata>;
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<number | null>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const [internalValue, setInternalValue] = useState(
@ -36,6 +35,10 @@ export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) {
const updateField = useUpdateGenericEntityField();
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
function handleSubmit() {
if (!canBeCastAsIntegerOrNull(internalValue)) {
return;
@ -47,7 +50,7 @@ export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) {
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
viewField,
currentEditableFieldDefinition,
castAsIntegerOrNull(internalValue),
);
}
@ -60,9 +63,6 @@ export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) {
function handleChange(newValue: string) {
setInternalValue(newValue);
}
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
return (
<div ref={wrapperRef}>

View File

@ -2,30 +2,25 @@ import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { PersonChip } from '@/people/components/PersonChip';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/editable-field/types/ViewField';
import { ViewFieldRelationMetadata } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldRelationMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
import { GenericEditableRelationFieldEditMode } from './GenericEditableRelationFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldRelationMetadata>;
};
function RelationChip({
fieldDefinition,
fieldValue,
}: {
fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
fieldDefinition: FieldDefinition<FieldRelationMetadata>;
fieldValue: any | null;
}) {
switch (fieldDefinition.metadata.relationType) {
@ -46,13 +41,18 @@ function RelationChip({
}
}
export function GenericEditableRelationField({ viewField }: OwnProps) {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
export function GenericEditableRelationField() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<ViewFieldRelationMetadata>;
const fieldValue = useRecoilValue<any | null>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
@ -64,12 +64,13 @@ export function GenericEditableRelationField({ viewField }: OwnProps) {
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
iconLabel={viewField.columnIcon}
editModeContent={
<GenericEditableRelationFieldEditMode viewField={viewField} />
}
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableRelationFieldEditMode />}
displayModeContent={
<RelationChip fieldDefinition={viewField} fieldValue={fieldValue} />
<RelationChip
fieldDefinition={currentEditableFieldDefinition}
fieldValue={fieldValue}
/>
}
isDisplayModeContentEmpty={!fieldValue}
isDisplayModeFixHeight

View File

@ -4,7 +4,6 @@ import { useRecoilState } from 'recoil';
import { PeoplePicker } from '@/people/components/PeoplePicker';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
ViewFieldRelationValue,
} from '@/ui/editable-field/types/ViewField';
@ -13,8 +12,10 @@ import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useEditableField } from '../hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldRelationMetadata } from '../types/FieldMetadata';
const RelationPickerContainer = styled.div`
left: 0px;
@ -22,17 +23,13 @@ const RelationPickerContainer = styled.div`
top: -8px;
`;
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldRelationMetadata>;
};
function RelationPicker({
fieldDefinition,
fieldValue,
handleEntitySubmit,
handleCancel,
}: {
fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
fieldDefinition: FieldDefinition<FieldRelationMetadata>;
fieldValue: ViewFieldRelationValue;
handleEntitySubmit: (newRelationId: EntityForSelect | null) => void;
handleCancel: () => void;
@ -55,14 +52,19 @@ function RelationPicker({
}
}
export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
export function GenericEditableRelationFieldEditMode() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<ViewFieldRelationMetadata>;
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<any | null>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
@ -79,7 +81,11 @@ export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) {
});
if (currentEditableFieldEntityId && updateField) {
updateField(currentEditableFieldEntityId, viewField, newRelation);
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
newRelation,
);
}
closeEditableField();
@ -92,7 +98,7 @@ export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) {
return (
<RelationPickerContainer>
<RelationPicker
fieldDefinition={viewField}
fieldDefinition={currentEditableFieldDefinition}
fieldValue={fieldValue}
handleEntitySubmit={handleSubmit}
handleCancel={handleCancel}

View File

@ -1,25 +1,22 @@
import { useContext } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import {
ViewFieldDefinition,
ViewFieldProbabilityMetadata,
} from '@/ui/editable-field/types/ViewField';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { ProbabilityEditableFieldEditMode } from './ProbabilityEditableFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldProbabilityMetadata>;
};
export function ProbabilityEditableField() {
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldDefinition = currentEditableField.fieldDefinition;
export function ProbabilityEditableField({ viewField }: OwnProps) {
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={viewField.columnIcon}
displayModeContent={
<ProbabilityEditableFieldEditMode viewField={viewField} />
}
iconLabel={currentEditableFieldDefinition.icon}
displayModeContent={<ProbabilityEditableFieldEditMode />}
displayModeContentOnly
disableHoverEffect
/>

View File

@ -5,12 +5,10 @@ import { useRecoilState } from 'recoil';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { EditableFieldContext } from '../states/EditableFieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldProbabilityMetadata,
} from '../types/ViewField';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldProbabilityMetadata } from '../types/FieldMetadata';
const StyledContainer = styled.div`
align-items: center;
@ -60,10 +58,6 @@ const StyledLabel = styled.div`
width: ${({ theme }) => theme.spacing(12)};
`;
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldProbabilityMetadata>;
};
const PROBABILITY_VALUES = [
{ label: '0%', value: 0 },
{ label: '25%', value: 25 },
@ -72,28 +66,38 @@ const PROBABILITY_VALUES = [
{ label: '100%', value: 100 },
];
export function ProbabilityEditableFieldEditMode({ viewField }: OwnProps) {
export function ProbabilityEditableFieldEditMode() {
const [nextProbabilityIndex, setNextProbabilityIndex] = useState<
number | null
>(null);
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableField = useContext(EditableFieldContext);
const currentEditableFieldEntityId = currentEditableField.entityId;
const currentEditableFieldDefinition =
currentEditableField.fieldDefinition as FieldDefinition<FieldProbabilityMetadata>;
const [fieldValue, setFieldValue] = useRecoilState<number>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: viewField.metadata.fieldName,
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const probabilityIndex = Math.ceil(fieldValue / 25);
const { closeEditableField } = useEditableField();
const updateField = useUpdateGenericEntityField();
const probabilityIndex = Math.ceil(fieldValue / 25);
function handleChange(newValue: number) {
setFieldValue(newValue);
if (currentEditableFieldEntityId && updateField) {
updateField(currentEditableFieldEntityId, viewField, newValue);
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
newValue,
);
}
closeEditableField();
}