From 3666980ccc7e8f5ee6c4265a5a11470f73e0cae4 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Wed, 9 Aug 2023 05:08:37 +0200 Subject: [PATCH] Feat/generic editable board card (#1089) * Fixed BoardColumnMenu * Fixed naming * Optimized board loading * Added GenericEditableField * Introduce GenericEditableField for BoardCards * remove logs * delete unused files * fix stories --------- Co-authored-by: corentin --- front/package.json | 2 + front/src/AppNavbar.tsx | 18 +- front/src/generated/graphql.tsx | 32 +-- .../companies/__stories__/Board.stories.tsx | 5 +- .../__stories__/CompanyBoardCard.stories.tsx | 5 +- .../companies/components/CompanyBoardCard.tsx | 139 +++------- .../components/HooksCompanyBoard.tsx | 224 ++++------------ .../companies/constants/companyViewFields.tsx | 20 +- .../companies/hooks/useUpdateBoardCardIds.ts | 30 +++ .../hooks/useUpdateCompanyBoardColumns.ts | 133 ++++++++++ .../states/companyBoardIndexState.ts | 2 +- .../people/components/PeoplePicker.tsx | 8 +- .../people/constants/peopleViewFields.tsx | 20 +- .../PipelineProgressPointOfContactPicker.tsx | 94 ------- .../pipeline/constants/pipelineViewFields.tsx | 67 +++++ ...ineProgressPointOfContactEditableField.tsx | 53 ---- ...gressPointOfContactPickerFieldEditMode.tsx | 50 ---- .../PipelineProgressAmountEditableField.tsx | 79 ------ front/src/modules/pipeline/queries/update.ts | 18 +- .../ui/board/components/BoardColumn.tsx | 12 +- .../ui/board/components/BoardColumnMenu.tsx | 11 + .../ui/board/components/EntityBoard.tsx | 1 + .../ui/board/components/EntityBoardCard.tsx | 10 +- .../ui/board/components/EntityBoardColumn.tsx | 3 +- .../ui/board/states/FieldDefinitionContext.ts | 9 + .../ui/board/states/fieldsDefinitionsState.ts | 13 + .../components/GenericEditableDateField.tsx | 49 ++++ .../GenericEditableDateFieldEditMode.tsx | 44 ++++ .../components/GenericEditableField.tsx | 35 +++ .../components/GenericEditableNumberField.tsx | 43 +++ .../GenericEditableNumberFieldEditMode.tsx | 78 ++++++ .../GenericEditableRelationField.tsx | 80 ++++++ .../GenericEditableRelationFieldEditMode.tsx | 102 ++++++++ .../components/ProbabilityEditableField.tsx | 20 +- .../ProbabilityEditableFieldEditMode.tsx | 136 ++++++++++ .../hooks/useCurrentEntityId.ts | 7 + .../hooks/useUpdateGenericEntityField.ts | 246 ++++++++++++++++++ .../states/EditableFieldContext.ts | 3 + .../editable-field/states/EntityIdContext.ts | 3 + .../states/genericEntitiesFamilyState.ts | 9 + .../genericEntityFieldFamilySelector.ts | 18 ++ .../types/ViewField.ts | 12 +- .../types/guards/isViewFieldChip.ts | 0 .../types/guards/isViewFieldChipValue.ts | 0 .../types/guards/isViewFieldDate.ts | 0 .../types/guards/isViewFieldDateValue.ts | 0 .../types/guards/isViewFieldDoubleText.ts | 0 .../types/guards/isViewFieldDoubleTextChip.ts | 0 .../guards/isViewFieldDoubleTextChipValue.ts | 0 .../guards/isViewFieldDoubleTextValue.ts | 0 .../types/guards/isViewFieldNumber.ts | 0 .../types/guards/isViewFieldNumberValue.ts | 0 .../types/guards/isViewFieldPhone.ts | 0 .../types/guards/isViewFieldPhoneValue.ts | 0 .../types/guards/isViewFieldProbability.ts | 11 + .../guards/isViewFieldProbabilityValue.ts | 12 + .../types/guards/isViewFieldRelation.ts | 0 .../types/guards/isViewFieldRelationValue.ts | 0 .../types/guards/isViewFieldText.ts | 0 .../types/guards/isViewFieldTextValue.ts | 0 .../types/guards/isViewFieldURL.ts | 0 .../types/guards/isViewFieldURLValue.ts | 0 .../variants/components/DateEditableField.tsx | 32 +-- .../components/EditableFieldEditModeDate.tsx | 10 +- .../components/NumberEditableField.tsx | 73 ------ .../NumberEditableField.stories.tsx | 32 --- .../states/AvailableFiltersContext.ts | 5 + .../components/EntityTableColumnMenu.tsx | 2 +- .../ui/table/components/EntityTableHeader.tsx | 8 +- .../components/GenericEntityTableData.tsx | 6 +- .../components/GenericEditableCell.tsx | 20 +- .../components/GenericEditableChipCell.tsx | 4 +- .../GenericEditableChipCellDisplayMode.tsx | 8 +- .../GenericEditableChipCellEditMode.tsx | 8 +- .../components/GenericEditableDateCell.tsx | 8 +- .../GenericEditableDateCellEditMode.tsx | 8 +- .../GenericEditableDoubleTextCell.tsx | 8 +- .../GenericEditableDoubleTextCellEditMode.tsx | 8 +- .../GenericEditableDoubleTextChipCell.tsx | 6 +- ...cEditableDoubleTextChipCellDisplayMode.tsx | 8 +- ...ericEditableDoubleTextChipCellEditMode.tsx | 8 +- .../components/GenericEditableNumberCell.tsx | 8 +- .../GenericEditableNumberCellEditMode.tsx | 8 +- .../components/GenericEditablePhoneCell.tsx | 8 +- .../GenericEditablePhoneCellEditMode.tsx | 8 +- .../GenericEditableRelationCell.tsx | 6 +- ...GenericEditableRelationCellDisplayMode.tsx | 8 +- .../GenericEditableRelationCellEditMode.tsx | 8 +- .../components/GenericEditableTextCell.tsx | 8 +- .../GenericEditableTextCellEditMode.tsx | 8 +- .../components/GenericEditableURLCell.tsx | 8 +- .../GenericEditableURLCellEditMode.tsx | 8 +- .../src/modules/ui/table/hooks/useLoadView.ts | 6 +- .../ui/table/hooks/useUpdateEntityField.ts | 38 +-- .../ui/table/states/ViewFieldContext.ts | 5 +- .../ui/table/states/viewFieldsState.ts | 2 +- .../components/OptionsDropdownButton.tsx | 8 +- .../components/OptionsDropdownSection.tsx | 2 +- .../src/pages/opportunities/Opportunities.tsx | 28 +- front/src/sync-hooks/AuthAutoRouter.tsx | 12 +- .../testing/mock-data/pipeline-progress.ts | 22 +- front/src/utils/isDeeplyEqual.ts | 5 + front/yarn.lock | 31 ++- 103 files changed, 1551 insertions(+), 922 deletions(-) create mode 100644 front/src/modules/companies/hooks/useUpdateBoardCardIds.ts create mode 100644 front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts delete mode 100644 front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx create mode 100644 front/src/modules/pipeline/constants/pipelineViewFields.tsx delete mode 100644 front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx delete mode 100644 front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx delete mode 100644 front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx create mode 100644 front/src/modules/ui/board/states/FieldDefinitionContext.ts create mode 100644 front/src/modules/ui/board/states/fieldsDefinitionsState.ts create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableField.tsx create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx create mode 100644 front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx rename front/src/modules/{pipeline => ui}/editable-field/components/ProbabilityEditableField.tsx (51%) create mode 100644 front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx create mode 100644 front/src/modules/ui/editable-field/hooks/useCurrentEntityId.ts create mode 100644 front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts create mode 100644 front/src/modules/ui/editable-field/states/EditableFieldContext.ts create mode 100644 front/src/modules/ui/editable-field/states/EntityIdContext.ts create mode 100644 front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts create mode 100644 front/src/modules/ui/editable-field/states/genericEntityFieldFamilySelector.ts rename front/src/modules/ui/{table => editable-field}/types/ViewField.ts (90%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldChip.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldChipValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldDate.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldDateValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldDoubleText.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldDoubleTextChip.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldDoubleTextChipValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldDoubleTextValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldNumber.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldNumberValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldPhone.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldPhoneValue.ts (100%) create mode 100644 front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts create mode 100644 front/src/modules/ui/editable-field/types/guards/isViewFieldProbabilityValue.ts rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldRelation.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldRelationValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldText.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldTextValue.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldURL.ts (100%) rename front/src/modules/ui/{table => editable-field}/types/guards/isViewFieldURLValue.ts (100%) delete mode 100644 front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx delete mode 100644 front/src/modules/ui/editable-field/variants/components/__stories__/NumberEditableField.stories.tsx create mode 100644 front/src/modules/ui/filter-n-sort/states/AvailableFiltersContext.ts create mode 100644 front/src/utils/isDeeplyEqual.ts diff --git a/front/package.json b/front/package.json index 8e5d72049..88ac53033 100644 --- a/front/package.json +++ b/front/package.json @@ -23,6 +23,7 @@ "apollo-upload-client": "^17.0.0", "cmdk": "^0.2.0", "date-fns": "^2.30.0", + "deep-equal": "^2.2.2", "framer-motion": "^10.12.17", "graphql": "^16.6.0", "hex-rgb": "^5.0.0", @@ -124,6 +125,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/apollo-upload-client": "^17.0.2", + "@types/deep-equal": "^1.0.1", "@types/jest": "^27.5.2", "@types/js-cookie": "^3.0.3", "@types/lodash.debounce": "^4.0.7", diff --git a/front/src/AppNavbar.tsx b/front/src/AppNavbar.tsx index 5a38eb9b4..2d273f6a1 100644 --- a/front/src/AppNavbar.tsx +++ b/front/src/AppNavbar.tsx @@ -1,4 +1,4 @@ -import { useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { useTheme } from '@emotion/react'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; @@ -17,11 +17,15 @@ import MainNavbar from '@/ui/navbar/components/MainNavbar'; import NavItem from '@/ui/navbar/components/NavItem'; import NavTitle from '@/ui/navbar/components/NavTitle'; +import { measureTotalFrameLoad } from './utils/measureTotalFrameLoad'; + export function AppNavbar() { const theme = useTheme(); const currentPath = useLocation().pathname; const { openCommandMenu } = useCommandMenu(); + const navigate = useNavigate(); + const isInSubMenu = useIsSubMenuNavbarDisplayed(); return ( @@ -62,12 +66,22 @@ export function AppNavbar() { { + measureTotalFrameLoad('people'); + + navigate('/people'); + }} icon={} active={currentPath === '/people'} /> { + measureTotalFrameLoad('opportunities'); + + navigate('/opportunities'); + }} icon={} active={currentPath === '/opportunities'} /> diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 2f7a33648..7ae659d45 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -1942,7 +1942,7 @@ export type User = { phoneNumber?: Maybe; settings: UserSettings; settingsId: Scalars['String']; - supportUserHash: Scalars['String']; + supportUserHash?: Maybe; updatedAt: Scalars['DateTime']; workspaceMember?: Maybe; }; @@ -2437,7 +2437,7 @@ export type VerifyMutationVariables = Exact<{ }>; -export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash: string, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type RenewTokenMutationVariables = Exact<{ refreshToken: Scalars['String']; @@ -2451,7 +2451,7 @@ export type ImpersonateMutationVariables = Exact<{ }>; -export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash: string, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; @@ -2613,15 +2613,12 @@ export type UpdatePipelineStageMutationVariables = Exact<{ export type UpdatePipelineStageMutation = { __typename?: 'Mutation', updateOnePipelineStage?: { __typename?: 'PipelineStage', id: string, name: string, color: string } | null }; export type UpdateOnePipelineProgressMutationVariables = Exact<{ - id?: InputMaybe; - amount?: InputMaybe; - closeDate?: InputMaybe; - probability?: InputMaybe; - pointOfContactId?: InputMaybe; + data: PipelineProgressUpdateInput; + where: PipelineProgressWhereUniqueInput; }>; -export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string, amount?: number | null, closeDate?: string | null } | null }; +export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string, amount?: number | null, closeDate?: string | null, probability?: number | null } | null }; export type UpdateOnePipelineProgressStageMutationVariables = Exact<{ id?: InputMaybe; @@ -2685,7 +2682,7 @@ export type SearchActivityQuery = { __typename?: 'Query', searchResults: Array<{ export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, canImpersonate: boolean, supportUserHash: string, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } }; export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>; @@ -4564,14 +4561,12 @@ export type UpdatePipelineStageMutationHookResult = ReturnType; export type UpdatePipelineStageMutationOptions = Apollo.BaseMutationOptions; export const UpdateOnePipelineProgressDocument = gql` - mutation UpdateOnePipelineProgress($id: String, $amount: Int, $closeDate: DateTime, $probability: Int, $pointOfContactId: String) { - updateOnePipelineProgress( - where: {id: $id} - data: {amount: $amount, closeDate: $closeDate, probability: $probability, pointOfContact: {connect: {id: $pointOfContactId}}} - ) { + mutation UpdateOnePipelineProgress($data: PipelineProgressUpdateInput!, $where: PipelineProgressWhereUniqueInput!) { + updateOnePipelineProgress(where: $where, data: $data) { id amount closeDate + probability } } `; @@ -4590,11 +4585,8 @@ export type UpdateOnePipelineProgressMutationFn = Apollo.MutationFunction = { decorators: [ (Story) => ( - + diff --git a/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx b/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx index 26362732d..a5e1000c5 100644 --- a/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx +++ b/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx @@ -19,10 +19,7 @@ const meta: Meta = { decorators: [ (Story) => ( - + diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index 7be4e3444..223fa92c3 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -1,27 +1,21 @@ -import { ReactNode, useCallback, useContext } from 'react'; -import { getOperationName } from '@apollo/client/utilities'; +import { ReactNode, useContext } from 'react'; import styled from '@emotion/styled'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; -import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; -import { PipelineProgressPointOfContactEditableField } from '@/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField'; -import { ProbabilityEditableField } from '@/pipeline/editable-field/components/ProbabilityEditableField'; -import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries'; import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState'; import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState'; import { EntityChipVariant } from '@/ui/chip/components/EntityChip'; -import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField'; -import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField'; -import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon'; -import { IconCalendarEvent } from '@/ui/icon'; +import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField'; import { Checkbox, CheckboxVariant, } from '@/ui/input/checkbox/components/Checkbox'; +import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { getLogoUrlFromDomainName } from '~/utils'; -import { PipelineProgressForBoard } from '../types/CompanyProgress'; +import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState'; import { CompanyChip } from './CompanyChip'; @@ -106,8 +100,6 @@ const StyledFieldContainer = styled.div` `; export function CompanyBoardCard() { - const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation(); - const boardCardId = useContext(BoardCardIdContext); const [companyProgress] = useRecoilState( @@ -118,6 +110,7 @@ export function CompanyBoardCard() { const [selectedBoardCards, setSelectedBoardCards] = useRecoilState( selectedBoardCardIdsState, ); + const fieldsDefinitions = useRecoilValue(fieldsDefinitionsState); const selected = selectedBoardCards.includes(boardCardId ?? ''); @@ -131,25 +124,6 @@ export function CompanyBoardCard() { } } - const handleCardUpdate = useCallback( - async (pipelineProgress: PipelineProgressForBoard) => { - await updatePipelineProgress({ - variables: { - id: pipelineProgress.id, - amount: pipelineProgress.amount, - closeDate: pipelineProgress.closeDate, - probability: pipelineProgress.probability, - pointOfContactId: pipelineProgress.pointOfContactId || undefined, - }, - refetchQueries: [ - getOperationName(GET_PIPELINE_PROGRESS) ?? '', - getOperationName(GET_PIPELINES) ?? '', - ], - }); - }, - [updatePipelineProgress], - ); - if (!company || !pipelineProgress) { return null; } @@ -171,71 +145,40 @@ export function CompanyBoardCard() { } return ( - - setSelected(!selected)} - > - - - - setSelected(!selected)} - variant={CheckboxVariant.Secondary} + + + setSelected(!selected)} + > + + - - - - - } - value={pipelineProgress.closeDate} - onSubmit={(value) => - handleCardUpdate({ - ...pipelineProgress, - closeDate: value, - }) - } - /> - - - } - placeholder="Opportunity amount" - value={pipelineProgress.amount} - onSubmit={(value) => - handleCardUpdate({ - ...pipelineProgress, - amount: value, - }) - } - /> - - - } - value={pipelineProgress.probability} - onSubmit={(value) => { - handleCardUpdate({ - ...pipelineProgress, - probability: value, - }); - }} - /> - - - - - - - + + setSelected(!selected)} + variant={CheckboxVariant.Secondary} + /> + + + + {fieldsDefinitions.map((viewField) => { + return ( + + + + ); + })} + + + + ); } diff --git a/front/src/modules/companies/components/HooksCompanyBoard.tsx b/front/src/modules/companies/components/HooksCompanyBoard.tsx index 77fbd153e..0bde12d68 100644 --- a/front/src/modules/companies/components/HooksCompanyBoard.tsx +++ b/front/src/modules/companies/components/HooksCompanyBoard.tsx @@ -1,24 +1,13 @@ import { useEffect, useMemo } from 'react'; -import { useRecoilCallback, useRecoilState } from 'recoil'; +import { useRecoilState, useSetRecoilState } from 'recoil'; -import { useInitializeCompanyBoardFilters } from '@/companies/hooks/useInitializeCompanyBoardFilters'; -import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; -import { - CompanyForBoard, - CompanyProgress, - PipelineProgressForBoard, -} from '@/companies/types/CompanyProgress'; -import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; -import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState'; -import { boardColumnsState } from '@/ui/board/states/boardColumnsState'; +import { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields'; +import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState'; import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState'; -import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; -import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { - GetPipelineProgressQuery, PipelineProgressableType, PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By, } from '~/generated/graphql'; @@ -29,83 +18,44 @@ import { useGetPipelinesQuery, } from '~/generated/graphql'; +import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds'; +import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns'; import { CompanyBoardContext } from '../states/CompanyBoardContext'; export function HooksCompanyBoard({ - availableFilters, orderBy, }: { - availableFilters: FilterDefinition[]; orderBy: PipelineProgresses_Order_By[]; }) { - useInitializeCompanyBoardFilters({ - availableFilters, - }); + const setFieldsDefinitionsState = useSetRecoilState(fieldsDefinitionsState); - const [currentPipeline] = useRecoilState(currentPipelineState); - const [, setBoardColumns] = useRecoilState(boardColumnsState); + useEffect(() => { + setFieldsDefinitionsState(pipelineViewFields); + }); const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState); - const updateBoardColumns = useRecoilCallback( - ({ set, snapshot }) => - (pipeline: Pipeline) => { - const currentPipeline = snapshot - .getLoadable(currentPipelineState) - .valueOrThrow(); + const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext); - const currentBoardColumns = snapshot - .getLoadable(boardColumnsState) - .valueOrThrow(); + const updateCompanyBoard = useUpdateCompanyBoard(); - if (JSON.stringify(pipeline) !== JSON.stringify(currentPipeline)) { - set(currentPipelineState, pipeline); - } - - const pipelineStages = pipeline?.pipelineStages ?? []; - - const orderedPipelineStages = [...pipelineStages].sort((a, b) => { - if (!a.index || !b.index) return 0; - return a.index - b.index; - }); - - const newBoardColumns: BoardColumnDefinition[] = - orderedPipelineStages?.map((pipelineStage) => ({ - id: pipelineStage.id, - title: pipelineStage.name, - colorCode: pipelineStage.color, - index: pipelineStage.index ?? 0, - })); - - if ( - JSON.stringify(currentBoardColumns) !== - JSON.stringify(newBoardColumns) - ) { - setBoardColumns(newBoardColumns); - } + const { data: pipelineData, loading: loadingGetPipelines } = + useGetPipelinesQuery({ + variables: { + where: { + pipelineProgressableType: { + equals: PipelineProgressableType.Company, + }, + }, }, - [], - ); + }); - useGetPipelinesQuery({ - variables: { - where: { - pipelineProgressableType: { equals: PipelineProgressableType.Company }, - }, - }, - onCompleted: async (data) => { - const pipeline = data?.findManyPipeline[0] as Pipeline; + const pipeline = pipelineData?.findManyPipeline[0] as Pipeline | undefined; - updateBoardColumns(pipeline); - }, - }); - - const pipelineStageIds = currentPipeline?.pipelineStages + const pipelineStageIds = pipeline?.pipelineStages ?.map((pipelineStage) => pipelineStage.id) .flat(); - const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext); - const whereFilters = useMemo(() => { return { AND: [ @@ -115,114 +65,52 @@ export function HooksCompanyBoard({ }; }, [filters, pipelineStageIds]) as any; - const updateBoardCardIds = useRecoilCallback( - ({ snapshot, set }) => - ( - pipelineProgresses: GetPipelineProgressQuery['findManyPipelineProgress'], - ) => { - const boardColumns = snapshot - .getLoadable(boardColumnsState) - .valueOrThrow(); + const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds(); - for (const boardColumn of boardColumns) { - const boardCardIds = pipelineProgresses - .filter( - (pipelineProgressToFilter) => - pipelineProgressToFilter.pipelineStageId === boardColumn.id, - ) - .map((pipelineProgress) => pipelineProgress.id); - - set(boardCardIdsByColumnIdFamilyState(boardColumn.id), boardCardIds); - } + const { data: pipelineProgressData, loading: loadingGetPipelineProgress } = + useGetPipelineProgressQuery({ + variables: { + where: whereFilters, + orderBy, }, - [], - ); + onCompleted: (data) => { + const pipelineProgresses = data?.findManyPipelineProgress || []; - const pipelineProgressesQuery = useGetPipelineProgressQuery({ - variables: { - where: whereFilters, - orderBy, - }, - onCompleted: (data) => { - const pipelineProgresses = data?.findManyPipelineProgress || []; + updateCompanyBoardCardIds(pipelineProgresses); - updateBoardCardIds(pipelineProgresses); + setIsBoardLoaded(true); + }, + }); - setIsBoardLoaded(true); - }, - }); + const pipelineProgresses = useMemo(() => { + return pipelineProgressData?.findManyPipelineProgress || []; + }, [pipelineProgressData]); - const pipelineProgresses = - pipelineProgressesQuery.data?.findManyPipelineProgress || []; - - const entitiesQueryResult = useGetCompaniesQuery({ - variables: { - where: { - id: { - in: pipelineProgresses.map((item) => item.companyId || ''), + const { data: companiesData, loading: loadingGetCompanies } = + useGetCompaniesQuery({ + variables: { + where: { + id: { + in: pipelineProgresses.map((item) => item.companyId || ''), + }, }, }, - }, - }); - - const indexCompanyByIdReducer = ( - acc: { [key: string]: CompanyForBoard }, - company: CompanyForBoard, - ) => ({ - ...acc, - [company.id]: company, - }); - - const companiesDict = - entitiesQueryResult.data?.companies.reduce( - indexCompanyByIdReducer, - {} as { [key: string]: CompanyForBoard }, - ) || {}; - - const indexPipelineProgressByIdReducer = ( - acc: { - [key: string]: CompanyProgress; - }, - pipelineProgress: PipelineProgressForBoard, - ) => { - const company = - pipelineProgress.companyId && companiesDict[pipelineProgress.companyId]; - if (!company) return acc; - return { - ...acc, - [pipelineProgress.id]: { - pipelineProgress, - company, - }, - }; - }; - const companyBoardIndex = pipelineProgresses.reduce( - indexPipelineProgressByIdReducer, - {} as { [key: string]: CompanyProgress }, - ); - - const synchronizeCompanyProgresses = useRecoilCallback( - ({ snapshot, set }) => - (companyBoardIndex: { [key: string]: CompanyProgress }) => { - Object.entries(companyBoardIndex).forEach(([id, companyProgress]) => { - if ( - JSON.stringify( - snapshot.getLoadable(companyProgressesFamilyState(id)).getValue(), - ) !== JSON.stringify(companyProgress) - ) { - set(companyProgressesFamilyState(id), companyProgress); - } - }); - }, - [], - ); + }); const loading = - entitiesQueryResult.loading || pipelineProgressesQuery.loading; + loadingGetPipelines || loadingGetPipelineProgress || loadingGetCompanies; useEffect(() => { - !loading && synchronizeCompanyProgresses(companyBoardIndex); - }, [loading, companyBoardIndex, synchronizeCompanyProgresses]); + if (!loading && pipeline && pipelineProgresses && companiesData) { + updateCompanyBoard(pipeline, pipelineProgresses, companiesData.companies); + } + }, [ + loading, + pipeline, + pipelineProgresses, + companiesData, + updateCompanyBoard, + ]); return <>; } diff --git a/front/src/modules/companies/constants/companyViewFields.tsx b/front/src/modules/companies/constants/companyViewFields.tsx index df077e6cc..6d842ab94 100644 --- a/front/src/modules/companies/constants/companyViewFields.tsx +++ b/front/src/modules/companies/constants/companyViewFields.tsx @@ -1,3 +1,13 @@ +import { + ViewFieldChipMetadata, + ViewFieldDateMetadata, + ViewFieldDefinition, + ViewFieldMetadata, + ViewFieldNumberMetadata, + ViewFieldRelationMetadata, + ViewFieldTextMetadata, + ViewFieldURLMetadata, +} from '@/ui/editable-field/types/ViewField'; import { IconBrandLinkedin, IconBuildingSkyscraper, @@ -8,16 +18,6 @@ import { IconUsers, } from '@/ui/icon/index'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { - ViewFieldChipMetadata, - ViewFieldDateMetadata, - ViewFieldDefinition, - ViewFieldMetadata, - ViewFieldNumberMetadata, - ViewFieldRelationMetadata, - ViewFieldTextMetadata, - ViewFieldURLMetadata, -} from '@/ui/table/types/ViewField'; export const companyViewFields: ViewFieldDefinition[] = [ { diff --git a/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts b/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts new file mode 100644 index 000000000..921eecb2c --- /dev/null +++ b/front/src/modules/companies/hooks/useUpdateBoardCardIds.ts @@ -0,0 +1,30 @@ +import { useRecoilCallback } from 'recoil'; + +import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState'; +import { boardColumnsState } from '@/ui/board/states/boardColumnsState'; +import { GetPipelineProgressQuery } from '~/generated/graphql'; + +export function useUpdateCompanyBoardCardIds() { + return useRecoilCallback( + ({ snapshot, set }) => + ( + pipelineProgresses: GetPipelineProgressQuery['findManyPipelineProgress'], + ) => { + const boardColumns = snapshot + .getLoadable(boardColumnsState) + .valueOrThrow(); + + for (const boardColumn of boardColumns) { + const boardCardIds = pipelineProgresses + .filter( + (pipelineProgressToFilter) => + pipelineProgressToFilter.pipelineStageId === boardColumn.id, + ) + .map((pipelineProgress) => pipelineProgress.id); + + set(boardCardIdsByColumnIdFamilyState(boardColumn.id), boardCardIds); + } + }, + [], + ); +} diff --git a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts new file mode 100644 index 000000000..5ec6348df --- /dev/null +++ b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts @@ -0,0 +1,133 @@ +import { useRecoilCallback } from 'recoil'; + +import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; +import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState'; +import { boardColumnsState } from '@/ui/board/states/boardColumnsState'; +import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition'; +import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState'; +import { Pipeline } from '~/generated/graphql'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; + +import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState'; +import { + CompanyForBoard, + CompanyProgressDict, + PipelineProgressForBoard, +} from '../types/CompanyProgress'; + +export function useUpdateCompanyBoard() { + return useRecoilCallback( + ({ set, snapshot }) => + ( + pipeline: Pipeline, + pipelineProgresses: (PipelineProgressForBoard & { + pipelineStageId: string; + })[], + companies: CompanyForBoard[], + ) => { + const indexCompanyByIdReducer = ( + acc: { [key: string]: CompanyForBoard }, + company: CompanyForBoard, + ) => ({ + ...acc, + [company.id]: company, + }); + + const companiesDict = + companies.reduce( + indexCompanyByIdReducer, + {} as { [key: string]: CompanyForBoard }, + ) ?? {}; + + const indexPipelineProgressByIdReducer = ( + acc: CompanyProgressDict, + pipelineProgress: PipelineProgressForBoard, + ) => { + const company = + pipelineProgress.companyId && + companiesDict[pipelineProgress.companyId]; + + if (!company) return acc; + + return { + ...acc, + [pipelineProgress.id]: { + pipelineProgress, + company, + }, + }; + }; + + const companyBoardIndex = pipelineProgresses.reduce( + indexPipelineProgressByIdReducer, + {} as CompanyProgressDict, + ); + + for (const [id, companyProgress] of Object.entries(companyBoardIndex)) { + const currentCompanyProgress = snapshot + .getLoadable(companyProgressesFamilyState(id)) + .valueOrThrow(); + + if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) { + set(companyProgressesFamilyState(id), companyProgress); + set( + genericEntitiesFamilyState(id), + companyProgress.pipelineProgress, + ); + } + } + + const currentPipeline = snapshot + .getLoadable(currentPipelineState) + .valueOrThrow(); + + const currentBoardColumns = snapshot + .getLoadable(boardColumnsState) + .valueOrThrow(); + + if (!isDeeplyEqual(pipeline, currentPipeline)) { + set(currentPipelineState, pipeline); + } + + const pipelineStages = pipeline?.pipelineStages ?? []; + + const orderedPipelineStages = [...pipelineStages].sort((a, b) => { + if (!a.index || !b.index) return 0; + return a.index - b.index; + }); + + const newBoardColumns: BoardColumnDefinition[] = + orderedPipelineStages?.map((pipelineStage) => ({ + id: pipelineStage.id, + title: pipelineStage.name, + colorCode: pipelineStage.color, + index: pipelineStage.index ?? 0, + })); + + if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) { + set(boardColumnsState, newBoardColumns); + } + + for (const boardColumn of newBoardColumns) { + const boardCardIds = pipelineProgresses + .filter( + (pipelineProgressToFilter) => + pipelineProgressToFilter.pipelineStageId === boardColumn.id, + ) + .map((pipelineProgress) => pipelineProgress.id); + + const currentBoardCardIds = snapshot + .getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) + .valueOrThrow(); + + if (!isDeeplyEqual(currentBoardCardIds, boardCardIds)) { + set( + boardCardIdsByColumnIdFamilyState(boardColumn.id), + boardCardIds, + ); + } + } + }, + [], + ); +} diff --git a/front/src/modules/companies/states/companyBoardIndexState.ts b/front/src/modules/companies/states/companyBoardIndexState.ts index 0c838433a..b9280b813 100644 --- a/front/src/modules/companies/states/companyBoardIndexState.ts +++ b/front/src/modules/companies/states/companyBoardIndexState.ts @@ -6,6 +6,6 @@ export const companyBoardIndexState = atomFamily< CompanyProgress | undefined, string >({ - key: 'currentPipelineState', + key: 'companyBoardIndexState', default: undefined, }); diff --git a/front/src/modules/people/components/PeoplePicker.tsx b/front/src/modules/people/components/PeoplePicker.tsx index 498668b20..06783b7ec 100644 --- a/front/src/modules/people/components/PeoplePicker.tsx +++ b/front/src/modules/people/components/PeoplePicker.tsx @@ -7,8 +7,8 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi import { useSearchPeopleQuery } from '~/generated/graphql'; export type OwnProps = { - personId: string; - onSubmit: (newPersonId: string | null) => void; + personId: string | null; + onSubmit: (newPersonId: PersonForSelect | null) => void; onCancel?: () => void; }; @@ -23,7 +23,7 @@ export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) { const people = useFilteredSearchEntityQuery({ queryHook: useSearchPeopleQuery, - selectedIds: [personId], + selectedIds: [personId ?? ''], searchFilter: searchFilter, mappingFunction: (person) => ({ entityType: Entity.Person, @@ -39,7 +39,7 @@ export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) { async function handleEntitySelected( selectedPerson: PersonForSelect | null | undefined, ) { - onSubmit(selectedPerson?.id ?? null); + onSubmit(selectedPerson ?? null); } return ( diff --git a/front/src/modules/people/constants/peopleViewFields.tsx b/front/src/modules/people/constants/peopleViewFields.tsx index 61bf948e2..d52a086f1 100644 --- a/front/src/modules/people/constants/peopleViewFields.tsx +++ b/front/src/modules/people/constants/peopleViewFields.tsx @@ -1,3 +1,13 @@ +import { + ViewFieldDateMetadata, + ViewFieldDefinition, + ViewFieldDoubleTextChipMetadata, + ViewFieldMetadata, + ViewFieldPhoneMetadata, + ViewFieldRelationMetadata, + ViewFieldTextMetadata, + ViewFieldURLMetadata, +} from '@/ui/editable-field/types/ViewField'; import { IconBrandLinkedin, IconBriefcase, @@ -9,16 +19,6 @@ import { IconUser, } from '@/ui/icon/index'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { - ViewFieldDateMetadata, - ViewFieldDefinition, - ViewFieldDoubleTextChipMetadata, - ViewFieldMetadata, - ViewFieldPhoneMetadata, - ViewFieldRelationMetadata, - ViewFieldTextMetadata, - ViewFieldURLMetadata, -} from '@/ui/table/types/ViewField'; export const peopleViewFields: ViewFieldDefinition[] = [ { diff --git a/front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx b/front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx deleted file mode 100644 index 75d6852b2..000000000 --- a/front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { getOperationName } from '@apollo/client/utilities'; -import { Key } from 'ts-key-enum'; - -import { useFilteredSearchPeopleQuery } from '@/people/queries'; -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 { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -import { - Person, - PipelineProgress, - useUpdateOnePipelineProgressMutation, -} from '~/generated/graphql'; - -import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '../queries'; - -export type OwnProps = { - pipelineProgress: Pick & { - pointOfContact?: Pick | null; - }; - onSubmit?: () => void; - onCancel?: () => void; -}; - -export function PipelineProgressPointOfContactPicker({ - pipelineProgress, - onSubmit, - onCancel, -}: OwnProps) { - const [, setIsCreating] = useRecoilScopedState(isCreateModeScopedState); - - const [searchFilter] = useRecoilScopedState( - relationPickerSearchFilterScopedState, - ); - const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation(); - - const people = useFilteredSearchPeopleQuery({ - searchFilter, - selectedIds: pipelineProgress.pointOfContact?.id - ? [pipelineProgress.pointOfContact.id] - : [], - }); - - async function handleEntitySelected( - entity: EntityForSelect | null | undefined, - ) { - if (!entity) { - return; - } - - await updatePipelineProgress({ - variables: { - ...pipelineProgress, - pointOfContactId: entity.id, - }, - refetchQueries: [ - getOperationName(GET_PIPELINE_PROGRESS) ?? '', - getOperationName(GET_PIPELINES) ?? '', - ], - }); - - onSubmit?.(); - } - - function handleCreate() { - setIsCreating(true); - onSubmit?.(); - } - - useScopedHotkeys( - Key.Escape, - () => { - onCancel && onCancel(); - }, - RelationPickerHotkeyScope.RelationPicker, - [], - ); - - return ( - - ); -} diff --git a/front/src/modules/pipeline/constants/pipelineViewFields.tsx b/front/src/modules/pipeline/constants/pipelineViewFields.tsx new file mode 100644 index 000000000..59d7df110 --- /dev/null +++ b/front/src/modules/pipeline/constants/pipelineViewFields.tsx @@ -0,0 +1,67 @@ +import { + ViewFieldDateMetadata, + ViewFieldDefinition, + ViewFieldMetadata, + ViewFieldNumberMetadata, + ViewFieldProbabilityMetadata, + ViewFieldRelationMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { + IconCalendarEvent, + IconCurrencyDollar, + IconProgressCheck, + IconUser, +} from '@/ui/icon'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; + +export const pipelineViewFields: ViewFieldDefinition[] = [ + { + id: 'closeDate', + columnLabel: 'Close Date', + columnIcon: , + columnSize: 150, + columnOrder: 4, + metadata: { + type: 'date', + fieldName: 'closeDate', + }, + isVisible: true, + } satisfies ViewFieldDefinition, + { + id: 'amount', + columnLabel: 'Amount', + columnIcon: , + columnSize: 150, + columnOrder: 4, + metadata: { + type: 'number', + fieldName: 'amount', + }, + isVisible: true, + } satisfies ViewFieldDefinition, + { + id: 'probability', + columnLabel: 'Probability', + columnIcon: , + columnSize: 150, + columnOrder: 4, + metadata: { + type: 'probability', + fieldName: 'probability', + }, + isVisible: true, + } satisfies ViewFieldDefinition, + { + id: 'pointOfContact', + columnLabel: 'Point of Contact', + columnIcon: , + columnSize: 150, + columnOrder: 4, + metadata: { + type: 'relation', + fieldName: 'pointOfContact', + relationType: Entity.Person, + }, + isVisible: true, + } satisfies ViewFieldDefinition, +]; diff --git a/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx b/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx deleted file mode 100644 index 521ddb2d2..000000000 --- a/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { PersonChip } from '@/people/components/PersonChip'; -import { EditableField } from '@/ui/editable-field/components/EditableField'; -import { FieldContext } from '@/ui/editable-field/states/FieldContext'; -import { IconUser } from '@/ui/icon'; -import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { Person, PipelineProgress } from '~/generated/graphql'; - -import { PipelineProgressPointOfContactPickerFieldEditMode } from './PipelineProgressPointOfContactPickerFieldEditMode'; - -type OwnProps = { - pipelineProgress: Pick & { - pointOfContact?: Pick | null; - }; -}; - -export function PipelineProgressPointOfContactEditableField({ - pipelineProgress, -}: OwnProps) { - return ( - - - } - editModeContent={ - - } - displayModeContent={ - pipelineProgress.pointOfContact ? ( - - ) : ( - <> - ) - } - isDisplayModeContentEmpty={!pipelineProgress.pointOfContact} - isDisplayModeFixHeight - /> - - - ); -} diff --git a/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx b/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx deleted file mode 100644 index 5e6023359..000000000 --- a/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import styled from '@emotion/styled'; - -import { PipelineProgressPointOfContactPicker } from '@/pipeline/components/PipelineProgressPointOfContactPicker'; -import { useEditableField } from '@/ui/editable-field/hooks/useEditableField'; -import { Person, PipelineProgress } from '~/generated/graphql'; - -const PipelineProgressPointOfContactPickerContainer = styled.div` - left: 0px; - position: absolute; - top: -8px; -`; - -export type OwnProps = { - pipelineProgress: Pick & { - pointOfContact?: Pick< - Person, - 'id' | 'firstName' | 'lastName' | 'displayName' - > | null; - }; - onSubmit?: () => void; - onCancel?: () => void; -}; - -export function PipelineProgressPointOfContactPickerFieldEditMode({ - pipelineProgress, - onSubmit, - onCancel, -}: OwnProps) { - const { closeEditableField } = useEditableField(); - - function handleSubmit() { - closeEditableField(); - onSubmit?.(); - } - - function handleCancel() { - closeEditableField(); - onCancel?.(); - } - - return ( - - - - ); -} diff --git a/front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx b/front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx deleted file mode 100644 index e67cebe31..000000000 --- a/front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useState } from 'react'; - -import { EditableField } from '@/ui/editable-field/components/EditableField'; -import { FieldContext } from '@/ui/editable-field/states/FieldContext'; -import { IconCurrencyDollar } from '@/ui/icon'; -import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { - PipelineProgress, - useUpdateOnePipelineProgressMutation, -} from '~/generated/graphql'; - -type OwnProps = { - progress: Pick; -}; - -export function PipelineProgressAmountEditableField({ progress }: OwnProps) { - const [internalValue, setInternalValue] = useState( - progress.amount?.toString(), - ); - - const [updateOnePipelineProgress] = useUpdateOnePipelineProgressMutation(); - - useEffect(() => { - setInternalValue(progress.amount?.toString()); - }, [progress.amount]); - - async function handleChange(newValue: string) { - setInternalValue(newValue); - } - - async function handleSubmit() { - if (!internalValue) return; - - try { - const numberValue = parseInt(internalValue); - - if (isNaN(numberValue)) { - throw new Error('Not a number'); - } - - await updateOnePipelineProgress({ - variables: { - id: progress.id, - amount: numberValue, - }, - }); - - setInternalValue(numberValue.toString()); - } catch { - handleCancel(); - } - } - - async function handleCancel() { - setInternalValue(progress.amount?.toString()); - } - - return ( - - } - editModeContent={ - { - handleChange(newValue); - }} - /> - } - displayModeContent={internalValue} - /> - - ); -} diff --git a/front/src/modules/pipeline/queries/update.ts b/front/src/modules/pipeline/queries/update.ts index 295a78deb..00bf4653e 100644 --- a/front/src/modules/pipeline/queries/update.ts +++ b/front/src/modules/pipeline/queries/update.ts @@ -20,24 +20,14 @@ export const UPDATE_PIPELINE_STAGE = gql` export const UPDATE_PIPELINE_PROGRESS = gql` mutation UpdateOnePipelineProgress( - $id: String - $amount: Int - $closeDate: DateTime - $probability: Int - $pointOfContactId: String + $data: PipelineProgressUpdateInput! + $where: PipelineProgressWhereUniqueInput! ) { - updateOnePipelineProgress( - where: { id: $id } - data: { - amount: $amount - closeDate: $closeDate - probability: $probability - pointOfContact: { connect: { id: $pointOfContactId } } - } - ) { + updateOnePipelineProgress(where: $where, data: $data) { id amount closeDate + probability } } `; diff --git a/front/src/modules/ui/board/components/BoardColumn.tsx b/front/src/modules/ui/board/components/BoardColumn.tsx index ccd8c5f4d..9175bd5dd 100644 --- a/front/src/modules/ui/board/components/BoardColumn.tsx +++ b/front/src/modules/ui/board/components/BoardColumn.tsx @@ -1,10 +1,8 @@ import React from 'react'; import styled from '@emotion/styled'; -import { Key } from 'ts-key-enum'; import { Tag } from '@/ui/tag/components/Tag'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; @@ -77,7 +75,6 @@ const StyledNumChildren = styled.div` type OwnProps = { color?: string; title: string; - pipelineStageId?: string; onTitleEdit: (title: string) => void; onColumnColorEdit: (color: string) => void; totalAmount?: number; @@ -104,13 +101,6 @@ export function BoardColumn({ goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - useScopedHotkeys( - [Key.Escape, Key.Enter], - handleClose, - BoardColumnHotkeyScope.BoardColumn, - [], - ); - function handleTitleClick() { setIsBoardColumnMenuOpen(true); setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, { @@ -132,7 +122,7 @@ export function BoardColumn({ {isBoardColumnMenuOpen && ( setIsBoardColumnMenuOpen(false)} + onClose={handleClose} onTitleEdit={onTitleEdit} onColumnColorEdit={onColumnColorEdit} title={title} diff --git a/front/src/modules/ui/board/components/BoardColumnMenu.tsx b/front/src/modules/ui/board/components/BoardColumnMenu.tsx index 9ba79dbca..55ed3f8d9 100644 --- a/front/src/modules/ui/board/components/BoardColumnMenu.tsx +++ b/front/src/modules/ui/board/components/BoardColumnMenu.tsx @@ -1,13 +1,17 @@ import { useRef, useState } from 'react'; import styled from '@emotion/styled'; import { IconPencil } from '@tabler/icons-react'; +import { Key } from 'ts-key-enum'; import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { icon } from '@/ui/theme/constants/icon'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; + import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu'; const StyledMenuContainer = styled.div` @@ -39,6 +43,13 @@ export function BoardColumnMenu({ callback: onClose, }); + useScopedHotkeys( + [Key.Escape, Key.Enter], + onClose, + BoardColumnHotkeyScope.BoardColumn, + [], + ); + return ( diff --git a/front/src/modules/ui/board/components/EntityBoard.tsx b/front/src/modules/ui/board/components/EntityBoard.tsx index 9ba74c0c1..28ca6ab54 100644 --- a/front/src/modules/ui/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/board/components/EntityBoard.tsx @@ -48,6 +48,7 @@ export function EntityBoard({ onEditColumnColor: (columnId: string, color: string) => void; }) { const [boardColumns] = useRecoilState(boardColumnsState); + const theme = useTheme(); const [updatePipelineProgressStage] = useUpdateOnePipelineProgressStageMutation(); diff --git a/front/src/modules/ui/board/components/EntityBoardCard.tsx b/front/src/modules/ui/board/components/EntityBoardCard.tsx index b604b2ef4..3e472ee52 100644 --- a/front/src/modules/ui/board/components/EntityBoardCard.tsx +++ b/front/src/modules/ui/board/components/EntityBoardCard.tsx @@ -4,19 +4,15 @@ import { BoardOptions } from '../types/BoardOptions'; export function EntityBoardCard({ boardOptions, - pipelineProgressId, + cardId, index, }: { boardOptions: BoardOptions; - pipelineProgressId: string; + cardId: string; index: number; }) { return ( - + {(draggableProvided) => (
diff --git a/front/src/modules/ui/board/states/FieldDefinitionContext.ts b/front/src/modules/ui/board/states/FieldDefinitionContext.ts new file mode 100644 index 000000000..c5fb7dc36 --- /dev/null +++ b/front/src/modules/ui/board/states/FieldDefinitionContext.ts @@ -0,0 +1,9 @@ +import { createContext } from 'react'; + +import { + ViewFieldDefinition, + ViewFieldMetadata, +} from '../../editable-field/types/ViewField'; + +export const FieldDefinitionContext = + createContext | null>(null); diff --git a/front/src/modules/ui/board/states/fieldsDefinitionsState.ts b/front/src/modules/ui/board/states/fieldsDefinitionsState.ts new file mode 100644 index 000000000..6fd293646 --- /dev/null +++ b/front/src/modules/ui/board/states/fieldsDefinitionsState.ts @@ -0,0 +1,13 @@ +import { atom } from 'recoil'; + +import type { + ViewFieldDefinition, + ViewFieldMetadata, +} from '../../editable-field/types/ViewField'; + +export const fieldsDefinitionsState = atom< + ViewFieldDefinition[] +>({ + key: 'fieldsDefinitionState', + default: [], +}); diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx new file mode 100644 index 000000000..6093b85cf --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx @@ -0,0 +1,49 @@ +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { + ViewFieldDateMetadata, + ViewFieldDefinition, +} from '@/ui/editable-field/types/ViewField'; +import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; +import { parseDate } from '~/utils/date-utils'; + +import { FieldContext } from '../states/FieldContext'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; + +import { EditableField } from './EditableField'; +import { GenericEditableDateFieldEditMode } from './GenericEditableDateFieldEditMode'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +export function GenericEditableDateField({ viewField }: OwnProps) { + const currentEntityId = useContext(BoardCardIdContext); + + const fieldValue = useRecoilValue( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + const internalDateValue = fieldValue + ? parseDate(fieldValue).toJSDate() + : null; + + 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 new file mode 100644 index 000000000..5e98881f9 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx @@ -0,0 +1,44 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { + ViewFieldDateMetadata, + ViewFieldDefinition, +} from '@/ui/editable-field/types/ViewField'; + +import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { EditableFieldEditModeDate } from '../variants/components/EditableFieldEditModeDate'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +export function GenericEditableDateFieldEditMode({ viewField }: OwnProps) { + const currentEntityId = useContext(BoardCardIdContext); + + // TODO: we could use a hook that would return the field value with the right type + const [fieldValue, setFieldValue] = useRecoilState( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + const updateField = useUpdateGenericEntityField(); + + function handleSubmit(newDateISO: string) { + if (newDateISO === fieldValue || !newDateISO) return; + + setFieldValue(newDateISO); + + if (currentEntityId && updateField && newDateISO) { + updateField(currentEntityId, viewField, newDateISO); + } + } + + return ( + + ); +} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableField.tsx new file mode 100644 index 000000000..70d639a80 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableField.tsx @@ -0,0 +1,35 @@ +import { + ViewFieldDefinition, + ViewFieldMetadata, +} from '@/ui/editable-field/types/ViewField'; + +import { isViewFieldDate } from '../types/guards/isViewFieldDate'; +import { isViewFieldNumber } from '../types/guards/isViewFieldNumber'; +import { isViewFieldProbability } from '../types/guards/isViewFieldProbability'; +import { isViewFieldRelation } from '../types/guards/isViewFieldRelation'; + +import { GenericEditableDateField } from './GenericEditableDateField'; +import { GenericEditableNumberField } from './GenericEditableNumberField'; +import { GenericEditableRelationField } from './GenericEditableRelationField'; +import { ProbabilityEditableField } from './ProbabilityEditableField'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +export function GenericEditableField({ viewField: fieldDefinition }: OwnProps) { + if (isViewFieldDate(fieldDefinition)) { + return ; + } else if (isViewFieldNumber(fieldDefinition)) { + return ; + } else if (isViewFieldRelation(fieldDefinition)) { + return ; + } else if (isViewFieldProbability(fieldDefinition)) { + return ; + } else { + console.warn( + `Unknown field metadata type: ${fieldDefinition.metadata.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 new file mode 100644 index 000000000..c77671379 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx @@ -0,0 +1,43 @@ +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { + ViewFieldDefinition, + ViewFieldNumberMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; + +import { FieldContext } from '../states/FieldContext'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; + +import { EditableField } from './EditableField'; +import { GenericEditableNumberFieldEditMode } from './GenericEditableNumberFieldEditMode'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +export function GenericEditableNumberField({ viewField }: OwnProps) { + const currentEntityId = useContext(BoardCardIdContext); + + const fieldValue = useRecoilValue( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + return ( + + + } + displayModeContent={fieldValue} + isDisplayModeContentEmpty={!fieldValue} + /> + + ); +} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx new file mode 100644 index 000000000..4dea3dfa0 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx @@ -0,0 +1,78 @@ +import { useContext, useRef, useState } from 'react'; +import { useRecoilState } from 'recoil'; + +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { + ViewFieldDefinition, + ViewFieldNumberMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit'; +import { + canBeCastAsIntegerOrNull, + castAsIntegerOrNull, +} from '~/utils/cast-as-integer-or-null'; + +import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers'; +import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) { + const currentEntityId = useContext(BoardCardIdContext); + + // TODO: we could use a hook that would return the field value with the right type + const [fieldValue, setFieldValue] = useRecoilState( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + const [internalValue, setInternalValue] = useState( + fieldValue ? fieldValue.toString() : '', + ); + + const updateField = useUpdateGenericEntityField(); + + function handleSubmit() { + if (!canBeCastAsIntegerOrNull(internalValue)) { + return; + } + if (internalValue === fieldValue) return; + + setFieldValue(castAsIntegerOrNull(internalValue)); + + if (currentEntityId && updateField) { + updateField( + currentEntityId, + viewField, + castAsIntegerOrNull(internalValue), + ); + } + } + + function onCancel() { + setFieldValue(fieldValue); + } + + function handleChange(newValue: string) { + setInternalValue(newValue); + } + const wrapperRef = useRef(null); + + useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel); + + return ( +
+ { + handleChange(newValue); + }} + /> +
+ ); +} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx new file mode 100644 index 000000000..1c79f68fd --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx @@ -0,0 +1,80 @@ +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { PersonChip } from '@/people/components/PersonChip'; +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { + ViewFieldDefinition, + ViewFieldRelationMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; +import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; + +import { FieldContext } from '../states/FieldContext'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; + +import { EditableField } from './EditableField'; +import { GenericEditableRelationFieldEditMode } from './GenericEditableRelationFieldEditMode'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +function RelationChip({ + fieldDefinition, + fieldValue, +}: { + fieldDefinition: ViewFieldDefinition; + fieldValue: any | null; +}) { + switch (fieldDefinition.metadata.relationType) { + case Entity.Person: { + return ( + + ); + } + default: + console.warn( + `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`, + ); + return <> ; + } +} + +export function GenericEditableRelationField({ viewField }: OwnProps) { + const currentEntityId = useContext(BoardCardIdContext); + + const fieldValue = useRecoilValue( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + return ( + + + + } + displayModeContent={ + + } + isDisplayModeContentEmpty={!fieldValue} + isDisplayModeFixHeight + /> + + + ); +} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx new file mode 100644 index 000000000..f37fe0dd0 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx @@ -0,0 +1,102 @@ +import { useContext } from 'react'; +import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; + +import { PeoplePicker } from '@/people/components/PeoplePicker'; +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { + ViewFieldDefinition, + ViewFieldRelationMetadata, + ViewFieldRelationValue, +} from '@/ui/editable-field/types/ViewField'; +import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; + +import { useEditableField } from '../hooks/useEditableField'; +import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; + +const RelationPickerContainer = styled.div` + left: 0px; + position: absolute; + top: -8px; +`; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +function RelationPicker({ + fieldDefinition, + fieldValue, + handleEntitySubmit, + handleCancel, +}: { + fieldDefinition: ViewFieldDefinition; + fieldValue: ViewFieldRelationValue; + handleEntitySubmit: (newRelationId: EntityForSelect | null) => void; + handleCancel: () => void; +}) { + switch (fieldDefinition.metadata.relationType) { + case Entity.Person: { + return ( + + ); + } + default: + console.warn( + `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`, + ); + return <> ; + } +} + +export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) { + const currentEntityId = useContext(BoardCardIdContext); + + // TODO: we could use a hook that would return the field value with the right type + const [fieldValue, setFieldValue] = useRecoilState( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + const updateField = useUpdateGenericEntityField(); + const { closeEditableField } = useEditableField(); + + function handleSubmit(newRelation: EntityForSelect | null) { + if (newRelation?.id === fieldValue?.id) return; + + setFieldValue({ + id: newRelation?.id ?? null, + displayName: newRelation?.name ?? null, + avatarUrl: newRelation?.avatarUrl ?? null, + }); + + if (currentEntityId && updateField) { + updateField(currentEntityId, viewField, newRelation); + } + + closeEditableField(); + } + + function handleCancel() { + closeEditableField(); + } + + return ( + + + + ); +} diff --git a/front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx b/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx similarity index 51% rename from front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx rename to front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx index 8c7f0fd48..c0336b0b7 100644 --- a/front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx +++ b/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx @@ -1,25 +1,27 @@ import { EditableField } from '@/ui/editable-field/components/EditableField'; import { FieldContext } from '@/ui/editable-field/states/FieldContext'; +import { + ViewFieldDefinition, + ViewFieldProbabilityMetadata, +} from '@/ui/editable-field/types/ViewField'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { ProbabilityFieldEditMode } from './ProbabilityFieldEditMode'; +import { ProbabilityEditableFieldEditMode } from './ProbabilityEditableFieldEditMode'; type OwnProps = { - icon?: React.ReactNode; - value: number | null | undefined; - onSubmit?: (newValue: number) => void; + viewField: ViewFieldDefinition; }; -export function ProbabilityEditableField({ icon, value, onSubmit }: OwnProps) { +export function ProbabilityEditableField({ viewField }: OwnProps) { return ( + } displayModeContentOnly disableHoverEffect - displayModeContent={ - - } /> ); diff --git a/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx new file mode 100644 index 000000000..592436165 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx @@ -0,0 +1,136 @@ +import { useContext, useState } from 'react'; +import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; + +import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; +import { useEditableField } from '@/ui/editable-field/hooks/useEditableField'; + +import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { + ViewFieldDefinition, + ViewFieldProbabilityMetadata, +} from '../types/ViewField'; + +const StyledContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; + justify-content: flex-start; + width: 100%; +`; + +const StyledProgressBarItemContainer = styled.div` + align-items: center; + display: flex; + height: ${({ theme }) => theme.spacing(4)}; + padding-right: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledProgressBarItem = styled.div<{ + isFirst: boolean; + isLast: boolean; + isActive: boolean; +}>` + background-color: ${({ theme, isActive }) => + isActive + ? theme.font.color.secondary + : theme.background.transparent.medium}; + border-bottom-left-radius: ${({ theme, isFirst }) => + isFirst ? theme.border.radius.sm : theme.border.radius.xs}; + border-bottom-right-radius: ${({ theme, isLast }) => + isLast ? theme.border.radius.sm : theme.border.radius.xs}; + border-top-left-radius: ${({ theme, isFirst }) => + isFirst ? theme.border.radius.sm : theme.border.radius.xs}; + border-top-right-radius: ${({ theme, isLast }) => + isLast ? theme.border.radius.sm : theme.border.radius.xs}; + height: ${({ theme }) => theme.spacing(2)}; + width: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledProgressBarContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; + justify-content: flex-start; + width: 100%; +`; + +const StyledLabel = styled.div` + width: ${({ theme }) => theme.spacing(12)}; +`; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +const PROBABILITY_VALUES = [ + { label: '0%', value: 0 }, + { label: '25%', value: 25 }, + { label: '50%', value: 50 }, + { label: '75%', value: 75 }, + { label: '100%', value: 100 }, +]; + +export function ProbabilityEditableFieldEditMode({ viewField }: OwnProps) { + const [nextProbabilityIndex, setNextProbabilityIndex] = useState< + number | null + >(null); + const currentEntityId = useContext(BoardCardIdContext); + const [fieldValue, setFieldValue] = useRecoilState( + genericEntityFieldFamilySelector({ + entityId: currentEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + const probabilityIndex = Math.ceil(fieldValue / 25); + const { closeEditableField } = useEditableField(); + + const updateField = useUpdateGenericEntityField(); + + function handleChange(newValue: number) { + setFieldValue(newValue); + if (currentEntityId && updateField && newValue) { + updateField(currentEntityId, viewField, newValue); + } + closeEditableField(); + } + + console.log(probabilityIndex); + + return ( + + + { + PROBABILITY_VALUES[ + nextProbabilityIndex || nextProbabilityIndex === 0 + ? nextProbabilityIndex + : probabilityIndex + ].label + } + + + {PROBABILITY_VALUES.map((probability, i) => ( + handleChange(probability.value)} + onMouseEnter={() => setNextProbabilityIndex(i)} + onMouseLeave={() => setNextProbabilityIndex(null)} + > + + + ))} + + + ); +} diff --git a/front/src/modules/ui/editable-field/hooks/useCurrentEntityId.ts b/front/src/modules/ui/editable-field/hooks/useCurrentEntityId.ts new file mode 100644 index 000000000..32793d3a9 --- /dev/null +++ b/front/src/modules/ui/editable-field/hooks/useCurrentEntityId.ts @@ -0,0 +1,7 @@ +import { useContext } from 'react'; + +import { EntityIdContext } from '../states/EntityIdContext'; + +export function useCurrentEntityId() { + return useContext(EntityIdContext); +} diff --git a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts new file mode 100644 index 000000000..87165dd9f --- /dev/null +++ b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts @@ -0,0 +1,246 @@ +import { useContext } from 'react'; + +import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip'; +import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; + +import { isViewFieldChipValue } from '../types/guards/isViewFieldChipValue'; +import { isViewFieldDate } from '../types/guards/isViewFieldDate'; +import { isViewFieldDateValue } from '../types/guards/isViewFieldDateValue'; +import { isViewFieldDoubleText } from '../types/guards/isViewFieldDoubleText'; +import { isViewFieldDoubleTextChip } from '../types/guards/isViewFieldDoubleTextChip'; +import { isViewFieldDoubleTextChipValue } from '../types/guards/isViewFieldDoubleTextChipValue'; +import { isViewFieldDoubleTextValue } from '../types/guards/isViewFieldDoubleTextValue'; +import { isViewFieldNumber } from '../types/guards/isViewFieldNumber'; +import { isViewFieldNumberValue } from '../types/guards/isViewFieldNumberValue'; +import { isViewFieldPhone } from '../types/guards/isViewFieldPhone'; +import { isViewFieldPhoneValue } from '../types/guards/isViewFieldPhoneValue'; +import { isViewFieldProbability } from '../types/guards/isViewFieldProbability'; +import { isViewFieldProbabilityValue } from '../types/guards/isViewFieldProbabilityValue'; +import { isViewFieldRelation } from '../types/guards/isViewFieldRelation'; +import { isViewFieldRelationValue } from '../types/guards/isViewFieldRelationValue'; +import { isViewFieldText } from '../types/guards/isViewFieldText'; +import { isViewFieldTextValue } from '../types/guards/isViewFieldTextValue'; +import { isViewFieldURL } from '../types/guards/isViewFieldURL'; +import { isViewFieldURLValue } from '../types/guards/isViewFieldURLValue'; +import { + ViewFieldChipMetadata, + ViewFieldChipValue, + ViewFieldDateMetadata, + ViewFieldDateValue, + ViewFieldDefinition, + ViewFieldDoubleTextChipMetadata, + ViewFieldDoubleTextChipValue, + ViewFieldDoubleTextMetadata, + ViewFieldDoubleTextValue, + ViewFieldMetadata, + ViewFieldNumberMetadata, + ViewFieldNumberValue, + ViewFieldPhoneMetadata, + ViewFieldPhoneValue, + ViewFieldProbabilityMetadata, + ViewFieldProbabilityValue, + ViewFieldRelationMetadata, + ViewFieldRelationValue, + ViewFieldTextMetadata, + ViewFieldTextValue, + ViewFieldURLMetadata, + ViewFieldURLValue, +} from '../types/ViewField'; + +export function useUpdateGenericEntityField() { + const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext); + + const [updateEntity] = useUpdateEntityMutation(); + + return function updatePeopleField< + MetadataType extends ViewFieldMetadata, + ValueType extends MetadataType extends ViewFieldDoubleTextMetadata + ? ViewFieldDoubleTextValue + : MetadataType extends ViewFieldTextMetadata + ? ViewFieldTextValue + : MetadataType extends ViewFieldPhoneMetadata + ? ViewFieldPhoneValue + : MetadataType extends ViewFieldURLMetadata + ? ViewFieldURLValue + : MetadataType extends ViewFieldNumberMetadata + ? ViewFieldNumberValue + : MetadataType extends ViewFieldDateMetadata + ? ViewFieldDateValue + : MetadataType extends ViewFieldChipMetadata + ? ViewFieldChipValue + : MetadataType extends ViewFieldDoubleTextChipMetadata + ? ViewFieldDoubleTextChipValue + : MetadataType extends ViewFieldRelationMetadata + ? ViewFieldRelationValue + : MetadataType extends ViewFieldProbabilityMetadata + ? ViewFieldProbabilityValue + : unknown, + >( + currentEntityId: string, + viewField: ViewFieldDefinition, + newFieldValue: ValueType, + ) { + const newFieldValueUnknown = newFieldValue as unknown; + // TODO: improve type guards organization, maybe with a common typeguard for all view fields + // taking an object of options as parameter ? + // + // The goal would be to check that the view field value not only is valid, + // but also that it is validated against the corresponding view field type + + // Relation + if ( + isViewFieldRelation(viewField) && + isViewFieldRelationValue(newFieldValueUnknown) + ) { + const newSelectedEntity = newFieldValueUnknown; + + const fieldName = viewField.metadata.fieldName; + + if (!newSelectedEntity) { + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { + [fieldName]: { + disconnect: true, + }, + }, + }, + }); + } else { + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { + [fieldName]: { + connect: { id: newSelectedEntity.id }, + }, + }, + }, + }); + } + // Chip + } else if ( + isViewFieldChip(viewField) && + isViewFieldChipValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.contentFieldName]: newContent }, + }, + }); + // Text + } else if ( + isViewFieldText(viewField) && + isViewFieldTextValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + // Double text + } else if ( + isViewFieldDoubleText(viewField) && + isViewFieldDoubleTextValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { + [viewField.metadata.firstValueFieldName]: newContent.firstValue, + [viewField.metadata.secondValueFieldName]: newContent.secondValue, + }, + }, + }); + // Double Text Chip + } else if ( + isViewFieldDoubleTextChip(viewField) && + isViewFieldDoubleTextChipValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { + [viewField.metadata.firstValueFieldName]: newContent.firstValue, + [viewField.metadata.secondValueFieldName]: newContent.secondValue, + }, + }, + }); + // Phone + } else if ( + isViewFieldPhone(viewField) && + isViewFieldPhoneValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + // URL + } else if ( + isViewFieldURL(viewField) && + isViewFieldURLValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + // Number + } else if ( + isViewFieldNumber(viewField) && + isViewFieldNumberValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + // Date + } else if ( + isViewFieldDate(viewField) && + isViewFieldDateValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + } else if ( + isViewFieldProbability(viewField) && + isViewFieldProbabilityValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + } + }; +} diff --git a/front/src/modules/ui/editable-field/states/EditableFieldContext.ts b/front/src/modules/ui/editable-field/states/EditableFieldContext.ts new file mode 100644 index 000000000..a8b1df315 --- /dev/null +++ b/front/src/modules/ui/editable-field/states/EditableFieldContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const EditableFieldContext = createContext(null); diff --git a/front/src/modules/ui/editable-field/states/EntityIdContext.ts b/front/src/modules/ui/editable-field/states/EntityIdContext.ts new file mode 100644 index 000000000..75bd8e6d0 --- /dev/null +++ b/front/src/modules/ui/editable-field/states/EntityIdContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const EntityIdContext = createContext(null); diff --git a/front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts b/front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts new file mode 100644 index 000000000..410784e46 --- /dev/null +++ b/front/src/modules/ui/editable-field/states/genericEntitiesFamilyState.ts @@ -0,0 +1,9 @@ +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/genericEntityFieldFamilySelector.ts b/front/src/modules/ui/editable-field/states/genericEntityFieldFamilySelector.ts new file mode 100644 index 000000000..d624a1cdf --- /dev/null +++ b/front/src/modules/ui/editable-field/states/genericEntityFieldFamilySelector.ts @@ -0,0 +1,18 @@ +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/table/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts similarity index 90% rename from front/src/modules/ui/table/types/ViewField.ts rename to front/src/modules/ui/editable-field/types/ViewField.ts index eaa6d06fd..695ecfdbb 100644 --- a/front/src/modules/ui/table/types/ViewField.ts +++ b/front/src/modules/ui/editable-field/types/ViewField.ts @@ -10,7 +10,8 @@ export type ViewFieldType = | 'number' | 'date' | 'phone' - | 'url'; + | 'url' + | 'probability'; export type ViewFieldTextMetadata = { type: 'text'; @@ -72,6 +73,11 @@ export type ViewFieldDoubleTextChipMetadata = { entityType: Entity; }; +export type ViewFieldProbabilityMetadata = { + type: 'probability'; + fieldName: string; +}; + export type ViewFieldMetadata = { type: ViewFieldType } & ( | ViewFieldTextMetadata | ViewFieldRelationMetadata @@ -82,6 +88,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & ( | ViewFieldURLMetadata | ViewFieldNumberMetadata | ViewFieldDateMetadata + | ViewFieldProbabilityMetadata ); export type ViewFieldDefinition = { @@ -101,7 +108,8 @@ export type ViewFieldChipValue = string; export type ViewFieldDateValue = string; export type ViewFieldPhoneValue = string; export type ViewFieldURLValue = string; -export type ViewFieldNumberValue = number; +export type ViewFieldNumberValue = number | null; +export type ViewFieldProbabilityValue = number; export type ViewFieldDoubleTextValue = { firstValue: string; diff --git a/front/src/modules/ui/table/types/guards/isViewFieldChip.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldChip.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldChip.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldChip.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldChipValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldChipValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldChipValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldChipValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldDate.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDate.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldDate.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldDate.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldDateValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDateValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldDateValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldDateValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldDoubleText.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleText.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldDoubleText.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleText.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldDoubleTextChip.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChip.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldDoubleTextChip.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChip.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldDoubleTextChipValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldDoubleTextChipValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldDoubleTextValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldDoubleTextValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldDoubleTextValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldNumber.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldNumber.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldNumber.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldNumber.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldNumberValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldNumberValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldNumberValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldNumberValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldPhone.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldPhone.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldPhone.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldPhone.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldPhoneValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldPhoneValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldPhoneValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldPhoneValue.ts diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts new file mode 100644 index 000000000..8ac4e0ee7 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldProbability.ts @@ -0,0 +1,11 @@ +import { + ViewFieldDefinition, + ViewFieldMetadata, + ViewFieldProbabilityMetadata, +} from '../ViewField'; + +export function isViewFieldProbability( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return 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 new file mode 100644 index 000000000..87bb65804 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldProbabilityValue.ts @@ -0,0 +1,12 @@ +import { ViewFieldProbabilityValue } from '../ViewField'; + +// TODO: add yup +export function isViewFieldProbabilityValue( + fieldValue: unknown, +): fieldValue is ViewFieldProbabilityValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'number' + ); +} diff --git a/front/src/modules/ui/table/types/guards/isViewFieldRelation.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldRelation.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldRelation.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldRelation.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldRelationValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldRelationValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldRelationValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldRelationValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldText.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldText.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldText.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldText.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldTextValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldTextValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldTextValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldTextValue.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldURL.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldURL.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldURL.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldURL.ts diff --git a/front/src/modules/ui/table/types/guards/isViewFieldURLValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldURLValue.ts similarity index 100% rename from front/src/modules/ui/table/types/guards/isViewFieldURLValue.ts rename to front/src/modules/ui/editable-field/types/guards/isViewFieldURLValue.ts 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 9fae1e920..da5c159fb 100644 --- a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx +++ b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx @@ -1,5 +1,3 @@ -import { useEffect, useState } from 'react'; - import { EditableField } from '@/ui/editable-field/components/EditableField'; import { FieldContext } from '@/ui/editable-field/states/FieldContext'; import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; @@ -16,49 +14,29 @@ type OwnProps = { }; export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) { - const [internalValue, setInternalValue] = useState(value); - - useEffect(() => { - setInternalValue(value); - }, [value]); - async function handleChange(newValue: string) { - setInternalValue(newValue); - onSubmit?.(newValue); } - async function handleSubmit() { - if (!internalValue) return; - - onSubmit?.(internalValue); - } - - async function handleCancel() { - setInternalValue(value); - } - - const internalDateValue = internalValue - ? parseDate(internalValue).toJSDate() - : null; + const internalDateValue = value ? parseDate(value).toJSDate() : null; return ( { handleChange(newValue); }} /> } displayModeContent={} - isDisplayModeContentEmpty={!internalValue} + isDisplayModeContentEmpty={!value} /> ); diff --git a/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx b/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx index 0333c1e79..f01df4b4b 100644 --- a/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx +++ b/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx @@ -1,3 +1,5 @@ +import { useEffect, useState } from 'react'; + import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { parseDate } from '~/utils/date-utils'; @@ -11,6 +13,12 @@ type OwnProps = { }; export function EditableFieldEditModeDate({ value, onChange }: OwnProps) { + const [internalValue, setInternalValue] = useState(value); + + useEffect(() => { + setInternalValue(value); + }, [value]); + const { closeEditableField } = useEditableField(); function handleChange(newValue: string) { @@ -20,7 +28,7 @@ export function EditableFieldEditModeDate({ value, onChange }: OwnProps) { return ( { handleChange(newDate.toISOString()); }} diff --git a/front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx deleted file mode 100644 index 1e51228c8..000000000 --- a/front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useEffect, useState } from 'react'; - -import { EditableField } from '@/ui/editable-field/components/EditableField'; -import { FieldContext } from '@/ui/editable-field/states/FieldContext'; -import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit'; -import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { - canBeCastAsIntegerOrNull, - castAsIntegerOrNull, -} from '~/utils/cast-as-integer-or-null'; - -type OwnProps = { - icon?: React.ReactNode; - placeholder?: string; - value: number | null | undefined; - onSubmit?: (newValue: number | null) => void; -}; - -export function NumberEditableField({ - icon, - placeholder, - value, - onSubmit, -}: OwnProps) { - const [internalValue, setInternalValue] = useState(value?.toString()); - - useEffect(() => { - setInternalValue(value?.toString()); - }, [value]); - - async function handleChange(newValue: string) { - setInternalValue(newValue); - } - - async function handleSubmit() { - if (!canBeCastAsIntegerOrNull(internalValue)) { - handleCancel(); - return; - } - - const valueCastedAsNumberOrNull = castAsIntegerOrNull(internalValue); - - onSubmit?.(valueCastedAsNumberOrNull); - - setInternalValue(valueCastedAsNumberOrNull?.toString()); - } - - async function handleCancel() { - setInternalValue(value?.toString()); - } - - return ( - - { - handleChange(newValue); - }} - /> - } - displayModeContent={internalValue} - isDisplayModeContentEmpty={!(internalValue !== '' && internalValue)} - /> - - ); -} diff --git a/front/src/modules/ui/editable-field/variants/components/__stories__/NumberEditableField.stories.tsx b/front/src/modules/ui/editable-field/variants/components/__stories__/NumberEditableField.stories.tsx deleted file mode 100644 index ca96e2b07..000000000 --- a/front/src/modules/ui/editable-field/variants/components/__stories__/NumberEditableField.stories.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { IconCurrencyDollar } from '@tabler/icons-react'; - -import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; - -import { NumberEditableField } from '../NumberEditableField'; - -const meta: Meta = { - title: 'UI/EditableField/NumberEditableField', - component: NumberEditableField, - decorators: [ComponentDecorator], - argTypes: { - icon: { - type: 'boolean', - mapping: { - true: , - false: undefined, - }, - }, - value: { control: { type: 'number' } }, - }, - args: { - value: 10, - icon: true, - placeholder: 'Number', - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/front/src/modules/ui/filter-n-sort/states/AvailableFiltersContext.ts b/front/src/modules/ui/filter-n-sort/states/AvailableFiltersContext.ts new file mode 100644 index 000000000..cabc5a13a --- /dev/null +++ b/front/src/modules/ui/filter-n-sort/states/AvailableFiltersContext.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +import { FilterDefinition } from '../types/FilterDefinition'; + +export const AvailableFiltersContext = createContext([]); diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx index 522b2caf5..3afc80d99 100644 --- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx +++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx @@ -12,7 +12,7 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis import type { ViewFieldDefinition, ViewFieldMetadata, -} from '../types/ViewField'; +} from '../../editable-field/types/ViewField'; const StyledColumnMenu = styled(DropdownMenu)` font-weight: ${({ theme }) => theme.font.weight.regular}; diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx index baf3a66bf..c4bfd99c6 100644 --- a/front/src/modules/ui/table/components/EntityTableHeader.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx @@ -13,6 +13,10 @@ import { useUpdateViewFieldMutation, } from '~/generated/graphql'; +import type { + ViewFieldDefinition, + ViewFieldMetadata, +} from '../../editable-field/types/ViewField'; import { toViewFieldInput } from '../hooks/useLoadView'; import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState'; import { @@ -21,10 +25,6 @@ import { viewFieldsState, visibleViewFieldsState, } from '../states/viewFieldsState'; -import type { - ViewFieldDefinition, - ViewFieldMetadata, -} from '../types/ViewField'; import { ColumnHead } from './ColumnHead'; import { EntityTableColumnMenu } from './EntityTableColumnMenu'; diff --git a/front/src/modules/ui/table/components/GenericEntityTableData.tsx b/front/src/modules/ui/table/components/GenericEntityTableData.tsx index c6fbebb2d..bed37bc81 100644 --- a/front/src/modules/ui/table/components/GenericEntityTableData.tsx +++ b/front/src/modules/ui/table/components/GenericEntityTableData.tsx @@ -1,10 +1,10 @@ import { defaultOrderBy } from '@/people/queries'; -import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; -import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { ViewFieldDefinition, ViewFieldMetadata, -} from '@/ui/table/types/ViewField'; +} from '@/ui/editable-field/types/ViewField'; +import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; +import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { useLoadView } from '../hooks/useLoadView'; diff --git a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx index 607e5f165..8e37f77e4 100644 --- a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx +++ b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx @@ -1,17 +1,17 @@ +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 { 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 { ViewFieldDefinition, ViewFieldMetadata, -} from '@/ui/table/types/ViewField'; +} from '@/ui/editable-field/types/ViewField'; -import { isViewFieldChip } from '../../types/guards/isViewFieldChip'; -import { isViewFieldDate } from '../../types/guards/isViewFieldDate'; -import { isViewFieldDoubleText } from '../../types/guards/isViewFieldDoubleText'; -import { isViewFieldDoubleTextChip } from '../../types/guards/isViewFieldDoubleTextChip'; -import { isViewFieldNumber } from '../../types/guards/isViewFieldNumber'; -import { isViewFieldPhone } from '../../types/guards/isViewFieldPhone'; -import { isViewFieldRelation } from '../../types/guards/isViewFieldRelation'; -import { isViewFieldText } from '../../types/guards/isViewFieldText'; -import { isViewFieldURL } from '../../types/guards/isViewFieldURL'; +import { isViewFieldChip } from '../../../editable-field/types/guards/isViewFieldChip'; import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell'; import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell'; import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell'; 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 index 8915a2da1..acb50f495 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCell.tsx @@ -1,8 +1,8 @@ -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { ViewFieldChipMetadata, ViewFieldDefinition, -} from '@/ui/table/types/ViewField'; +} from '@/ui/editable-field/types/ViewField'; +import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode'; import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode'; 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 index 7d391ae14..02046311f 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellDisplayMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellDisplayMode.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; import { CompanyChip } from '@/companies/components/CompanyChip'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldChipMetadata, ViewFieldDefinition, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { getLogoUrlFromDomainName } from '~/utils'; type OwnProps = { 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 index 77bf25662..fac65c418 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableChipCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldChipMetadata, ViewFieldDefinition, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { TextCellEdit } from './TextCellEdit'; 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 index c466f5e77..7607c9165 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; +import { + ViewFieldDateMetadata, + ViewFieldDefinition, +} from '@/ui/editable-field/types/ViewField'; import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; -import { - ViewFieldDateMetadata, - ViewFieldDefinition, -} from '@/ui/table/types/ViewField'; import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode'; 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 index 7480f9b23..fe2244f3b 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx @@ -1,13 +1,13 @@ import { DateTime } from 'luxon'; import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDateMetadata, ViewFieldDefinition, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { DateCellEdit } from './DateCellEdit'; 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 index 979e11b78..a3a4b8cdc 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCell.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; +import { + ViewFieldDefinition, + ViewFieldDoubleTextMetadata, +} from '@/ui/editable-field/types/ViewField'; import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; -import { - ViewFieldDefinition, - ViewFieldDoubleTextMetadata, -} from '@/ui/table/types/ViewField'; import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode'; 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 index 89173f095..d53349c1a 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldDoubleTextMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { DoubleTextCellEdit } from './DoubleTextCellEdit'; 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 index b17b6723c..481ef48a1 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCell.tsx @@ -1,9 +1,9 @@ -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; import { ViewFieldDefinition, ViewFieldDoubleTextChipMetadata, -} from '@/ui/table/types/ViewField'; +} from '@/ui/editable-field/types/ViewField'; +import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; +import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; import { GenericEditableDoubleTextChipCellDisplayMode } from './GenericEditableDoubleTextChipCellDisplayMode'; import { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode'; 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 index 2261dac20..d4c61d051 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellDisplayMode.tsx @@ -2,13 +2,13 @@ import { useRecoilState } from 'recoil'; import { CompanyChip } from '@/companies/components/CompanyChip'; import { PersonChip } from '@/people/components/PersonChip'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldDoubleTextChipMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; type OwnProps = { viewField: ViewFieldDefinition; 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 index c7d0637ff..9a04faea8 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextChipCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldDoubleTextChipMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { DoubleTextCellEdit } from './DoubleTextCellEdit'; 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 index 7920f1721..693e8b38a 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCell.tsx @@ -1,12 +1,12 @@ import { useRecoilValue } from 'recoil'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldNumberMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { GenericEditableNumberCellEditMode } from './GenericEditableNumberCellEditMode'; 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 index 3b9e22832..39b77ba2f 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableNumberCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldNumberMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { TextCellEdit } from './TextCellEdit'; 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 index ffcc76a60..907a43110 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; +import { + ViewFieldDefinition, + ViewFieldPhoneMetadata, +} from '@/ui/editable-field/types/ViewField'; import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; -import { - ViewFieldDefinition, - ViewFieldPhoneMetadata, -} from '@/ui/table/types/ViewField'; import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode'; 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 index 9cbe3ab5b..fb9e94be2 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldPhoneMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { TextCellEdit } from './TextCellEdit'; 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 index 786baa0d1..97d0906d6 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCell.tsx @@ -1,9 +1,9 @@ -import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { ViewFieldDefinition, ViewFieldRelationMetadata, -} from '@/ui/table/types/ViewField'; +} 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 { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode'; import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode'; 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 index 22c7ea4e3..ff9b0168d 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellDisplayMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellDisplayMode.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; import { CompanyChip } from '@/companies/components/CompanyChip'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldRelationMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { UserChip } from '@/users/components/UserChip'; import { getLogoUrlFromDomainName } from '~/utils'; 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 index ab5ff5ee6..0fca2ebc2 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableRelationCellEditMode.tsx @@ -1,16 +1,16 @@ import { useRecoilState } from 'recoil'; import { CompanyPickerCell } from '@/companies/components/CompanyPickerCell'; +import { + ViewFieldDefinition, + 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/tableEntityFieldFamilySelector'; -import { - ViewFieldDefinition, - ViewFieldRelationMetadata, -} from '@/ui/table/types/ViewField'; import { UserPicker } from '@/users/components/UserPicker'; type OwnProps = { 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 index 9c9bc8df2..700c800d3 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCell.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; +import { + ViewFieldDefinition, + ViewFieldTextMetadata, +} from '@/ui/editable-field/types/ViewField'; import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; -import { - ViewFieldDefinition, - ViewFieldTextMetadata, -} from '@/ui/table/types/ViewField'; import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode'; 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 index 42d4984e1..7a54f275d 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableTextCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldTextMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { TextCellEdit } from './TextCellEdit'; 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 index 7a080f781..6cfda6691 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil'; +import { + ViewFieldDefinition, + ViewFieldURLMetadata, +} from '@/ui/editable-field/types/ViewField'; import { InplaceInputURLDisplayMode } from '@/ui/input/url/components/URLTextInputDisplay'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; -import { - ViewFieldDefinition, - ViewFieldURLMetadata, -} from '@/ui/table/types/ViewField'; import { sanitizeURL } from '~/utils'; import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode'; 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 index 9c0ffbeaf..23da20a19 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCellEditMode.tsx @@ -1,12 +1,12 @@ import { useRecoilState } from 'recoil'; -import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; -import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; -import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { ViewFieldDefinition, ViewFieldURLMetadata, -} from '@/ui/table/types/ViewField'; +} 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/tableEntityFieldFamilySelector'; import { TextCellEdit } from './TextCellEdit'; diff --git a/front/src/modules/ui/table/hooks/useLoadView.ts b/front/src/modules/ui/table/hooks/useLoadView.ts index 2030f9cc9..801531aef 100644 --- a/front/src/modules/ui/table/hooks/useLoadView.ts +++ b/front/src/modules/ui/table/hooks/useLoadView.ts @@ -8,13 +8,13 @@ import { useGetViewFieldsQuery, } from '~/generated/graphql'; -import { entityTableDimensionsState } from '../states/entityTableDimensionsState'; -import { viewFieldsState } from '../states/viewFieldsState'; import type { ViewFieldDefinition, ViewFieldMetadata, ViewFieldTextMetadata, -} from '../types/ViewField'; +} from '../../editable-field/types/ViewField'; +import { entityTableDimensionsState } from '../states/entityTableDimensionsState'; +import { viewFieldsState } from '../states/viewFieldsState'; const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = { type: 'text', diff --git a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts index e070763ac..5dcd1fbdc 100644 --- a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts +++ b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts @@ -1,25 +1,25 @@ import { useContext } from 'react'; +import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip'; +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 { 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 { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; -import { isViewFieldChip } from '@/ui/table/types/guards/isViewFieldChip'; -import { isViewFieldRelation } from '@/ui/table/types/guards/isViewFieldRelation'; -import { isViewFieldText } from '@/ui/table/types/guards/isViewFieldText'; -import { isViewFieldChipValue } from '../types/guards/isViewFieldChipValue'; -import { isViewFieldDate } from '../types/guards/isViewFieldDate'; -import { isViewFieldDateValue } from '../types/guards/isViewFieldDateValue'; -import { isViewFieldDoubleText } from '../types/guards/isViewFieldDoubleText'; -import { isViewFieldDoubleTextChip } from '../types/guards/isViewFieldDoubleTextChip'; -import { isViewFieldDoubleTextChipValue } from '../types/guards/isViewFieldDoubleTextChipValue'; -import { isViewFieldDoubleTextValue } from '../types/guards/isViewFieldDoubleTextValue'; -import { isViewFieldNumber } from '../types/guards/isViewFieldNumber'; -import { isViewFieldNumberValue } from '../types/guards/isViewFieldNumberValue'; -import { isViewFieldPhone } from '../types/guards/isViewFieldPhone'; -import { isViewFieldPhoneValue } from '../types/guards/isViewFieldPhoneValue'; -import { isViewFieldRelationValue } from '../types/guards/isViewFieldRelationValue'; -import { isViewFieldTextValue } from '../types/guards/isViewFieldTextValue'; -import { isViewFieldURL } from '../types/guards/isViewFieldURL'; -import { isViewFieldURLValue } from '../types/guards/isViewFieldURLValue'; +import { isViewFieldChipValue } from '../../editable-field/types/guards/isViewFieldChipValue'; import { ViewFieldChipMetadata, ViewFieldChipValue, @@ -41,7 +41,7 @@ import { ViewFieldTextValue, ViewFieldURLMetadata, ViewFieldURLValue, -} from '../types/ViewField'; +} from '../../editable-field/types/ViewField'; export function useUpdateEntityField() { const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext); diff --git a/front/src/modules/ui/table/states/ViewFieldContext.ts b/front/src/modules/ui/table/states/ViewFieldContext.ts index 5c0502fd3..179eed39b 100644 --- a/front/src/modules/ui/table/states/ViewFieldContext.ts +++ b/front/src/modules/ui/table/states/ViewFieldContext.ts @@ -1,6 +1,9 @@ import { createContext } from 'react'; -import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField'; +import { + ViewFieldDefinition, + ViewFieldMetadata, +} from '../../editable-field/types/ViewField'; export const ViewFieldContext = createContext | null>(null); diff --git a/front/src/modules/ui/table/states/viewFieldsState.ts b/front/src/modules/ui/table/states/viewFieldsState.ts index 47f1a007d..e02b57e9e 100644 --- a/front/src/modules/ui/table/states/viewFieldsState.ts +++ b/front/src/modules/ui/table/states/viewFieldsState.ts @@ -6,7 +6,7 @@ import { peopleViewFields } from '@/people/constants/peopleViewFields'; import type { ViewFieldDefinition, ViewFieldMetadata, -} from '../types/ViewField'; +} from '../../editable-field/types/ViewField'; export const viewFieldsState = atom<{ objectName: 'company' | 'person' | ''; diff --git a/front/src/modules/views/components/OptionsDropdownButton.tsx b/front/src/modules/views/components/OptionsDropdownButton.tsx index 8400b005a..d574f13a6 100644 --- a/front/src/modules/views/components/OptionsDropdownButton.tsx +++ b/front/src/modules/views/components/OptionsDropdownButton.tsx @@ -8,6 +8,10 @@ import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader' import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; +import { + ViewFieldDefinition, + ViewFieldMetadata, +} from '@/ui/editable-field/types/ViewField'; import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton'; import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope'; import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon'; @@ -15,10 +19,6 @@ import { hiddenViewFieldsState, visibleViewFieldsState, } from '@/ui/table/states/viewFieldsState'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/table/types/ViewField'; import { useUpdateViewFieldMutation } from '~/generated/graphql'; import { GET_VIEW_FIELDS } from '../queries/select'; diff --git a/front/src/modules/views/components/OptionsDropdownSection.tsx b/front/src/modules/views/components/OptionsDropdownSection.tsx index f38dea820..4af6bc9f7 100644 --- a/front/src/modules/views/components/OptionsDropdownSection.tsx +++ b/front/src/modules/views/components/OptionsDropdownSection.tsx @@ -10,7 +10,7 @@ import { DropdownMenuSubheader } from '@/ui/dropdown/components/DropdownMenuSubh import { ViewFieldDefinition, ViewFieldMetadata, -} from '@/ui/table/types/ViewField'; +} from '@/ui/editable-field/types/ViewField'; type OptionsDropdownSectionProps = { renderActions: ( diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index 03211aba8..10f579347 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -14,6 +14,7 @@ import { EntityBoard } from '@/ui/board/components/EntityBoard'; import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar'; import { BoardOptionsContext } from '@/ui/board/states/BoardOptionsContext'; import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers'; +import { AvailableFiltersContext } from '@/ui/filter-n-sort/states/AvailableFiltersContext'; import { IconTargetArrow } from '@/ui/icon/index'; import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; @@ -83,19 +84,20 @@ export function Opportunities() { > - - - - - + + + + + + + diff --git a/front/src/sync-hooks/AuthAutoRouter.tsx b/front/src/sync-hooks/AuthAutoRouter.tsx index 44758b2a9..274a35982 100644 --- a/front/src/sync-hooks/AuthAutoRouter.tsx +++ b/front/src/sync-hooks/AuthAutoRouter.tsx @@ -129,11 +129,13 @@ export function AuthAutoRouter() { } } - eventTracker('pageview', { - location: { - pathname: location.pathname, - }, - }); + setTimeout(() => { + eventTracker('pageview', { + location: { + pathname: location.pathname, + }, + }); + }, 500); }, [ onboardingStatus, navigate, diff --git a/front/src/testing/mock-data/pipeline-progress.ts b/front/src/testing/mock-data/pipeline-progress.ts index c3d717727..8f03f0923 100644 --- a/front/src/testing/mock-data/pipeline-progress.ts +++ b/front/src/testing/mock-data/pipeline-progress.ts @@ -2,7 +2,15 @@ import { PipelineProgress, User } from '../../generated/graphql'; type MockedPipelineProgress = Pick< PipelineProgress, - 'id' | 'amount' | 'closeDate' | 'companyId' | 'pipelineStageId' + | 'id' + | 'amount' + | 'closeDate' + | 'companyId' + | 'pipelineStageId' + | 'probability' + | 'pointOfContact' + | 'pointOfContactId' + | 'personId' > & { accountOwner: Pick< User, @@ -32,6 +40,10 @@ export const mockedPipelineProgressData: Array = [ companyId: '0', accountOwner: accountOwner, pipelineStageId: 'another-pipeline-stage-1', + probability: null, + pointOfContact: null, + pointOfContactId: null, + personId: null, }, { id: 'fe256b39-3ec3-4fe7-8998-b76aa0bfb600', @@ -40,6 +52,10 @@ export const mockedPipelineProgressData: Array = [ amount: 7, closeDate: '2021-10-01T00:00:00.000Z', accountOwner, + probability: null, + pointOfContact: null, + pointOfContactId: null, + personId: null, }, { id: '4a886c90-f4f2-4984-8222-882ebbb905d6', @@ -48,5 +64,9 @@ export const mockedPipelineProgressData: Array = [ closeDate: '2021-10-01T00:00:00.000Z', accountOwner, pipelineStageId: 'fe256b39-3ec3-4fe3-8998-b76aa0bfb600', + probability: null, + pointOfContact: null, + pointOfContactId: null, + personId: null, }, ]; diff --git a/front/src/utils/isDeeplyEqual.ts b/front/src/utils/isDeeplyEqual.ts new file mode 100644 index 000000000..1cdb73568 --- /dev/null +++ b/front/src/utils/isDeeplyEqual.ts @@ -0,0 +1,5 @@ +import deepEqual from 'deep-equal'; + +export function isDeeplyEqual(a: T, b: T) { + return deepEqual(a, b); +} diff --git a/front/yarn.lock b/front/yarn.lock index c0dca93c1..7adcdb047 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -5028,6 +5028,11 @@ dependencies: "@types/ms" "*" +"@types/deep-equal@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" + integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg== + "@types/detect-port@^1.3.0": version "1.3.3" resolved "https://registry.yarnpkg.com/@types/detect-port/-/detect-port-1.3.3.tgz#124c5d4c283f48a21f80826bcf39433b3e64aa81" @@ -8604,6 +8609,30 @@ deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.9" +deep-equal@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.2.tgz#9b2635da569a13ba8e1cc159c2f744071b115daa" + integrity sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.1" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -10293,7 +10322,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==