FieldDisplay & FieldInput (#1708)

* Removed view field duplicate types

* wip

* wip 2

* wip 3

* Unified state for fields

* Renaming

* Wip

* Post merge

* Post post merge

* wip

* Delete unused file

* Boolean and Probability

* Finished InlineCell

* Renamed EditableCell to TableCell

* Finished double texts

* Finished MoneyField

* Fixed bug inline cell click outside

* Fixed hotkey scope

* Final fixes

* Phone

* Fix url and number input validation

* Fix

* Fix position

* wip refactor activity editor

* Fixed activity editor

---------

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

View File

@ -7,10 +7,9 @@ import { useBoardContext } from '@/ui/board/hooks/useBoardContext';
import { useCurrentCardSelected } from '@/ui/board/hooks/useCurrentCardSelected';
import { visibleBoardCardFieldsScopedSelector } from '@/ui/board/states/selectors/visibleBoardCardFieldsScopedSelector';
import { EntityChipVariant } from '@/ui/chip/components/EntityChip';
import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField';
import { EditableFieldDefinitionContext } from '@/ui/editable-field/contexts/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '@/ui/editable-field/contexts/EditableFieldEntityIdContext';
import { EditableFieldMutationContext } from '@/ui/editable-field/contexts/EditableFieldMutationContext';
import { InlineCell } from '@/ui/editable-field/components/InlineCell';
import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope';
import { FieldContext } from '@/ui/field/contexts/FieldContext';
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
@ -158,27 +157,28 @@ export const CompanyBoardCard = () => {
</StyledCheckboxContainer>
</StyledBoardCardHeader>
<StyledBoardCardBody>
<EditableFieldMutationContext.Provider
value={useUpdateOnePipelineProgressMutation}
>
<EditableFieldEntityIdContext.Provider value={boardCardId}>
{visibleBoardCardFields.map((viewField) => (
<PreventSelectOnClickContainer key={viewField.key}>
<EditableFieldDefinitionContext.Provider
value={{
key: viewField.key,
name: viewField.name,
Icon: viewField.Icon,
type: viewField.metadata.type,
metadata: viewField.metadata,
}}
>
<GenericEditableField />
</EditableFieldDefinitionContext.Provider>
</PreventSelectOnClickContainer>
))}
</EditableFieldEntityIdContext.Provider>
</EditableFieldMutationContext.Provider>
{visibleBoardCardFields.map((viewField) => (
<PreventSelectOnClickContainer key={viewField.key}>
<FieldContext.Provider
value={{
entityId: boardCardId,
recoilScopeId: boardCardId + viewField.key,
fieldDefinition: {
key: viewField.key,
name: viewField.name,
Icon: viewField.Icon,
type: viewField.type,
metadata: viewField.metadata,
useEditButton: viewField.useEditButton,
},
useUpdateEntityMutation: useUpdateOnePipelineProgressMutation,
hotkeyScope: EditableFieldHotkeyScope.EditableField,
}}
>
<InlineCell />
</FieldContext.Provider>
</PreventSelectOnClickContainer>
))}
</StyledBoardCardBody>
</StyledBoardCard>
</StyledBoardCardWrapper>

View File

@ -1,102 +0,0 @@
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
import { IconBuildingSkyscraper } from '@/ui/icon';
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
import { DoubleTextCellEdit } from '@/ui/table/editable-cell/type/components/DoubleTextCellEdit';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useInsertOneCompanyMutation } from '~/generated/graphql';
export type OwnProps = {
companyId: string | null;
onSubmit: (newCompany: CompanyPickerSelectedCompany | null) => void;
onCancel?: () => void;
createModeEnabled?: boolean;
width?: number;
};
export type CompanyPickerSelectedCompany = EntityForSelect & {
domainName: string;
};
export const CompanyPickerCell = ({
companyId,
onSubmit,
onCancel,
createModeEnabled,
width,
}: OwnProps) => {
const [isCreateMode, setIsCreateMode] = useRecoilScopedState(
isCreateModeScopedState,
);
const [insertCompany] = useInsertOneCompanyMutation();
const [relationPickerSearchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const setHotkeyScope = useSetHotkeyScope();
const companies = useFilteredSearchCompanyQuery({
searchFilter: relationPickerSearchFilter,
selectedIds: [companyId ?? ''],
});
const handleCompanySelected = async (
company: CompanyPickerSelectedCompany | null | undefined,
) => {
onSubmit(company ?? null);
};
const handleStartCreation = () => {
setIsCreateMode(true);
setHotkeyScope(TableHotkeyScope.CellDoubleTextInput);
};
const handleCreate = async (firstValue: string, secondValue: string) => {
const insertCompanyRequest = await insertCompany({
variables: {
data: {
name: firstValue,
domainName: secondValue,
address: '',
},
},
});
const companyCreated = insertCompanyRequest.data?.createOneCompany;
companyCreated &&
onSubmit({
id: companyCreated.id,
name: companyCreated.name,
entityType: Entity.Company,
domainName: companyCreated.domainName,
});
setIsCreateMode(false);
};
return isCreateMode ? (
<DoubleTextCellEdit
firstValue={relationPickerSearchFilter}
secondValue=""
firstValuePlaceholder="Name"
secondValuePlaceholder="Url"
onSubmit={handleCreate}
/>
) : (
<SingleEntitySelect
EmptyIcon={IconBuildingSkyscraper}
emptyLabel="No Company"
entitiesToSelect={companies.entitiesToSelect}
loading={companies.loading}
onCancel={onCancel}
onCreate={createModeEnabled ? handleStartCreation : undefined}
onEntitySelected={handleCompanySelected}
selectedEntity={companies.selectedEntities[0]}
width={width}
/>
);
};

View File

@ -1,14 +1,14 @@
import {
ViewFieldBooleanMetadata,
ViewFieldChipMetadata,
ViewFieldDateMetadata,
ViewFieldMetadata,
ViewFieldMoneyMetadata,
ViewFieldNumberMetadata,
ViewFieldRelationMetadata,
ViewFieldTextMetadata,
ViewFieldURLMetadata,
} from '@/ui/editable-field/types/ViewField';
FieldBooleanMetadata,
FieldChipMetadata,
FieldDateMetadata,
FieldMetadata,
FieldMoneyMetadata,
FieldNumberMetadata,
FieldRelationMetadata,
FieldTextMetadata,
FieldURLMetadata,
} from '@/ui/field/types/FieldMetadata';
import {
IconBrandLinkedin,
IconBrandX,
@ -24,7 +24,7 @@ import {
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { ColumnDefinition } from '@/ui/table/types/ColumnDefinition';
export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadata>[] =
export const companiesAvailableColumnDefinitions: ColumnDefinition<FieldMetadata>[] =
[
{
key: 'name',
@ -32,125 +32,131 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
Icon: IconBuildingSkyscraper,
size: 180,
index: 0,
type: 'chip',
metadata: {
type: 'chip',
urlFieldName: 'domainName',
contentFieldName: 'name',
relationType: Entity.Company,
placeHolder: 'Company Name',
},
isVisible: true,
} as ColumnDefinition<ViewFieldChipMetadata>,
} satisfies ColumnDefinition<FieldChipMetadata>,
{
key: 'domainName',
name: 'URL',
Icon: IconLink,
size: 100,
index: 1,
type: 'url',
metadata: {
type: 'url',
fieldName: 'domainName',
placeHolder: 'example.com',
},
isVisible: true,
} as ColumnDefinition<ViewFieldURLMetadata>,
useEditButton: true,
} satisfies ColumnDefinition<FieldURLMetadata>,
{
key: 'accountOwner',
name: 'Account Owner',
Icon: IconUserCircle,
size: 150,
index: 2,
type: 'relation',
metadata: {
type: 'relation',
fieldName: 'accountOwner',
relationType: Entity.User,
},
isVisible: true,
} satisfies ColumnDefinition<ViewFieldRelationMetadata>,
} satisfies ColumnDefinition<FieldRelationMetadata>,
{
key: 'createdAt',
name: 'Creation',
Icon: IconCalendarEvent,
size: 150,
index: 3,
type: 'date',
metadata: {
type: 'date',
fieldName: 'createdAt',
},
isVisible: true,
} satisfies ColumnDefinition<ViewFieldDateMetadata>,
} satisfies ColumnDefinition<FieldDateMetadata>,
{
key: 'employees',
name: 'Employees',
Icon: IconUsers,
size: 150,
index: 4,
type: 'number',
metadata: {
type: 'number',
fieldName: 'employees',
isPositive: true,
placeHolder: 'Employees',
},
isVisible: true,
} satisfies ColumnDefinition<ViewFieldNumberMetadata>,
} satisfies ColumnDefinition<FieldNumberMetadata>,
{
key: 'linkedin',
name: 'LinkedIn',
Icon: IconBrandLinkedin,
size: 170,
index: 5,
type: 'url',
metadata: {
type: 'url',
fieldName: 'linkedinUrl',
placeHolder: 'LinkedIn URL',
},
isVisible: true,
} satisfies ColumnDefinition<ViewFieldURLMetadata>,
useEditButton: true,
} satisfies ColumnDefinition<FieldURLMetadata>,
{
key: 'address',
name: 'Address',
Icon: IconMap,
size: 170,
index: 6,
type: 'text',
metadata: {
type: 'text',
fieldName: 'address',
placeHolder: 'Address', // Hack: Fake character to prevent password-manager from filling the field
},
isVisible: true,
} satisfies ColumnDefinition<ViewFieldTextMetadata>,
} satisfies ColumnDefinition<FieldTextMetadata>,
{
key: 'idealCustomerProfile',
name: 'ICP',
Icon: IconTarget,
size: 150,
index: 7,
type: 'boolean',
metadata: {
type: 'boolean',
fieldName: 'idealCustomerProfile',
},
isVisible: false,
} satisfies ColumnDefinition<ViewFieldBooleanMetadata>,
} satisfies ColumnDefinition<FieldBooleanMetadata>,
{
key: 'annualRecurringRevenue',
name: 'ARR',
Icon: IconMoneybag,
size: 150,
index: 8,
type: 'moneyAmount',
metadata: {
type: 'moneyAmount',
fieldName: 'annualRecurringRevenue',
placeHolder: 'ARR',
},
} satisfies ColumnDefinition<ViewFieldMoneyMetadata>,
} satisfies ColumnDefinition<FieldMoneyMetadata>,
{
key: 'xUrl',
name: 'Twitter',
Icon: IconBrandX,
size: 150,
index: 9,
type: 'url',
metadata: {
type: 'url',
fieldName: 'xUrl',
placeHolder: 'X',
},
isVisible: false,
} satisfies ColumnDefinition<ViewFieldURLMetadata>,
useEditButton: true,
} satisfies ColumnDefinition<FieldURLMetadata>,
];

View File

@ -1,12 +1,11 @@
import { useSetRecoilState } from 'recoil';
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState';
import { useGetCompanyQuery } from '~/generated/graphql';
export const useCompanyQuery = (id: string) => {
const updateCompanyShowPage = useSetRecoilState(
genericEntitiesFamilyState(id),
);
const updateCompanyShowPage = useSetRecoilState(entityFieldsFamilyState(id));
return useGetCompanyQuery({
variables: { where: { id } },
onCompleted: (data) => {

View File

@ -28,6 +28,7 @@ export const useFilteredSearchCompanyQuery = ({
avatarUrl: getLogoUrlFromDomainName(company.domainName),
domainName: company.domainName,
avatarType: 'squared',
originalEntity: company,
}),
selectedIds: selectedIds,
limit,

View File

@ -5,7 +5,7 @@ import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardId
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
import { savedBoardColumnsState } from '@/ui/board/states/savedBoardColumnsState';
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState';
import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor';
import { Pipeline } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@ -72,10 +72,7 @@ export const useUpdateCompanyBoard = () =>
if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) {
set(companyProgressesFamilyState(id), companyProgress);
set(
genericEntitiesFamilyState(id),
companyProgress.pipelineProgress,
);
set(entityFieldsFamilyState(id), companyProgress.pipelineProgress);
}
}

View File

@ -15,7 +15,7 @@ export const CompanyTableMockMode = () => {
ViewBarRecoilScopeContext: TableRecoilScopeContext,
}}
>
<EntityTable updateEntityMutation={[useUpdateOneCompanyMutation()]} />
<EntityTable updateEntityMutation={useUpdateOneCompanyMutation} />
</ViewBarContext.Provider>
</>
);