diff --git a/front/package.json b/front/package.json index 589b12233..37c90f03b 100644 --- a/front/package.json +++ b/front/package.json @@ -61,7 +61,8 @@ "uuid": "^9.0.0", "web-vitals": "^2.1.4", "xlsx-ugnis": "^0.19.3", - "yup": "^1.2.0" + "yup": "^1.2.0", + "zod": "^3.22.2" }, "scripts": { "start": "PORT=3001 craco start --max-warnings=0", diff --git a/front/src/modules/activities/components/ActivityAssigneePicker.tsx b/front/src/modules/activities/components/ActivityAssigneePicker.tsx index 987c0d31f..17378a4a5 100644 --- a/front/src/modules/activities/components/ActivityAssigneePicker.tsx +++ b/front/src/modules/activities/components/ActivityAssigneePicker.tsx @@ -58,6 +58,7 @@ export const ActivityAssigneePicker = ({ lastName: user.lastName, avatarType: 'rounded', avatarUrl: user.avatarUrl ?? '', + originalEntity: user, }), selectedIds: activity?.accountOwner?.id ? [activity?.accountOwner?.id] : [], }); diff --git a/front/src/modules/activities/components/ActivityEditor.tsx b/front/src/modules/activities/components/ActivityEditor.tsx index c10f018a3..9ff3ae3bf 100644 --- a/front/src/modules/activities/components/ActivityEditor.tsx +++ b/front/src/modules/activities/components/ActivityEditor.tsx @@ -8,9 +8,7 @@ import { ActivityComments } from '@/activities/components/ActivityComments'; import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown'; import { GET_ACTIVITIES } from '@/activities/graphql/queries/getActivities'; import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox'; -import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope'; -import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField'; -import { IconCalendar } from '@/ui/icon/index'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { Activity, @@ -22,6 +20,7 @@ import { import { debounce } from '~/utils/debounce'; import { ActivityAssigneeEditableField } from '../editable-fields/components/ActivityAssigneeEditableField'; +import { ActivityEditorDateField } from '../editable-fields/components/ActivityEditorDateField'; import { ActivityRelationEditableField } from '../editable-fields/components/ActivityRelationEditableField'; import { ACTIVITY_UPDATE_FRAGMENT } from '../graphql/fragments/activityUpdateFragment'; import { CommentForDrawer } from '../types/CommentForDrawer'; @@ -185,26 +184,12 @@ export const ActivityEditor = ({ {activity.type === ActivityType.Task && ( <> - { - updateActivityMutation({ - variables: { - where: { - id: activity.id, - }, - data: { - dueAt: newDate, - }, - }, - refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''], - }); - }} - hotkeyScope={EditableFieldHotkeyScope.EditableField} - /> - + + + + + + )} diff --git a/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx b/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx index 1682680e2..a50f9fc10 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx @@ -1,12 +1,11 @@ -import { EditableField } from '@/ui/editable-field/components/EditableField'; -import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; +import { InlineCell } from '@/ui/editable-field/components/InlineCell'; +import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; +import { FieldDefinition } from '@/ui/field/types/FieldDefinition'; +import { FieldRelationMetadata } from '@/ui/field/types/FieldMetadata'; import { IconUserCircle } from '@/ui/icon'; -import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { UserChip } from '@/users/components/UserChip'; -import { Company, User } from '~/generated/graphql'; - -import { ActivityAssigneeEditableFieldEditMode } from './ActivityAssigneeEditableFieldEditMode'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; +import { Company, User, useUpdateActivityMutation } from '~/generated/graphql'; type OwnProps = { activity: Pick & { @@ -16,32 +15,25 @@ type OwnProps = { export const ActivityAssigneeEditableField = ({ activity }: OwnProps) => { return ( - - - - } - displayModeContent={ - activity.assignee?.displayName ? ( - - ) : ( - <> - ) - } - isDisplayModeContentEmpty={!activity.assignee} - isDisplayModeFixHeight={true} - /> - - + , + useUpdateEntityMutation: useUpdateActivityMutation, + hotkeyScope: EditableFieldHotkeyScope.EditableField, + }} + > + + ); }; diff --git a/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableFieldEditMode.tsx b/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableFieldEditMode.tsx index d9f957999..1087607dd 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableFieldEditMode.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableFieldEditMode.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { ActivityAssigneePicker } from '@/activities/components/ActivityAssigneePicker'; -import { useEditableField } from '@/ui/editable-field/hooks/useEditableField'; +import { useInlineCell } from '@/ui/editable-field/hooks/useInlineCell'; import { Activity, User } from '~/generated/graphql'; const StyledContainer = styled.div` @@ -23,7 +23,7 @@ export const ActivityAssigneeEditableFieldEditMode = ({ onSubmit, onCancel, }: OwnProps) => { - const { closeEditableField } = useEditableField(); + const { closeInlineCell: closeEditableField } = useInlineCell(); const handleSubmit = () => { closeEditableField(); diff --git a/front/src/modules/activities/editable-fields/components/ActivityEditorDateField.tsx b/front/src/modules/activities/editable-fields/components/ActivityEditorDateField.tsx new file mode 100644 index 000000000..23758e390 --- /dev/null +++ b/front/src/modules/activities/editable-fields/components/ActivityEditorDateField.tsx @@ -0,0 +1,38 @@ +import { InlineCell } from '@/ui/editable-field/components/InlineCell'; +import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; +import { FieldDefinition } from '@/ui/field/types/FieldDefinition'; +import { FieldDateMetadata } from '@/ui/field/types/FieldMetadata'; +import { IconCalendar } from '@/ui/icon/index'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; +import { useUpdateActivityMutation } from '~/generated/graphql'; + +type OwnProps = { + activityId: string; +}; + +export const ActivityEditorDateField = ({ activityId }: OwnProps) => { + return ( + + , + useUpdateEntityMutation: useUpdateActivityMutation, + hotkeyScope: EditableFieldHotkeyScope.EditableField, + }} + > + + + + ); +}; diff --git a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx index a81e10ce7..d0e4fa589 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx @@ -1,5 +1,5 @@ import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips'; -import { EditableField } from '@/ui/editable-field/components/EditableField'; +import { InlineCellContainer } from '@/ui/editable-field/components/InlineCellContainer'; import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; import { IconArrowUpRight } from '@/ui/icon'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; @@ -23,7 +23,7 @@ export const ActivityRelationEditableField = ({ activity }: OwnProps) => { return ( - { handleCheckItemsChange(selectedEntityIds, entitiesToSelect); diff --git a/front/src/modules/activities/right-drawer/components/RightDrawerActivity.tsx b/front/src/modules/activities/right-drawer/components/RightDrawerActivity.tsx index 0d09cbd2d..5982ab5cc 100644 --- a/front/src/modules/activities/right-drawer/components/RightDrawerActivity.tsx +++ b/front/src/modules/activities/right-drawer/components/RightDrawerActivity.tsx @@ -1,7 +1,9 @@ import React from 'react'; import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; import { ActivityEditor } from '@/activities/components/ActivityEditor'; +import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState'; import { useGetActivityQuery } from '~/generated/graphql'; import '@blocknote/core/style.css'; @@ -27,12 +29,20 @@ export const RightDrawerActivity = ({ showComment = true, autoFillTitle = false, }: OwnProps) => { + const [, setEntityFields] = useRecoilState( + entityFieldsFamilyState(activityId), + ); + const { data } = useGetActivityQuery({ variables: { activityId: activityId ?? '', }, skip: !activityId, + onCompleted: (data) => { + setEntityFields(data?.findManyActivities[0] ?? {}); + }, }); + const activity = data?.findManyActivities[0]; if (!activity) { diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index cd1cf98de..a8011ea0f 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -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 = () => { - - - {visibleBoardCardFields.map((viewField) => ( - - - - - - ))} - - + {visibleBoardCardFields.map((viewField) => ( + + + + + + ))} diff --git a/front/src/modules/companies/components/CompanyPickerCell.tsx b/front/src/modules/companies/components/CompanyPickerCell.tsx deleted file mode 100644 index 2765261f7..000000000 --- a/front/src/modules/companies/components/CompanyPickerCell.tsx +++ /dev/null @@ -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 ? ( - - ) : ( - - ); -}; diff --git a/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx b/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx index d083ddd49..c4b9077a1 100644 --- a/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx +++ b/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx @@ -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[] = +export const companiesAvailableColumnDefinitions: ColumnDefinition[] = [ { key: 'name', @@ -32,125 +32,131 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + } satisfies ColumnDefinition, { key: 'domainName', name: 'URL', Icon: IconLink, size: 100, index: 1, + type: 'url', metadata: { - type: 'url', fieldName: 'domainName', placeHolder: 'example.com', }, isVisible: true, - } as ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, { 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, + } satisfies ColumnDefinition, { key: 'createdAt', name: 'Creation', Icon: IconCalendarEvent, size: 150, index: 3, + type: 'date', metadata: { - type: 'date', fieldName: 'createdAt', }, isVisible: true, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { 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, + } satisfies ColumnDefinition, { key: 'linkedin', name: 'LinkedIn', Icon: IconBrandLinkedin, size: 170, index: 5, + type: 'url', metadata: { - type: 'url', fieldName: 'linkedinUrl', placeHolder: 'LinkedIn URL', }, isVisible: true, - } satisfies ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, { key: 'address', name: 'Address', Icon: IconMap, size: 170, index: 6, + type: 'text', metadata: { - type: 'text', fieldName: 'address', placeHolder: 'Addre​ss', // Hack: Fake character to prevent password-manager from filling the field }, isVisible: true, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'idealCustomerProfile', name: 'ICP', Icon: IconTarget, size: 150, index: 7, + type: 'boolean', metadata: { - type: 'boolean', fieldName: 'idealCustomerProfile', }, isVisible: false, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'annualRecurringRevenue', name: 'ARR', Icon: IconMoneybag, size: 150, index: 8, + type: 'moneyAmount', metadata: { - type: 'moneyAmount', fieldName: 'annualRecurringRevenue', + placeHolder: 'ARR', }, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'xUrl', name: 'Twitter', Icon: IconBrandX, size: 150, index: 9, + type: 'url', metadata: { - type: 'url', fieldName: 'xUrl', placeHolder: 'X', }, isVisible: false, - } satisfies ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, ]; diff --git a/front/src/modules/companies/hooks/useCompanyQuery.ts b/front/src/modules/companies/hooks/useCompanyQuery.ts index 6f76d5975..bcd7e6a34 100644 --- a/front/src/modules/companies/hooks/useCompanyQuery.ts +++ b/front/src/modules/companies/hooks/useCompanyQuery.ts @@ -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) => { diff --git a/front/src/modules/companies/hooks/useFilteredSearchCompanyQuery.ts b/front/src/modules/companies/hooks/useFilteredSearchCompanyQuery.ts index 76ee3efdd..561d22c6c 100644 --- a/front/src/modules/companies/hooks/useFilteredSearchCompanyQuery.ts +++ b/front/src/modules/companies/hooks/useFilteredSearchCompanyQuery.ts @@ -28,6 +28,7 @@ export const useFilteredSearchCompanyQuery = ({ avatarUrl: getLogoUrlFromDomainName(company.domainName), domainName: company.domainName, avatarType: 'squared', + originalEntity: company, }), selectedIds: selectedIds, limit, diff --git a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts index 38d8fb96d..773cfbe36 100644 --- a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts +++ b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts @@ -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); } } diff --git a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx index c26f7551e..404759618 100644 --- a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx +++ b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx @@ -15,7 +15,7 @@ export const CompanyTableMockMode = () => { ViewBarRecoilScopeContext: TableRecoilScopeContext, }} > - + ); diff --git a/front/src/modules/people/components/PeoplePicker.tsx b/front/src/modules/people/components/PeoplePicker.tsx index 14d654507..9777535fd 100644 --- a/front/src/modules/people/components/PeoplePicker.tsx +++ b/front/src/modules/people/components/PeoplePicker.tsx @@ -55,6 +55,7 @@ export const PeoplePicker = ({ name: `${person.firstName} ${person.lastName}`, avatarType: 'rounded', avatarUrl: person.avatarUrl ?? '', + originalEntity: person, }), orderByField: 'firstName', excludeEntityIds: excludePersonIds, diff --git a/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx b/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx index d209a8250..d72c6a9a7 100644 --- a/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx +++ b/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx @@ -1,13 +1,13 @@ import { - ViewFieldDateMetadata, - ViewFieldDoubleTextChipMetadata, - ViewFieldEmailMetadata, - ViewFieldMetadata, - ViewFieldPhoneMetadata, - ViewFieldRelationMetadata, - ViewFieldTextMetadata, - ViewFieldURLMetadata, -} from '@/ui/editable-field/types/ViewField'; + FieldDateMetadata, + FieldDoubleTextChipMetadata, + FieldEmailMetadata, + FieldMetadata, + FieldPhoneMetadata, + FieldRelationMetadata, + FieldTextMetadata, + FieldURLMetadata, +} from '@/ui/field/types/FieldMetadata'; import { IconBrandLinkedin, IconBrandX, @@ -22,7 +22,7 @@ import { import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { ColumnDefinition } from '@/ui/table/types/ColumnDefinition'; -export const peopleAvailableColumnDefinitions: ColumnDefinition[] = +export const peopleAvailableColumnDefinitions: ColumnDefinition[] = [ { key: 'displayName', @@ -30,8 +30,8 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + } satisfies ColumnDefinition, { key: 'email', name: 'Email', Icon: IconMail, size: 150, + type: 'email', index: 1, metadata: { - type: 'email', fieldName: 'email', placeHolder: 'Ema​il', // Hack: Fake character to prevent password-manager from filling the field }, - } satisfies ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, { key: 'company', name: 'Company', Icon: IconBuildingSkyscraper, size: 150, index: 2, + type: 'relation', metadata: { - type: 'relation', fieldName: 'company', relationType: Entity.Company, }, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'phone', name: 'Phone', Icon: IconPhone, size: 150, index: 3, + type: 'phone', metadata: { - type: 'phone', fieldName: 'phone', placeHolder: 'Phon​e', // Hack: Fake character to prevent password-manager from filling the field }, - } satisfies ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, { key: 'createdAt', name: 'Creation', Icon: IconCalendarEvent, size: 150, index: 4, + type: 'date', metadata: { - type: 'date', fieldName: 'createdAt', }, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'city', name: 'City', Icon: IconMap, size: 150, index: 5, + type: 'text', metadata: { - type: 'text', fieldName: 'city', placeHolder: 'Cit​y', // Hack: Fake character to prevent password-manager from filling the field }, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'jobTitle', name: 'Job title', Icon: IconBriefcase, size: 150, index: 6, + type: 'text', metadata: { - type: 'text', fieldName: 'jobTitle', placeHolder: 'Job title', }, - } satisfies ColumnDefinition, + } satisfies ColumnDefinition, { key: 'linkedin', name: 'LinkedIn', Icon: IconBrandLinkedin, size: 150, index: 7, + type: 'url', metadata: { - type: 'url', fieldName: 'linkedinUrl', placeHolder: 'LinkedIn', }, - } satisfies ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, { key: 'x', name: 'Twitter', Icon: IconBrandX, size: 150, index: 8, + type: 'url', metadata: { - type: 'url', fieldName: 'xUrl', placeHolder: 'X', }, - } satisfies ColumnDefinition, + useEditButton: true, + } satisfies ColumnDefinition, ]; diff --git a/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx b/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx index 5bb71451d..60531b6f9 100644 --- a/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx +++ b/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { DoubleTextInputEdit } from '@/ui/input/components/DoubleTextInputEdit'; +import { EntityTitleDoubleTextInput } from '@/ui/input/components/EntityTitleDoubleTextInput'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { Person, useUpdateOnePersonMutation } from '~/generated/graphql'; @@ -47,7 +47,7 @@ export const PeopleFullNameEditableField = ({ people }: OwnProps) => { return ( - { @@ -20,7 +20,7 @@ export const useCreateActivityForPeople = () => { const relatedEntites: ActivityTargetableEntity[] = []; for (const id of selectedRowIds) { const person = snapshot - .getLoadable(tableEntitiesFamilyState(id)) + .getLoadable(entityFieldsFamilyState(id)) .getValue() as Person; if ( person?.company?.id && diff --git a/front/src/modules/people/hooks/usePersonQuery.ts b/front/src/modules/people/hooks/usePersonQuery.ts index 2c7ad4b9e..887d647c4 100644 --- a/front/src/modules/people/hooks/usePersonQuery.ts +++ b/front/src/modules/people/hooks/usePersonQuery.ts @@ -1,12 +1,11 @@ import { useSetRecoilState } from 'recoil'; -import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState'; +import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState'; import { useGetPersonQuery } from '~/generated/graphql'; export const usePersonQuery = (id: string) => { - const updatePersonShowPage = useSetRecoilState( - genericEntitiesFamilyState(id), - ); + const updatePersonShowPage = useSetRecoilState(entityFieldsFamilyState(id)); + return useGetPersonQuery({ variables: { id }, onCompleted: (data) => { diff --git a/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx b/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx index 22a40615d..0ef1f1aa4 100644 --- a/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx +++ b/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx @@ -1,11 +1,11 @@ +import { BoardFieldDefinition } from '@/ui/board/types/BoardFieldDefinition'; import { - ViewFieldDateMetadata, - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldNumberMetadata, - ViewFieldProbabilityMetadata, - ViewFieldRelationMetadata, -} from '@/ui/editable-field/types/ViewField'; + FieldDateMetadata, + FieldMetadata, + FieldNumberMetadata, + FieldProbabilityMetadata, + FieldRelationMetadata, +} from '@/ui/field/types/FieldMetadata'; import { IconCalendarEvent, IconCurrencyDollar, @@ -14,52 +14,54 @@ import { } from '@/ui/icon'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -export const pipelineAvailableFieldDefinitions: ViewFieldDefinition[] = +export const pipelineAvailableFieldDefinitions: BoardFieldDefinition[] = [ { key: 'closeDate', name: 'Close Date', Icon: IconCalendarEvent, index: 0, + type: 'date', metadata: { - type: 'date', fieldName: 'closeDate', }, isVisible: true, - } satisfies ViewFieldDefinition, + } satisfies BoardFieldDefinition, { key: 'amount', name: 'Amount', Icon: IconCurrencyDollar, index: 1, + type: 'number', metadata: { - type: 'number', fieldName: 'amount', + placeHolder: '0', }, isVisible: true, - } satisfies ViewFieldDefinition, + } satisfies BoardFieldDefinition, { key: 'probability', name: 'Probability', Icon: IconProgressCheck, index: 2, + type: 'probability', metadata: { - type: 'probability', fieldName: 'probability', }, isVisible: true, - } satisfies ViewFieldDefinition, + } satisfies BoardFieldDefinition, { key: 'pointOfContact', name: 'Point of Contact', Icon: IconUser, index: 3, + type: 'relation', metadata: { - type: 'relation', fieldName: 'pointOfContact', relationType: Entity.Person, useEditButton: true, }, isVisible: true, - } satisfies ViewFieldDefinition, + useEditButton: true, + } satisfies BoardFieldDefinition, ]; diff --git a/front/src/modules/ui/board/hooks/useBoardCardFields.ts b/front/src/modules/ui/board/hooks/useBoardCardFields.ts index b763312c5..a06d697e3 100644 --- a/front/src/modules/ui/board/hooks/useBoardCardFields.ts +++ b/front/src/modules/ui/board/hooks/useBoardCardFields.ts @@ -1,39 +1,26 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; +import { ViewFieldForVisibility } from '@/ui/view-bar/types/ViewFieldForVisibility'; import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState'; -import { boardCardFieldsByKeyScopedSelector } from '../states/selectors/boardCardFieldsByKeyScopedSelector'; import { useBoardContext } from './useBoardContext'; export const useBoardCardFields = () => { const { BoardRecoilScopeContext } = useBoardContext(); - const [boardCardFields, setBoardCardFields] = useRecoilScopedState( + const [, setBoardCardFields] = useRecoilScopedState( boardCardFieldsScopedState, BoardRecoilScopeContext, ); - const boardCardFieldsByKey = useRecoilScopedValue( - boardCardFieldsByKeyScopedSelector, - BoardRecoilScopeContext, - ); - const handleFieldVisibilityChange = ( - field: ViewFieldDefinition, - ) => { - const nextFields = boardCardFieldsByKey[field.key] - ? boardCardFields.map((previousField) => - previousField.key === field.key - ? { ...previousField, isVisible: !field.isVisible } - : previousField, - ) - : [...boardCardFields, { ...field, isVisible: true }]; - - setBoardCardFields(nextFields); + const handleFieldVisibilityChange = (field: ViewFieldForVisibility) => { + setBoardCardFields((previousFields) => + previousFields.map((previousField) => + previousField.key === field.key + ? { ...previousField, isVisible: !field.isVisible } + : previousField, + ), + ); }; return { handleFieldVisibilityChange }; diff --git a/front/src/modules/ui/board/states/availableBoardCardFieldsScopedState.ts b/front/src/modules/ui/board/states/availableBoardCardFieldsScopedState.ts index 4f8b40ccd..691b04d79 100644 --- a/front/src/modules/ui/board/states/availableBoardCardFieldsScopedState.ts +++ b/front/src/modules/ui/board/states/availableBoardCardFieldsScopedState.ts @@ -1,12 +1,11 @@ import { atomFamily } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; + +import { BoardFieldDefinition } from '../types/BoardFieldDefinition'; export const availableBoardCardFieldsScopedState = atomFamily< - ViewFieldDefinition[], + BoardFieldDefinition[], string >({ key: 'availableBoardCardFieldsScopedState', diff --git a/front/src/modules/ui/board/states/boardCardFieldsScopedState.ts b/front/src/modules/ui/board/states/boardCardFieldsScopedState.ts index 072965210..225540004 100644 --- a/front/src/modules/ui/board/states/boardCardFieldsScopedState.ts +++ b/front/src/modules/ui/board/states/boardCardFieldsScopedState.ts @@ -1,12 +1,11 @@ import { atomFamily } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; + +import { BoardFieldDefinition } from '../types/BoardFieldDefinition'; export const boardCardFieldsScopedState = atomFamily< - ViewFieldDefinition[], + BoardFieldDefinition[], string >({ key: 'boardCardFieldsScopedState', diff --git a/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts b/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts index c029f169b..1bd5e0a43 100644 --- a/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts +++ b/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts @@ -1,12 +1,11 @@ import { atomFamily } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; + +import { BoardFieldDefinition } from '../types/BoardFieldDefinition'; export const savedBoardCardFieldsFamilyState = atomFamily< - ViewFieldDefinition[], + BoardFieldDefinition[], string | undefined >({ key: 'savedBoardCardFieldsFamilyState', diff --git a/front/src/modules/ui/board/states/selectors/boardCardFieldsByKeyScopedSelector.ts b/front/src/modules/ui/board/states/selectors/boardCardFieldsByKeyScopedSelector.ts index cb8b66208..b08f533f6 100644 --- a/front/src/modules/ui/board/states/selectors/boardCardFieldsByKeyScopedSelector.ts +++ b/front/src/modules/ui/board/states/selectors/boardCardFieldsByKeyScopedSelector.ts @@ -1,10 +1,8 @@ import { selectorFamily } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; +import { BoardFieldDefinition } from '../../types/BoardFieldDefinition'; import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState'; export const boardCardFieldsByKeyScopedSelector = selectorFamily({ @@ -13,6 +11,6 @@ export const boardCardFieldsByKeyScopedSelector = selectorFamily({ (scopeId: string) => ({ get }) => get(boardCardFieldsScopedState(scopeId)).reduce< - Record> + Record> >((result, field) => ({ ...result, [field.key]: field }), {}), }); diff --git a/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts b/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts index d1944203e..ca3fb59e0 100644 --- a/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts +++ b/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts @@ -1,10 +1,8 @@ import { selectorFamily } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; +import { BoardFieldDefinition } from '../../types/BoardFieldDefinition'; import { savedBoardCardFieldsFamilyState } from '../savedBoardCardFieldsFamilyState'; export const savedBoardCardFieldsByKeyFamilySelector = selectorFamily({ @@ -13,6 +11,6 @@ export const savedBoardCardFieldsByKeyFamilySelector = selectorFamily({ (viewId: string | undefined) => ({ get }) => get(savedBoardCardFieldsFamilyState(viewId)).reduce< - Record> + Record> >((result, field) => ({ ...result, [field.key]: field }), {}), }); diff --git a/front/src/modules/ui/board/types/BoardFieldDefinition.ts b/front/src/modules/ui/board/types/BoardFieldDefinition.ts new file mode 100644 index 000000000..c9715065c --- /dev/null +++ b/front/src/modules/ui/board/types/BoardFieldDefinition.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '@/ui/field/types/FieldDefinition'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; + +export type BoardFieldDefinition = + FieldDefinition & { + index: number; + isVisible?: boolean; + }; diff --git a/front/src/modules/ui/dropdown/components/DropdownButton.tsx b/front/src/modules/ui/dropdown/components/DropdownButton.tsx index 01687e122..f57c5795b 100644 --- a/front/src/modules/ui/dropdown/components/DropdownButton.tsx +++ b/front/src/modules/ui/dropdown/components/DropdownButton.tsx @@ -1,6 +1,7 @@ import { useRef } from 'react'; import { Keys } from 'react-hotkeys-hook'; import { flip, offset, Placement, useFloating } from '@floating-ui/react'; +import { Key } from 'ts-key-enum'; import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; @@ -71,7 +72,7 @@ export const DropdownButton = ({ }); useScopedHotkeys( - 'esc', + Key.Escape, () => { closeDropdownButton(); }, diff --git a/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx deleted file mode 100644 index fdf044af7..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useContext } from 'react'; - -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldBooleanMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditableBooleanFieldDisplayMode } from './GenericEditableBooleanFieldDisplayMode'; - -export const GenericEditableBooleanField = () => { - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - return ( - - } - displayModeContentOnly - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableBooleanFieldDisplayMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableBooleanFieldDisplayMode.tsx deleted file mode 100644 index 231a55fe7..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableBooleanFieldDisplayMode.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; - -import { BooleanInput } from '@/ui/input/components/BooleanInput'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldBooleanMetadata } from '../types/FieldMetadata'; - -export const GenericEditableBooleanFieldDisplayMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - - const handleSubmit = (newValue: boolean) => { - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newValue, - ); - - // TODO: use optimistic effect instead, but needs generic refactor - setFieldValue(newValue); - } - }; - - return ; -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx deleted file mode 100644 index 4c700c93f..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { DateDisplay } from '@/ui/content-display/components/DateDisplay'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldDateMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditableDateFieldEditMode } from './GenericEditableDateFieldEditMode'; - -export const GenericEditableDateField = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - return ( - - } - displayModeContent={} - isDisplayModeContentEmpty={!fieldValue} - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx deleted file mode 100644 index b00e4194f..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; - -import { DateInput } from '@/ui/input/components/DateInput'; -import { Nullable } from '~/types/Nullable'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldDateMetadata } from '../types/FieldMetadata'; - -export const GenericEditableDateFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - - const handleSubmit = (newDate: Nullable) => { - if (!newDate) { - setFieldValue(''); - - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - '', - ); - } - } - - const newDateISO = newDate?.toISOString(); - - if (newDateISO === fieldValue || !newDateISO) return; - - setFieldValue(newDateISO); - - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newDateISO, - ); - } - }; - - const { handleEnter, handleEscape, handleClickOutside } = - useFieldInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableField.tsx deleted file mode 100644 index fd4fa4da8..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableField.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useContext } from 'react'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { isFieldBoolean } from '../types/guards/isFieldBoolean'; -import { isFieldDate } from '../types/guards/isFieldDate'; -import { isFieldNumber } from '../types/guards/isFieldNumber'; -import { isFieldPhone } from '../types/guards/isFieldPhone'; -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 { GenericEditableBooleanField } from './GenericEditableBooleanField'; -import { GenericEditableDateField } from './GenericEditableDateField'; -import { GenericEditableNumberField } from './GenericEditableNumberField'; -import { GenericEditablePhoneField } from './GenericEditablePhoneField'; -import { GenericEditableRelationField } from './GenericEditableRelationField'; -import { GenericEditableTextField } from './GenericEditableTextField'; -import { GenericEditableURLField } from './GenericEditableURLField'; -import { ProbabilityEditableField } from './ProbabilityEditableField'; - -export const GenericEditableField = () => { - const fieldDefinition = useContext(EditableFieldDefinitionContext); - - if (isFieldRelation(fieldDefinition)) { - return ; - } else if (isFieldDate(fieldDefinition)) { - return ; - } else if (isFieldNumber(fieldDefinition)) { - return ; - } else if (isFieldProbability(fieldDefinition)) { - return ; - } else if (isFieldURL(fieldDefinition)) { - return ; - } else if (isFieldText(fieldDefinition)) { - return ; - } else if (isFieldPhone(fieldDefinition)) { - return ; - } else if (isFieldBoolean(fieldDefinition)) { - return ; - } else { - console.warn( - `Unknown field metadata type: ${fieldDefinition.type} in GenericEditableField`, - ); - return <>; - } -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx deleted file mode 100644 index abdcb7c1e..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { NumberDisplay } from '@/ui/content-display/components/NumberDisplay'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldNumberMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditableNumberFieldEditMode } from './GenericEditableNumberFieldEditMode'; - -export const GenericEditableNumberField = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - return ( - - } - displayModeContent={} - isDisplayModeContentEmpty={!fieldValue} - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx deleted file mode 100644 index ba7404ca9..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; - -import { TextInput } from '@/ui/input/components/TextInput'; -import { - canBeCastAsIntegerOrNull, - castAsIntegerOrNull, -} from '~/utils/cast-as-integer-or-null'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldNumberMetadata } from '../types/FieldMetadata'; - -export const GenericEditableNumberFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - - const handleSubmit = (newValue: string) => { - if (!canBeCastAsIntegerOrNull(newValue)) { - return; - } - - if (newValue === fieldValue) return; - - const castedValue = castAsIntegerOrNull(newValue); - - setFieldValue(castedValue); - - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - castedValue, - ); - } - }; - - const { handleEnter, handleEscape, handleClickOutside } = - useFieldInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx b/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx deleted file mode 100644 index 65b7f2e8d..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { PhoneDisplay } from '@/ui/content-display/components/PhoneDisplay'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldPhoneMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditablePhoneFieldEditMode } from './GenericEditablePhoneFieldEditMode'; - -export const GenericEditablePhoneField = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - return ( - - } - displayModeContent={} - isDisplayModeContentEmpty={!fieldValue} - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditablePhoneFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditablePhoneFieldEditMode.tsx deleted file mode 100644 index 102eddad1..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditablePhoneFieldEditMode.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useContext } from 'react'; -import { isPossiblePhoneNumber } from 'react-phone-number-input'; -import { useRecoilState } from 'recoil'; - -import { PhoneInput } from '@/ui/input/components/PhoneInput'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldPhoneMetadata } from '../types/FieldMetadata'; - -export const GenericEditablePhoneFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - - const handleSubmit = (newValue: string) => { - if (!isPossiblePhoneNumber(newValue)) return; - - setFieldValue(newValue); - - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newValue, - ); - } - }; - - const { handleEnter, handleEscape, handleClickOutside } = - useFieldInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx deleted file mode 100644 index 5101db4b1..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldRelationMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditableRelationFieldDisplayMode } from './GenericEditableRelationFieldDisplayMode'; -import { GenericEditableRelationFieldEditMode } from './GenericEditableRelationFieldEditMode'; - -export const GenericEditableRelationField = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - return ( - - - } - displayModeContent={} - isDisplayModeContentEmpty={!fieldValue} - isDisplayModeFixHeight - /> - - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldDisplayMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldDisplayMode.tsx deleted file mode 100644 index f217f6fa9..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldDisplayMode.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { CompanyChip } from '@/companies/components/CompanyChip'; -import { PersonChip } from '@/people/components/PersonChip'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { UserChip } from '@/users/components/UserChip'; -import { getLogoUrlFromDomainName } from '~/utils'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldRelationMetadata } from '../types/FieldMetadata'; - -export const GenericEditableRelationFieldDisplayMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - switch (currentEditableFieldDefinition.metadata.relationType) { - case Entity.Person: { - return ( - - ); - } - case Entity.User: { - return ( - - ); - } - case Entity.Company: { - return ( - - ); - } - default: - console.warn( - `Unknown relation type: "${currentEditableFieldDefinition.metadata.relationType}" - in GenericEditableRelationField`, - ); - return <> ; - } -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx deleted file mode 100644 index c8592556d..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { useContext } from 'react'; -import styled from '@emotion/styled'; -import { useRecoilState } from 'recoil'; - -import { CompanyPicker } from '@/companies/components/CompanyPicker'; -import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; -import { PeoplePicker } from '@/people/components/PeoplePicker'; -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 { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useEditableField } from '../hooks/useEditableField'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { - FieldRelationMetadata, - FieldRelationValue, -} from '../types/FieldMetadata'; - -const StyledRelationPickerContainer = styled.div` - left: 0px; - position: absolute; - top: -8px; -`; - -const RelationPicker = ({ - fieldDefinition, - fieldValue, - handleEntitySubmit, - handleCancel, -}: { - fieldDefinition: FieldDefinition; - fieldValue: FieldRelationValue & { companyId?: string }; - handleEntitySubmit: (newRelationId: EntityForSelect | null) => void; - handleCancel: () => void; -}) => { - switch (fieldDefinition.metadata.relationType) { - case Entity.Person: { - return ( - - ); - } - case Entity.User: { - return ( - - ); - } - case Entity.Company: { - return ( - - ); - } - default: - console.warn( - `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`, - ); - return <>; - } -}; - -export const GenericEditableRelationFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const [companyProgress] = useRecoilState( - companyProgressesFamilyState(currentEditableFieldEntityId ?? ''), - ); - const { company } = companyProgress ?? {}; - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - const { closeEditableField } = useEditableField(); - - const handleSubmit = (newRelation: EntityForSelect | null) => { - if (newRelation?.id === fieldValue?.id) return; - - setFieldValue({ - id: newRelation?.id ?? null, - displayName: newRelation?.name ?? null, - avatarUrl: newRelation?.avatarUrl ?? null, - }); - - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newRelation, - ); - } - - closeEditableField(); - }; - - const handleCancel = () => { - closeEditableField(); - }; - - return ( - - - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx deleted file mode 100644 index 04639c221..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { TextDisplay } from '@/ui/content-display/components/TextDisplay'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldNumberMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditableTextFieldEditMode } from './GenericEditableTextFieldEditMode'; - -export const GenericEditableTextField = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - return ( - - } - displayModeContent={} - isDisplayModeContentEmpty={!fieldValue} - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableTextFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableTextFieldEditMode.tsx deleted file mode 100644 index 14dde0be1..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableTextFieldEditMode.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; - -import { TextInput } from '@/ui/input/components/TextInput'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldTextMetadata } from '../types/FieldMetadata'; - -export const GenericEditableTextFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - - const handleSubmit = (newValue: string) => { - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newValue, - ); - - // TODO: use optimistic effect instead, but needs generic refactor - setFieldValue(newValue); - } - }; - - const { handleEnter, handleEscape, handleClickOutside } = - useFieldInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx deleted file mode 100644 index 81036af46..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { URLDisplay } from '@/ui/content-display/components/URLDisplay'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldNumberMetadata } from '../types/FieldMetadata'; - -import { EditableField } from './EditableField'; -import { GenericEditableURLFieldEditMode } from './GenericEditableURLFieldEditMode'; - -export const GenericEditableURLField = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const fieldValue = useRecoilValue( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - return ( - - } - displayModeContent={} - isDisplayModeContentEmpty={!fieldValue} - isDisplayModeFixHeight - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableURLFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableURLFieldEditMode.tsx deleted file mode 100644 index d91c43966..000000000 --- a/front/src/modules/ui/editable-field/components/GenericEditableURLFieldEditMode.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; - -import { TextInput } from '@/ui/input/components/TextInput'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; -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 const GenericEditableURLFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const updateField = useUpdateGenericEntityField(); - - const handleSubmit = (newValue: string) => { - setFieldValue(newValue); - - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newValue, - ); - } - }; - - const { handleEnter, handleEscape, handleClickOutside } = - useFieldInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/InlineCell.tsx b/front/src/modules/ui/editable-field/components/InlineCell.tsx new file mode 100644 index 000000000..65a9fe881 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/InlineCell.tsx @@ -0,0 +1,87 @@ +import { useContext } from 'react'; + +import { FieldDisplay } from '@/ui/field/components/FieldDisplay'; +import { FieldInput } from '@/ui/field/components/FieldInput'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; +import { useIsFieldEmpty } from '@/ui/field/hooks/useIsFieldEmpty'; +import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly'; +import { FieldInputEvent } from '@/ui/field/types/FieldInputEvent'; +import { isFieldRelation } from '@/ui/field/types/guards/isFieldRelation'; +import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; + +import { useInlineCell } from '../hooks/useInlineCell'; + +import { InlineCellContainer } from './InlineCellContainer'; + +export const InlineCell = () => { + const { fieldDefinition } = useContext(FieldContext); + + const isFieldEmpty = useIsFieldEmpty(); + + const isFieldInputOnly = useIsFieldInputOnly(); + + const { closeInlineCell } = useInlineCell(); + + const handleEnter: FieldInputEvent = (persistField) => { + persistField(); + closeInlineCell(); + }; + + const handleSubmit: FieldInputEvent = (persistField) => { + persistField(); + closeInlineCell(); + }; + + const handleCancel = () => { + closeInlineCell(); + }; + + const handleEscape = () => { + closeInlineCell(); + }; + + const handleTab: FieldInputEvent = (persistField) => { + persistField(); + closeInlineCell(); + }; + + const handleShiftTab: FieldInputEvent = (persistField) => { + persistField(); + closeInlineCell(); + }; + + const handleClickOutside: FieldInputEvent = (persistField) => { + persistField(); + closeInlineCell(); + }; + console.log(JSON.stringify({ fieldDefinition })); + + return ( + + } + displayModeContent={} + isDisplayModeContentEmpty={isFieldEmpty} + isDisplayModeFixHeight + editModeContentOnly={isFieldInputOnly} + /> + ); +}; diff --git a/front/src/modules/ui/editable-field/components/EditableField.tsx b/front/src/modules/ui/editable-field/components/InlineCellContainer.tsx similarity index 69% rename from front/src/modules/ui/editable-field/components/EditableField.tsx rename to front/src/modules/ui/editable-field/components/InlineCellContainer.tsx index 412a7ca48..03a8c206d 100644 --- a/front/src/modules/ui/editable-field/components/EditableField.tsx +++ b/front/src/modules/ui/editable-field/components/InlineCellContainer.tsx @@ -1,15 +1,16 @@ import { useState } from 'react'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import { IconComponent } from '@/ui/icon/types/IconComponent'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useEditableField } from '../hooks/useEditableField'; +import { useInlineCell } from '../hooks/useInlineCell'; -import { EditableFieldDisplayMode } from './EditableFieldDisplayMode'; -import { EditableFieldEditButton } from './EditableFieldEditButton'; -import { EditableFieldEditMode } from './EditableFieldEditMode'; +import { InlineCellDisplayMode } from './InlineCellDisplayMode'; +import { InlineCellEditButton } from './InlineCellEditButton'; +import { InlineCellEditMode } from './InlineCellEditMode'; const StyledIconContainer = styled.div` align-items: center; @@ -57,7 +58,7 @@ const StyledClickableContainer = styled.div` width: 100%; `; -const StyledEditableFieldBaseContainer = styled.div` +const StyledInlineCellBaseContainer = styled.div` align-items: center; box-sizing: border-box; @@ -77,7 +78,7 @@ type OwnProps = { labelFixedWidth?: number; useEditButton?: boolean; editModeContent?: React.ReactNode; - displayModeContentOnly?: boolean; + editModeContentOnly?: boolean; displayModeContent: React.ReactNode; customEditHotkeyScope?: HotkeyScope; isDisplayModeContentEmpty?: boolean; @@ -85,7 +86,7 @@ type OwnProps = { disableHoverEffect?: boolean; }; -export const EditableField = ({ +export const InlineCellContainer = ({ IconLabel, label, labelFixedWidth, @@ -94,7 +95,7 @@ export const EditableField = ({ displayModeContent, customEditHotkeyScope, isDisplayModeContentEmpty, - displayModeContentOnly, + editModeContentOnly, isDisplayModeFixHeight, disableHoverEffect, }: OwnProps) => { @@ -108,46 +109,61 @@ export const EditableField = ({ setIsHovered(false); }; - const { isFieldInEditMode, openEditableField } = useEditableField(); + const { isInlineCellInEditMode, openInlineCell } = useInlineCell(); const handleDisplayModeClick = () => { - if (!displayModeContentOnly) { - openEditableField(customEditHotkeyScope); + if (!editModeContentOnly) { + openInlineCell(customEditHotkeyScope); } }; const showEditButton = - !isFieldInEditMode && isHovered && useEditButton && !displayModeContentOnly; + !isInlineCellInEditMode && + isHovered && + useEditButton && + !editModeContentOnly; + + const theme = useTheme(); return ( - {IconLabel && ( - + )} {label && ( {label} )} - - {isFieldInEditMode ? ( - {editModeContent} + {isInlineCellInEditMode ? ( + {editModeContent} + ) : editModeContentOnly ? ( + + + {editModeContent} + + ) : ( - {displayModeContent} - + {showEditButton && ( - + )} )} - + ); }; diff --git a/front/src/modules/ui/editable-field/components/EditableFieldDisplayMode.tsx b/front/src/modules/ui/editable-field/components/InlineCellDisplayMode.tsx similarity index 97% rename from front/src/modules/ui/editable-field/components/EditableFieldDisplayMode.tsx rename to front/src/modules/ui/editable-field/components/InlineCellDisplayMode.tsx index ea0277c66..8ee759151 100644 --- a/front/src/modules/ui/editable-field/components/EditableFieldDisplayMode.tsx +++ b/front/src/modules/ui/editable-field/components/InlineCellDisplayMode.tsx @@ -57,7 +57,7 @@ type OwnProps = { isHovered?: boolean; }; -export const EditableFieldDisplayMode = ({ +export const InlineCellDisplayMode = ({ children, isDisplayModeContentEmpty, disableHoverEffect, diff --git a/front/src/modules/ui/editable-field/components/EditableFieldEditButton.tsx b/front/src/modules/ui/editable-field/components/InlineCellEditButton.tsx similarity index 53% rename from front/src/modules/ui/editable-field/components/EditableFieldEditButton.tsx rename to front/src/modules/ui/editable-field/components/InlineCellEditButton.tsx index fc84e7af2..b547a54ad 100644 --- a/front/src/modules/ui/editable-field/components/EditableFieldEditButton.tsx +++ b/front/src/modules/ui/editable-field/components/InlineCellEditButton.tsx @@ -1,13 +1,13 @@ import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; import { IconPencil } from '@/ui/icon'; -import { useEditableField } from '../hooks/useEditableField'; +import { useInlineCell } from '../hooks/useInlineCell'; -export const EditableFieldEditButton = () => { - const { openEditableField } = useEditableField(); +export const InlineCellEditButton = () => { + const { openInlineCell } = useInlineCell(); const handleClick = () => { - openEditableField(); + openInlineCell(); }; return ( @@ -15,7 +15,7 @@ export const EditableFieldEditButton = () => { size="small" onClick={handleClick} Icon={IconPencil} - data-testid="editable-field-edit-mode-container" + data-testid="inline-cell-edit-mode-container" /> ); }; diff --git a/front/src/modules/ui/editable-field/components/EditableFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/InlineCellEditMode.tsx similarity index 61% rename from front/src/modules/ui/editable-field/components/EditableFieldEditMode.tsx rename to front/src/modules/ui/editable-field/components/InlineCellEditMode.tsx index b493228b4..4986a7c4e 100644 --- a/front/src/modules/ui/editable-field/components/EditableFieldEditMode.tsx +++ b/front/src/modules/ui/editable-field/components/InlineCellEditMode.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; -const StyledEditableFieldEditModeContainer = styled.div` +const StyledInlineCellEditModeContainer = styled.div` align-items: center; display: flex; @@ -11,7 +11,7 @@ const StyledEditableFieldEditModeContainer = styled.div` z-index: 10; `; -const StyledEditableFieldInput = styled.div` +const StyledInlineCellInput = styled.div` align-items: center; background: ${({ theme }) => theme.background.transparent.secondary}; border: 1px solid ${({ theme }) => theme.border.color.medium}; @@ -30,8 +30,8 @@ type OwnProps = { children: React.ReactNode; }; -export const EditableFieldEditMode = ({ children }: OwnProps) => ( - - {children} - +export const InlineCellEditMode = ({ children }: OwnProps) => ( + + {children} + ); diff --git a/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx b/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx deleted file mode 100644 index e6e805527..000000000 --- a/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useContext } from 'react'; - -import { EditableField } from '@/ui/editable-field/components/EditableField'; -import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldProbabilityMetadata } from '../types/FieldMetadata'; - -import { ProbabilityEditableFieldEditMode } from './ProbabilityEditableFieldEditMode'; - -export const ProbabilityEditableField = () => { - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - return ( - - } - displayModeContentOnly - disableHoverEffect - /> - - ); -}; diff --git a/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx deleted file mode 100644 index e44874ffb..000000000 --- a/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; - -import { useEditableField } from '@/ui/editable-field/hooks/useEditableField'; -import { ProbabilityInput } from '@/ui/input/components/ProbabilityInput'; - -import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext'; -import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext'; -import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; -import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldProbabilityMetadata } from '../types/FieldMetadata'; - -export const ProbabilityEditableFieldEditMode = () => { - const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); - const currentEditableFieldDefinition = useContext( - EditableFieldDefinitionContext, - ) as FieldDefinition; - - const [fieldValue, setFieldValue] = useRecoilState( - genericEntityFieldFamilySelector({ - entityId: currentEditableFieldEntityId ?? '', - fieldName: currentEditableFieldDefinition - ? currentEditableFieldDefinition.metadata.fieldName - : '', - }), - ); - - const { closeEditableField } = useEditableField(); - - const updateField = useUpdateGenericEntityField(); - - const probabilityIndex = Math.ceil(fieldValue / 25); - - const handleChange = (newValue: number) => { - setFieldValue(newValue); - if (currentEditableFieldEntityId && updateField) { - updateField( - currentEditableFieldEntityId, - currentEditableFieldDefinition, - newValue, - ); - } - closeEditableField(); - }; - - return ( - - ); -}; diff --git a/front/src/modules/ui/editable-field/contexts/EditableFieldDefinitionContext.ts b/front/src/modules/ui/editable-field/contexts/EditableFieldDefinitionContext.ts deleted file mode 100644 index 6a804aeb7..000000000 --- a/front/src/modules/ui/editable-field/contexts/EditableFieldDefinitionContext.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext } from 'react'; - -import { FieldDefinition } from '../types/FieldDefinition'; -import { FieldMetadata } from '../types/FieldMetadata'; - -export const EditableFieldDefinitionContext = createContext< - FieldDefinition ->({} as FieldDefinition); diff --git a/front/src/modules/ui/editable-field/contexts/EditableFieldEntityIdContext.ts b/front/src/modules/ui/editable-field/contexts/EditableFieldEntityIdContext.ts deleted file mode 100644 index 0f1116746..000000000 --- a/front/src/modules/ui/editable-field/contexts/EditableFieldEntityIdContext.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from 'react'; - -export const EditableFieldEntityIdContext = createContext(''); diff --git a/front/src/modules/ui/editable-field/hooks/useFieldInputEventHandlers.ts b/front/src/modules/ui/editable-field/hooks/useFieldInputEventHandlers.ts deleted file mode 100644 index 32b9484b4..000000000 --- a/front/src/modules/ui/editable-field/hooks/useFieldInputEventHandlers.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useEditableField } from './useEditableField'; - -export const useFieldInputEventHandlers = ({ - onSubmit, - onCancel, -}: { - onSubmit?: (newValue: T) => void; - onCancel?: () => void; -}) => { - const { closeEditableField, isFieldInEditMode } = useEditableField(); - - return { - handleClickOutside: (_event: MouseEvent | TouchEvent, newValue: T) => { - if (isFieldInEditMode) { - onSubmit?.(newValue); - closeEditableField(); - } - }, - handleEscape: () => { - closeEditableField(); - onCancel?.(); - }, - handleEnter: (newValue: T) => { - onSubmit?.(newValue); - closeEditableField(); - }, - }; -}; diff --git a/front/src/modules/ui/editable-field/hooks/useEditableField.ts b/front/src/modules/ui/editable-field/hooks/useInlineCell.ts similarity index 51% rename from front/src/modules/ui/editable-field/hooks/useEditableField.ts rename to front/src/modules/ui/editable-field/hooks/useInlineCell.ts index 38c043e18..16f09950a 100644 --- a/front/src/modules/ui/editable-field/hooks/useEditableField.ts +++ b/front/src/modules/ui/editable-field/hooks/useInlineCell.ts @@ -1,15 +1,18 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '@/ui/field/contexts/FieldContext'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedState'; -import { FieldRecoilScopeContext } from '../states/recoil-scope-contexts/FieldRecoilScopeContext'; +import { isInlineCellInEditModeScopedState } from '../states/isInlineCellInEditModeScopedState'; import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; -export const useEditableField = () => { - const [isFieldInEditMode, setIsFieldInEditMode] = useRecoilScopedState( - isFieldInEditModeScopedState, - FieldRecoilScopeContext, +export const useInlineCell = () => { + const { recoilScopeId } = useContext(FieldContext); + + const [isInlineCellInEditMode, setIsInlineCellInEditMode] = useRecoilState( + isInlineCellInEditModeScopedState(recoilScopeId), ); const { @@ -17,14 +20,14 @@ export const useEditableField = () => { goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - const closeEditableField = () => { - setIsFieldInEditMode(false); + const closeInlineCell = () => { + setIsInlineCellInEditMode(false); goBackToPreviousHotkeyScope(); }; - const openEditableField = (customEditHotkeyScopeForField?: HotkeyScope) => { - setIsFieldInEditMode(true); + const openInlineCell = (customEditHotkeyScopeForField?: HotkeyScope) => { + setIsInlineCellInEditMode(true); if (customEditHotkeyScopeForField) { setHotkeyScopeAndMemorizePreviousScope( @@ -39,8 +42,8 @@ export const useEditableField = () => { }; return { - isFieldInEditMode, - closeEditableField, - openEditableField, + isInlineCellInEditMode, + closeInlineCell, + openInlineCell, }; }; diff --git a/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts b/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts deleted file mode 100644 index b1edbbdd0..000000000 --- a/front/src/modules/ui/editable-field/hooks/useRegisterCloseFieldHandlers.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; - -import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; - -import { useEditableField } from './useEditableField'; - -export const useRegisterCloseFieldHandlers = ( - wrapperRef: React.RefObject, - onSubmit?: () => void, - onCancel?: () => void, -) => { - const { closeEditableField, isFieldInEditMode } = useEditableField(); - - useListenClickOutside({ - refs: [wrapperRef], - callback: () => { - if (isFieldInEditMode) { - onSubmit?.(); - closeEditableField(); - } - }, - }); - - useScopedHotkeys( - 'enter', - () => { - onSubmit?.(); - closeEditableField(); - }, - EditableFieldHotkeyScope.EditableField, - [closeEditableField, onSubmit], - ); - - useScopedHotkeys( - 'esc', - () => { - closeEditableField(); - onCancel?.(); - }, - EditableFieldHotkeyScope.EditableField, - [closeEditableField, onCancel], - ); -}; diff --git a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts deleted file mode 100644 index 25fdd6468..000000000 --- a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { useContext } from 'react'; - -import { EditableFieldMutationContext } from '../contexts/EditableFieldMutationContext'; -import { FieldDefinition } from '../types/FieldDefinition'; -import { - FieldBooleanMetadata, - FieldBooleanValue, - FieldChipMetadata, - FieldChipValue, - FieldDateMetadata, - FieldDateValue, - FieldDoubleTextChipMetadata, - FieldDoubleTextChipValue, - FieldDoubleTextMetadata, - FieldDoubleTextValue, - FieldMetadata, - FieldNumberMetadata, - FieldNumberValue, - FieldPhoneMetadata, - FieldPhoneValue, - FieldProbabilityMetadata, - FieldProbabilityValue, - FieldRelationMetadata, - FieldRelationValue, - FieldTextMetadata, - FieldTextValue, - FieldURLMetadata, - FieldURLValue, -} from '../types/FieldMetadata'; -import { isFieldBoolean } from '../types/guards/isFieldBoolean'; -import { isFieldBooleanValue } from '../types/guards/isFieldBooleanValue'; -import { isFieldChip } from '../types/guards/isFieldChip'; -import { isFieldChipValue } from '../types/guards/isFieldChipValue'; -import { isFieldDate } from '../types/guards/isFieldDate'; -import { isFieldDateValue } from '../types/guards/isFieldDateValue'; -import { isFieldDoubleText } from '../types/guards/isFieldDoubleText'; -import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip'; -import { isFieldDoubleTextChipValue } from '../types/guards/isFieldDoubleTextChipValue'; -import { isFieldDoubleTextValue } from '../types/guards/isFieldDoubleTextValue'; -import { isFieldNumber } from '../types/guards/isFieldNumber'; -import { isFieldNumberValue } from '../types/guards/isFieldNumberValue'; -import { isFieldPhone } from '../types/guards/isFieldPhone'; -import { isFieldPhoneValue } from '../types/guards/isFieldPhoneValue'; -import { isFieldProbability } from '../types/guards/isFieldProbability'; -import { isFieldProbabilityValue } from '../types/guards/isFieldProbabilityValue'; -import { isFieldRelation } from '../types/guards/isFieldRelation'; -import { isFieldRelationValue } from '../types/guards/isFieldRelationValue'; -import { isFieldText } from '../types/guards/isFieldText'; -import { isFieldTextValue } from '../types/guards/isFieldTextValue'; -import { isFieldURL } from '../types/guards/isFieldURL'; -import { isFieldURLValue } from '../types/guards/isFieldURLValue'; - -export const useUpdateGenericEntityField = () => { - const useUpdateEntityMutation = useContext(EditableFieldMutationContext); - - const [updateEntity] = useUpdateEntityMutation(); - - const updateEntityField = < - ValueType extends FieldMetadata extends FieldDoubleTextMetadata - ? FieldDoubleTextValue - : FieldMetadata extends FieldTextMetadata - ? FieldTextValue - : FieldMetadata extends FieldPhoneMetadata - ? FieldPhoneValue - : FieldMetadata extends FieldURLMetadata - ? FieldURLValue - : FieldMetadata extends FieldNumberMetadata - ? FieldNumberValue - : FieldMetadata extends FieldDateMetadata - ? FieldDateValue - : FieldMetadata extends FieldChipMetadata - ? FieldChipValue - : FieldMetadata extends FieldDoubleTextChipMetadata - ? FieldDoubleTextChipValue - : FieldMetadata extends FieldRelationMetadata - ? FieldRelationValue - : FieldMetadata extends FieldProbabilityMetadata - ? FieldProbabilityValue - : FieldMetadata extends FieldBooleanMetadata - ? FieldBooleanValue - : unknown, - >( - currentEntityId: string, - field: FieldDefinition, - newFieldValue: ValueType | null, - ) => { - // TODO: improve type guards organization, maybe with a common typeguard for all fields - // taking an object of options as parameter ? - // - // The goal would be to check that the field value not only is valid, - // but also that it is validated against the corresponding field type - - if ( - // Relation - isFieldRelation(field) && - isFieldRelationValue(newFieldValue) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { - [field.metadata.fieldName]: newFieldValue - ? { connect: { id: newFieldValue.id } } - : { disconnect: true }, - }, - }, - }); - return; - } - - if ( - // Chip - isFieldChip(field) && - isFieldChipValue(newFieldValue) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { [field.metadata.contentFieldName]: newFieldValue }, - }, - }); - return; - } - - if ( - // Text - (isFieldText(field) && isFieldTextValue(newFieldValue)) || - // Phone - (isFieldPhone(field) && isFieldPhoneValue(newFieldValue)) || - // URL - (isFieldURL(field) && isFieldURLValue(newFieldValue)) || - // Number - (isFieldNumber(field) && isFieldNumberValue(newFieldValue)) || - // Date - (isFieldDate(field) && isFieldDateValue(newFieldValue)) || - // Probability - (isFieldProbability(field) && isFieldProbabilityValue(newFieldValue)) || - // Boolean - (isFieldBoolean(field) && isFieldBooleanValue(newFieldValue)) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { [field.metadata.fieldName]: newFieldValue }, - }, - }); - return; - } - - if ( - // Double text - (isFieldDoubleText(field) && isFieldDoubleTextValue(newFieldValue)) || - // Double Text Chip - (isFieldDoubleTextChip(field) && - isFieldDoubleTextChipValue(newFieldValue)) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { - [field.metadata.firstValueFieldName]: newFieldValue.firstValue, - [field.metadata.secondValueFieldName]: newFieldValue.secondValue, - }, - }, - }); - } - }; - - return updateEntityField; -}; diff --git a/front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts b/front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts deleted file mode 100644 index 410784e46..000000000 --- a/front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { atomFamily } from 'recoil'; - -export const genericEntitiesFamilyState = atomFamily< - Record | null, - string ->({ - key: 'genericEntitiesFamilyState', - default: null, -}); diff --git a/front/src/modules/ui/editable-field/states/isFieldInEditModeScopedState.ts b/front/src/modules/ui/editable-field/states/isFieldInEditModeScopedState.ts deleted file mode 100644 index 9a32f6fa9..000000000 --- a/front/src/modules/ui/editable-field/states/isFieldInEditModeScopedState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { atomFamily } from 'recoil'; - -export const isFieldInEditModeScopedState = atomFamily({ - key: 'isFieldInEditModeScopedState', - default: false, -}); diff --git a/front/src/modules/ui/editable-field/states/isInlineCellInEditModeScopedState.ts b/front/src/modules/ui/editable-field/states/isInlineCellInEditModeScopedState.ts new file mode 100644 index 000000000..bdc57ccb3 --- /dev/null +++ b/front/src/modules/ui/editable-field/states/isInlineCellInEditModeScopedState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const isInlineCellInEditModeScopedState = atomFamily({ + key: 'isInlineCellInEditModeScopedState', + default: false, +}); diff --git a/front/src/modules/ui/editable-field/states/selectors/genericEntityFieldFamilySelector.ts b/front/src/modules/ui/editable-field/states/selectors/genericEntityFieldFamilySelector.ts deleted file mode 100644 index c3b988a8e..000000000 --- a/front/src/modules/ui/editable-field/states/selectors/genericEntityFieldFamilySelector.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { selectorFamily } from 'recoil'; - -import { genericEntitiesFamilyState } from '../genericEntitiesFamilyState'; - -export const genericEntityFieldFamilySelector = selectorFamily({ - key: 'genericEntityFieldFamilySelector', - get: - ({ fieldName, entityId }: { fieldName: string; entityId: string }) => - ({ get }) => - get(genericEntitiesFamilyState(entityId))?.[fieldName] as T, - set: - ({ fieldName, entityId }: { fieldName: string; entityId: string }) => - ({ set }, newValue: T) => - set(genericEntitiesFamilyState(entityId), (prevState) => ({ - ...prevState, - [fieldName]: newValue, - })), -}); diff --git a/front/src/modules/ui/editable-field/types/FieldDefinition.ts b/front/src/modules/ui/editable-field/types/FieldDefinition.ts deleted file mode 100644 index 4904b1440..000000000 --- a/front/src/modules/ui/editable-field/types/FieldDefinition.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IconComponent } from '@/ui/icon/types/IconComponent'; - -import { FieldMetadata, FieldType } from './FieldMetadata'; - -export type FieldDefinition = { - key: string; - name: string; - Icon?: IconComponent; - type: FieldType; - metadata: T; -}; diff --git a/front/src/modules/ui/editable-field/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts deleted file mode 100644 index 77c022770..000000000 --- a/front/src/modules/ui/editable-field/types/ViewField.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { IconComponent } from '@/ui/icon/types/IconComponent'; -import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; - -export type ViewFieldType = - | 'text' - | 'relation' - | 'chip' - | 'double-text-chip' - | 'double-text' - | 'number' - | 'date' - | 'phone' - | 'email' - | 'url' - | 'probability' - | 'boolean' - | 'moneyAmount'; - -export type ViewFieldTextMetadata = { - type: 'text'; - placeHolder: string; - fieldName: string; -}; - -export type ViewFieldPhoneMetadata = { - type: 'phone'; - placeHolder: string; - fieldName: string; -}; - -export type ViewFieldEmailMetadata = { - type: 'email'; - 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; - isPositive?: boolean; -}; - -export type ViewFieldMoneyMetadata = { - type: 'moneyAmount'; - fieldName: string; -}; - -export type ViewFieldBooleanMetadata = { - type: 'boolean'; - fieldName: string; -}; - -export type ViewFieldRelationMetadata = { - type: 'relation'; - relationType: Entity; - fieldName: string; - useEditButton?: boolean; -}; - -export type ViewFieldChipMetadata = { - type: 'chip'; - relationType: Entity; - contentFieldName: string; - urlFieldName: string; - placeHolder: string; -}; - -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; - avatarUrlFieldName: string; - entityType: Entity; -}; - -export type ViewFieldProbabilityMetadata = { - type: 'probability'; - fieldName: string; -}; - -export type ViewFieldMetadata = { type: ViewFieldType } & ( - | ViewFieldTextMetadata - | ViewFieldRelationMetadata - | ViewFieldChipMetadata - | ViewFieldDoubleTextChipMetadata - | ViewFieldDoubleTextMetadata - | ViewFieldPhoneMetadata - | ViewFieldEmailMetadata - | ViewFieldURLMetadata - | ViewFieldNumberMetadata - | ViewFieldBooleanMetadata - | ViewFieldDateMetadata - | ViewFieldProbabilityMetadata - | ViewFieldMoneyMetadata -); - -export type ViewFieldDefinition = { - Icon?: IconComponent; - index: number; - isVisible?: boolean; - key: string; - metadata: T; - name: string; -}; - -export type ViewFieldTextValue = string; - -export type ViewFieldChipValue = string; -export type ViewFieldDateValue = string; -export type ViewFieldPhoneValue = string; -export type ViewFieldEmailValue = string; -export type ViewFieldBooleanValue = boolean; -export type ViewFieldMoneyValue = number | null; -export type ViewFieldURLValue = string; -export type ViewFieldNumberValue = number | null; -export type ViewFieldProbabilityValue = number; - -export type ViewFieldDoubleTextValue = { - firstValue: string; - secondValue: string; -}; - -export type ViewFieldDoubleTextChipValue = { - firstValue: string; - secondValue: string; -}; - -export type ViewFieldRelationValue = EntityForSelect | null; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldBoolean.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldBoolean.ts deleted file mode 100644 index aee09c11b..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldBoolean.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldBooleanMetadata, - ViewFieldDefinition, - ViewFieldMetadata, -} from '../ViewField'; - -export const isViewFieldBoolean = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'boolean'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldBooleanValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldBooleanValue.ts deleted file mode 100644 index 1c0d56f33..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldBooleanValue.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ViewFieldBooleanValue } from '../ViewField'; - -export const isViewFieldBooleanValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldBooleanValue => typeof fieldValue === 'boolean'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldChip.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldChip.ts deleted file mode 100644 index f5471cc72..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldChip.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldChipMetadata, - ViewFieldDefinition, - ViewFieldMetadata, -} from '../ViewField'; - -export const isViewFieldChip = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'chip'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldChipValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldChipValue.ts deleted file mode 100644 index 8f36078cb..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldChipValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldChipValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldChipValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldChipValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'string'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldDate.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDate.ts deleted file mode 100644 index 70ba05d5f..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldDate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDateMetadata, - ViewFieldDefinition, - ViewFieldMetadata, -} from '../ViewField'; - -export const isViewFieldDate = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'date'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldDateValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDateValue.ts deleted file mode 100644 index f4591cb87..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldDateValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldDateValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldDateValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldDateValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'string'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleText.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleText.ts deleted file mode 100644 index 4305534f4..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleText.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldDoubleTextMetadata, - ViewFieldMetadata, -} from '../ViewField'; - -export const isViewFieldDoubleText = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'double-text'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChip.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChip.ts deleted file mode 100644 index a36fafd0c..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChip.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldDoubleTextChipMetadata, - ViewFieldMetadata, -} from '../ViewField'; - -export const isViewFieldDoubleTextChip = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'double-text-chip'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue.ts deleted file mode 100644 index 7cc5a0841..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldDoubleTextChipValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldDoubleTextChipValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldDoubleTextChipValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'object'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextValue.ts deleted file mode 100644 index 45cdd33fa..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldDoubleTextValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldDoubleTextValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldDoubleTextValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'object'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts deleted file mode 100644 index eaaff5f86..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldEmailMetadata, - ViewFieldMetadata, -} from '../ViewField'; - -export const isViewFieldEmail = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'email'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts deleted file mode 100644 index 2f4a349f3..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ViewFieldEmailValue } from '../ViewField'; - -export const isViewFieldEmailValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldEmailValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'string'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldMoney.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldMoney.ts deleted file mode 100644 index 563c82f02..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldMoney.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldMoneyMetadata, -} from '../ViewField'; - -export const isViewFieldMoney = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'moneyAmount'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldMoneyValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldMoneyValue.ts deleted file mode 100644 index e336369bb..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldMoneyValue.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ViewFieldMoneyValue } from '../ViewField'; - -export const isViewFieldMoneyValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldMoneyValue => - fieldValue === null || - (fieldValue !== undefined && typeof fieldValue === 'number'); diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldNumber.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldNumber.ts deleted file mode 100644 index 28301f77b..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldNumber.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldNumberMetadata, -} from '../ViewField'; - -export const isViewFieldNumber = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'number'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldNumberValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldNumberValue.ts deleted file mode 100644 index ff7981557..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldNumberValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldNumberValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldNumberValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldNumberValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'number'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldPhone.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldPhone.ts deleted file mode 100644 index 51ad9f7cf..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldPhone.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldPhoneMetadata, -} from '../ViewField'; - -export const isViewFieldPhone = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'phone'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldPhoneValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldPhoneValue.ts deleted file mode 100644 index 0fc0f3b3d..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldPhoneValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldPhoneValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldPhoneValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldPhoneValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'string'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts deleted file mode 100644 index eaf5d8a74..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldProbabilityMetadata, -} from '../ViewField'; - -export const isViewFieldProbability = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'probability'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldProbabilityValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldProbabilityValue.ts deleted file mode 100644 index b322acf96..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldProbabilityValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldProbabilityValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldProbabilityValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldProbabilityValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'number'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldRelation.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldRelation.ts deleted file mode 100644 index d757d3921..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldRelation.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldRelationMetadata, -} from '../ViewField'; - -export const isViewFieldRelation = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'relation'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldRelationValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldRelationValue.ts deleted file mode 100644 index 45ff1a519..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldRelationValue.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ViewFieldRelationValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldRelationValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldRelationValue => - fieldValue !== undefined && typeof fieldValue === 'object'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldText.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldText.ts deleted file mode 100644 index 57571f047..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldText.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldTextMetadata, -} from '../ViewField'; - -export const isViewFieldText = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'text'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldTextValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldTextValue.ts deleted file mode 100644 index e1568d2d3..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldTextValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ViewFieldTextValue } from '../ViewField'; - -// TODO: add yup -export const isViewFieldTextValue = ( - fieldValue: unknown, -): fieldValue is ViewFieldTextValue => - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === 'string'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldURL.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldURL.ts deleted file mode 100644 index c6ae9920e..000000000 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldURL.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldURLMetadata, -} from '../ViewField'; - -export const isViewFieldURL = ( - field: ViewFieldDefinition, -): field is ViewFieldDefinition => - field.metadata.type === 'url'; diff --git a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx index 8751f66a7..e0556c09a 100644 --- a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx +++ b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx @@ -1,6 +1,6 @@ -import { DateDisplay } from '@/ui/content-display/components/DateDisplay'; -import { EditableField } from '@/ui/editable-field/components/EditableField'; +import { InlineCellContainer } from '@/ui/editable-field/components/InlineCellContainer'; import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; +import { DateDisplay } from '@/ui/field/meta-types/display/content-display/components/DateDisplay'; import { IconComponent } from '@/ui/icon/types/IconComponent'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { parseDate } from '~/utils/date-utils'; @@ -30,7 +30,7 @@ export const DateEditableField = ({ return ( - { closeEditableField(); diff --git a/front/src/modules/ui/field/components/FieldDisplay.tsx b/front/src/modules/ui/field/components/FieldDisplay.tsx new file mode 100644 index 000000000..72d290440 --- /dev/null +++ b/front/src/modules/ui/field/components/FieldDisplay.tsx @@ -0,0 +1,59 @@ +import { useContext } from 'react'; + +import { FieldContext } from '../contexts/FieldContext'; +import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay'; +import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay'; +import { DoubleTextChipFieldDisplay } from '../meta-types/display/components/DoubleTextChipFieldDisplay'; +import { DoubleTextFieldDisplay } from '../meta-types/display/components/DoubleTextFieldDisplay'; +import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay'; +import { MoneyFieldDisplay } from '../meta-types/display/components/MoneyFieldDisplay'; +import { NumberFieldDisplay } from '../meta-types/display/components/NumberFieldDisplay'; +import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDisplay'; +import { RelationFieldDisplay } from '../meta-types/display/components/RelationFieldDisplay'; +import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay'; +import { URLFieldDisplay } from '../meta-types/display/components/URLFieldDisplay'; +import { isFieldChip } from '../types/guards/isFieldChip'; +import { isFieldDate } from '../types/guards/isFieldDate'; +import { isFieldDoubleText } from '../types/guards/isFieldDoubleText'; +import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip'; +import { isFieldEmail } from '../types/guards/isFieldEmail'; +import { isFieldMoney } from '../types/guards/isFieldMoney'; +import { isFieldNumber } from '../types/guards/isFieldNumber'; +import { isFieldPhone } from '../types/guards/isFieldPhone'; +import { isFieldRelation } from '../types/guards/isFieldRelation'; +import { isFieldText } from '../types/guards/isFieldText'; +import { isFieldURL } from '../types/guards/isFieldURL'; + +export const FieldDisplay = () => { + const { fieldDefinition } = useContext(FieldContext); + + return ( + <> + {isFieldRelation(fieldDefinition) ? ( + + ) : isFieldText(fieldDefinition) ? ( + + ) : isFieldEmail(fieldDefinition) ? ( + + ) : isFieldDate(fieldDefinition) ? ( + + ) : isFieldNumber(fieldDefinition) ? ( + + ) : isFieldMoney(fieldDefinition) ? ( + + ) : isFieldURL(fieldDefinition) ? ( + + ) : isFieldPhone(fieldDefinition) ? ( + + ) : isFieldChip(fieldDefinition) ? ( + + ) : isFieldDoubleTextChip(fieldDefinition) ? ( + + ) : isFieldDoubleText(fieldDefinition) ? ( + + ) : ( + <> + )} + + ); +}; diff --git a/front/src/modules/ui/field/components/FieldInput.tsx b/front/src/modules/ui/field/components/FieldInput.tsx new file mode 100644 index 000000000..b7f9c5956 --- /dev/null +++ b/front/src/modules/ui/field/components/FieldInput.tsx @@ -0,0 +1,150 @@ +import { useContext } from 'react'; + +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; + +import { FieldContext } from '../contexts/FieldContext'; +import { BooleanFieldInput } from '../meta-types/input/components/BooleanFieldInput'; +import { ChipFieldInput } from '../meta-types/input/components/ChipFieldInput'; +import { DateFieldInput } from '../meta-types/input/components/DateFieldInput'; +import { DoubleTextChipFieldInput } from '../meta-types/input/components/DoubleTextChipFieldInput'; +import { DoubleTextFieldInput } from '../meta-types/input/components/DoubleTextFieldInput'; +import { EmailFieldInput } from '../meta-types/input/components/EmailFieldInput'; +import { MoneyFieldInput } from '../meta-types/input/components/MoneyFieldInput'; +import { NumberFieldInput } from '../meta-types/input/components/NumberFieldInput'; +import { PhoneFieldInput } from '../meta-types/input/components/PhoneFieldInput'; +import { ProbabilityFieldInput } from '../meta-types/input/components/ProbabilityFieldInput'; +import { RelationFieldInput } from '../meta-types/input/components/RelationFieldInput'; +import { TextFieldInput } from '../meta-types/input/components/TextFieldInput'; +import { URLFieldInput } from '../meta-types/input/components/URLFieldInput'; +import { FieldInputEvent } from '../types/FieldInputEvent'; +import { isFieldBoolean } from '../types/guards/isFieldBoolean'; +import { isFieldChip } from '../types/guards/isFieldChip'; +import { isFieldDate } from '../types/guards/isFieldDate'; +import { isFieldDoubleText } from '../types/guards/isFieldDoubleText'; +import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip'; +import { isFieldEmail } from '../types/guards/isFieldEmail'; +import { isFieldMoney } from '../types/guards/isFieldMoney'; +import { isFieldNumber } from '../types/guards/isFieldNumber'; +import { isFieldPhone } from '../types/guards/isFieldPhone'; +import { isFieldProbability } from '../types/guards/isFieldProbability'; +import { isFieldRelation } from '../types/guards/isFieldRelation'; +import { isFieldText } from '../types/guards/isFieldText'; +import { isFieldURL } from '../types/guards/isFieldURL'; + +type OwnProps = { + onSubmit?: FieldInputEvent; + onCancel?: () => void; + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const FieldInput = ({ + onCancel, + onSubmit, + onEnter, + onEscape, + onShiftTab, + onTab, + onClickOutside, +}: OwnProps) => { + const { fieldDefinition } = useContext(FieldContext); + + return ( + <> + {isFieldRelation(fieldDefinition) ? ( + + + + ) : isFieldText(fieldDefinition) ? ( + + ) : isFieldEmail(fieldDefinition) ? ( + + ) : isFieldDate(fieldDefinition) ? ( + + ) : isFieldNumber(fieldDefinition) ? ( + + ) : isFieldURL(fieldDefinition) ? ( + + ) : isFieldPhone(fieldDefinition) ? ( + + ) : isFieldBoolean(fieldDefinition) ? ( + + ) : isFieldProbability(fieldDefinition) ? ( + + ) : isFieldChip(fieldDefinition) ? ( + + ) : isFieldDoubleTextChip(fieldDefinition) ? ( + + ) : isFieldDoubleText(fieldDefinition) ? ( + + ) : isFieldMoney(fieldDefinition) ? ( + + ) : ( + <> + )} + + ); +}; diff --git a/front/src/modules/ui/field/contexts/FieldContext.ts b/front/src/modules/ui/field/contexts/FieldContext.ts new file mode 100644 index 000000000..cc11d549a --- /dev/null +++ b/front/src/modules/ui/field/contexts/FieldContext.ts @@ -0,0 +1,17 @@ +import { createContext } from 'react'; + +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldMetadata } from '../types/FieldMetadata'; + +type GenericFieldContextType = { + fieldDefinition: FieldDefinition; + // TODO: add better typing for mutation hook + useUpdateEntityMutation: () => [(params: any) => void, any]; + entityId: string; + recoilScopeId: string; + hotkeyScope: string; +}; + +export const FieldContext = createContext( + {} as GenericFieldContextType, +); diff --git a/front/src/modules/ui/field/hooks/useIsFieldEmpty.ts b/front/src/modules/ui/field/hooks/useIsFieldEmpty.ts new file mode 100644 index 000000000..85a92889f --- /dev/null +++ b/front/src/modules/ui/field/hooks/useIsFieldEmpty.ts @@ -0,0 +1,23 @@ +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { FieldContext } from '../contexts/FieldContext'; +import { isEntityFieldEmptyFamilySelector } from '../states/selectors/isEntityFieldEmptyFamilySelector'; + +export const useIsFieldEmpty = () => { + const { entityId, fieldDefinition } = useContext(FieldContext); + + const isFieldEmpty = useRecoilValue( + isEntityFieldEmptyFamilySelector({ + fieldDefinition: { + key: fieldDefinition.key, + name: fieldDefinition.name, + type: fieldDefinition.type, + metadata: fieldDefinition.metadata, + }, + entityId, + }), + ); + + return isFieldEmpty; +}; diff --git a/front/src/modules/ui/field/hooks/useIsFieldInputOnly.ts b/front/src/modules/ui/field/hooks/useIsFieldInputOnly.ts new file mode 100644 index 000000000..855fd247e --- /dev/null +++ b/front/src/modules/ui/field/hooks/useIsFieldInputOnly.ts @@ -0,0 +1,15 @@ +import { useContext } from 'react'; + +import { FieldContext } from '../contexts/FieldContext'; +import { isFieldBoolean } from '../types/guards/isFieldBoolean'; +import { isFieldProbability } from '../types/guards/isFieldProbability'; + +export const useIsFieldInputOnly = () => { + const { fieldDefinition } = useContext(FieldContext); + + if (isFieldBoolean(fieldDefinition) || isFieldProbability(fieldDefinition)) { + return true; + } + + return false; +}; diff --git a/front/src/modules/ui/field/hooks/usePersistField.ts b/front/src/modules/ui/field/hooks/usePersistField.ts new file mode 100644 index 000000000..2316faa5c --- /dev/null +++ b/front/src/modules/ui/field/hooks/usePersistField.ts @@ -0,0 +1,184 @@ +import { useContext } from 'react'; +import { useRecoilCallback } from 'recoil'; + +import { FieldContext } from '../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../states/selectors/entityFieldsFamilySelector'; +import { isFieldBoolean } from '../types/guards/isFieldBoolean'; +import { isFieldBooleanValue } from '../types/guards/isFieldBooleanValue'; +import { isFieldChip } from '../types/guards/isFieldChip'; +import { isFieldChipValue } from '../types/guards/isFieldChipValue'; +import { isFieldDate } from '../types/guards/isFieldDate'; +import { isFieldDateValue } from '../types/guards/isFieldDateValue'; +import { isFieldDoubleText } from '../types/guards/isFieldDoubleText'; +import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip'; +import { isFieldDoubleTextChipValue } from '../types/guards/isFieldDoubleTextChipValue'; +import { isFieldDoubleTextValue } from '../types/guards/isFieldDoubleTextValue'; +import { isFieldEmail } from '../types/guards/isFieldEmail'; +import { isFieldEmailValue } from '../types/guards/isFieldEmailValue'; +import { isFieldMoney } from '../types/guards/isFieldMoney'; +import { isFieldMoneyValue } from '../types/guards/isFieldMoneyValue'; +import { isFieldNumber } from '../types/guards/isFieldNumber'; +import { isFieldNumberValue } from '../types/guards/isFieldNumberValue'; +import { isFieldPhone } from '../types/guards/isFieldPhone'; +import { isFieldPhoneValue } from '../types/guards/isFieldPhoneValue'; +import { isFieldProbability } from '../types/guards/isFieldProbability'; +import { isFieldProbabilityValue } from '../types/guards/isFieldProbabilityValue'; +import { isFieldRelation } from '../types/guards/isFieldRelation'; +import { isFieldRelationValue } from '../types/guards/isFieldRelationValue'; +import { isFieldText } from '../types/guards/isFieldText'; +import { isFieldTextValue } from '../types/guards/isFieldTextValue'; +import { isFieldURL } from '../types/guards/isFieldURL'; +import { isFieldURLValue } from '../types/guards/isFieldURLValue'; + +export const usePersistField = () => { + const { entityId, fieldDefinition, useUpdateEntityMutation } = + useContext(FieldContext); + + const [updateEntity] = useUpdateEntityMutation(); + + const persistField = useRecoilCallback( + ({ set }) => + (valueToPersist: unknown) => { + const fieldIsRelation = + isFieldRelation(fieldDefinition) && + isFieldRelationValue(valueToPersist); + + const fieldIsChip = + isFieldChip(fieldDefinition) && isFieldChipValue(valueToPersist); + + const fieldIsDoubleText = + isFieldDoubleText(fieldDefinition) && + isFieldDoubleTextValue(valueToPersist); + + const fieldIsDoubleTextChip = + isFieldDoubleTextChip(fieldDefinition) && + isFieldDoubleTextChipValue(valueToPersist); + + const fieldIsText = + isFieldText(fieldDefinition) && isFieldTextValue(valueToPersist); + + const fieldIsEmail = + isFieldEmail(fieldDefinition) && isFieldEmailValue(valueToPersist); + + const fieldIsDate = + isFieldDate(fieldDefinition) && isFieldDateValue(valueToPersist); + + const fieldIsURL = + isFieldURL(fieldDefinition) && isFieldURLValue(valueToPersist); + + const fieldIsBoolean = + isFieldBoolean(fieldDefinition) && + isFieldBooleanValue(valueToPersist); + + const fieldIsProbability = + isFieldProbability(fieldDefinition) && + isFieldProbabilityValue(valueToPersist); + + const fieldIsNumber = + isFieldNumber(fieldDefinition) && isFieldNumberValue(valueToPersist); + + const fieldIsMoney = + isFieldMoney(fieldDefinition) && isFieldMoneyValue(valueToPersist); + + const fieldIsPhone = + isFieldPhone(fieldDefinition) && isFieldPhoneValue(valueToPersist); + + if (fieldIsRelation) { + const fieldName = fieldDefinition.metadata.fieldName; + + set( + entityFieldsFamilySelector({ entityId, fieldName }), + valueToPersist, + ); + + updateEntity({ + variables: { + where: { id: entityId }, + data: { + [fieldName]: valueToPersist + ? { connect: { id: valueToPersist.id } } + : { disconnect: true }, + }, + }, + }); + } else if (fieldIsChip) { + const fieldName = fieldDefinition.metadata.contentFieldName; + + set( + entityFieldsFamilySelector({ entityId, fieldName }), + valueToPersist, + ); + + updateEntity({ + variables: { + where: { id: entityId }, + data: { + [fieldName]: valueToPersist, + }, + }, + }); + } else if (fieldIsDoubleText || fieldIsDoubleTextChip) { + set( + entityFieldsFamilySelector({ + entityId, + fieldName: fieldDefinition.metadata.firstValueFieldName, + }), + valueToPersist.firstValue, + ); + + set( + entityFieldsFamilySelector({ + entityId, + fieldName: fieldDefinition.metadata.secondValueFieldName, + }), + valueToPersist.secondValue, + ); + + updateEntity({ + variables: { + where: { id: entityId }, + data: { + [fieldDefinition.metadata.firstValueFieldName]: + valueToPersist.firstValue, + [fieldDefinition.metadata.secondValueFieldName]: + valueToPersist.secondValue, + }, + }, + }); + } else if ( + fieldIsText || + fieldIsBoolean || + fieldIsURL || + fieldIsEmail || + fieldIsProbability || + fieldIsNumber || + fieldIsMoney || + fieldIsDate || + fieldIsPhone + ) { + const fieldName = fieldDefinition.metadata.fieldName; + + set( + entityFieldsFamilySelector({ entityId, fieldName }), + valueToPersist, + ); + + updateEntity({ + variables: { + where: { id: entityId }, + data: { + [fieldName]: valueToPersist, + }, + }, + }); + } else { + throw new Error( + `Invalid value to persist: ${valueToPersist} for type : ${fieldDefinition.type}, type may not be implemented in usePersistField.`, + ); + } + }, + [entityId, fieldDefinition, updateEntity], + ); + + return persistField; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/ChipFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/ChipFieldDisplay.tsx new file mode 100644 index 000000000..cab576cdd --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/ChipFieldDisplay.tsx @@ -0,0 +1,16 @@ +import { useChipField } from '../../hooks/useChipField'; +import { ChipDisplay } from '../content-display/components/ChipDisplay'; + +export const ChipFieldDisplay = () => { + const { avatarFieldValue, contentFieldValue, entityType, entityId } = + useChipField(); + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/DateFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/DateFieldDisplay.tsx new file mode 100644 index 000000000..46bc4a700 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/DateFieldDisplay.tsx @@ -0,0 +1,9 @@ +import { DateDisplay } from '@/ui/field/meta-types/display/content-display/components/DateDisplay'; + +import { useDateField } from '../../hooks/useDateField'; + +export const DateFieldDisplay = () => { + const { fieldValue } = useDateField(); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/DoubleTextChipFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/DoubleTextChipFieldDisplay.tsx new file mode 100644 index 000000000..653a19e12 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/DoubleTextChipFieldDisplay.tsx @@ -0,0 +1,18 @@ +import { useDoubleTextChipField } from '../../hooks/useDoubleTextChipField'; +import { ChipDisplay } from '../content-display/components/ChipDisplay'; + +export const DoubleTextChipFieldDisplay = () => { + const { avatarUrl, firstValue, secondValue, entityType, entityId } = + useDoubleTextChipField(); + + const content = [firstValue, secondValue].filter(Boolean).join(' '); + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/DoubleTextFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/DoubleTextFieldDisplay.tsx new file mode 100644 index 000000000..e12382a2e --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/DoubleTextFieldDisplay.tsx @@ -0,0 +1,10 @@ +import { useDoubleTextField } from '../../hooks/useDoubleTextField'; +import { TextDisplay } from '../content-display/components/TextDisplay'; + +export const DoubleTextFieldDisplay = () => { + const { firstValue, secondValue } = useDoubleTextField(); + + const content = [firstValue, secondValue].filter(Boolean).join(' '); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/EmailFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/EmailFieldDisplay.tsx new file mode 100644 index 000000000..7da95089b --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/EmailFieldDisplay.tsx @@ -0,0 +1,8 @@ +import { useEmailField } from '../../hooks/useEmailField'; +import { EmailDisplay } from '../content-display/components/EmailDisplay'; + +export const EmailFieldDisplay = () => { + const { fieldValue } = useEmailField(); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/MoneyFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/MoneyFieldDisplay.tsx new file mode 100644 index 000000000..d04b7ba5f --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/MoneyFieldDisplay.tsx @@ -0,0 +1,8 @@ +import { useMoneyField } from '../../hooks/useMoneyField'; +import { MoneyDisplay } from '../content-display/components/MoneyDisplay'; + +export const MoneyFieldDisplay = () => { + const { fieldValue } = useMoneyField(); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/NumberFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/NumberFieldDisplay.tsx new file mode 100644 index 000000000..f4635da19 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/NumberFieldDisplay.tsx @@ -0,0 +1,9 @@ +import { NumberDisplay } from '@/ui/field/meta-types/display/content-display/components/NumberDisplay'; + +import { useNumberField } from '../../hooks/useNumberField'; + +export const NumberFieldDisplay = () => { + const { fieldValue } = useNumberField(); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/PhoneFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/PhoneFieldDisplay.tsx new file mode 100644 index 000000000..839cde784 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/PhoneFieldDisplay.tsx @@ -0,0 +1,9 @@ +import { PhoneDisplay } from '@/ui/field/meta-types/display/content-display/components/PhoneDisplay'; + +import { usePhoneField } from '../../hooks/usePhoneField'; + +export const PhoneFieldDisplay = () => { + const { fieldValue } = usePhoneField(); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/RelationFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/RelationFieldDisplay.tsx new file mode 100644 index 000000000..62e8690d2 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/RelationFieldDisplay.tsx @@ -0,0 +1,51 @@ +import { CompanyChip } from '@/companies/components/CompanyChip'; +import { PersonChip } from '@/people/components/PersonChip'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; +import { UserChip } from '@/users/components/UserChip'; +import { getLogoUrlFromDomainName } from '~/utils'; + +import { useRelationField } from '../../hooks/useRelationField'; + +export const RelationFieldDisplay = () => { + const { fieldDefinition, fieldValue } = useRelationField(); + + switch (fieldDefinition.metadata.relationType) { + case Entity.Person: { + return ( + + ); + } + case Entity.User: { + return ( + + ); + } + case Entity.Company: { + return ( + + ); + } + default: + console.warn( + `Unknown relation type: "${fieldDefinition.metadata.relationType}" + in RelationFieldDisplay`, + ); + return <> ; + } +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/TextFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/TextFieldDisplay.tsx new file mode 100644 index 000000000..c10f87d6a --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/TextFieldDisplay.tsx @@ -0,0 +1,9 @@ +import { TextDisplay } from '@/ui/field/meta-types/display/content-display/components/TextDisplay'; + +import { useTextField } from '../../hooks/useTextField'; + +export const TextFieldDisplay = () => { + const { fieldValue } = useTextField(); + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/display/components/URLFieldDisplay.tsx b/front/src/modules/ui/field/meta-types/display/components/URLFieldDisplay.tsx new file mode 100644 index 000000000..1ac055af5 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/display/components/URLFieldDisplay.tsx @@ -0,0 +1,9 @@ +import { URLDisplay } from '@/ui/field/meta-types/display/content-display/components/URLDisplay'; + +import { useURLField } from '../../hooks/useURLField'; + +export const URLFieldDisplay = () => { + const { fieldValue } = useURLField(); + + return ; +}; diff --git a/front/src/modules/ui/content-display/components/DoubleTextChipDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/ChipDisplay.tsx similarity index 75% rename from front/src/modules/ui/content-display/components/DoubleTextChipDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/ChipDisplay.tsx index 70c2c3239..bd8798c8f 100644 --- a/front/src/modules/ui/content-display/components/DoubleTextChipDisplay.tsx +++ b/front/src/modules/ui/field/meta-types/display/content-display/components/ChipDisplay.tsx @@ -1,6 +1,7 @@ import { CompanyChip } from '@/companies/components/CompanyChip'; import { PersonChip } from '@/people/components/PersonChip'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; +import { getLogoUrlFromDomainName } from '~/utils'; type OwnProps = { entityType: Entity; @@ -9,7 +10,7 @@ type OwnProps = { avatarUrlValue?: string; }; -export const DoubleTextChipDisplay = ({ +export const ChipDisplay = ({ entityType, displayName, entityId, @@ -17,7 +18,13 @@ export const DoubleTextChipDisplay = ({ }: OwnProps) => { switch (entityType) { case Entity.Company: { - return ; + return ( + + ); } case Entity.Person: { return ( diff --git a/front/src/modules/ui/content-display/components/DateDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/DateDisplay.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/DateDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/DateDisplay.tsx diff --git a/front/src/modules/ui/content-display/components/DoubleTextDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/DoubleTextDisplay.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/DoubleTextDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/DoubleTextDisplay.tsx diff --git a/front/src/modules/ui/content-display/components/EmailDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/EmailDisplay.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/EmailDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/EmailDisplay.tsx diff --git a/front/src/modules/ui/content-display/components/MoneyDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/MoneyDisplay.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/MoneyDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/MoneyDisplay.tsx diff --git a/front/src/modules/ui/content-display/components/NumberDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/NumberDisplay.tsx similarity index 92% rename from front/src/modules/ui/content-display/components/NumberDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/NumberDisplay.tsx index 3a57531af..6190f25bd 100644 --- a/front/src/modules/ui/content-display/components/NumberDisplay.tsx +++ b/front/src/modules/ui/field/meta-types/display/content-display/components/NumberDisplay.tsx @@ -10,7 +10,7 @@ const StyledNumberDisplay = styled.div` `; type OwnProps = { - value: string; + value: string | number | null; }; export const NumberDisplay = ({ value }: OwnProps) => ( diff --git a/front/src/modules/ui/content-display/components/PhoneDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/PhoneDisplay.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/PhoneDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/PhoneDisplay.tsx diff --git a/front/src/modules/ui/content-display/components/TextDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/TextDisplay.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/TextDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/TextDisplay.tsx diff --git a/front/src/modules/ui/content-display/components/URLDisplay.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/URLDisplay.tsx similarity index 92% rename from front/src/modules/ui/content-display/components/URLDisplay.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/URLDisplay.tsx index f77261696..31b55d5f6 100644 --- a/front/src/modules/ui/content-display/components/URLDisplay.tsx +++ b/front/src/modules/ui/field/meta-types/display/content-display/components/URLDisplay.tsx @@ -15,7 +15,7 @@ const StyledRawLink = styled(RoundedLink)` `; type OwnProps = { - value: string; + value: string | null; }; const checkUrlType = (url: string) => { @@ -37,24 +37,27 @@ export const URLDisplay = ({ value }: OwnProps) => { const handleClick = (event: MouseEvent) => { event.stopPropagation(); }; + const absoluteUrl = value ? value.startsWith('http') ? value : 'https://' + value : ''; + const displayedValue = value ?? ''; + const type = checkUrlType(absoluteUrl); if (type === LinkType.LinkedIn || type === LinkType.Twitter) { return ( - {value} + {displayedValue} ); } return ( - {value} + {displayedValue} ); }; diff --git a/front/src/modules/ui/content-display/components/__stories__/PhoneInputDisplay.stories.tsx b/front/src/modules/ui/field/meta-types/display/content-display/components/__stories__/PhoneInputDisplay.stories.tsx similarity index 100% rename from front/src/modules/ui/content-display/components/__stories__/PhoneInputDisplay.stories.tsx rename to front/src/modules/ui/field/meta-types/display/content-display/components/__stories__/PhoneInputDisplay.stories.tsx diff --git a/front/src/modules/ui/field/meta-types/hooks/useBooleanField.ts b/front/src/modules/ui/field/meta-types/hooks/useBooleanField.ts new file mode 100644 index 000000000..b9f130975 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useBooleanField.ts @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldBoolean } from '../../types/guards/isFieldBoolean'; + +export const useBooleanField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('boolean', isFieldBoolean, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useChipField.ts b/front/src/modules/ui/field/meta-types/hooks/useChipField.ts new file mode 100644 index 000000000..dffefaa7b --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useChipField.ts @@ -0,0 +1,43 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldChip } from '../../types/guards/isFieldChip'; + +export const useChipField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('chip', isFieldChip, fieldDefinition); + + const contentFieldName = fieldDefinition.metadata.contentFieldName; + const avatarUrlFieldName = fieldDefinition.metadata.urlFieldName; + + const [contentFieldValue, setContentFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: contentFieldName, + }), + ); + + const [avatarFieldValue, setAvatarFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: avatarUrlFieldName, + }), + ); + + const entityType = fieldDefinition.metadata.relationType; + + return { + fieldDefinition, + contentFieldValue, + setContentFieldValue, + avatarFieldValue, + setAvatarFieldValue, + entityType, + entityId, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useDateField.ts b/front/src/modules/ui/field/meta-types/hooks/useDateField.ts new file mode 100644 index 000000000..c47a0a700 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useDateField.ts @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldDate } from '../../types/guards/isFieldDate'; + +export const useDateField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('date', isFieldDate, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useDoubleTextChipField.ts b/front/src/modules/ui/field/meta-types/hooks/useDoubleTextChipField.ts new file mode 100644 index 000000000..cefda738a --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useDoubleTextChipField.ts @@ -0,0 +1,56 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldDoubleTextChip } from '../../types/guards/isFieldDoubleTextChip'; + +export const useDoubleTextChipField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata( + 'double-text-chip', + isFieldDoubleTextChip, + fieldDefinition, + ); + + const [firstValue, setFirstValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldDefinition.metadata.firstValueFieldName, + }), + ); + + const [secondValue, setSecondValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldDefinition.metadata.secondValueFieldName, + }), + ); + + const [avatarUrl, setAvatarUrl] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldDefinition.metadata.avatarUrlFieldName, + }), + ); + + const fullValue = [firstValue, secondValue].filter(Boolean).join(' '); + + const entityType = fieldDefinition.metadata.entityType; + + return { + fieldDefinition, + avatarUrl, + setAvatarUrl, + secondValue, + setSecondValue, + firstValue, + setFirstValue, + fullValue, + entityType, + entityId, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useDoubleTextField.ts b/front/src/modules/ui/field/meta-types/hooks/useDoubleTextField.ts new file mode 100644 index 000000000..d4d0690b4 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useDoubleTextField.ts @@ -0,0 +1,39 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldDoubleText } from '../../types/guards/isFieldDoubleText'; + +export const useDoubleTextField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('double-text', isFieldDoubleText, fieldDefinition); + + const [firstValue, setFirstValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldDefinition.metadata.firstValueFieldName, + }), + ); + + const [secondValue, setSecondValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldDefinition.metadata.secondValueFieldName, + }), + ); + + const fullValue = [firstValue, secondValue].filter(Boolean).join(' '); + + return { + fieldDefinition, + secondValue, + setSecondValue, + firstValue, + setFirstValue, + fullValue, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useEmailField.ts b/front/src/modules/ui/field/meta-types/hooks/useEmailField.ts new file mode 100644 index 000000000..2402b28f0 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useEmailField.ts @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldEmail } from '../../types/guards/isFieldEmail'; + +export const useEmailField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('email', isFieldEmail, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useMoneyField.ts b/front/src/modules/ui/field/meta-types/hooks/useMoneyField.ts new file mode 100644 index 000000000..595bc2376 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useMoneyField.ts @@ -0,0 +1,48 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { + canBeCastAsIntegerOrNull, + castAsIntegerOrNull, +} from '~/utils/cast-as-integer-or-null'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { usePersistField } from '../../hooks/usePersistField'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldMoney } from '../../types/guards/isFieldMoney'; + +export const useMoneyField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('moneyAmount', isFieldMoney, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + const persistField = usePersistField(); + + const persistMoneyField = (newValue: string) => { + if (!canBeCastAsIntegerOrNull(newValue)) { + return; + } + + const castedValue = castAsIntegerOrNull(newValue); + + persistField(castedValue); + }; + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + persistMoneyField, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useNumberField.ts b/front/src/modules/ui/field/meta-types/hooks/useNumberField.ts new file mode 100644 index 000000000..830156849 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useNumberField.ts @@ -0,0 +1,48 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { + canBeCastAsIntegerOrNull, + castAsIntegerOrNull, +} from '~/utils/cast-as-integer-or-null'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { usePersistField } from '../../hooks/usePersistField'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldNumber } from '../../types/guards/isFieldNumber'; + +export const useNumberField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('number', isFieldNumber, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + const persistField = usePersistField(); + + const persistNumberField = (newValue: string) => { + if (!canBeCastAsIntegerOrNull(newValue)) { + return; + } + + const castedValue = castAsIntegerOrNull(newValue); + + persistField(castedValue); + }; + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + persistNumberField, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/usePhoneField.ts b/front/src/modules/ui/field/meta-types/hooks/usePhoneField.ts new file mode 100644 index 000000000..58c4d0f0c --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/usePhoneField.ts @@ -0,0 +1,40 @@ +import { useContext } from 'react'; +import { isPossiblePhoneNumber } from 'libphonenumber-js'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { usePersistField } from '../../hooks/usePersistField'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldPhone } from '../../types/guards/isFieldPhone'; + +export const usePhoneField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('phone', isFieldPhone, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + const persistField = usePersistField(); + + const persistPhoneField = (newPhoneValue: string) => { + if (!isPossiblePhoneNumber(newPhoneValue)) return; + + persistField(newPhoneValue); + }; + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + persistPhoneField, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useProbabilityField.ts b/front/src/modules/ui/field/meta-types/hooks/useProbabilityField.ts new file mode 100644 index 000000000..54a088497 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useProbabilityField.ts @@ -0,0 +1,31 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldProbability } from '../../types/guards/isFieldProbability'; + +export const useProbabilityField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('probability', isFieldProbability, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + const probabilityIndex = Math.ceil((fieldValue ?? 0) / 25); + + return { + fieldDefinition, + probabilityIndex, + setFieldValue, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useRelationField.ts b/front/src/modules/ui/field/meta-types/hooks/useRelationField.ts new file mode 100644 index 000000000..116ed73bf --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useRelationField.ts @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldRelation } from '../../types/guards/isFieldRelation'; + +// TODO: we will be able to type more precisely when we will have custom field and custom entities support +export const useRelationField = () => { + const { entityId, fieldDefinition } = useContext(FieldContext); + + assertFieldMetadata('relation', isFieldRelation, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + return { + fieldDefinition, + fieldValue, + setFieldValue, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useTextField.ts b/front/src/modules/ui/field/meta-types/hooks/useTextField.ts new file mode 100644 index 000000000..01bb9931c --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useTextField.ts @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldText } from '../../types/guards/isFieldText'; + +export const useTextField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('text', isFieldText, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/hooks/useURLField.ts b/front/src/modules/ui/field/meta-types/hooks/useURLField.ts new file mode 100644 index 000000000..7c79e0b46 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/hooks/useURLField.ts @@ -0,0 +1,43 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { isURL } from '~/utils/is-url'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { usePersistField } from '../../hooks/usePersistField'; +import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; +import { isFieldURL } from '../../types/guards/isFieldURL'; + +export const useURLField = () => { + const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + + assertFieldMetadata('url', isFieldURL, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + entityFieldsFamilySelector({ + entityId: entityId, + fieldName: fieldName, + }), + ); + + const persistField = usePersistField(); + + const persistURLField = (newValue: string) => { + if (!isURL(newValue)) { + return; + } + + persistField(newValue); + }; + + return { + fieldDefinition, + fieldValue, + setFieldValue, + hotkeyScope, + persistURLField, + }; +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx new file mode 100644 index 000000000..e9810dd20 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx @@ -0,0 +1,22 @@ +import { BooleanInput } from '@/ui/input/components/BooleanInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useBooleanField } from '../../hooks/useBooleanField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onSubmit?: FieldInputEvent; +}; + +export const BooleanFieldInput = ({ onSubmit }: OwnProps) => { + const { fieldValue } = useBooleanField(); + + const persistField = usePersistField(); + + const handleToggle = (newValue: boolean) => { + onSubmit?.(() => persistField(newValue)); + }; + + return ; +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx new file mode 100644 index 000000000..38b633511 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx @@ -0,0 +1,63 @@ +import { TextInput } from '@/ui/input/components/TextInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useChipField } from '../../hooks/useChipField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const ChipFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, contentFieldValue, hotkeyScope } = useChipField(); + + const persistField = usePersistField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx new file mode 100644 index 000000000..10f47f9b1 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx @@ -0,0 +1,62 @@ +import { DateInput } from '@/ui/input/components/DateInput'; +import { Nullable } from '~/types/Nullable'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useDateField } from '../../hooks/useDateField'; + +export type FieldInputEvent = (persist: () => void) => void; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const DateFieldInput = ({ + onEnter, + onEscape, + onClickOutside, +}: OwnProps) => { + const { fieldValue, hotkeyScope } = useDateField(); + + const persistField = usePersistField(); + + const persistDate = (newDate: Nullable) => { + if (!newDate) { + persistField(''); + } else { + const newDateISO = newDate?.toISOString(); + + persistField(newDateISO); + } + }; + + const handleEnter = (newDate: Nullable) => { + onEnter?.(() => persistDate(newDate)); + }; + + const handleEscape = (newDate: Nullable) => { + onEscape?.(() => persistDate(newDate)); + }; + + const handleClickOutside = ( + _event: MouseEvent | TouchEvent, + newDate: Nullable, + ) => { + onClickOutside?.(() => persistDate(newDate)); + }; + + const dateValue = fieldValue ? new Date(fieldValue) : null; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx new file mode 100644 index 000000000..fdafe6ab6 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx @@ -0,0 +1,66 @@ +import { FieldDoubleText } from '@/ui/field/types/FieldDoubleText'; +import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useDoubleTextChipField } from '../../hooks/useDoubleTextChipField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const DoubleTextChipFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, firstValue, secondValue, hotkeyScope } = + useDoubleTextChipField(); + + const persistField = usePersistField(); + + const handleEnter = (newDoubleText: FieldDoubleText) => { + onEnter?.(() => persistField(newDoubleText)); + }; + + const handleEscape = (newDoubleText: FieldDoubleText) => { + onEscape?.(() => persistField(newDoubleText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newDoubleText: FieldDoubleText, + ) => { + onClickOutside?.(() => persistField(newDoubleText)); + }; + + const handleTab = (newDoubleText: FieldDoubleText) => { + onTab?.(() => persistField(newDoubleText)); + }; + + const handleShiftTab = (newDoubleText: FieldDoubleText) => { + onShiftTab?.(() => persistField(newDoubleText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx new file mode 100644 index 000000000..5538255e5 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx @@ -0,0 +1,66 @@ +import { FieldDoubleText } from '@/ui/field/types/FieldDoubleText'; +import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useDoubleTextField } from '../../hooks/useDoubleTextField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const DoubleTextFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, firstValue, secondValue, hotkeyScope } = + useDoubleTextField(); + + const persistField = usePersistField(); + + const handleEnter = (newDoubleText: FieldDoubleText) => { + onEnter?.(() => persistField(newDoubleText)); + }; + + const handleEscape = (newDoubleText: FieldDoubleText) => { + onEscape?.(() => persistField(newDoubleText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newDoubleText: FieldDoubleText, + ) => { + onClickOutside?.(() => persistField(newDoubleText)); + }; + + const handleTab = (newDoubleText: FieldDoubleText) => { + onTab?.(() => persistField(newDoubleText)); + }; + + const handleShiftTab = (newDoubleText: FieldDoubleText) => { + onShiftTab?.(() => persistField(newDoubleText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx new file mode 100644 index 000000000..9a90b67f0 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx @@ -0,0 +1,63 @@ +import { TextInput } from '@/ui/input/components/TextInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useEmailField } from '../../hooks/useEmailField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const EmailFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, fieldValue, hotkeyScope } = useEmailField(); + + const persistField = usePersistField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx new file mode 100644 index 000000000..321108d26 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx @@ -0,0 +1,61 @@ +import { TextInput } from '@/ui/input/components/TextInput'; + +import { useMoneyField } from '../../hooks/useMoneyField'; + +export type FieldInputEvent = (persist: () => void) => void; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const MoneyFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, fieldValue, hotkeyScope, persistMoneyField } = + useMoneyField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistMoneyField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistMoneyField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistMoneyField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistMoneyField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistMoneyField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx new file mode 100644 index 000000000..b7f6ec331 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx @@ -0,0 +1,61 @@ +import { TextInput } from '@/ui/input/components/TextInput'; + +import { useNumberField } from '../../hooks/useNumberField'; + +export type FieldInputEvent = (persist: () => void) => void; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const NumberFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, fieldValue, hotkeyScope, persistNumberField } = + useNumberField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistNumberField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistNumberField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistNumberField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistNumberField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistNumberField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx new file mode 100644 index 000000000..772b19617 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx @@ -0,0 +1,61 @@ +import { PhoneInput } from '@/ui/input/components/PhoneInput'; + +import { usePhoneField } from '../../hooks/usePhoneField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const PhoneFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, fieldValue, hotkeyScope, persistPhoneField } = + usePhoneField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistPhoneField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistPhoneField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistPhoneField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistPhoneField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistPhoneField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx new file mode 100644 index 000000000..996f87d03 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx @@ -0,0 +1,27 @@ +import { ProbabilityInput } from '@/ui/input/components/ProbabilityInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useProbabilityField } from '../../hooks/useProbabilityField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onSubmit?: FieldInputEvent; +}; + +export const ProbabilityFieldInput = ({ onSubmit }: OwnProps) => { + const { probabilityIndex } = useProbabilityField(); + + const persistField = usePersistField(); + + const handleChange = (newValue: number) => { + onSubmit?.(() => persistField(newValue)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx new file mode 100644 index 000000000..5998b7f8d --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/RelationFieldInput.tsx @@ -0,0 +1,58 @@ +import styled from '@emotion/styled'; + +import { CompanyPicker } from '@/companies/components/CompanyPicker'; +import { PeoplePicker } from '@/people/components/PeoplePicker'; +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 { usePersistField } from '../../../hooks/usePersistField'; +import { useRelationField } from '../../hooks/useRelationField'; + +import { FieldInputEvent } from './DateFieldInput'; + +const StyledRelationPickerContainer = styled.div` + left: 0px; + position: absolute; + top: -8px; +`; + +type OwnProps = { + onSubmit?: FieldInputEvent; + onCancel?: () => void; +}; + +export const RelationFieldInput = ({ onSubmit, onCancel }: OwnProps) => { + const { fieldDefinition, fieldValue } = useRelationField(); + + const persistField = usePersistField(); + + const handleSubmit = (newEntity: EntityForSelect | null) => { + onSubmit?.(() => persistField(newEntity?.originalEntity ?? null)); + }; + + return ( + + {fieldDefinition.metadata.relationType === Entity.Person ? ( + + ) : fieldDefinition.metadata.relationType === Entity.User ? ( + + ) : fieldDefinition.metadata.relationType === Entity.Company ? ( + + ) : null} + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx new file mode 100644 index 000000000..737266425 --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx @@ -0,0 +1,63 @@ +import { TextInput } from '@/ui/input/components/TextInput'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useTextField } from '../../hooks/useTextField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const TextFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, fieldValue, hotkeyScope } = useTextField(); + + const persistField = usePersistField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx new file mode 100644 index 000000000..3ad605aba --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx @@ -0,0 +1,61 @@ +import { TextInput } from '@/ui/input/components/TextInput'; + +import { useURLField } from '../../hooks/useURLField'; + +import { FieldInputEvent } from './DateFieldInput'; + +type OwnProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; + onTab?: FieldInputEvent; + onShiftTab?: FieldInputEvent; +}; + +export const URLFieldInput = ({ + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: OwnProps) => { + const { fieldDefinition, fieldValue, hotkeyScope, persistURLField } = + useURLField(); + + const handleEnter = (newText: string) => { + onEnter?.(() => persistURLField(newText)); + }; + + const handleEscape = (newText: string) => { + onEscape?.(() => persistURLField(newText)); + }; + + const handleClickOutside = ( + event: MouseEvent | TouchEvent, + newText: string, + ) => { + onClickOutside?.(() => persistURLField(newText)); + }; + + const handleTab = (newText: string) => { + onTab?.(() => persistURLField(newText)); + }; + + const handleShiftTab = (newText: string) => { + onShiftTab?.(() => persistURLField(newText)); + }; + + return ( + + ); +}; diff --git a/front/src/modules/ui/table/states/tableEntitiesFamilyState.ts b/front/src/modules/ui/field/states/entityFieldsFamilyState.ts similarity index 54% rename from front/src/modules/ui/table/states/tableEntitiesFamilyState.ts rename to front/src/modules/ui/field/states/entityFieldsFamilyState.ts index 3a3f394e6..6ddec3775 100644 --- a/front/src/modules/ui/table/states/tableEntitiesFamilyState.ts +++ b/front/src/modules/ui/field/states/entityFieldsFamilyState.ts @@ -1,9 +1,9 @@ import { atomFamily } from 'recoil'; -export const tableEntitiesFamilyState = atomFamily< +export const entityFieldsFamilyState = atomFamily< Record | null, string >({ - key: 'tableEntitiesFamilyState', + key: 'entityFieldsFamilyState', default: null, }); diff --git a/front/src/modules/ui/field/states/isFieldEmptyScopedState.ts b/front/src/modules/ui/field/states/isFieldEmptyScopedState.ts new file mode 100644 index 000000000..8045f5cc9 --- /dev/null +++ b/front/src/modules/ui/field/states/isFieldEmptyScopedState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const isFieldEmptyScopedState = atomFamily({ + key: 'isFieldEmptyScopedState', + default: false, +}); diff --git a/front/src/modules/ui/table/states/selectors/tableEntityFieldFamilySelector.ts b/front/src/modules/ui/field/states/selectors/entityFieldsFamilySelector.ts similarity index 51% rename from front/src/modules/ui/table/states/selectors/tableEntityFieldFamilySelector.ts rename to front/src/modules/ui/field/states/selectors/entityFieldsFamilySelector.ts index c4b83d27f..89d29ea5c 100644 --- a/front/src/modules/ui/table/states/selectors/tableEntityFieldFamilySelector.ts +++ b/front/src/modules/ui/field/states/selectors/entityFieldsFamilySelector.ts @@ -1,17 +1,17 @@ import { selectorFamily } from 'recoil'; -import { tableEntitiesFamilyState } from '../tableEntitiesFamilyState'; +import { entityFieldsFamilyState } from '../entityFieldsFamilyState'; -export const tableEntityFieldFamilySelector = selectorFamily({ - key: 'tableEntityFieldFamilySelector', +export const entityFieldsFamilySelector = selectorFamily({ + key: 'entityFieldsFamilySelector', get: ({ fieldName, entityId }: { fieldName: string; entityId: string }) => ({ get }) => - get(tableEntitiesFamilyState(entityId))?.[fieldName] as T, + get(entityFieldsFamilyState(entityId))?.[fieldName] as T, set: ({ fieldName, entityId }: { fieldName: string; entityId: string }) => ({ set }, newValue: T) => - set(tableEntitiesFamilyState(entityId), (prevState) => ({ + set(entityFieldsFamilyState(entityId), (prevState) => ({ ...prevState, [fieldName]: newValue, })), diff --git a/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts b/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts new file mode 100644 index 000000000..d89f04b1a --- /dev/null +++ b/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts @@ -0,0 +1,59 @@ +import { selectorFamily } from 'recoil'; + +import { FieldDefinition } from '../../types/FieldDefinition'; +import { FieldMetadata } from '../../types/FieldMetadata'; +import { isFieldDate } from '../../types/guards/isFieldDate'; +import { isFieldEmail } from '../../types/guards/isFieldEmail'; +import { isFieldMoney } from '../../types/guards/isFieldMoney'; +import { isFieldNumber } from '../../types/guards/isFieldNumber'; +import { isFieldPhone } from '../../types/guards/isFieldPhone'; +import { isFieldRelation } from '../../types/guards/isFieldRelation'; +import { isFieldRelationValue } from '../../types/guards/isFieldRelationValue'; +import { isFieldText } from '../../types/guards/isFieldText'; +import { isFieldURL } from '../../types/guards/isFieldURL'; +import { entityFieldsFamilyState } from '../entityFieldsFamilyState'; + +export const isEntityFieldEmptyFamilySelector = selectorFamily({ + key: 'isEntityFieldEmptyFamilySelector', + get: ({ + fieldDefinition, + entityId, + }: { + fieldDefinition: Pick< + FieldDefinition, + 'type' | 'metadata' | 'key' | 'name' + >; + entityId: string; + }) => { + return ({ get }) => { + if ( + isFieldText(fieldDefinition) || + isFieldURL(fieldDefinition) || + isFieldDate(fieldDefinition) || + isFieldNumber(fieldDefinition) || + isFieldMoney(fieldDefinition) || + isFieldEmail(fieldDefinition) || + isFieldPhone(fieldDefinition) + ) { + const fieldName = fieldDefinition.metadata.fieldName; + const fieldValue = get(entityFieldsFamilyState(entityId))?.[ + fieldName + ] as string | null; + + return ( + fieldValue === null || fieldValue === undefined || fieldValue === '' + ); + } else if (isFieldRelation(fieldDefinition)) { + const fieldName = fieldDefinition.metadata.fieldName; + + const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName]; + + if (isFieldRelationValue(fieldValue)) { + return fieldValue === null || fieldValue === undefined; + } + } + + return false; + }; + }, +}); diff --git a/front/src/modules/ui/field/types/FieldDefinition.ts b/front/src/modules/ui/field/types/FieldDefinition.ts new file mode 100644 index 000000000..e42b7654e --- /dev/null +++ b/front/src/modules/ui/field/types/FieldDefinition.ts @@ -0,0 +1,13 @@ +import { IconComponent } from '@/ui/icon/types/IconComponent'; + +import { FieldMetadata } from './FieldMetadata'; +import { FieldType } from './FieldType'; + +export type FieldDefinition = { + key: string; + name: string; + Icon?: IconComponent; + type: FieldType; + metadata: T; + useEditButton?: boolean; +}; diff --git a/front/src/modules/ui/field/types/FieldDefinitionWithTypeOnly.ts b/front/src/modules/ui/field/types/FieldDefinitionWithTypeOnly.ts new file mode 100644 index 000000000..6e1a8ccca --- /dev/null +++ b/front/src/modules/ui/field/types/FieldDefinitionWithTypeOnly.ts @@ -0,0 +1,7 @@ +import { FieldDefinition } from './FieldDefinition'; +import { FieldMetadata } from './FieldMetadata'; + +export type FieldDefinitionSerializable = Omit< + FieldDefinition, + 'Icon' +>; diff --git a/front/src/modules/ui/field/types/FieldDoubleText.ts b/front/src/modules/ui/field/types/FieldDoubleText.ts new file mode 100644 index 000000000..1f5db8494 --- /dev/null +++ b/front/src/modules/ui/field/types/FieldDoubleText.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +import { DoubleTextTypeResolver } from './resolvers/DoubleTextTypeResolver'; + +export type FieldDoubleText = z.infer; diff --git a/front/src/modules/ui/field/types/FieldInputEvent.ts b/front/src/modules/ui/field/types/FieldInputEvent.ts new file mode 100644 index 000000000..bf967a050 --- /dev/null +++ b/front/src/modules/ui/field/types/FieldInputEvent.ts @@ -0,0 +1 @@ +export type FieldInputEvent = (persist: () => void) => void; diff --git a/front/src/modules/ui/editable-field/types/FieldMetadata.ts b/front/src/modules/ui/field/types/FieldMetadata.ts similarity index 91% rename from front/src/modules/ui/editable-field/types/FieldMetadata.ts rename to front/src/modules/ui/field/types/FieldMetadata.ts index 3a7003a3c..cf64c9d63 100644 --- a/front/src/modules/ui/editable-field/types/FieldMetadata.ts +++ b/front/src/modules/ui/field/types/FieldMetadata.ts @@ -1,22 +1,6 @@ import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -export type FieldType = - | 'unknown' - | 'text' - | 'relation' - | 'chip' - | 'double-text-chip' - | 'double-text' - | 'number' - | 'email' - | 'boolean' - | 'date' - | 'phone' - | 'url' - | 'probability' - | 'moneyAmount'; - export type FieldTextMetadata = { placeHolder: string; fieldName: string; @@ -39,6 +23,13 @@ export type FieldDateMetadata = { export type FieldNumberMetadata = { fieldName: string; placeHolder: string; + isPositive?: boolean; +}; + +export type FieldMoneyMetadata = { + fieldName: string; + placeHolder: string; + isPositive?: boolean; }; export type FieldEmailMetadata = { @@ -92,6 +83,7 @@ export type FieldMetadata = | FieldPhoneMetadata | FieldURLMetadata | FieldNumberMetadata + | FieldMoneyMetadata | FieldEmailMetadata | FieldDateMetadata | FieldProbabilityMetadata @@ -104,6 +96,7 @@ export type FieldDateValue = string | null; export type FieldPhoneValue = string; export type FieldURLValue = string; export type FieldNumberValue = number | null; +export type FieldMoneyValue = number | null; export type FieldEmailValue = string; export type FieldProbabilityValue = number; export type FieldBooleanValue = boolean; diff --git a/front/src/modules/ui/field/types/FieldType.ts b/front/src/modules/ui/field/types/FieldType.ts new file mode 100644 index 000000000..ad4992377 --- /dev/null +++ b/front/src/modules/ui/field/types/FieldType.ts @@ -0,0 +1,14 @@ +export type FieldType = + | 'text' + | 'relation' + | 'chip' + | 'double-text-chip' + | 'double-text' + | 'number' + | 'email' + | 'boolean' + | 'date' + | 'phone' + | 'url' + | 'probability' + | 'moneyAmount'; diff --git a/front/src/modules/ui/field/types/guards/assertFieldMetadata.ts b/front/src/modules/ui/field/types/guards/assertFieldMetadata.ts new file mode 100644 index 000000000..fde2f235f --- /dev/null +++ b/front/src/modules/ui/field/types/guards/assertFieldMetadata.ts @@ -0,0 +1,71 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { + FieldBooleanMetadata, + FieldChipMetadata, + FieldDateMetadata, + FieldDoubleTextChipMetadata, + FieldDoubleTextMetadata, + FieldEmailMetadata, + FieldMetadata, + FieldMoneyMetadata, + FieldNumberMetadata, + FieldPhoneMetadata, + FieldProbabilityMetadata, + FieldRelationMetadata, + FieldTextMetadata, + FieldURLMetadata, +} from '../FieldMetadata'; +import { FieldType } from '../FieldType'; + +type AssertFieldMetadataFunction = < + E extends FieldType, + T extends E extends 'text' + ? FieldTextMetadata + : E extends 'relation' + ? FieldRelationMetadata + : E extends 'chip' + ? FieldChipMetadata + : E extends 'double-text-chip' + ? FieldDoubleTextChipMetadata + : E extends 'double-text' + ? FieldDoubleTextMetadata + : E extends 'number' + ? FieldNumberMetadata + : E extends 'email' + ? FieldEmailMetadata + : E extends 'boolean' + ? FieldBooleanMetadata + : E extends 'date' + ? FieldDateMetadata + : E extends 'phone' + ? FieldPhoneMetadata + : E extends 'url' + ? FieldURLMetadata + : E extends 'probability' + ? FieldProbabilityMetadata + : E extends 'moneyAmount' + ? FieldMoneyMetadata + : never, +>( + fieldType: E, + fieldTypeGuard: ( + a: FieldDefinition, + ) => a is FieldDefinition, + fieldDefinition: FieldDefinition, +) => asserts fieldDefinition is FieldDefinition; + +export const assertFieldMetadata: AssertFieldMetadataFunction = ( + fieldType, + fieldTypeGuard, + fieldDefinition, +) => { + const fieldDefinitionType = fieldDefinition.type; + + if (!fieldTypeGuard(fieldDefinition) || fieldDefinitionType !== fieldType) { + throw new Error( + `Trying to use a "${fieldDefinitionType}" field as a "${fieldType}" field. Verify that the field is defined as a type "${fieldDefinitionType}" field in assertFieldMetadata.ts.`, + ); + } else { + return; + } +}; diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldBoolean.ts b/front/src/modules/ui/field/types/guards/isFieldBoolean.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldBoolean.ts rename to front/src/modules/ui/field/types/guards/isFieldBoolean.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldBooleanValue.ts b/front/src/modules/ui/field/types/guards/isFieldBooleanValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldBooleanValue.ts rename to front/src/modules/ui/field/types/guards/isFieldBooleanValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldChip.ts b/front/src/modules/ui/field/types/guards/isFieldChip.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldChip.ts rename to front/src/modules/ui/field/types/guards/isFieldChip.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldChipValue.ts b/front/src/modules/ui/field/types/guards/isFieldChipValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldChipValue.ts rename to front/src/modules/ui/field/types/guards/isFieldChipValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDate.ts b/front/src/modules/ui/field/types/guards/isFieldDate.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldDate.ts rename to front/src/modules/ui/field/types/guards/isFieldDate.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDateValue.ts b/front/src/modules/ui/field/types/guards/isFieldDateValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldDateValue.ts rename to front/src/modules/ui/field/types/guards/isFieldDateValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleText.ts b/front/src/modules/ui/field/types/guards/isFieldDoubleText.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldDoubleText.ts rename to front/src/modules/ui/field/types/guards/isFieldDoubleText.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChip.ts b/front/src/modules/ui/field/types/guards/isFieldDoubleTextChip.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChip.ts rename to front/src/modules/ui/field/types/guards/isFieldDoubleTextChip.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChipValue.ts b/front/src/modules/ui/field/types/guards/isFieldDoubleTextChipValue.ts similarity index 63% rename from front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChipValue.ts rename to front/src/modules/ui/field/types/guards/isFieldDoubleTextChipValue.ts index 64a711f99..ae90fe9ce 100644 --- a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChipValue.ts +++ b/front/src/modules/ui/field/types/guards/isFieldDoubleTextChipValue.ts @@ -1,9 +1,9 @@ import { FieldDoubleTextChipValue } from '../FieldMetadata'; +import { DoubleTextTypeResolver } from '../resolvers/DoubleTextTypeResolver'; -// TODO: add yup export const isFieldDoubleTextChipValue = ( fieldValue: unknown, ): fieldValue is FieldDoubleTextChipValue => fieldValue !== null && fieldValue !== undefined && - typeof fieldValue === 'object'; + DoubleTextTypeResolver.safeParse(fieldValue).success; diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextValue.ts b/front/src/modules/ui/field/types/guards/isFieldDoubleTextValue.ts similarity index 63% rename from front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextValue.ts rename to front/src/modules/ui/field/types/guards/isFieldDoubleTextValue.ts index ad9eb3cb0..cd38e733f 100644 --- a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextValue.ts +++ b/front/src/modules/ui/field/types/guards/isFieldDoubleTextValue.ts @@ -1,4 +1,5 @@ import { FieldDoubleTextValue } from '../FieldMetadata'; +import { DoubleTextTypeResolver } from '../resolvers/DoubleTextTypeResolver'; // TODO: add yup export const isFieldDoubleTextValue = ( @@ -6,4 +7,4 @@ export const isFieldDoubleTextValue = ( ): fieldValue is FieldDoubleTextValue => fieldValue !== null && fieldValue !== undefined && - typeof fieldValue === 'object'; + DoubleTextTypeResolver.safeParse(fieldValue).success; diff --git a/front/src/modules/ui/field/types/guards/isFieldEmail.ts b/front/src/modules/ui/field/types/guards/isFieldEmail.ts new file mode 100644 index 000000000..7554c1df7 --- /dev/null +++ b/front/src/modules/ui/field/types/guards/isFieldEmail.ts @@ -0,0 +1,6 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldEmailMetadata, FieldMetadata } from '../FieldMetadata'; + +export const isFieldEmail = ( + field: FieldDefinition, +): field is FieldDefinition => field.type === 'email'; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldURLValue.ts b/front/src/modules/ui/field/types/guards/isFieldEmailValue.ts similarity index 50% rename from front/src/modules/ui/editable-field/types/guards/isViewFieldURLValue.ts rename to front/src/modules/ui/field/types/guards/isFieldEmailValue.ts index 486fc205c..72ffdd649 100644 --- a/front/src/modules/ui/editable-field/types/guards/isViewFieldURLValue.ts +++ b/front/src/modules/ui/field/types/guards/isFieldEmailValue.ts @@ -1,9 +1,9 @@ -import { ViewFieldURLValue } from '../ViewField'; +import { FieldEmailValue } from '../FieldMetadata'; // TODO: add yup -export const isViewFieldURLValue = ( +export const isFieldEmailValue = ( fieldValue: unknown, -): fieldValue is ViewFieldURLValue => +): fieldValue is FieldEmailValue => fieldValue !== null && fieldValue !== undefined && typeof fieldValue === 'string'; diff --git a/front/src/modules/ui/field/types/guards/isFieldMoney.ts b/front/src/modules/ui/field/types/guards/isFieldMoney.ts new file mode 100644 index 000000000..b4e301349 --- /dev/null +++ b/front/src/modules/ui/field/types/guards/isFieldMoney.ts @@ -0,0 +1,6 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldMoneyMetadata } from '../FieldMetadata'; + +export const isFieldMoney = ( + field: FieldDefinition, +): field is FieldDefinition => field.type === 'moneyAmount'; diff --git a/front/src/modules/ui/field/types/guards/isFieldMoneyValue.ts b/front/src/modules/ui/field/types/guards/isFieldMoneyValue.ts new file mode 100644 index 000000000..7820c0592 --- /dev/null +++ b/front/src/modules/ui/field/types/guards/isFieldMoneyValue.ts @@ -0,0 +1,8 @@ +import { FieldMoneyValue } from '../FieldMetadata'; + +// TODO: add yup +export const isFieldMoneyValue = ( + fieldValue: unknown, +): fieldValue is FieldMoneyValue => + fieldValue === null || + (fieldValue !== undefined && typeof fieldValue === 'number'); diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldNumber.ts b/front/src/modules/ui/field/types/guards/isFieldNumber.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldNumber.ts rename to front/src/modules/ui/field/types/guards/isFieldNumber.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldNumberValue.ts b/front/src/modules/ui/field/types/guards/isFieldNumberValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldNumberValue.ts rename to front/src/modules/ui/field/types/guards/isFieldNumberValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldPhone.ts b/front/src/modules/ui/field/types/guards/isFieldPhone.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldPhone.ts rename to front/src/modules/ui/field/types/guards/isFieldPhone.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldPhoneValue.ts b/front/src/modules/ui/field/types/guards/isFieldPhoneValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldPhoneValue.ts rename to front/src/modules/ui/field/types/guards/isFieldPhoneValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldProbability.ts b/front/src/modules/ui/field/types/guards/isFieldProbability.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldProbability.ts rename to front/src/modules/ui/field/types/guards/isFieldProbability.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldProbabilityValue.ts b/front/src/modules/ui/field/types/guards/isFieldProbabilityValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldProbabilityValue.ts rename to front/src/modules/ui/field/types/guards/isFieldProbabilityValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldRelation.ts b/front/src/modules/ui/field/types/guards/isFieldRelation.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldRelation.ts rename to front/src/modules/ui/field/types/guards/isFieldRelation.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldRelationValue.ts b/front/src/modules/ui/field/types/guards/isFieldRelationValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldRelationValue.ts rename to front/src/modules/ui/field/types/guards/isFieldRelationValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldText.ts b/front/src/modules/ui/field/types/guards/isFieldText.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldText.ts rename to front/src/modules/ui/field/types/guards/isFieldText.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldTextValue.ts b/front/src/modules/ui/field/types/guards/isFieldTextValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldTextValue.ts rename to front/src/modules/ui/field/types/guards/isFieldTextValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldURL.ts b/front/src/modules/ui/field/types/guards/isFieldURL.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldURL.ts rename to front/src/modules/ui/field/types/guards/isFieldURL.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldURLValue.ts b/front/src/modules/ui/field/types/guards/isFieldURLValue.ts similarity index 100% rename from front/src/modules/ui/editable-field/types/guards/isFieldURLValue.ts rename to front/src/modules/ui/field/types/guards/isFieldURLValue.ts diff --git a/front/src/modules/ui/field/types/resolvers/DoubleTextTypeResolver.ts b/front/src/modules/ui/field/types/resolvers/DoubleTextTypeResolver.ts new file mode 100644 index 000000000..ad9ce300a --- /dev/null +++ b/front/src/modules/ui/field/types/resolvers/DoubleTextTypeResolver.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const DoubleTextTypeResolver = z.object({ + firstValue: z.string(), + secondValue: z.string(), +}); diff --git a/front/src/modules/ui/input/components/BooleanInput.tsx b/front/src/modules/ui/input/components/BooleanInput.tsx index 697c5f905..8d12471bf 100644 --- a/front/src/modules/ui/input/components/BooleanInput.tsx +++ b/front/src/modules/ui/input/components/BooleanInput.tsx @@ -8,6 +8,9 @@ const StyledEditableBooleanFieldContainer = styled.div` align-items: center; cursor: pointer; display: flex; + + height: 100%; + width: 100%; `; const StyledEditableBooleanFieldValue = styled.div` diff --git a/front/src/modules/ui/input/components/DateInput.tsx b/front/src/modules/ui/input/components/DateInput.tsx index debeaadad..e8d74e9ce 100644 --- a/front/src/modules/ui/input/components/DateInput.tsx +++ b/front/src/modules/ui/input/components/DateInput.tsx @@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { flip, offset, useFloating } from '@floating-ui/react'; -import { DateDisplay } from '@/ui/content-display/components/DateDisplay'; +import { DateDisplay } from '@/ui/field/meta-types/display/content-display/components/DateDisplay'; import { Nullable } from '~/types/Nullable'; import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents'; diff --git a/front/src/modules/ui/input/components/DoubleTextInput.tsx b/front/src/modules/ui/input/components/DoubleTextInput.tsx index 9a917d65f..97426d4d7 100644 --- a/front/src/modules/ui/input/components/DoubleTextInput.tsx +++ b/front/src/modules/ui/input/components/DoubleTextInput.tsx @@ -1,19 +1,14 @@ -import { ChangeEvent, Ref } from 'react'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import styled from '@emotion/styled'; +import { Key } from 'ts-key-enum'; + +import { FieldDoubleText } from '@/ui/field/types/FieldDoubleText'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { isDefined } from '~/utils/isDefined'; import { StyledInput } from './TextInput'; -type OwnProps = { - firstValue: string; - secondValue: string; - firstValuePlaceholder: string; - secondValuePlaceholder: string; - onChange: (firstValue: string, secondValue: string) => void; - firstValueInputRef?: Ref; - secondValueInputRef?: Ref; - containerRef?: Ref; -}; - const StyledContainer = styled.div` align-items: center; display: flex; @@ -29,35 +24,146 @@ const StyledContainer = styled.div` } `; +type OwnProps = { + firstValue: string; + secondValue: string; + firstValuePlaceholder: string; + secondValuePlaceholder: string; + hotkeyScope: string; + onEnter: (newDoubleTextValue: FieldDoubleText) => void; + onEscape: (newDoubleTextValue: FieldDoubleText) => void; + onTab?: (newDoubleTextValue: FieldDoubleText) => void; + onShiftTab?: (newDoubleTextValue: FieldDoubleText) => void; + onClickOutside: ( + event: MouseEvent | TouchEvent, + newDoubleTextValue: FieldDoubleText, + ) => void; +}; + export const DoubleTextInput = ({ firstValue, secondValue, firstValuePlaceholder, secondValuePlaceholder, - firstValueInputRef, - secondValueInputRef, - onChange, - containerRef, + hotkeyScope, + onClickOutside, + onEnter, + onEscape, + onShiftTab, + onTab, }: OwnProps) => { + const [firstInternalValue, setFirstInternalValue] = useState(firstValue); + const [secondInternalValue, setSecondInternalValue] = useState(secondValue); + + const firstValueInputRef = useRef(null); + const secondValueInputRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + setFirstInternalValue(firstValue); + setSecondInternalValue(secondValue); + }, [firstValue, secondValue]); + + const handleChange = ( + newFirstValue: string, + newSecondValue: string, + ): void => { + setFirstInternalValue(newFirstValue); + setSecondInternalValue(newSecondValue); + }; + + const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left'); + + useScopedHotkeys( + Key.Enter, + () => { + onEnter({ + firstValue: firstInternalValue, + secondValue: secondInternalValue, + }); + }, + hotkeyScope, + [onEnter, firstInternalValue, secondInternalValue], + ); + + useScopedHotkeys( + Key.Escape, + () => { + onEscape({ + firstValue: firstInternalValue, + secondValue: secondInternalValue, + }); + }, + hotkeyScope, + [onEscape, firstInternalValue, secondInternalValue], + ); + + useScopedHotkeys( + 'tab', + () => { + if (focusPosition === 'left') { + setFocusPosition('right'); + secondValueInputRef.current?.focus(); + } else { + onTab?.({ + firstValue: firstInternalValue, + secondValue: secondInternalValue, + }); + } + }, + hotkeyScope, + [onTab, firstInternalValue, secondInternalValue, focusPosition], + ); + + useScopedHotkeys( + 'shift+tab', + () => { + if (focusPosition === 'right') { + setFocusPosition('left'); + firstValueInputRef.current?.focus(); + } else { + onShiftTab?.({ + firstValue: firstInternalValue, + secondValue: secondInternalValue, + }); + } + }, + hotkeyScope, + [onShiftTab, firstInternalValue, secondInternalValue, focusPosition], + ); + + useListenClickOutside({ + refs: [containerRef], + callback: (event) => { + onClickOutside?.(event, { + firstValue: firstInternalValue, + secondValue: secondInternalValue, + }); + }, + enabled: isDefined(onClickOutside), + }); + return ( setFocusPosition('left')} ref={firstValueInputRef} - value={firstValue} + placeholder={firstValuePlaceholder} + value={firstInternalValue} onChange={(event: ChangeEvent) => { - onChange(event.target.value, secondValue); + handleChange(event.target.value, secondInternalValue); }} /> setFocusPosition('right')} ref={secondValueInputRef} - value={secondValue} + placeholder={secondValuePlaceholder} + value={secondInternalValue} onChange={(event: ChangeEvent) => { - onChange(firstValue, event.target.value); + handleChange(firstInternalValue, event.target.value); }} /> diff --git a/front/src/modules/ui/input/components/DoubleTextInputEdit.tsx b/front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx similarity index 97% rename from front/src/modules/ui/input/components/DoubleTextInputEdit.tsx rename to front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx index a9651e82b..9720b11de 100644 --- a/front/src/modules/ui/input/components/DoubleTextInputEdit.tsx +++ b/front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx @@ -32,7 +32,7 @@ const StyledTextInput = styled(StyledInput)` } `; -export const DoubleTextInputEdit = ({ +export const EntityTitleDoubleTextInput = ({ firstValue, secondValue, firstValuePlaceholder, diff --git a/front/src/modules/ui/input/components/ProbabilityInput.tsx b/front/src/modules/ui/input/components/ProbabilityInput.tsx index 999c2b093..f01b12dbc 100644 --- a/front/src/modules/ui/input/components/ProbabilityInput.tsx +++ b/front/src/modules/ui/input/components/ProbabilityInput.tsx @@ -58,43 +58,44 @@ const PROBABILITY_VALUES = [ ]; type OwnProps = { - probabilityIndex: number; + probabilityIndex: number | null; onChange: (newValue: number) => void; }; export const ProbabilityInput = ({ onChange, probabilityIndex }: OwnProps) => { - const [nextProbabilityIndex, setNextProbabilityIndex] = useState< + const [hoveredProbabilityIndex, setHoveredProbabilityIndex] = useState< number | null >(null); + const probabilityIndexToShow = + hoveredProbabilityIndex ?? probabilityIndex ?? 0; + return ( - { - PROBABILITY_VALUES[ - nextProbabilityIndex || nextProbabilityIndex === 0 - ? nextProbabilityIndex - : probabilityIndex - ].label - } + {PROBABILITY_VALUES[probabilityIndexToShow].label} - {PROBABILITY_VALUES.map((probability, i) => ( + {PROBABILITY_VALUES.map((probability, probabilityIndexToSelect) => ( onChange(probability.value)} - onMouseEnter={() => setNextProbabilityIndex(i)} - onMouseLeave={() => setNextProbabilityIndex(null)} + onMouseEnter={() => + setHoveredProbabilityIndex(probabilityIndexToSelect) + } + onMouseLeave={() => setHoveredProbabilityIndex(null)} > ))} diff --git a/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx b/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx index fe2e26339..afe556a1b 100644 --- a/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx +++ b/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx @@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; -import { EmailDisplay } from '../../../content-display/components/EmailDisplay'; +import { EmailDisplay } from '../../../field/meta-types/display/content-display/components/EmailDisplay'; const meta: Meta = { title: 'UI/Input/EmailInputDisplay', diff --git a/front/src/modules/ui/input/hooks/useRegisterInputEvents.ts b/front/src/modules/ui/input/hooks/useRegisterInputEvents.ts index eb8814fec..36e180652 100644 --- a/front/src/modules/ui/input/hooks/useRegisterInputEvents.ts +++ b/front/src/modules/ui/input/hooks/useRegisterInputEvents.ts @@ -24,6 +24,8 @@ export const useRegisterInputEvents = ({ useListenClickOutside({ refs: [inputRef], callback: (event) => { + event.stopImmediatePropagation(); + onClickOutside?.(event, inputValue); }, enabled: isDefined(onClickOutside), diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx index cd6a97f2e..d9c943154 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx @@ -50,8 +50,6 @@ export const SingleEntitySelect = < refs: [containerRef], callback: (event) => { event.stopImmediatePropagation(); - event.stopPropagation(); - event.preventDefault(); onCancel?.(); }, diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx index 36628d879..bb5d6cc48 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx @@ -49,6 +49,7 @@ export const SingleEntitySelectBase = < showCreateButton, }: SingleEntitySelectBaseProps) => { const containerRef = useRef(null); + const entitiesInDropdown = [selectedEntity, ...entitiesToSelect].filter( (entity): entity is CustomEntityForSelect => assertNotNull(entity) && isNonEmptyString(entity.name.trim()), diff --git a/front/src/modules/ui/input/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx b/front/src/modules/ui/input/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx index d77f696bf..6427ed362 100644 --- a/front/src/modules/ui/input/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx +++ b/front/src/modules/ui/input/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx @@ -18,6 +18,7 @@ const entities = mockedPeopleData.map((person) => ({ id: person.id, entityType: Entity.Person, name: person.displayName, + originalEntity: person, })); const meta: Meta = { diff --git a/front/src/modules/ui/input/relation-picker/types/EntityForSelect.ts b/front/src/modules/ui/input/relation-picker/types/EntityForSelect.ts index c1cf6fd4e..87dad1d9d 100644 --- a/front/src/modules/ui/input/relation-picker/types/EntityForSelect.ts +++ b/front/src/modules/ui/input/relation-picker/types/EntityForSelect.ts @@ -8,4 +8,5 @@ export type EntityForSelect = { name: string; avatarUrl?: string; avatarType?: AvatarType; + originalEntity: any; }; diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx index 48185477f..c9c732817 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx @@ -2,13 +2,13 @@ import styled from '@emotion/styled'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity'; +import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { IconButton } from '@/ui/button/components/IconButton'; import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconCheckbox, IconNotes, IconPlus } from '@/ui/icon/index'; -import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { ActivityType } from '~/generated/graphql'; @@ -66,7 +66,7 @@ export const ShowPageAddButton = ({ } dropdownHotkeyScope={{ - scope: RelationPickerHotkeyScope.RelationPicker, + scope: PageHotkeyScope.ShowPage, }} /> diff --git a/front/src/modules/ui/table/components/ColumnHead.tsx b/front/src/modules/ui/table/components/ColumnHead.tsx index 3685093e7..291ec8f40 100644 --- a/front/src/modules/ui/table/components/ColumnHead.tsx +++ b/front/src/modules/ui/table/components/ColumnHead.tsx @@ -2,14 +2,14 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../types/ColumnDefinition'; import { EntityTableHeaderOptions } from './EntityTableHeaderOptions'; type OwnProps = { - column: ColumnDefinition; + column: ColumnDefinition; isFirstColumn: boolean; isLastColumn: boolean; primaryColumnKey: string; diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx index 737940f0c..e6bb13ba2 100644 --- a/front/src/modules/ui/table/components/EntityTable.tsx +++ b/front/src/modules/ui/table/components/EntityTable.tsx @@ -83,7 +83,7 @@ const StyledTableContainer = styled.div` `; type OwnProps = { - updateEntityMutation: any; + updateEntityMutation: (params: any) => void; }; export const EntityTable = ({ updateEntityMutation }: OwnProps) => { diff --git a/front/src/modules/ui/table/components/EntityTableCell.tsx b/front/src/modules/ui/table/components/EntityTableCell.tsx index b60b40b4d..031590d2e 100644 --- a/front/src/modules/ui/table/components/EntityTableCell.tsx +++ b/front/src/modules/ui/table/components/EntityTableCell.tsx @@ -3,16 +3,23 @@ import { useSetRecoilState } from 'recoil'; import { contextMenuIsOpenState } from '@/ui/context-menu/states/contextMenuIsOpenState'; import { contextMenuPositionState } from '@/ui/context-menu/states/contextMenuPositionState'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; +import { isFieldRelation } from '@/ui/field/types/guards/isFieldRelation'; +import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { ColumnContext } from '../contexts/ColumnContext'; import { ColumnIndexContext } from '../contexts/ColumnIndexContext'; -import { GenericEditableCell } from '../editable-cell/components/GenericEditableCell'; +import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext'; +import { RowIdContext } from '../contexts/RowIdContext'; +import { TableCell } from '../editable-cell/components/TableCell'; import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected'; +import { TableHotkeyScope } from '../types/TableHotkeyScope'; export const EntityTableCell = ({ cellIndex }: { cellIndex: number }) => { const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); + const currentRowId = useContext(RowIdContext); const { setCurrentRowSelected } = useCurrentRowSelected(); @@ -28,15 +35,31 @@ export const EntityTableCell = ({ cellIndex }: { cellIndex: number }) => { const columnDefinition = useContext(ColumnContext); - if (!columnDefinition) { + const updateEntityMutation = useContext(EntityUpdateMutationContext); + + if (!columnDefinition || !currentRowId) { return null; } + const customHotkeyScope = isFieldRelation(columnDefinition) + ? RelationPickerHotkeyScope.RelationPicker + : TableHotkeyScope.CellEditMode; + return ( handleContextMenu(event)}> - + [updateEntityMutation, {}], + hotkeyScope: customHotkeyScope, + }} + > + + diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx index a5de30c73..ce9ac162d 100644 --- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx +++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx @@ -3,16 +3,16 @@ import styled from '@emotion/styled'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { IconPlus } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { ColumnDefinition } from '@/ui/table/types/ColumnDefinition'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useTableColumns } from '../hooks/useTableColumns'; import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext'; import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector'; -import { ColumnDefinition } from '../types/ColumnDefinition'; const StyledColumnMenu = styled(StyledDropdownMenu)` font-weight: ${({ theme }) => theme.font.weight.regular}; @@ -43,7 +43,7 @@ export const EntityTableColumnMenu = ({ const { handleColumnVisibilityChange } = useTableColumns(); const handleAddColumn = useCallback( - (column: ColumnDefinition) => { + (column: ColumnDefinition) => { onAddColumn?.(); handleColumnVisibilityChange(column); }, diff --git a/front/src/modules/ui/table/components/EntityTableHeaderOptions.tsx b/front/src/modules/ui/table/components/EntityTableHeaderOptions.tsx index d30809246..a88dbc78c 100644 --- a/front/src/modules/ui/table/components/EntityTableHeaderOptions.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeaderOptions.tsx @@ -14,10 +14,10 @@ const StyledDropdownContainer = styled.div` `; export const EntityTableHeaderOptions = ({ - column, isFirstColumn, isLastColumn, primaryColumnKey, + column, }: EntityTableHeaderOptionsProps) => { return ( diff --git a/front/src/modules/ui/table/components/TableColumnDropdownMenu.tsx b/front/src/modules/ui/table/components/TableColumnDropdownMenu.tsx index 591a3cfc1..45699652c 100644 --- a/front/src/modules/ui/table/components/TableColumnDropdownMenu.tsx +++ b/front/src/modules/ui/table/components/TableColumnDropdownMenu.tsx @@ -1,7 +1,7 @@ import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { IconArrowLeft, IconArrowRight, IconEyeOff } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; @@ -10,7 +10,7 @@ import { useTableColumns } from '../hooks/useTableColumns'; import { ColumnDefinition } from '../types/ColumnDefinition'; export type EntityTableHeaderOptionsProps = { - column: ColumnDefinition; + column: ColumnDefinition; isFirstColumn: boolean; isLastColumn: boolean; primaryColumnKey: string; diff --git a/front/src/modules/ui/table/contexts/ColumnContext.ts b/front/src/modules/ui/table/contexts/ColumnContext.ts index 10de920f2..ac1a9297e 100644 --- a/front/src/modules/ui/table/contexts/ColumnContext.ts +++ b/front/src/modules/ui/table/contexts/ColumnContext.ts @@ -1,8 +1,8 @@ import { createContext } from 'react'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../types/ColumnDefinition'; export const ColumnContext = - createContext | null>(null); + createContext | null>(null); diff --git a/front/src/modules/ui/table/contexts/EntityUpdateMutationHookContext.ts b/front/src/modules/ui/table/contexts/EntityUpdateMutationHookContext.ts index 384ee7a45..828697d62 100644 --- a/front/src/modules/ui/table/contexts/EntityUpdateMutationHookContext.ts +++ b/front/src/modules/ui/table/contexts/EntityUpdateMutationHookContext.ts @@ -1,3 +1,5 @@ import { createContext } from 'react'; -export const EntityUpdateMutationContext = createContext(null); +export const EntityUpdateMutationContext = createContext<(params: any) => void>( + {} as any, +); diff --git a/front/src/modules/ui/table/contexts/TableContext.ts b/front/src/modules/ui/table/contexts/TableContext.ts index 8fca610ac..a5e079bda 100644 --- a/front/src/modules/ui/table/contexts/TableContext.ts +++ b/front/src/modules/ui/table/contexts/TableContext.ts @@ -1,11 +1,11 @@ import { createContext } from 'react'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../types/ColumnDefinition'; export const TableContext = createContext<{ onColumnsChange?: ( - columns: ColumnDefinition[], + columns: ColumnDefinition[], ) => void | Promise; }>({}); diff --git a/front/src/modules/ui/table/editable-cell/components/EditableCellDisplayMode.tsx b/front/src/modules/ui/table/editable-cell/components/EditableCellDisplayMode.tsx deleted file mode 100644 index 9ed86ee14..000000000 --- a/front/src/modules/ui/table/editable-cell/components/EditableCellDisplayMode.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useEditableCell } from '../hooks/useEditableCell'; -import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell'; - -import { EditableCellDisplayContainer } from './EditableCellDisplayContainer'; - -export const EditableCellDisplayMode = ({ - children, - isHovered, -}: React.PropsWithChildren & { isHovered?: boolean }) => { - const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell(); - - const { openEditableCell } = useEditableCell(); - - const handleClick = () => { - setSoftFocusOnCurrentCell(); - openEditableCell(); - }; - - return ( - - {children} - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx deleted file mode 100644 index bbe7b1e94..000000000 --- a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewFieldBoolean'; -import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip'; -import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate'; -import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText'; -import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip'; -import { isViewFieldEmail } from '@/ui/editable-field/types/guards/isViewFieldEmail'; -import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney'; -import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber'; -import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone'; -import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation'; -import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldText'; -import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; - -import { ColumnDefinition } from '../../types/ColumnDefinition'; -import { GenericEditableBooleanCell } from '../type/components/GenericEditableBooleanCell'; -import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell'; -import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell'; -import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell'; -import { GenericEditableDoubleTextChipCell } from '../type/components/GenericEditableDoubleTextChipCell'; -import { GenericEditableEmailCell } from '../type/components/GenericEditableEmailCell'; -import { GenericEditableMoneyCell } from '../type/components/GenericEditableMoneyCell'; -import { GenericEditableNumberCell } from '../type/components/GenericEditableNumberCell'; -import { GenericEditablePhoneCell } from '../type/components/GenericEditablePhoneCell'; -import { GenericEditableRelationCell } from '../type/components/GenericEditableRelationCell'; -import { GenericEditableTextCell } from '../type/components/GenericEditableTextCell'; -import { GenericEditableURLCell } from '../type/components/GenericEditableURLCell'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableCell = ({ columnDefinition }: OwnProps) => { - if (isViewFieldEmail(columnDefinition)) { - return ; - } else if (isViewFieldText(columnDefinition)) { - return ; - } else if (isViewFieldRelation(columnDefinition)) { - return ; - } else if (isViewFieldDoubleTextChip(columnDefinition)) { - return ( - - ); - } else if (isViewFieldDoubleText(columnDefinition)) { - return ( - - ); - } else if (isViewFieldPhone(columnDefinition)) { - return ; - } else if (isViewFieldURL(columnDefinition)) { - return ; - } else if (isViewFieldDate(columnDefinition)) { - return ; - } else if (isViewFieldNumber(columnDefinition)) { - return ; - } else if (isViewFieldBoolean(columnDefinition)) { - return ; - } else if (isViewFieldChip(columnDefinition)) { - return ; - } else if (isViewFieldMoney(columnDefinition)) { - return ; - } else { - console.warn( - `Unknown field metadata type: ${columnDefinition.metadata.type} in GenericEditableCell`, - ); - return <>; - } -}; diff --git a/front/src/modules/ui/table/editable-cell/components/TableCell.tsx b/front/src/modules/ui/table/editable-cell/components/TableCell.tsx new file mode 100644 index 000000000..580f8b034 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/components/TableCell.tsx @@ -0,0 +1,74 @@ +import { useContext } from 'react'; + +import { FieldDisplay } from '@/ui/field/components/FieldDisplay'; +import { FieldInput } from '@/ui/field/components/FieldInput'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; +import { FieldInputEvent } from '@/ui/field/types/FieldInputEvent'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; + +import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus'; +import { useTableCell } from '../hooks/useTableCell'; + +import { TableCellContainer } from './TableCellContainer'; + +export const TableCell = ({ + customHotkeyScope, +}: { + customHotkeyScope: HotkeyScope; +}) => { + const { fieldDefinition } = useContext(FieldContext); + + const { closeTableCell } = useTableCell(); + + const { moveLeft, moveRight, moveDown } = useMoveSoftFocus(); + + const handleEnter: FieldInputEvent = (persistField) => { + persistField(); + closeTableCell(); + moveDown(); + }; + + const handleSubmit: FieldInputEvent = (persistField) => { + persistField(); + closeTableCell(); + }; + + const handleCancel = () => { + closeTableCell(); + }; + + const handleEscape = () => { + closeTableCell(); + }; + + const handleTab: FieldInputEvent = (persistField) => { + persistField(); + closeTableCell(); + moveRight(); + }; + + const handleShiftTab: FieldInputEvent = (persistField) => { + persistField(); + closeTableCell(); + moveLeft(); + }; + + return ( + + } + nonEditModeContent={} + useEditButton={fieldDefinition.useEditButton} + > + ); +}; diff --git a/front/src/modules/ui/table/editable-cell/components/EditableCell.tsx b/front/src/modules/ui/table/editable-cell/components/TableCellContainer.tsx similarity index 54% rename from front/src/modules/ui/table/editable-cell/components/EditableCell.tsx rename to front/src/modules/ui/table/editable-cell/components/TableCellContainer.tsx index 9421c68f4..ca923ca25 100644 --- a/front/src/modules/ui/table/editable-cell/components/EditableCell.tsx +++ b/front/src/modules/ui/table/editable-cell/components/TableCellContainer.tsx @@ -1,19 +1,20 @@ import { ReactElement, useState } from 'react'; import styled from '@emotion/styled'; +import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode'; -import { useEditableCell } from '../hooks/useEditableCell'; -import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell'; -import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell'; +import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode'; +import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell'; +import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell'; +import { useTableCell } from '../hooks/useTableCell'; -import { EditableCellDisplayMode } from './EditableCellDisplayMode'; -import { EditableCellEditButton } from './EditableCellEditButton'; -import { EditableCellEditMode } from './EditableCellEditMode'; -import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode'; +import { TableCellDisplayMode } from './TableCellDisplayMode'; +import { TableCellEditButton } from './TableCellEditButton'; +import { TableCellEditMode } from './TableCellEditMode'; +import { TableCellSoftFocusMode } from './TableCellSoftFocusMode'; const StyledCellBaseContainer = styled.div` align-items: center; @@ -43,7 +44,7 @@ const DEFAULT_CELL_SCOPE: HotkeyScope = { scope: TableHotkeyScope.CellEditMode, }; -export const EditableCell = ({ +export const TableCellContainer = ({ editModeHorizontalAlign = 'left', editModeVerticalPosition = 'over', editModeContent, @@ -53,16 +54,16 @@ export const EditableCell = ({ maxContentWidth, useEditButton, }: EditableCellProps) => { - const { isCurrentCellInEditMode } = useCurrentCellEditMode(); + const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); const [isHovered, setIsHovered] = useState(false); - const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell(); + const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell(); - const { openEditableCell } = useEditableCell(); + const { openTableCell } = useTableCell(); const handlePenClick = () => { - setSoftFocusOnCurrentCell(); - openEditableCell(); + setSoftFocusOnCurrentTableCell(); + openTableCell(); }; const handleContainerMouseEnter = () => { @@ -73,9 +74,15 @@ export const EditableCell = ({ setIsHovered(false); }; - const showEditButton = useEditButton && isHovered && !isCurrentCellInEditMode; + const editModeContentOnly = useIsFieldInputOnly(); - const hasSoftFocus = useIsSoftFocusOnCurrentCell(); + const showEditButton = + useEditButton && + isHovered && + !isCurrentTableCellInEditMode && + !editModeContentOnly; + + const hasSoftFocus = useIsSoftFocusOnCurrentTableCell(); return ( - {isCurrentCellInEditMode ? ( - {editModeContent} - + ) : hasSoftFocus ? ( <> - {showEditButton && ( - - )} - - {nonEditModeContent} - + {showEditButton && } + + {editModeContentOnly ? editModeContent : nonEditModeContent} + ) : ( <> - {showEditButton && ( - - )} - - {nonEditModeContent} - + {showEditButton && } + + {editModeContentOnly ? editModeContent : nonEditModeContent} + )} diff --git a/front/src/modules/ui/table/editable-cell/components/EditableCellDisplayContainer.tsx b/front/src/modules/ui/table/editable-cell/components/TableCellDisplayContainer.tsx similarity index 97% rename from front/src/modules/ui/table/editable-cell/components/EditableCellDisplayContainer.tsx rename to front/src/modules/ui/table/editable-cell/components/TableCellDisplayContainer.tsx index 656e65892..1e027a149 100644 --- a/front/src/modules/ui/table/editable-cell/components/EditableCellDisplayContainer.tsx +++ b/front/src/modules/ui/table/editable-cell/components/TableCellDisplayContainer.tsx @@ -36,7 +36,7 @@ const StyledEditableCellDisplayModeInnerContainer = styled.div` width: 100%; `; -export const EditableCellDisplayContainer = ({ +export const TableCellDisplayContainer = ({ children, softFocus, onClick, diff --git a/front/src/modules/ui/table/editable-cell/components/TableCellDisplayMode.tsx b/front/src/modules/ui/table/editable-cell/components/TableCellDisplayMode.tsx new file mode 100644 index 000000000..0ceb6d4a0 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/components/TableCellDisplayMode.tsx @@ -0,0 +1,31 @@ +import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly'; + +import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell'; +import { useTableCell } from '../hooks/useTableCell'; + +import { TableCellDisplayContainer } from './TableCellDisplayContainer'; + +export const TableCellDisplayMode = ({ + children, + isHovered, +}: React.PropsWithChildren & { isHovered?: boolean }) => { + const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentTableCell(); + + const isFieldInputOnly = useIsFieldInputOnly(); + + const { openTableCell } = useTableCell(); + + const handleClick = () => { + setSoftFocusOnCurrentCell(); + + if (!isFieldInputOnly) { + openTableCell(); + } + }; + + return ( + + {children} + + ); +}; diff --git a/front/src/modules/ui/table/editable-cell/components/EditableCellEditButton.tsx b/front/src/modules/ui/table/editable-cell/components/TableCellEditButton.tsx similarity index 94% rename from front/src/modules/ui/table/editable-cell/components/EditableCellEditButton.tsx rename to front/src/modules/ui/table/editable-cell/components/TableCellEditButton.tsx index 9c6da2805..131dc6eb6 100644 --- a/front/src/modules/ui/table/editable-cell/components/EditableCellEditButton.tsx +++ b/front/src/modules/ui/table/editable-cell/components/TableCellEditButton.tsx @@ -13,7 +13,7 @@ type EditableCellEditButtonProps = { onClick?: () => void; }; -export const EditableCellEditButton = ({ +export const TableCellEditButton = ({ onClick, }: EditableCellEditButtonProps) => ( ; -export const EditableCellSoftFocusMode = ({ children }: OwnProps) => { - const { openEditableCell } = useEditableCell(); +export const TableCellSoftFocusMode = ({ children }: OwnProps) => { + const { openTableCell } = useTableCell(); + + const isFieldInputOnly = useIsFieldInputOnly(); const scrollRef = useRef(null); @@ -19,17 +22,16 @@ export const EditableCellSoftFocusMode = ({ children }: OwnProps) => { scrollRef.current?.scrollIntoView({ block: 'nearest' }); }, []); - const openEditMode = () => { - openEditableCell(); - }; - useScopedHotkeys( 'enter', () => { - openEditMode(); + openTableCell(); }, TableHotkeyScope.TableSoftFocus, - [openEditMode], + [openTableCell], + { + enabled: !isFieldInputOnly, + }, ); useScopedHotkeys( @@ -44,26 +46,29 @@ export const EditableCellSoftFocusMode = ({ children }: OwnProps) => { return; } - openEditMode(); + openTableCell(); }, TableHotkeyScope.TableSoftFocus, - [openEditMode], + [openTableCell], { preventDefault: false, + enabled: !isFieldInputOnly, }, ); const handleClick = () => { - openEditMode(); + if (!isFieldInputOnly) { + openTableCell(); + } }; return ( - {children} - + ); }; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellEditMode.ts b/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellEditMode.ts deleted file mode 100644 index 7219a2a0a..000000000 --- a/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellEditMode.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useCallback } from 'react'; -import { useRecoilState } from 'recoil'; - -import { useMoveEditModeToCellPosition } from '../../hooks/useMoveEditModeToCellPosition'; -import { isCellInEditModeFamilyState } from '../../states/isCellInEditModeFamilyState'; - -import { useCurrentCellPosition } from './useCurrentCellPosition'; - -export const useCurrentCellEditMode = () => { - const moveEditModeToCellPosition = useMoveEditModeToCellPosition(); - - const currentCellPosition = useCurrentCellPosition(); - - const [isCurrentCellInEditMode] = useRecoilState( - isCellInEditModeFamilyState(currentCellPosition), - ); - - const setCurrentCellInEditMode = useCallback(() => { - moveEditModeToCellPosition(currentCellPosition); - }, [currentCellPosition, moveEditModeToCellPosition]); - - return { isCurrentCellInEditMode, setCurrentCellInEditMode }; -}; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts b/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts index 68f5247be..08432bcf0 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts @@ -2,13 +2,13 @@ import { useContext, useMemo } from 'react'; import { ColumnIndexContext } from '../../contexts/ColumnIndexContext'; import { RowIndexContext } from '../../contexts/RowIndexContext'; -import { CellPosition } from '../../types/CellPosition'; +import { TableCellPosition } from '../../types/TableCellPosition'; -export const useCurrentCellPosition = () => { +export const useCurrentTableCellPosition = () => { const currentRowNumber = useContext(RowIndexContext); const currentColumnNumber = useContext(ColumnIndexContext); - const currentCellPosition: CellPosition = useMemo( + const currentTableCellPosition: TableCellPosition = useMemo( () => ({ column: currentColumnNumber, row: currentRowNumber, @@ -16,5 +16,5 @@ export const useCurrentCellPosition = () => { [currentColumnNumber, currentRowNumber], ); - return currentCellPosition; + return currentTableCellPosition; }; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useCurrentTableCellEditMode.ts b/front/src/modules/ui/table/editable-cell/hooks/useCurrentTableCellEditMode.ts new file mode 100644 index 000000000..98759de86 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/hooks/useCurrentTableCellEditMode.ts @@ -0,0 +1,26 @@ +import { useCallback } from 'react'; +import { useRecoilState } from 'recoil'; + +import { useMoveEditModeToTableCellPosition } from '../../hooks/useMoveEditModeToCellPosition'; +import { isTableCellInEditModeFamilyState } from '../../states/isTableCellInEditModeFamilyState'; + +import { useCurrentTableCellPosition } from './useCurrentCellPosition'; + +export const useCurrentTableCellEditMode = () => { + const moveEditModeToTableCellPosition = useMoveEditModeToTableCellPosition(); + + const currentTableCellPosition = useCurrentTableCellPosition(); + + const [isCurrentTableCellInEditMode] = useRecoilState( + isTableCellInEditModeFamilyState(currentTableCellPosition), + ); + + const setCurrentTableCellInEditMode = useCallback(() => { + moveEditModeToTableCellPosition(currentTableCellPosition); + }, [currentTableCellPosition, moveEditModeToTableCellPosition]); + + return { + isCurrentTableCellInEditMode, + setCurrentTableCellInEditMode, + }; +}; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useIsSoftFocusOnCurrentCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useIsSoftFocusOnCurrentCell.ts deleted file mode 100644 index d874f5b9c..000000000 --- a/front/src/modules/ui/table/editable-cell/hooks/useIsSoftFocusOnCurrentCell.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { isSoftFocusOnCellFamilyState } from '../../states/isSoftFocusOnCellFamilyState'; - -import { useCurrentCellPosition } from './useCurrentCellPosition'; - -export const useIsSoftFocusOnCurrentCell = () => { - const currentCellPosition = useCurrentCellPosition(); - - const isSoftFocusOnCell = useRecoilValue( - isSoftFocusOnCellFamilyState(currentCellPosition), - ); - - return isSoftFocusOnCell; -}; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useIsSoftFocusOnCurrentTableCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useIsSoftFocusOnCurrentTableCell.ts new file mode 100644 index 000000000..ac6c35415 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/hooks/useIsSoftFocusOnCurrentTableCell.ts @@ -0,0 +1,15 @@ +import { useRecoilValue } from 'recoil'; + +import { isSoftFocusOnTableCellFamilyState } from '../../states/isSoftFocusOnTableCellFamilyState'; + +import { useCurrentTableCellPosition } from './useCurrentCellPosition'; + +export const useIsSoftFocusOnCurrentTableCell = () => { + const currentTableCellPosition = useCurrentTableCellPosition(); + + const isSoftFocusOnTableCell = useRecoilValue( + isSoftFocusOnTableCellFamilyState(currentTableCellPosition), + ); + + return isSoftFocusOnTableCell; +}; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts deleted file mode 100644 index c22be885b..000000000 --- a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; - -import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus'; -import { TableHotkeyScope } from '../../types/TableHotkeyScope'; - -import { useCurrentCellEditMode } from './useCurrentCellEditMode'; -import { useEditableCell } from './useEditableCell'; - -export const useRegisterCloseCellHandlers = ( - wrapperRef: React.RefObject, - onSubmit?: () => void, - onCancel?: () => void, -) => { - const { closeEditableCell } = useEditableCell(); - const { isCurrentCellInEditMode } = useCurrentCellEditMode(); - - useListenClickOutside({ - refs: [wrapperRef], - callback: (event) => { - if (isCurrentCellInEditMode) { - event.stopImmediatePropagation(); - - onSubmit?.(); - - closeEditableCell(); - } - }, - }); - - const { moveRight, moveLeft, moveDown } = useMoveSoftFocus(); - - useScopedHotkeys( - 'enter', - () => { - onSubmit?.(); - closeEditableCell(); - moveDown(); - }, - TableHotkeyScope.CellEditMode, - [closeEditableCell, onSubmit, moveDown], - ); - - useScopedHotkeys( - 'esc', - () => { - closeEditableCell(); - onCancel?.(); - }, - TableHotkeyScope.CellEditMode, - [closeEditableCell, onCancel], - ); - - useScopedHotkeys( - 'tab', - () => { - onSubmit?.(); - closeEditableCell(); - moveRight(); - }, - TableHotkeyScope.CellEditMode, - [closeEditableCell, onSubmit, moveRight], - ); - - useScopedHotkeys( - 'shift+tab', - () => { - onSubmit?.(); - closeEditableCell(); - moveLeft(); - }, - TableHotkeyScope.CellEditMode, - [closeEditableCell, onSubmit, moveRight], - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentTableCell.ts similarity index 66% rename from front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts rename to front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentTableCell.ts index 60270950f..e734d9f29 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentTableCell.ts @@ -6,24 +6,24 @@ import { useSetSoftFocusPosition } from '../../hooks/useSetSoftFocusPosition'; import { isSoftFocusActiveState } from '../../states/isSoftFocusActiveState'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -import { useCurrentCellPosition } from './useCurrentCellPosition'; +import { useCurrentTableCellPosition } from './useCurrentCellPosition'; -export const useSetSoftFocusOnCurrentCell = () => { +export const useSetSoftFocusOnCurrentTableCell = () => { const setSoftFocusPosition = useSetSoftFocusPosition(); - const currentCellPosition = useCurrentCellPosition(); + const currentTableCellPosition = useCurrentTableCellPosition(); const setHotkeyScope = useSetHotkeyScope(); return useRecoilCallback( ({ set }) => () => { - setSoftFocusPosition(currentCellPosition); + setSoftFocusPosition(currentTableCellPosition); set(isSoftFocusActiveState, true); setHotkeyScope(TableHotkeyScope.TableSoftFocus); }, - [setHotkeyScope, currentCellPosition, setSoftFocusPosition], + [setHotkeyScope, currentTableCellPosition, setSoftFocusPosition], ); }; diff --git a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useTableCell.ts similarity index 66% rename from front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts rename to front/src/modules/ui/table/editable-cell/hooks/useTableCell.ts index a0016d933..bd5ba081b 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useTableCell.ts @@ -5,34 +5,34 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; -import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode'; +import { useCloseCurrentTableCellInEditMode } from '../../hooks/useCloseCurrentTableCellInEditMode'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -import { useCurrentCellEditMode } from './useCurrentCellEditMode'; +import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode'; const DEFAULT_CELL_SCOPE: HotkeyScope = { scope: TableHotkeyScope.CellEditMode, }; -export const useEditableCell = () => { - const { setCurrentCellInEditMode } = useCurrentCellEditMode(); +export const useTableCell = () => { + const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); const setHotkeyScope = useSetHotkeyScope(); const { setDragSelectionStartEnabled } = useDragSelect(); - const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode(); + const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode(); const customCellHotkeyScope = useContext(CellHotkeyScopeContext); - const closeEditableCell = () => { + const closeTableCell = () => { setDragSelectionStartEnabled(true); - closeCurrentCellInEditMode(); + closeCurrentTableCellInEditMode(); setHotkeyScope(TableHotkeyScope.TableSoftFocus); }; - const openEditableCell = () => { + const openTableCell = () => { setDragSelectionStartEnabled(false); - setCurrentCellInEditMode(); + setCurrentTableCellInEditMode(); if (customCellHotkeyScope) { setHotkeyScope( @@ -45,7 +45,7 @@ export const useEditableCell = () => { }; return { - closeEditableCell, - openEditableCell, + closeTableCell, + openTableCell, }; }; diff --git a/front/src/modules/ui/table/editable-cell/type/components/DoubleTextCellEdit.tsx b/front/src/modules/ui/table/editable-cell/type/components/DoubleTextCellEdit.tsx deleted file mode 100644 index 6d949dc5b..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/DoubleTextCellEdit.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { Key } from 'ts-key-enum'; - -import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput'; -import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell'; -import { useRegisterCloseCellHandlers } from '@/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers'; -import { useMoveSoftFocus } from '@/ui/table/hooks/useMoveSoftFocus'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; - -type OwnProps = { - firstValue: string; - secondValue: string; - firstValuePlaceholder: string; - secondValuePlaceholder: string; - onChange?: (firstValue: string, secondValue: string) => void; - onSubmit?: (firstValue: string, secondValue: string) => void; - onCancel?: () => void; -}; - -export const DoubleTextCellEdit = ({ - firstValue, - secondValue, - firstValuePlaceholder, - secondValuePlaceholder, - onSubmit, - onCancel, -}: OwnProps) => { - const [firstInternalValue, setFirstInternalValue] = useState(firstValue); - const [secondInternalValue, setSecondInternalValue] = useState(secondValue); - - useEffect(() => { - setFirstInternalValue(firstValue); - setSecondInternalValue(secondValue); - }, [firstValue, secondValue]); - - const handleOnChange = ( - newFirstValue: string, - newSecondValue: string, - ): void => { - setFirstInternalValue(newFirstValue); - setSecondInternalValue(newSecondValue); - }; - - const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left'); - - const firstValueInputRef = useRef(null); - const secondValueInputRef = useRef(null); - - const { closeEditableCell } = useEditableCell(); - const { moveRight, moveLeft, moveDown } = useMoveSoftFocus(); - - const closeCell = () => { - setFocusPosition('left'); - closeEditableCell(); - }; - - const handleCancel = () => { - setFirstInternalValue(firstValue); - setSecondInternalValue(secondValue); - - onCancel?.(); - }; - - const handleSubmit = () => { - onSubmit?.(firstInternalValue, secondInternalValue); - }; - - useScopedHotkeys( - Key.Enter, - () => { - closeCell(); - moveDown(); - handleSubmit(); - }, - TableHotkeyScope.CellDoubleTextInput, - [closeCell], - ); - - useScopedHotkeys( - Key.Escape, - () => { - handleCancel(); - closeCell(); - }, - TableHotkeyScope.CellDoubleTextInput, - [closeCell], - ); - - useScopedHotkeys( - 'tab', - () => { - if (focusPosition === 'left') { - setFocusPosition('right'); - secondValueInputRef.current?.focus(); - } else { - handleSubmit(); - - closeCell(); - moveRight(); - } - }, - TableHotkeyScope.CellDoubleTextInput, - [closeCell, moveRight, focusPosition], - ); - - useScopedHotkeys( - 'shift+tab', - () => { - if (focusPosition === 'right') { - setFocusPosition('left'); - firstValueInputRef.current?.focus(); - } else { - handleSubmit(); - closeCell(); - moveLeft(); - } - }, - TableHotkeyScope.CellDoubleTextInput, - [closeCell, moveRight, focusPosition], - ); - - const wrapperRef = useRef(null); - - useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableBooleanCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableBooleanCell.tsx deleted file mode 100644 index 713312bd5..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableBooleanCell.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import styled from '@emotion/styled'; -import { useRecoilState } from 'recoil'; - -import { ViewFieldBooleanMetadata } from '@/ui/editable-field/types/ViewField'; -import { BooleanInput } from '@/ui/input/components/BooleanInput'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; -import { EditableCellDisplayContainer } from '../../components/EditableCellDisplayContainer'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -const StyledCellBaseContainer = styled.div` - align-items: center; - box-sizing: border-box; - cursor: pointer; - display: flex; - height: ${({ theme }) => theme.spacing(8)}; - position: relative; - user-select: none; - width: 100%; -`; - -export const GenericEditableBooleanCell = ({ columnDefinition }: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleClick = () => { - const newValue = !fieldValue; - - try { - setFieldValue(newValue); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, newValue); - } - } catch (error) { - console.warn( - `In GenericEditableBooleanCellEditMode, Invalid value: ${newValue}, ${error}`, - ); - } - }; - - return ( - - - - - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCell.tsx deleted file mode 100644 index f496bee7d..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCell.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ViewFieldChipMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode'; -import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; - placeholder?: string; -}; - -export const GenericEditableChipCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => ( - - } - nonEditModeContent={ - - } - > -); diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellDisplayMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellDisplayMode.tsx deleted file mode 100644 index 3d203c1e0..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellDisplayMode.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { CompanyChip } from '@/companies/components/CompanyChip'; -import { ViewFieldChipMetadata } from '@/ui/editable-field/types/ViewField'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { getLogoUrlFromDomainName } from '~/utils'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableChipCellDisplayMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const content = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.contentFieldName, - }), - ); - - const chipUrl = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.urlFieldName, - }), - ); - - switch (columnDefinition.metadata.relationType) { - case Entity.Company: { - return ( - - ); - } - default: - console.warn( - `Unknown relation type: "${columnDefinition.metadata.relationType}" in GenericEditableChipCellEditMode`, - ); - return <> ; - } -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellEditMode.tsx deleted file mode 100644 index 5d4908176..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellEditMode.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldChipMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; - -import { TextInput } from '../../../../input/components/TextInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableChipCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.contentFieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newText: string) => { - if (newText === fieldValue) return; - - setFieldValue(newText); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, newText); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx deleted file mode 100644 index bdb50e2d6..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { DateDisplay } from '@/ui/content-display/components/DateDisplay'; -import { ViewFieldDateMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditableDateCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx deleted file mode 100644 index 08e09f7e7..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { DateTime } from 'luxon'; -import { useRecoilState } from 'recoil'; - -import { ViewFieldDateMetadata } from '@/ui/editable-field/types/ViewField'; -import { DateInput } from '@/ui/input/components/DateInput'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; -import { Nullable } from '~/types/Nullable'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableDateCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - // Wrap this into a hook - const handleSubmit = (newDate: Nullable) => { - const fieldValueDate = fieldValue - ? DateTime.fromISO(fieldValue).toJSDate() - : null; - - const newDateISO = newDate ? DateTime.fromJSDate(newDate).toISO() : null; - - if (newDate === fieldValueDate || !newDateISO) return; - - setFieldValue(newDateISO); - - if (currentRowEntityId && updateField && newDateISO) { - updateField(currentRowEntityId, columnDefinition, newDateISO); - } - }; - - const { handleEnter, handleEscape, handleClickOutside } = - useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCell.tsx deleted file mode 100644 index e70a11c99..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCell.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { DoubleTextDisplay } from '@/ui/content-display/components/DoubleTextDisplay'; -import { ViewFieldDoubleTextMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableDoubleTextCell = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const firstValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.firstValueFieldName, - }), - ); - - const secondValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.secondValueFieldName, - }), - ); - - const displayName = `${firstValue ?? ''} ${secondValue ?? ''}`; - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx deleted file mode 100644 index 8c14357ab..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldDoubleTextMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { DoubleTextCellEdit } from './DoubleTextCellEdit'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableDoubleTextCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [firstValue, setFirstValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.firstValueFieldName, - }), - ); - - const [secondValue, setSecondValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.secondValueFieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newFirstValue: string, newSecondValue: string) => { - if (newFirstValue === firstValue && newSecondValue === secondValue) return; - - setFirstValue(newFirstValue); - setSecondValue(newSecondValue); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, { - firstValue: newFirstValue, - secondValue: newSecondValue, - }); - } - }; - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCell.tsx deleted file mode 100644 index 2f1de5d23..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCell.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableDoubleTextChipCellDisplayMode } from './GenericEditableDoubleTextChipCellDisplayMode'; -import { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableDoubleTextChipCell = ({ - columnDefinition, -}: OwnProps) => ( - - } - nonEditModeContent={ - - } - > -); diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx deleted file mode 100644 index 53dc18217..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { DoubleTextChipDisplay } from '@/ui/content-display/components/DoubleTextChipDisplay'; -import { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableDoubleTextChipCellDisplayMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const [firstValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.firstValueFieldName, - }), - ); - - const [secondValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.secondValueFieldName, - }), - ); - - const [avatarUrlValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.avatarUrlFieldName, - }), - ); - - const displayName = [firstValue, secondValue].filter(Boolean).join(' '); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellEditMode.tsx deleted file mode 100644 index 5cc443d1e..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellEditMode.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { DoubleTextCellEdit } from './DoubleTextCellEdit'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableDoubleTextChipCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [firstValue, setFirstValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.firstValueFieldName, - }), - ); - - const [secondValue, setSecondValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.secondValueFieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const 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, columnDefinition, { - firstValue: firstValueChanged ? newFirstValue : firstValue, - secondValue: secondValueChanged ? newSecondValue : secondValue, - }); - } - }; - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx deleted file mode 100644 index 39ba47db9..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { EmailDisplay } from '@/ui/content-display/components/EmailDisplay'; -import { ViewFieldEmailMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableEmailCellEditMode } from './GenericEditableEmailCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditableEmailCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx deleted file mode 100644 index 1996e45c5..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldEmailMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; - -import { TextInput } from '../../../../input/components/TextInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableEmailCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newEmail: string) => { - if (newEmail === fieldValue) return; - - setFieldValue(newEmail); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, newEmail); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx deleted file mode 100644 index 22ee87e83..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { MoneyDisplay } from '@/ui/content-display/components/MoneyDisplay'; -import { ViewFieldMoneyMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableMoneyCellEditMode } from './GenericEditableMoneyCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditableMoneyCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx deleted file mode 100644 index 64a82a4dd..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldMoneyMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; - -import { TextInput } from '../../../../input/components/TextInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableMoneyCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - // TODO: handle this logic in a number input - const handleSubmit = (newText: string) => { - if (newText === fieldValue) return; - - try { - const numberValue = newText !== '' ? parseInt(newText) : null; - - if (numberValue && isNaN(numberValue)) { - throw new Error('Not a number'); - } - - if (numberValue && numberValue > 2000000000) { - throw new Error('Number too big'); - } - - setFieldValue(numberValue ? numberValue.toString() : ''); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, numberValue); - } - } catch (error) { - console.warn( - `In GenericEditableMoneyCellEditMode, Invalid number: ${newText}, ${error}`, - ); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - // TODO: use a number input - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCell.tsx deleted file mode 100644 index c8e1192f6..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCell.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { NumberDisplay } from '@/ui/content-display/components/NumberDisplay'; -import { ViewFieldNumberMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableNumberCellEditMode } from './GenericEditableNumberCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditableNumberCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx deleted file mode 100644 index 34c61bc27..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldNumberMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; -import { - canBeCastAsPositiveIntegerOrNull, - castAsPositiveIntegerOrNull, -} from '~/utils/cast-as-positive-integer-or-null'; - -import { TextInput } from '../../../../input/components/TextInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableNumberCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newText: string) => { - if (newText === fieldValue) return; - - try { - let numberValue = parseInt(newText); - - if (isNaN(numberValue)) { - throw new Error('Not a number'); - } - - if (columnDefinition.metadata.isPositive) { - if (!canBeCastAsPositiveIntegerOrNull(newText)) { - return; - } - - const valueCastedAsPositiveNumberOrNull = - castAsPositiveIntegerOrNull(newText); - - if (valueCastedAsPositiveNumberOrNull === null) { - throw Error('Not a number'); - } - - numberValue = valueCastedAsPositiveNumberOrNull; - } - - // TODO: find a way to store this better in DB - if (numberValue > 2000000000) { - throw new Error('Number too big'); - } - - setFieldValue(numberValue.toString()); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, numberValue); - } - } catch (error) { - console.warn( - `In GenericEditableNumberCellEditMode, Invalid number: ${newText}, ${error}`, - ); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx deleted file mode 100644 index c1d4f1b43..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { PhoneDisplay } from '@/ui/content-display/components/PhoneDisplay'; -import { ViewFieldPhoneMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditablePhoneCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx deleted file mode 100644 index bcbbfe7fb..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { isPossiblePhoneNumber } from 'libphonenumber-js'; -import { useRecoilState } from 'recoil'; - -import { ViewFieldPhoneMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; - -import { PhoneInput } from '../../../../input/components/PhoneInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditablePhoneCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newValue: string) => { - if (!isPossiblePhoneNumber(newValue)) return; - - if (newValue === fieldValue) return; - - setFieldValue(newValue); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, newValue); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCell.tsx deleted file mode 100644 index bc0c305ec..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCell.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ViewFieldRelationMetadata } from '@/ui/editable-field/types/ViewField'; -import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode'; -import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; - placeholder?: string; -}; - -export const GenericEditableRelationCell = ({ - columnDefinition, - editModeHorizontalAlign, - placeholder, -}: OwnProps) => ( - - } - nonEditModeContent={ - - } - > -); diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellDisplayMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellDisplayMode.tsx deleted file mode 100644 index ea00e3923..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellDisplayMode.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { CompanyChip } from '@/companies/components/CompanyChip'; -import { ViewFieldRelationMetadata } from '@/ui/editable-field/types/ViewField'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { UserChip } from '@/users/components/UserChip'; -import { getLogoUrlFromDomainName } from '~/utils'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; - placeholder?: string; -}; - -export const GenericEditableRelationCellDisplayMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: type value with generic getter - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - switch (columnDefinition.metadata.relationType) { - case Entity.Company: { - return ( - - ); - } - case Entity.User: { - return ( - - ); - } - default: - console.warn( - `Unknown relation type: "${columnDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`, - ); - return <> ; - } -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellEditMode.tsx deleted file mode 100644 index 97092b811..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellEditMode.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { - CompanyPickerCell, - CompanyPickerSelectedCompany, -} from '@/companies/components/CompanyPickerCell'; -import { ViewFieldRelationMetadata } 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 { 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/selectors/tableEntityFieldFamilySelector'; -import { UserPicker } from '@/users/components/UserPicker'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableRelationCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const { closeEditableCell } = useEditableCell(); - - const [fieldValueEntity, setFieldValueEntity] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - const updateEntityField = useUpdateEntityField(); - - const updateCachedPersonField = (newFieldEntity: EntityForSelect | null) => { - setFieldValueEntity({ - avatarUrl: newFieldEntity?.avatarUrl ?? '', - entityType: Entity.Company, - id: newFieldEntity?.id ?? '', - displayName: newFieldEntity?.name ?? '', - }); - }; - - const updateCachedCompanyField = ( - newFieldEntity: CompanyPickerSelectedCompany | null, - ) => { - setFieldValueEntity({ - id: newFieldEntity?.id ?? '', - name: newFieldEntity?.name ?? '', - domainName: newFieldEntity?.domainName ?? '', - }); - }; - - const handleCompanySubmit = ( - newFieldEntity: CompanyPickerSelectedCompany | null, - ) => { - if ( - newFieldEntity?.id !== fieldValueEntity?.id && - currentRowEntityId && - updateEntityField - ) { - updateCachedCompanyField(newFieldEntity); - updateEntityField( - currentRowEntityId, - columnDefinition, - newFieldEntity, - ); - } - - closeEditableCell(); - }; - - const handlePersonSubmit = (newFieldEntity: EntityForSelect | null) => { - if ( - newFieldEntity?.id !== fieldValueEntity?.id && - currentRowEntityId && - updateEntityField - ) { - updateCachedPersonField(newFieldEntity); - updateEntityField(currentRowEntityId, columnDefinition, newFieldEntity); - } - - closeEditableCell(); - }; - - const handleCancel = () => { - closeEditableCell(); - }; - - switch (columnDefinition.metadata.relationType) { - case Entity.Company: { - return ( - - ); - } - case Entity.User: { - return ( - - ); - } - default: - console.warn( - `Unknown relation type: "${columnDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`, - ); - return <>; - } -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCell.tsx deleted file mode 100644 index 9677a1e67..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCell.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { TextDisplay } from '@/ui/content-display/components/TextDisplay'; -import { ViewFieldTextMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditableTextCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCellEditMode.tsx deleted file mode 100644 index 1e337acaa..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCellEditMode.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldTextMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; - -import { TextInput } from '../../../../input/components/TextInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableTextCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newText: string) => { - if (newText === fieldValue) return; - - setFieldValue(newText); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, newText); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx deleted file mode 100644 index 6f7456377..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { URLDisplay } from '@/ui/content-display/components/URLDisplay'; -import { ViewFieldURLMetadata } from '@/ui/editable-field/types/ViewField'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { sanitizeURL } from '~/utils'; - -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode'; - -type OwnProps = { - columnDefinition: ColumnDefinition; - editModeHorizontalAlign?: 'left' | 'right'; -}; - -export const GenericEditableURLCell = ({ - columnDefinition, - editModeHorizontalAlign, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - const fieldValue = useRecoilValue( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - return ( - - } - nonEditModeContent={} - > - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx deleted file mode 100644 index 94a191ed7..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { ViewFieldURLMetadata } from '@/ui/editable-field/types/ViewField'; -import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; -import { isURL } from '~/utils/is-url'; - -import { TextInput } from '../../../../input/components/TextInput'; -import { ColumnDefinition } from '../../../types/ColumnDefinition'; - -type OwnProps = { - columnDefinition: ColumnDefinition; -}; - -export const GenericEditableURLCellEditMode = ({ - columnDefinition, -}: OwnProps) => { - const currentRowEntityId = useCurrentRowEntityId(); - - // TODO: we could use a hook that would return the field value with the right type - const [fieldValue, setFieldValue] = useRecoilState( - tableEntityFieldFamilySelector({ - entityId: currentRowEntityId ?? '', - fieldName: columnDefinition.metadata.fieldName, - }), - ); - - const updateField = useUpdateEntityField(); - - const handleSubmit = (newText: string) => { - if (newText === fieldValue) return; - - if (newText !== '' && !isURL(newText)) return; - - setFieldValue(newText); - - if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, columnDefinition, newText); - } - }; - - const { - handleEnter, - handleEscape, - handleTab, - handleShiftTab, - handleClickOutside, - } = useCellInputEventHandlers({ - onSubmit: handleSubmit, - }); - - return ( - - ); -}; diff --git a/front/src/modules/ui/table/editable-cell/type/components/__stories__/PhoneCellEdit.stories.tsx b/front/src/modules/ui/table/editable-cell/type/components/__stories__/PhoneCellEdit.stories.tsx deleted file mode 100644 index 2972fb98e..000000000 --- a/front/src/modules/ui/table/editable-cell/type/components/__stories__/PhoneCellEdit.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { PhoneInput } from '@/ui/input/components/PhoneInput'; -import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; -import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; - -const meta: Meta = { - title: 'UI/Table/EditableCell/PhoneCellEdit', - component: PhoneInput, - decorators: [ComponentWithRecoilScopeDecorator], - args: { - value: '+33714446494', - autoFocus: true, - }, - parameters: { - customRecoilScopeContext: TableRecoilScopeContext, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/front/src/modules/ui/table/hooks/useCellInputEventHandlers.ts b/front/src/modules/ui/table/hooks/useCellInputEventHandlers.ts index d58d689a2..92d8fbf82 100644 --- a/front/src/modules/ui/table/hooks/useCellInputEventHandlers.ts +++ b/front/src/modules/ui/table/hooks/useCellInputEventHandlers.ts @@ -1,5 +1,5 @@ -import { useCurrentCellEditMode } from '../editable-cell/hooks/useCurrentCellEditMode'; -import { useEditableCell } from '../editable-cell/hooks/useEditableCell'; +import { useCurrentTableCellEditMode } from '../editable-cell/hooks/useCurrentTableCellEditMode'; +import { useTableCell } from '../editable-cell/hooks/useTableCell'; import { useMoveSoftFocus } from './useMoveSoftFocus'; @@ -10,8 +10,9 @@ export const useCellInputEventHandlers = ({ onSubmit?: (newValue: T) => void; onCancel?: () => void; }) => { - const { closeEditableCell } = useEditableCell(); - const { isCurrentCellInEditMode } = useCurrentCellEditMode(); + const { closeTableCell: closeEditableCell } = useTableCell(); + const { isCurrentTableCellInEditMode: isCurrentCellInEditMode } = + useCurrentTableCellEditMode(); const { moveRight, moveLeft, moveDown } = useMoveSoftFocus(); return { diff --git a/front/src/modules/ui/table/hooks/useClearCellInEditMode.ts b/front/src/modules/ui/table/hooks/useClearCellInEditMode.ts deleted file mode 100644 index 87d2eef65..000000000 --- a/front/src/modules/ui/table/hooks/useClearCellInEditMode.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { currentCellInEditModePositionState } from '../states/currentCellInEditModePositionState'; -import { isCellInEditModeFamilyState } from '../states/isCellInEditModeFamilyState'; - -export const useCloseCurrentCellInEditMode = () => - useRecoilCallback(({ set, snapshot }) => { - return async () => { - const currentCellInEditModePosition = await snapshot.getPromise( - currentCellInEditModePositionState, - ); - - set(isCellInEditModeFamilyState(currentCellInEditModePosition), false); - }; - }, []); diff --git a/front/src/modules/ui/table/hooks/useCloseCurrentTableCellInEditMode.ts b/front/src/modules/ui/table/hooks/useCloseCurrentTableCellInEditMode.ts new file mode 100644 index 000000000..6c0e7d604 --- /dev/null +++ b/front/src/modules/ui/table/hooks/useCloseCurrentTableCellInEditMode.ts @@ -0,0 +1,18 @@ +import { useRecoilCallback } from 'recoil'; + +import { currentTableCellInEditModePositionState } from '../states/currentTableCellInEditModePositionState'; +import { isTableCellInEditModeFamilyState } from '../states/isTableCellInEditModeFamilyState'; + +export const useCloseCurrentTableCellInEditMode = () => + useRecoilCallback(({ set, snapshot }) => { + return async () => { + const currentTableCellInEditModePosition = await snapshot.getPromise( + currentTableCellInEditModePositionState, + ); + + set( + isTableCellInEditModeFamilyState(currentTableCellInEditModePosition), + false, + ); + }; + }, []); diff --git a/front/src/modules/ui/table/hooks/useDisableSoftFocus.ts b/front/src/modules/ui/table/hooks/useDisableSoftFocus.ts index 9a3820866..beef9378e 100644 --- a/front/src/modules/ui/table/hooks/useDisableSoftFocus.ts +++ b/front/src/modules/ui/table/hooks/useDisableSoftFocus.ts @@ -1,7 +1,7 @@ import { useRecoilCallback } from 'recoil'; import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState'; -import { isSoftFocusOnCellFamilyState } from '../states/isSoftFocusOnCellFamilyState'; +import { isSoftFocusOnTableCellFamilyState } from '../states/isSoftFocusOnTableCellFamilyState'; import { softFocusPositionState } from '../states/softFocusPositionState'; export const useDisableSoftFocus = () => @@ -13,6 +13,6 @@ export const useDisableSoftFocus = () => set(isSoftFocusActiveState, false); - set(isSoftFocusOnCellFamilyState(currentPosition), false); + set(isSoftFocusOnTableCellFamilyState(currentPosition), false); }; }, []); diff --git a/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts b/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts index 4c0e966f7..8ba90fcca 100644 --- a/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts +++ b/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts @@ -5,12 +5,12 @@ import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/c import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState'; import { TableHotkeyScope } from '../types/TableHotkeyScope'; -import { useCloseCurrentCellInEditMode } from './useClearCellInEditMode'; +import { useCloseCurrentTableCellInEditMode } from './useCloseCurrentTableCellInEditMode'; import { useDisableSoftFocus } from './useDisableSoftFocus'; export const useLeaveTableFocus = () => { const disableSoftFocus = useDisableSoftFocus(); - const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode(); + const closeCurrentCellInEditMode = useCloseCurrentTableCellInEditMode(); return useRecoilCallback( ({ snapshot }) => diff --git a/front/src/modules/ui/table/hooks/useMoveEditModeToCellPosition.ts b/front/src/modules/ui/table/hooks/useMoveEditModeToCellPosition.ts index 1165d23cf..b39a6910c 100644 --- a/front/src/modules/ui/table/hooks/useMoveEditModeToCellPosition.ts +++ b/front/src/modules/ui/table/hooks/useMoveEditModeToCellPosition.ts @@ -1,20 +1,23 @@ import { useRecoilCallback } from 'recoil'; -import { currentCellInEditModePositionState } from '../states/currentCellInEditModePositionState'; -import { isCellInEditModeFamilyState } from '../states/isCellInEditModeFamilyState'; -import { CellPosition } from '../types/CellPosition'; +import { currentTableCellInEditModePositionState } from '../states/currentTableCellInEditModePositionState'; +import { isTableCellInEditModeFamilyState } from '../states/isTableCellInEditModeFamilyState'; +import { TableCellPosition } from '../types/TableCellPosition'; -export const useMoveEditModeToCellPosition = () => +export const useMoveEditModeToTableCellPosition = () => useRecoilCallback(({ set, snapshot }) => { - return (newPosition: CellPosition) => { - const currentCellInEditModePosition = snapshot - .getLoadable(currentCellInEditModePositionState) + return (newPosition: TableCellPosition) => { + const currentTableCellInEditModePosition = snapshot + .getLoadable(currentTableCellInEditModePositionState) .valueOrThrow(); - set(isCellInEditModeFamilyState(currentCellInEditModePosition), false); + set( + isTableCellInEditModeFamilyState(currentTableCellInEditModePosition), + false, + ); - set(currentCellInEditModePositionState, newPosition); + set(currentTableCellInEditModePositionState, newPosition); - set(isCellInEditModeFamilyState(newPosition), true); + set(isTableCellInEditModeFamilyState(newPosition), true); }; }, []); diff --git a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts index 8ea338c53..141ea8895 100644 --- a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts +++ b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts @@ -1,8 +1,8 @@ import { useRecoilCallback } from 'recoil'; +import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState'; import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; -import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId'; import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; @@ -27,11 +27,11 @@ export const useSetEntityTableData = () => { ) => { for (const entity of newEntityArray) { const currentEntity = snapshot - .getLoadable(tableEntitiesFamilyState(entity.id)) + .getLoadable(entityFieldsFamilyState(entity.id)) .valueOrThrow(); if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) { - set(tableEntitiesFamilyState(entity.id), entity); + set(entityFieldsFamilyState(entity.id), entity); } } diff --git a/front/src/modules/ui/table/hooks/useSetSoftFocusPosition.ts b/front/src/modules/ui/table/hooks/useSetSoftFocusPosition.ts index 88c1ad254..72cea1f34 100644 --- a/front/src/modules/ui/table/hooks/useSetSoftFocusPosition.ts +++ b/front/src/modules/ui/table/hooks/useSetSoftFocusPosition.ts @@ -1,23 +1,23 @@ import { useRecoilCallback } from 'recoil'; import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState'; -import { isSoftFocusOnCellFamilyState } from '../states/isSoftFocusOnCellFamilyState'; +import { isSoftFocusOnTableCellFamilyState } from '../states/isSoftFocusOnTableCellFamilyState'; import { softFocusPositionState } from '../states/softFocusPositionState'; -import { CellPosition } from '../types/CellPosition'; +import { TableCellPosition } from '../types/TableCellPosition'; export const useSetSoftFocusPosition = () => useRecoilCallback(({ set, snapshot }) => { - return (newPosition: CellPosition) => { + return (newPosition: TableCellPosition) => { const currentPosition = snapshot .getLoadable(softFocusPositionState) .valueOrThrow(); set(isSoftFocusActiveState, true); - set(isSoftFocusOnCellFamilyState(currentPosition), false); + set(isSoftFocusOnTableCellFamilyState(currentPosition), false); set(softFocusPositionState, newPosition); - set(isSoftFocusOnCellFamilyState(newPosition), true); + set(isSoftFocusOnTableCellFamilyState(newPosition), true); }; }, []); diff --git a/front/src/modules/ui/table/hooks/useTableColumns.ts b/front/src/modules/ui/table/hooks/useTableColumns.ts index 155737233..96435758f 100644 --- a/front/src/modules/ui/table/hooks/useTableColumns.ts +++ b/front/src/modules/ui/table/hooks/useTableColumns.ts @@ -1,16 +1,16 @@ import { useCallback, useContext } from 'react'; import { useSetRecoilState } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; +import { ViewFieldForVisibility } from '@/ui/view-bar/types/ViewFieldForVisibility'; import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns'; import { TableContext } from '../contexts/TableContext'; import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext'; import { savedTableColumnsFamilyState } from '../states/savedTableColumnsFamilyState'; -import { tableColumnsByKeyScopedSelector } from '../states/selectors/tableColumnsByKeyScopedSelector'; import { tableColumnsScopedState } from '../states/tableColumnsScopedState'; import { ColumnDefinition } from '../types/ColumnDefinition'; @@ -28,15 +28,11 @@ export const useTableColumns = () => { tableColumnsScopedState, TableRecoilScopeContext, ); - const tableColumnsByKey = useRecoilScopedValue( - tableColumnsByKeyScopedSelector, - TableRecoilScopeContext, - ); const { handleColumnMove } = useMoveViewColumns(); const handleColumnsChange = useCallback( - async (columns: ColumnDefinition[]) => { + async (columns: ColumnDefinition[]) => { setSavedTableColumns(columns); setTableColumns(columns); @@ -46,7 +42,7 @@ export const useTableColumns = () => { ); const handleColumnReorder = useCallback( - async (columns: ColumnDefinition[]) => { + async (columns: ColumnDefinition[]) => { const updatedColumns = columns.map((column, index) => ({ ...column, index, @@ -58,27 +54,20 @@ export const useTableColumns = () => { ); const handleColumnVisibilityChange = useCallback( - async (column: ColumnDefinition) => { - const nextColumns = tableColumnsByKey[column.key] - ? tableColumns.map((previousColumn) => - previousColumn.key === column.key - ? { ...previousColumn, isVisible: !column.isVisible } - : previousColumn, - ) - : [...tableColumns, { ...column, isVisible: true }].sort( - (columnA, columnB) => columnA.index - columnB.index, - ); + async (column: ViewFieldForVisibility) => { + const nextColumns = tableColumns.map((previousColumn) => + previousColumn.key === column.key + ? { ...previousColumn, isVisible: !column.isVisible } + : previousColumn, + ); await handleColumnsChange(nextColumns); }, - [tableColumnsByKey, tableColumns, handleColumnsChange], + [tableColumns, handleColumnsChange], ); const handleMoveTableColumn = useCallback( - ( - direction: 'left' | 'right', - column: ColumnDefinition, - ) => { + (direction: 'left' | 'right', column: ColumnDefinition) => { const currentColumnArrayIndex = tableColumns.findIndex( (tableColumn) => tableColumn.key === column.key, ); diff --git a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts deleted file mode 100644 index 25ee4216d..000000000 --- a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { useContext } from 'react'; - -import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewFieldBoolean'; -import { isViewFieldBooleanValue } from '@/ui/editable-field/types/guards/isViewFieldBooleanValue'; -import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip'; -import { isViewFieldChipValue } from '@/ui/editable-field/types/guards/isViewFieldChipValue'; -import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate'; -import { isViewFieldDateValue } from '@/ui/editable-field/types/guards/isViewFieldDateValue'; -import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText'; -import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip'; -import { isViewFieldDoubleTextChipValue } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue'; -import { isViewFieldDoubleTextValue } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextValue'; -import { isViewFieldEmail } from '@/ui/editable-field/types/guards/isViewFieldEmail'; -import { isViewFieldEmailValue } from '@/ui/editable-field/types/guards/isViewFieldEmailValue'; -import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney'; -import { isViewFieldMoneyValue } from '@/ui/editable-field/types/guards/isViewFieldMoneyValue'; -import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber'; -import { isViewFieldNumberValue } from '@/ui/editable-field/types/guards/isViewFieldNumberValue'; -import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone'; -import { isViewFieldPhoneValue } from '@/ui/editable-field/types/guards/isViewFieldPhoneValue'; -import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation'; -import { isViewFieldRelationValue } from '@/ui/editable-field/types/guards/isViewFieldRelationValue'; -import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldText'; -import { isViewFieldTextValue } from '@/ui/editable-field/types/guards/isViewFieldTextValue'; -import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL'; -import { isViewFieldURLValue } from '@/ui/editable-field/types/guards/isViewFieldURLValue'; -import { - ViewFieldChipMetadata, - ViewFieldChipValue, - ViewFieldDateMetadata, - ViewFieldDateValue, - ViewFieldDoubleTextChipMetadata, - ViewFieldDoubleTextChipValue, - ViewFieldDoubleTextMetadata, - ViewFieldDoubleTextValue, - ViewFieldMetadata, - ViewFieldNumberMetadata, - ViewFieldNumberValue, - ViewFieldPhoneMetadata, - ViewFieldPhoneValue, - ViewFieldRelationMetadata, - ViewFieldRelationValue, - ViewFieldTextMetadata, - ViewFieldTextValue, - ViewFieldURLMetadata, - ViewFieldURLValue, -} from '@/ui/editable-field/types/ViewField'; - -import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext'; -import { ColumnDefinition } from '../types/ColumnDefinition'; - -export const useUpdateEntityField = () => { - const updateEntity = useContext(EntityUpdateMutationContext); - - const updateEntityField = < - 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, - columnDefinition: ColumnDefinition, - newFieldValue: ValueType | null, - ) => { - // 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 - - if ( - // Relation - isViewFieldRelation(columnDefinition) && - isViewFieldRelationValue(newFieldValue) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { - [columnDefinition.metadata.fieldName]: - !newFieldValue || newFieldValue.id === '' - ? { disconnect: true } - : { connect: { id: newFieldValue.id } }, - }, - }, - }); - return; - } - - if ( - // Chip - isViewFieldChip(columnDefinition) && - isViewFieldChipValue(newFieldValue) - ) { - const newContent = newFieldValue; - - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { [columnDefinition.metadata.contentFieldName]: newContent }, - }, - }); - return; - } - - if ( - // Text - (isViewFieldText(columnDefinition) && - isViewFieldTextValue(newFieldValue)) || - // Phone - (isViewFieldPhone(columnDefinition) && - isViewFieldPhoneValue(newFieldValue)) || - // Email - (isViewFieldEmail(columnDefinition) && - isViewFieldEmailValue(newFieldValue)) || - // URL - (isViewFieldURL(columnDefinition) && - isViewFieldURLValue(newFieldValue)) || - // Number - (isViewFieldNumber(columnDefinition) && - isViewFieldNumberValue(newFieldValue)) || - // Boolean - (isViewFieldBoolean(columnDefinition) && - isViewFieldBooleanValue(newFieldValue)) || - // Money - (isViewFieldMoney(columnDefinition) && - isViewFieldMoneyValue(newFieldValue)) || - // Date - (isViewFieldDate(columnDefinition) && isViewFieldDateValue(newFieldValue)) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { [columnDefinition.metadata.fieldName]: newFieldValue }, - }, - }); - return; - } - - if ( - // Double text - (isViewFieldDoubleText(columnDefinition) && - isViewFieldDoubleTextValue(newFieldValue)) || - // Double Text Chip - (isViewFieldDoubleTextChip(columnDefinition) && - isViewFieldDoubleTextChipValue(newFieldValue)) - ) { - updateEntity({ - variables: { - where: { id: currentEntityId }, - data: { - [columnDefinition.metadata.firstValueFieldName]: - newFieldValue.firstValue, - [columnDefinition.metadata.secondValueFieldName]: - newFieldValue.secondValue, - }, - }, - }); - } - }; - - return updateEntityField; -}; diff --git a/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts b/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts index 5bf8a148a..eaa078292 100644 --- a/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts +++ b/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts @@ -1,17 +1,17 @@ import { useRecoilCallback } from 'recoil'; -import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState'; +import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState'; export const useUpsertEntityTableItem = () => useRecoilCallback( ({ set, snapshot }) => (entity: T) => { const currentEntity = snapshot - .getLoadable(tableEntitiesFamilyState(entity.id)) + .getLoadable(entityFieldsFamilyState(entity.id)) .valueOrThrow(); if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) { - set(tableEntitiesFamilyState(entity.id), entity); + set(entityFieldsFamilyState(entity.id), entity); } }, [], diff --git a/front/src/modules/ui/table/hooks/useUpsertEntityTableItems.ts b/front/src/modules/ui/table/hooks/useUpsertEntityTableItems.ts index 8e1c3926e..e7b7adf7e 100644 --- a/front/src/modules/ui/table/hooks/useUpsertEntityTableItems.ts +++ b/front/src/modules/ui/table/hooks/useUpsertEntityTableItems.ts @@ -1,6 +1,6 @@ import { useRecoilCallback } from 'recoil'; -import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState'; +import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState'; export const useUpsertEntityTableItems = () => useRecoilCallback( @@ -14,7 +14,7 @@ export const useUpsertEntityTableItems = () => // Filter out entities that are already the same in the state. const entitiesToUpdate = entities.filter((entity) => { const currentEntity = snapshot - .getLoadable(tableEntitiesFamilyState(entity.id)) + .getLoadable(entityFieldsFamilyState(entity.id)) .valueMaybe(); return ( @@ -26,7 +26,7 @@ export const useUpsertEntityTableItems = () => // Batch set state for the filtered entities. for (const entity of entitiesToUpdate) { - set(tableEntitiesFamilyState(entity.id), entity); + set(entityFieldsFamilyState(entity.id), entity); } }, [], diff --git a/front/src/modules/ui/table/states/availableTableColumnsScopedState.ts b/front/src/modules/ui/table/states/availableTableColumnsScopedState.ts index 0f9216a74..78d251413 100644 --- a/front/src/modules/ui/table/states/availableTableColumnsScopedState.ts +++ b/front/src/modules/ui/table/states/availableTableColumnsScopedState.ts @@ -1,11 +1,11 @@ import { atomFamily } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../types/ColumnDefinition'; export const availableTableColumnsScopedState = atomFamily< - ColumnDefinition[], + ColumnDefinition[], string >({ key: 'availableTableColumnsScopedState', diff --git a/front/src/modules/ui/table/states/currentCellInEditModePositionState.ts b/front/src/modules/ui/table/states/currentCellInEditModePositionState.ts deleted file mode 100644 index ca3e04999..000000000 --- a/front/src/modules/ui/table/states/currentCellInEditModePositionState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { atom } from 'recoil'; - -import { CellPosition } from '../types/CellPosition'; - -export const currentCellInEditModePositionState = atom({ - key: 'currentCellInEditModePositionState', - default: { - row: 0, - column: 1, - }, -}); diff --git a/front/src/modules/ui/table/states/currentTableCellInEditModePositionState.ts b/front/src/modules/ui/table/states/currentTableCellInEditModePositionState.ts new file mode 100644 index 000000000..1a2d2933f --- /dev/null +++ b/front/src/modules/ui/table/states/currentTableCellInEditModePositionState.ts @@ -0,0 +1,11 @@ +import { atom } from 'recoil'; + +import { TableCellPosition } from '../types/TableCellPosition'; + +export const currentTableCellInEditModePositionState = atom({ + key: 'currentTableCellInEditModePositionState', + default: { + row: 0, + column: 1, + }, +}); diff --git a/front/src/modules/ui/table/states/isCellInEditModeFamilyState.ts b/front/src/modules/ui/table/states/isCellInEditModeFamilyState.ts deleted file mode 100644 index d84b20f34..000000000 --- a/front/src/modules/ui/table/states/isCellInEditModeFamilyState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { atomFamily } from 'recoil'; - -import { CellPosition } from '../types/CellPosition'; - -export const isCellInEditModeFamilyState = atomFamily({ - key: 'isCellInEditModeFamilyState', - default: false, -}); diff --git a/front/src/modules/ui/table/states/isSoftFocusOnCellFamilyState.ts b/front/src/modules/ui/table/states/isSoftFocusOnCellFamilyState.ts deleted file mode 100644 index 1db20a4c3..000000000 --- a/front/src/modules/ui/table/states/isSoftFocusOnCellFamilyState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { atomFamily } from 'recoil'; - -import { CellPosition } from '../types/CellPosition'; - -export const isSoftFocusOnCellFamilyState = atomFamily({ - key: 'isSoftFocusOnCellFamilyState', - default: false, -}); diff --git a/front/src/modules/ui/table/states/isSoftFocusOnTableCellFamilyState.ts b/front/src/modules/ui/table/states/isSoftFocusOnTableCellFamilyState.ts new file mode 100644 index 000000000..931bb4101 --- /dev/null +++ b/front/src/modules/ui/table/states/isSoftFocusOnTableCellFamilyState.ts @@ -0,0 +1,11 @@ +import { atomFamily } from 'recoil'; + +import { TableCellPosition } from '../types/TableCellPosition'; + +export const isSoftFocusOnTableCellFamilyState = atomFamily< + boolean, + TableCellPosition +>({ + key: 'isSoftFocusOnTableCellFamilyState', + default: false, +}); diff --git a/front/src/modules/ui/table/states/isTableCellInEditModeFamilyState.ts b/front/src/modules/ui/table/states/isTableCellInEditModeFamilyState.ts new file mode 100644 index 000000000..8b10b7396 --- /dev/null +++ b/front/src/modules/ui/table/states/isTableCellInEditModeFamilyState.ts @@ -0,0 +1,11 @@ +import { atomFamily } from 'recoil'; + +import { TableCellPosition } from '../types/TableCellPosition'; + +export const isTableCellInEditModeFamilyState = atomFamily< + boolean, + TableCellPosition +>({ + key: 'isTableCellInEditModeFamilyState', + default: false, +}); diff --git a/front/src/modules/ui/table/states/savedTableColumnsFamilyState.ts b/front/src/modules/ui/table/states/savedTableColumnsFamilyState.ts index 4d732d3e0..c04c5c2c1 100644 --- a/front/src/modules/ui/table/states/savedTableColumnsFamilyState.ts +++ b/front/src/modules/ui/table/states/savedTableColumnsFamilyState.ts @@ -1,11 +1,11 @@ import { atomFamily } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../types/ColumnDefinition'; export const savedTableColumnsFamilyState = atomFamily< - ColumnDefinition[], + ColumnDefinition[], string | undefined >({ key: 'savedTableColumnsFamilyState', diff --git a/front/src/modules/ui/table/states/selectors/savedTableColumnsByKeyFamilySelector.ts b/front/src/modules/ui/table/states/selectors/savedTableColumnsByKeyFamilySelector.ts index 765550f17..a520b2c39 100644 --- a/front/src/modules/ui/table/states/selectors/savedTableColumnsByKeyFamilySelector.ts +++ b/front/src/modules/ui/table/states/selectors/savedTableColumnsByKeyFamilySelector.ts @@ -1,6 +1,6 @@ import { selectorFamily } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../../types/ColumnDefinition'; import { savedTableColumnsFamilyState } from '../savedTableColumnsFamilyState'; @@ -11,6 +11,6 @@ export const savedTableColumnsByKeyFamilySelector = selectorFamily({ (viewId: string | undefined) => ({ get }) => get(savedTableColumnsFamilyState(viewId)).reduce< - Record> + Record> >((result, column) => ({ ...result, [column.key]: column }), {}), }); diff --git a/front/src/modules/ui/table/states/selectors/tableColumnsByKeyScopedSelector.ts b/front/src/modules/ui/table/states/selectors/tableColumnsByKeyScopedSelector.ts index a743b3267..c3cb0bc86 100644 --- a/front/src/modules/ui/table/states/selectors/tableColumnsByKeyScopedSelector.ts +++ b/front/src/modules/ui/table/states/selectors/tableColumnsByKeyScopedSelector.ts @@ -1,6 +1,6 @@ import { selectorFamily } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../../types/ColumnDefinition'; import { tableColumnsScopedState } from '../tableColumnsScopedState'; @@ -11,6 +11,6 @@ export const tableColumnsByKeyScopedSelector = selectorFamily({ (scopeId: string) => ({ get }) => get(tableColumnsScopedState(scopeId)).reduce< - Record> + Record> >((result, column) => ({ ...result, [column.key]: column }), {}), }); diff --git a/front/src/modules/ui/table/states/softFocusPositionState.ts b/front/src/modules/ui/table/states/softFocusPositionState.ts index e4045918a..6eca1b479 100644 --- a/front/src/modules/ui/table/states/softFocusPositionState.ts +++ b/front/src/modules/ui/table/states/softFocusPositionState.ts @@ -1,8 +1,8 @@ import { atom } from 'recoil'; -import { CellPosition } from '../types/CellPosition'; +import { TableCellPosition } from '../types/TableCellPosition'; -export const softFocusPositionState = atom({ +export const softFocusPositionState = atom({ key: 'softFocusPositionState', default: { row: 0, diff --git a/front/src/modules/ui/table/states/tableColumnsScopedState.ts b/front/src/modules/ui/table/states/tableColumnsScopedState.ts index e9b520095..c58622db3 100644 --- a/front/src/modules/ui/table/states/tableColumnsScopedState.ts +++ b/front/src/modules/ui/table/states/tableColumnsScopedState.ts @@ -1,11 +1,11 @@ import { atomFamily } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { ColumnDefinition } from '../types/ColumnDefinition'; export const tableColumnsScopedState = atomFamily< - ColumnDefinition[], + ColumnDefinition[], string >({ key: 'tableColumnsScopedState', diff --git a/front/src/modules/ui/table/types/ColumnDefinition.ts b/front/src/modules/ui/table/types/ColumnDefinition.ts index c7841d0f0..49f7a9f67 100644 --- a/front/src/modules/ui/table/types/ColumnDefinition.ts +++ b/front/src/modules/ui/table/types/ColumnDefinition.ts @@ -1,9 +1,8 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { FieldDefinition } from '@/ui/field/types/FieldDefinition'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; -export type ColumnDefinition = - ViewFieldDefinition & { - size: number; - }; +export type ColumnDefinition = FieldDefinition & { + size: number; + index: number; + isVisible?: boolean; +}; diff --git a/front/src/modules/ui/table/types/CellPosition.ts b/front/src/modules/ui/table/types/TableCellPosition.ts similarity index 51% rename from front/src/modules/ui/table/types/CellPosition.ts rename to front/src/modules/ui/table/types/TableCellPosition.ts index ed2cbca42..a79088447 100644 --- a/front/src/modules/ui/table/types/CellPosition.ts +++ b/front/src/modules/ui/table/types/TableCellPosition.ts @@ -1,4 +1,4 @@ -export type CellPosition = { +export type TableCellPosition = { row: number; column: number; }; diff --git a/front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts b/front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts index 506bb5ba9..5425e0f01 100644 --- a/front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts +++ b/front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts @@ -80,6 +80,7 @@ export const useListenClickOutside = ({ } }, [refs, callback, mode, enabled]); }; + export const useListenClickOutsideByClassName = ({ classNames, excludeClassNames, diff --git a/front/src/modules/ui/view-bar/components/ViewFieldsVisibilityDropdownSection.tsx b/front/src/modules/ui/view-bar/components/ViewFieldsVisibilityDropdownSection.tsx index 24310656a..84685906f 100644 --- a/front/src/modules/ui/view-bar/components/ViewFieldsVisibilityDropdownSection.tsx +++ b/front/src/modules/ui/view-bar/components/ViewFieldsVisibilityDropdownSection.tsx @@ -10,16 +10,14 @@ import { import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; import { IconMinus, IconPlus } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; -type OwnProps = { - fields: Field[]; - onVisibilityChange: (field: Field) => void; +import { ViewFieldForVisibility } from '../types/ViewFieldForVisibility'; + +type OwnProps = { + fields: ViewFieldForVisibility[]; + onVisibilityChange: (field: ViewFieldForVisibility) => void; title: string; isDraggable: boolean; onDragEnd?: OnDragEndResponder; @@ -29,20 +27,18 @@ const StyledDropdownMenuItemWrapper = styled.div` width: 100%; `; -export const ViewFieldsVisibilityDropdownSection = < - Field extends ViewFieldDefinition, ->({ +export const ViewFieldsVisibilityDropdownSection = ({ fields, onVisibilityChange, title, isDraggable, onDragEnd, -}: OwnProps) => { +}: OwnProps) => { const handleOnDrag = (result: DropResult, provided: ResponderProvided) => { onDragEnd?.(result, provided); }; - const getIconButtons = (index: number, field: Field) => { + const getIconButtons = (index: number, field: ViewFieldForVisibility) => { if (index !== 0) { return [ { diff --git a/front/src/modules/ui/view-bar/types/ViewFieldForVisibility.ts b/front/src/modules/ui/view-bar/types/ViewFieldForVisibility.ts new file mode 100644 index 000000000..9a6fbbea3 --- /dev/null +++ b/front/src/modules/ui/view-bar/types/ViewFieldForVisibility.ts @@ -0,0 +1,10 @@ +import { FieldDefinition } from '@/ui/field/types/FieldDefinition'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; + +export type ViewFieldForVisibility = Pick< + FieldDefinition, + 'key' | 'name' | 'Icon' +> & { + isVisible?: boolean; + index: number; +}; diff --git a/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx b/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx index 720a9bb1f..b9d280273 100644 --- a/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx +++ b/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx @@ -33,12 +33,13 @@ export const FilterDropdownUserSearchSelect = ({ }, ], orderByField: 'lastName', - mappingFunction: (entity) => ({ - id: entity.id, + mappingFunction: (user) => ({ + id: user.id, entityType: Entity.User, - name: `${entity.displayName}`, + name: `${user.displayName}`, avatarType: 'rounded', - avatarUrl: entity.avatarUrl ?? '', + avatarUrl: user.avatarUrl ?? '', + originalEntity: user, }), selectedIds: filterDropdownSelectedEntityId ? [filterDropdownSelectedEntityId] diff --git a/front/src/modules/users/components/UserPicker.tsx b/front/src/modules/users/components/UserPicker.tsx index 8f6d8e87b..4da8f73ef 100644 --- a/front/src/modules/users/components/UserPicker.tsx +++ b/front/src/modules/users/components/UserPicker.tsx @@ -43,6 +43,7 @@ export const UserPicker = ({ name: user.displayName, avatarType: 'rounded', avatarUrl: user.avatarUrl ?? '', + originalEntity: user, }), selectedIds: userId ? [userId] : [], }); diff --git a/front/src/modules/views/hooks/useBoardViewFields.ts b/front/src/modules/views/hooks/useBoardViewFields.ts index e77f509b9..bc2ad6369 100644 --- a/front/src/modules/views/hooks/useBoardViewFields.ts +++ b/front/src/modules/views/hooks/useBoardViewFields.ts @@ -5,10 +5,8 @@ import { availableBoardCardFieldsScopedState } from '@/ui/board/states/available import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState'; import { savedBoardCardFieldsFamilyState } from '@/ui/board/states/savedBoardCardFieldsFamilyState'; import { savedBoardCardFieldsByKeyFamilySelector } from '@/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector'; -import type { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { BoardFieldDefinition } from '@/ui/board/types/BoardFieldDefinition'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; @@ -23,23 +21,23 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; const toViewFieldInput = ( objectId: 'company' | 'person', - fieldDefinition: ViewFieldDefinition, + columDefinition: BoardFieldDefinition, ) => ({ - key: fieldDefinition.key, - name: fieldDefinition.name, - index: fieldDefinition.index, - isVisible: fieldDefinition.isVisible ?? true, + key: columDefinition.key, + name: columDefinition.name, + index: columDefinition.index, + isVisible: columDefinition.isVisible ?? true, objectId, }); export const useBoardViewFields = ({ objectId, - fieldDefinitions, + viewFieldDefinition, skipFetch, RecoilScopeContext, }: { objectId: 'company' | 'person'; - fieldDefinitions: ViewFieldDefinition[]; + viewFieldDefinition: BoardFieldDefinition[]; skipFetch?: boolean; RecoilScopeContext: RecoilScopeContext; }) => { @@ -67,14 +65,14 @@ export const useBoardViewFields = ({ const [updateViewFieldMutation] = useUpdateViewFieldMutation(); const createViewFields = ( - fields: ViewFieldDefinition[], + viewFieldDefinitions: BoardFieldDefinition[], viewId = currentViewId, ) => { - if (!viewId || !fields.length) return; + if (!viewId || !viewFieldDefinitions.length) return; return createViewFieldsMutation({ variables: { - data: fields.map((field) => ({ + data: viewFieldDefinitions.map((field) => ({ ...toViewFieldInput(objectId, field), viewId, })), @@ -83,12 +81,12 @@ export const useBoardViewFields = ({ }; const updateViewFields = ( - fields: ViewFieldDefinition[], + viewFieldDefinitions: BoardFieldDefinition[], ) => { - if (!currentViewId || !fields.length) return; + if (!currentViewId || !viewFieldDefinitions.length) return; return Promise.all( - fields.map((field) => + viewFieldDefinitions.map((field) => updateViewFieldMutation({ variables: { data: { @@ -114,13 +112,13 @@ export const useBoardViewFields = ({ onCompleted: async (data) => { if (!data.viewFields.length) { // Populate if empty - await createViewFields(fieldDefinitions); + await createViewFields(viewFieldDefinition); return refetch(); } const nextFields = data.viewFields - .map | null>((viewField) => { - const fieldDefinition = fieldDefinitions.find( + .map | null>((viewField) => { + const fieldDefinition = viewFieldDefinition.find( ({ key }) => viewField.key === key, ); @@ -134,7 +132,7 @@ export const useBoardViewFields = ({ } : null; }) - .filter>(assertNotNull); + .filter>(assertNotNull); if (!isDeeplyEqual(boardCardFields, nextFields)) { setSavedBoardCardFields(nextFields); @@ -142,7 +140,7 @@ export const useBoardViewFields = ({ } if (!availableBoardCardFields.length) { - setAvailableBoardCardFields(fieldDefinitions); + setAvailableBoardCardFields(viewFieldDefinition); } }, }); diff --git a/front/src/modules/views/hooks/useBoardViews.ts b/front/src/modules/views/hooks/useBoardViews.ts index 63c612ab3..f1bef626c 100644 --- a/front/src/modules/views/hooks/useBoardViews.ts +++ b/front/src/modules/views/hooks/useBoardViews.ts @@ -1,10 +1,8 @@ import { RecoilScopeContext } from '@/types/RecoilScopeContext'; import { useBoardColumns } from '@/ui/board/hooks/useBoardColumns'; import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { BoardFieldDefinition } from '@/ui/board/types/BoardFieldDefinition'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; @@ -20,7 +18,7 @@ export const useBoardViews = ({ objectId, RecoilScopeContext, }: { - fieldDefinitions: ViewFieldDefinition[]; + fieldDefinitions: BoardFieldDefinition[]; objectId: 'company'; RecoilScopeContext: RecoilScopeContext; }) => { @@ -46,7 +44,7 @@ export const useBoardViews = ({ const { createViewFields, persistCardFields } = useBoardViewFields({ objectId, - fieldDefinitions, + viewFieldDefinition: fieldDefinitions, skipFetch: isFetchingViews, RecoilScopeContext, }); diff --git a/front/src/modules/views/hooks/useTableViewFields.ts b/front/src/modules/views/hooks/useTableViewFields.ts index afff981e3..caac39b2d 100644 --- a/front/src/modules/views/hooks/useTableViewFields.ts +++ b/front/src/modules/views/hooks/useTableViewFields.ts @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'; import { getOperationName } from '@apollo/client/utilities'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { availableTableColumnsScopedState } from '@/ui/table/states/availableTableColumnsScopedState'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { savedTableColumnsFamilyState } from '@/ui/table/states/savedTableColumnsFamilyState'; @@ -25,7 +25,7 @@ import { GET_VIEW_FIELDS } from '../graphql/queries/getViewFields'; const toViewFieldInput = ( objectId: 'company' | 'person', - fieldDefinition: ColumnDefinition, + fieldDefinition: ColumnDefinition, ) => ({ key: fieldDefinition.key, name: fieldDefinition.name, @@ -41,7 +41,7 @@ export const useTableViewFields = ({ skipFetch, }: { objectId: 'company' | 'person'; - columnDefinitions: ColumnDefinition[]; + columnDefinitions: ColumnDefinition[]; skipFetch?: boolean; }) => { const currentViewId = useRecoilScopedValue( @@ -69,10 +69,7 @@ export const useTableViewFields = ({ const [updateViewFieldMutation] = useUpdateViewFieldMutation(); const createViewFields = useCallback( - ( - columns: ColumnDefinition[], - viewId = currentViewId, - ) => { + (columns: ColumnDefinition[], viewId = currentViewId) => { if (!viewId || !columns.length) return; return createViewFieldsMutation({ @@ -89,7 +86,7 @@ export const useTableViewFields = ({ ); const updateViewFields = useCallback( - (columns: ColumnDefinition[]) => { + (columns: ColumnDefinition[]) => { if (!currentViewId || !columns.length) return; return Promise.all( @@ -127,7 +124,7 @@ export const useTableViewFields = ({ } const nextColumns = data.viewFields - .map | null>((viewField) => { + .map | null>((viewField) => { const columnDefinition = columnDefinitions.find( ({ key }) => viewField.key === key, ); @@ -143,7 +140,7 @@ export const useTableViewFields = ({ } : null; }) - .filter>(assertNotNull); + .filter>(assertNotNull); setSavedTableColumns(nextColumns); @@ -162,7 +159,7 @@ export const useTableViewFields = ({ }); const persistColumns = useCallback( - async (nextColumns: ColumnDefinition[]) => { + async (nextColumns: ColumnDefinition[]) => { if (!currentViewId) return; const viewFieldsToCreate = nextColumns.filter( diff --git a/front/src/modules/views/hooks/useTableViews.ts b/front/src/modules/views/hooks/useTableViews.ts index e9c671d54..bed52c5a5 100644 --- a/front/src/modules/views/hooks/useTableViews.ts +++ b/front/src/modules/views/hooks/useTableViews.ts @@ -1,4 +1,4 @@ -import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { FieldMetadata } from '@/ui/field/types/FieldMetadata'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState'; import { ColumnDefinition } from '@/ui/table/types/ColumnDefinition'; @@ -17,7 +17,7 @@ export const useTableViews = ({ columnDefinitions, }: { objectId: 'company' | 'person'; - columnDefinitions: ColumnDefinition[]; + columnDefinitions: ColumnDefinition[]; }) => { const tableColumns = useRecoilScopedValue( tableColumnsScopedState, diff --git a/front/src/pages/companies/CompanyShow.tsx b/front/src/pages/companies/CompanyShow.tsx index 06916622e..248d19130 100644 --- a/front/src/pages/companies/CompanyShow.tsx +++ b/front/src/pages/companies/CompanyShow.tsx @@ -7,11 +7,10 @@ import { useCompanyQuery } from '@/companies/hooks/useCompanyQuery'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { AppPath } from '@/types/AppPath'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; -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 { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox'; +import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; import { IconBuildingSkyscraper } from '@/ui/icon'; import { PageBody } from '@/ui/layout/components/PageBody'; import { PageContainer } from '@/ui/layout/components/PageContainer'; @@ -93,22 +92,22 @@ export const CompanyShow = () => { )} /> - - - {companyShowFieldDefinition.map((fieldDefinition) => { - return ( - - - - ); - })} - - + {companyShowFieldDefinition.map((fieldDefinition) => { + return ( + + + + ); + })} diff --git a/front/src/pages/companies/constants/companyShowFieldDefinition.tsx b/front/src/pages/companies/constants/companyShowFieldDefinition.tsx index ed4402da5..52dfd2576 100644 --- a/front/src/pages/companies/constants/companyShowFieldDefinition.tsx +++ b/front/src/pages/companies/constants/companyShowFieldDefinition.tsx @@ -1,4 +1,4 @@ -import { FieldDefinition } from '@/ui/editable-field/types/FieldDefinition'; +import { FieldDefinition } from '@/ui/field/types/FieldDefinition'; import { FieldBooleanMetadata, FieldDateMetadata, @@ -7,7 +7,7 @@ import { FieldRelationMetadata, FieldTextMetadata, FieldURLMetadata, -} from '@/ui/editable-field/types/FieldMetadata'; +} from '@/ui/field/types/FieldMetadata'; import { IconBrandX, IconCalendar, @@ -29,6 +29,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ fieldName: 'domainName', placeHolder: 'URL', }, + useEditButton: true, } satisfies FieldDefinition, { key: 'accountOwner', @@ -78,6 +79,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ fieldName: 'xUrl', placeHolder: 'X', }, + useEditButton: true, } satisfies FieldDefinition, { key: 'createdAt', diff --git a/front/src/pages/people/PersonShow.tsx b/front/src/pages/people/PersonShow.tsx index 037f3785d..da89a9af8 100644 --- a/front/src/pages/people/PersonShow.tsx +++ b/front/src/pages/people/PersonShow.tsx @@ -8,11 +8,10 @@ import { GET_PERSON } from '@/people/graphql/queries/getPerson'; import { usePersonQuery } from '@/people/hooks/usePersonQuery'; import { AppPath } from '@/types/AppPath'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; -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 { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox'; +import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; import { IconUser } from '@/ui/icon'; import { PageBody } from '@/ui/layout/components/PageBody'; import { PageContainer } from '@/ui/layout/components/PageContainer'; @@ -119,22 +118,22 @@ export const PersonShow = () => { onUploadPicture={onUploadPicture} /> - - - {personShowFieldDefinition.map((fieldDefinition) => { - return ( - - - - ); - })} - - + {personShowFieldDefinition.map((fieldDefinition) => { + return ( + + + + ); + })} [] = [ name: 'Company', Icon: IconBuildingSkyscraper, type: 'relation', + useEditButton: true, metadata: { fieldName: 'company', relationType: Entity.Company, - useEditButton: true, }, } satisfies FieldDefinition, { @@ -50,6 +50,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ fieldName: 'phone', placeHolder: 'Phone', }, + useEditButton: true, } satisfies FieldDefinition, { key: 'jobTitle', @@ -80,6 +81,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ fieldName: 'linkedinUrl', placeHolder: 'Linkedin URL', }, + useEditButton: true, } satisfies FieldDefinition, { key: 'xUrl', @@ -90,6 +92,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ fieldName: 'xUrl', placeHolder: 'X URL', }, + useEditButton: true, } satisfies FieldDefinition, { key: 'createdAt', diff --git a/front/src/types/ComponentParameters.ts b/front/src/types/ComponentParameters.ts new file mode 100644 index 000000000..8634828ca --- /dev/null +++ b/front/src/types/ComponentParameters.ts @@ -0,0 +1,3 @@ +export type ComponentParameters< + ComponentTypeToExtract extends (args: object) => JSX.Element, +> = Parameters[0]; diff --git a/front/yarn.lock b/front/yarn.lock index a5446daf2..e0211e924 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -19757,6 +19757,11 @@ zen-observable@0.8.15: resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== +zod@^3.22.2: + version "3.22.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b" + integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg== + zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"