Use FieldDefinition for company show page (#1171)

* Use FieldDefinition for company show page

* removing console.log

* fix conflicts

* fix address placeholder + company show page field definition ordering

* fix story

* add replacePlaceholder

* use AppPath enum in stories

* add routeParams

* fix people input story
This commit is contained in:
Weiko
2023-08-11 14:31:52 -07:00
committed by GitHub
parent 3978ef4edb
commit 4eb4d1488c
43 changed files with 463 additions and 478 deletions

View File

@ -1,7 +1,10 @@
import { createContext } from 'react';
import { FieldDefinition } from '@/ui/editable-field/types/FieldDefinition';
import { FieldMetadata } from '@/ui/editable-field/types/FieldMetadata';
import {
FieldMetadata,
FieldType,
} from '@/ui/editable-field/types/FieldMetadata';
export const FieldDefinitionContext = createContext<
FieldDefinition<FieldMetadata>
@ -9,6 +12,6 @@ export const FieldDefinitionContext = createContext<
id: '',
label: '',
icon: undefined,
type: '',
type: 'unknown' satisfies FieldType,
metadata: {} as FieldMetadata,
});

View File

@ -5,10 +5,14 @@ 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 { isFieldText } from '../types/guards/isFieldText';
import { isFieldURL } from '../types/guards/isFieldURL';
import { GenericEditableDateField } from './GenericEditableDateField';
import { GenericEditableNumberField } from './GenericEditableNumberField';
import { GenericEditableRelationField } from './GenericEditableRelationField';
import { GenericEditableTextField } from './GenericEditableTextField';
import { GenericEditableURLField } from './GenericEditableURLField';
import { ProbabilityEditableField } from './ProbabilityEditableField';
export function GenericEditableField() {
@ -22,9 +26,13 @@ export function GenericEditableField() {
return <GenericEditableNumberField />;
} else if (isFieldProbability(fieldDefinition)) {
return <ProbabilityEditableField />;
} else if (isFieldURL(fieldDefinition)) {
return <GenericEditableURLField />;
} else if (isFieldText(fieldDefinition)) {
return <GenericEditableTextField />;
} else {
console.warn(
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`,
`Unknown field metadata type: ${fieldDefinition.type} in GenericEditableField`,
);
return <></>;
}

View File

@ -69,6 +69,7 @@ export function GenericEditableNumberFieldEditMode() {
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue ? internalValue.toString() : ''}
onChange={(newValue: string) => {
handleChange(newValue);

View File

@ -34,7 +34,7 @@ export function GenericEditableRelationField() {
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
useEditButton
useEditButton={currentEditableFieldDefinition.metadata.useEditButton}
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}

View File

@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
import { PersonChip } from '@/people/components/PersonChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { UserChip } from '@/users/components/UserChip';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
@ -35,6 +36,15 @@ export function GenericEditableRelationFieldDisplayMode() {
/>
);
}
case Entity.User: {
return (
<UserChip
id={fieldValue?.id ?? ''}
name={fieldValue?.displayName ?? ''}
pictureUrl={fieldValue?.avatarUrl ?? ''}
/>
);
}
default:
console.warn(
`Unknown relation type: "${currentEditableFieldDefinition.metadata.relationType}"

View File

@ -3,9 +3,9 @@ import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { PeoplePicker } from '@/people/components/PeoplePicker';
import { ViewFieldRelationValue } from '@/ui/editable-field/types/ViewField';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { UserPicker } from '@/users/components/UserPicker';
import { useEditableField } from '../hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
@ -13,7 +13,10 @@ import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitio
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldRelationMetadata } from '../types/FieldMetadata';
import {
FieldRelationMetadata,
FieldRelationValue,
} from '../types/FieldMetadata';
const RelationPickerContainer = styled.div`
left: 0px;
@ -28,7 +31,7 @@ function RelationPicker({
handleCancel,
}: {
fieldDefinition: FieldDefinition<FieldRelationMetadata>;
fieldValue: ViewFieldRelationValue;
fieldValue: FieldRelationValue;
handleEntitySubmit: (newRelationId: EntityForSelect | null) => void;
handleCancel: () => void;
}) {
@ -36,7 +39,16 @@ function RelationPicker({
case Entity.Person: {
return (
<PeoplePicker
personId={fieldValue?.id ?? null}
personId={fieldValue ? fieldValue.id : ''}
onSubmit={handleEntitySubmit}
onCancel={handleCancel}
/>
);
}
case Entity.User: {
return (
<UserPicker
userId={fieldValue ? fieldValue.id : ''}
onSubmit={handleEntitySubmit}
onCancel={handleCancel}
/>
@ -46,7 +58,7 @@ function RelationPicker({
console.warn(
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`,
);
return <> </>;
return <></>;
}
}

View File

@ -0,0 +1,41 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
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 { GenericEditableTextFieldEditMode } from './GenericEditableTextFieldEditMode';
export function GenericEditableTextField() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldNumberMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableTextFieldEditMode />}
displayModeContent={fieldValue}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,72 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldTextMetadata } from '../types/FieldMetadata';
export function GenericEditableTextFieldEditMode() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldTextMetadata>;
// 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: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const [internalValue, setInternalValue] = useState(fieldValue);
const updateField = useUpdateGenericEntityField();
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
function handleSubmit() {
if (internalValue === fieldValue) return;
setFieldValue(internalValue);
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
internalValue,
);
}
}
function onCancel() {
setFieldValue(fieldValue);
}
function handleChange(newValue: string) {
setInternalValue(newValue);
}
return (
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
</div>
);
}

View File

@ -0,0 +1,43 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
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 { FieldDisplayURL } from './FieldDisplayURL';
import { GenericEditableURLFieldEditMode } from './GenericEditableURLFieldEditMode';
export function GenericEditableURLField() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldNumberMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
useEditButton
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableURLFieldEditMode />}
displayModeContent={<FieldDisplayURL URL={fieldValue} />}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,74 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldURLMetadata } from '../types/FieldMetadata';
// This one is very similar to GenericEditableTextFieldEditMode
// We could probably merge them since FieldURLMetadata is basically a FieldTextMetadata
export function GenericEditableURLFieldEditMode() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldURLMetadata>;
// 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: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const [internalValue, setInternalValue] = useState(fieldValue);
const updateField = useUpdateGenericEntityField();
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
function handleSubmit() {
if (internalValue === fieldValue) return;
setFieldValue(internalValue);
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
internalValue,
);
}
}
function onCancel() {
setFieldValue(fieldValue);
}
function handleChange(newValue: string) {
setInternalValue(newValue);
}
return (
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
</div>
);
}

View File

@ -1,7 +1,5 @@
import { useContext } from 'react';
import { isFieldChip } from '@/ui/editable-field/types/guards/isFieldChip';
import { EditableFieldMutationContext } from '../states/EditableFieldMutationContext';
import { FieldDefinition } from '../types/FieldDefinition';
import {
@ -27,6 +25,7 @@ import {
FieldURLMetadata,
FieldURLValue,
} from '../types/FieldMetadata';
import { isFieldChip } from '../types/guards/isFieldChip';
import { isFieldChipValue } from '../types/guards/isFieldChipValue';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDateValue } from '../types/guards/isFieldDateValue';
@ -53,31 +52,30 @@ export function useUpdateGenericEntityField() {
const [updateEntity] = useUpdateEntityMutation();
return function updateEntityField<
MetadataType extends FieldMetadata,
ValueType extends MetadataType extends FieldDoubleTextMetadata
ValueType extends FieldMetadata extends FieldDoubleTextMetadata
? FieldDoubleTextValue
: MetadataType extends FieldTextMetadata
: FieldMetadata extends FieldTextMetadata
? FieldTextValue
: MetadataType extends FieldPhoneMetadata
: FieldMetadata extends FieldPhoneMetadata
? FieldPhoneValue
: MetadataType extends FieldURLMetadata
: FieldMetadata extends FieldURLMetadata
? FieldURLValue
: MetadataType extends FieldNumberMetadata
: FieldMetadata extends FieldNumberMetadata
? FieldNumberValue
: MetadataType extends FieldDateMetadata
: FieldMetadata extends FieldDateMetadata
? FieldDateValue
: MetadataType extends FieldChipMetadata
: FieldMetadata extends FieldChipMetadata
? FieldChipValue
: MetadataType extends FieldDoubleTextChipMetadata
: FieldMetadata extends FieldDoubleTextChipMetadata
? FieldDoubleTextChipValue
: MetadataType extends FieldRelationMetadata
: FieldMetadata extends FieldRelationMetadata
? FieldRelationValue
: MetadataType extends FieldProbabilityMetadata
: FieldMetadata extends FieldProbabilityMetadata
? FieldProbabilityValue
: unknown,
>(
currentEntityId: string,
field: FieldDefinition<MetadataType>,
field: FieldDefinition<FieldMetadata>,
newFieldValue: ValueType,
) {
const newFieldValueUnknown = newFieldValue as unknown;

View File

@ -1,8 +1,8 @@
import { createContext } from 'react';
import { FieldDefinition } from '../types/FieldDefinition';
import { ViewFieldMetadata } from '../types/ViewField';
import { FieldMetadata } from '../types/FieldMetadata';
export const EditableFieldDefinitionContext = createContext<
FieldDefinition<ViewFieldMetadata>
>({} as FieldDefinition<ViewFieldMetadata>);
FieldDefinition<FieldMetadata>
>({} as FieldDefinition<FieldMetadata>);

View File

@ -1,9 +1,9 @@
import { FieldMetadata } from './FieldMetadata';
import { FieldMetadata, FieldType } from './FieldMetadata';
export type FieldDefinition<T extends FieldMetadata | unknown> = {
id: string;
label: string;
icon?: JSX.Element;
type: string;
type: FieldType;
metadata: T;
};

View File

@ -2,6 +2,7 @@ import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelec
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
export type FieldType =
| 'unknown'
| 'text'
| 'relation'
| 'chip'
@ -14,41 +15,36 @@ export type FieldType =
| 'probability';
export type FieldTextMetadata = {
type: 'text';
placeHolder: string;
fieldName: string;
};
export type FieldPhoneMetadata = {
type: 'phone';
placeHolder: string;
fieldName: string;
};
export type FieldURLMetadata = {
type: 'url';
placeHolder: string;
fieldName: string;
};
export type FieldDateMetadata = {
type: 'date';
fieldName: string;
};
export type FieldNumberMetadata = {
type: 'number';
fieldName: string;
placeHolder: string;
};
export type FieldRelationMetadata = {
type: 'relation';
relationType: Entity;
fieldName: string;
useEditButton?: boolean;
};
export type FieldChipMetadata = {
type: 'chip';
relationType: Entity;
contentFieldName: string;
urlFieldName: string;
@ -56,7 +52,6 @@ export type FieldChipMetadata = {
};
export type FieldDoubleTextMetadata = {
type: 'double-text';
firstValueFieldName: string;
firstValuePlaceholder: string;
secondValueFieldName: string;
@ -64,7 +59,6 @@ export type FieldDoubleTextMetadata = {
};
export type FieldDoubleTextChipMetadata = {
type: 'double-text-chip';
firstValueFieldName: string;
firstValuePlaceholder: string;
secondValueFieldName: string;
@ -74,11 +68,10 @@ export type FieldDoubleTextChipMetadata = {
};
export type FieldProbabilityMetadata = {
type: 'probability';
fieldName: string;
};
export type FieldMetadata = { type: FieldType } & (
export type FieldMetadata =
| FieldTextMetadata
| FieldRelationMetadata
| FieldChipMetadata
@ -88,8 +81,7 @@ export type FieldMetadata = { type: FieldType } & (
| FieldURLMetadata
| FieldNumberMetadata
| FieldDateMetadata
| FieldProbabilityMetadata
);
| FieldProbabilityMetadata;
export type FieldTextValue = string;

View File

@ -46,6 +46,7 @@ export type ViewFieldRelationMetadata = {
type: 'relation';
relationType: Entity;
fieldName: string;
useEditButton?: boolean;
};
export type ViewFieldChipMetadata = {