From 84018efc7d1ebbb533ee4d69b40f551d49b5288f Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 18 Jul 2023 21:02:45 +0200 Subject: [PATCH] Added two editable fields on company board card (#738) --- .../companies/components/CompanyBoardCard.tsx | 29 +++---- .../CompanyAccountOwnerEditableField.tsx | 8 +- .../CompanyCreatedAtEditableField.tsx | 2 +- .../PipelineProgressAmountEditableField.tsx | 79 +++++++++++++++++++ .../components/EditableFieldEditModeDate.tsx | 2 +- .../components/NumberEditableField.tsx | 79 +++++++++++++++++++ .../variants/components/TextEditableField.tsx | 62 +++++++++++++++ 7 files changed, 238 insertions(+), 23 deletions(-) create mode 100644 front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx rename front/src/modules/ui/editable-field/{variants => }/components/EditableFieldEditModeDate.tsx (91%) create mode 100644 front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx create mode 100644 front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index fee2491752..1bc5dae9f9 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -10,8 +10,8 @@ import { BoardCardContext } from '@/pipeline/states/BoardCardContext'; import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState'; import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState'; import { BoardCardEditableFieldDate } from '@/ui/board/card-field/components/BoardCardEditableFieldDate'; -import { BoardCardEditableFieldText } from '@/ui/board/card-field/components/BoardCardEditableFieldText'; import { ChipVariant } from '@/ui/chip/components/EntityChip'; +import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField'; import { IconCurrencyDollar } from '@/ui/icon'; import { IconCalendarEvent } from '@/ui/icon'; import { Checkbox } from '@/ui/input/components/Checkbox'; @@ -22,6 +22,8 @@ import { } from '~/generated/graphql'; import { getLogoUrlFromDomainName } from '~/utils'; +import { CompanyAccountOwnerEditableField } from '../editable-field/components/CompanyAccountOwnerEditableField'; + import { CompanyChip } from './CompanyChip'; const StyledBoardCard = styled.div<{ selected: boolean }>` @@ -143,19 +145,18 @@ export function CompanyBoardCard() { - - - - handleCardUpdate({ - ...pipelineProgress, - amount: parseInt(value), - }) - } - /> - + } + placeholder="Opportunity amount" + value={pipelineProgress.amount} + onSubmit={(value) => + handleCardUpdate({ + ...pipelineProgress, + amount: value, + }) + } + /> + } editModeContent={ @@ -44,6 +37,7 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) { <> ) } + isDisplayModeContentEmpty={!company.accountOwner} /> diff --git a/front/src/modules/companies/editable-field/components/CompanyCreatedAtEditableField.tsx b/front/src/modules/companies/editable-field/components/CompanyCreatedAtEditableField.tsx index 9eb61352a2..dcda0fe260 100644 --- a/front/src/modules/companies/editable-field/components/CompanyCreatedAtEditableField.tsx +++ b/front/src/modules/companies/editable-field/components/CompanyCreatedAtEditableField.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import { EditableField } from '@/ui/editable-field/components/EditableField'; +import { EditableFieldEditModeDate } from '@/ui/editable-field/components/EditableFieldEditModeDate'; import { FieldContext } from '@/ui/editable-field/states/FieldContext'; -import { EditableFieldEditModeDate } from '@/ui/editable-field/variants/components/EditableFieldEditModeDate'; import { IconCalendar } from '@/ui/icon'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { Company, useUpdateCompanyMutation } from '~/generated/graphql'; diff --git a/front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx b/front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx new file mode 100644 index 0000000000..338b2807ac --- /dev/null +++ b/front/src/modules/pipeline/progress/components/PipelineProgressAmountEditableField.tsx @@ -0,0 +1,79 @@ +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 { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText'; +import { RecoilScope } from '@/ui/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/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx b/front/src/modules/ui/editable-field/components/EditableFieldEditModeDate.tsx similarity index 91% rename from front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx rename to front/src/modules/ui/editable-field/components/EditableFieldEditModeDate.tsx index 28878d1ddc..fa0a6f784d 100644 --- a/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx +++ b/front/src/modules/ui/editable-field/components/EditableFieldEditModeDate.tsx @@ -2,7 +2,7 @@ import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope'; import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate'; import { parseDate } from '~/utils/date-utils'; -import { useEditableField } from '../../hooks/useEditableField'; +import { useEditableField } from '../hooks/useEditableField'; type OwnProps = { value: string; diff --git a/front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx new file mode 100644 index 0000000000..2a23b2dfd8 --- /dev/null +++ b/front/src/modules/ui/editable-field/variants/components/NumberEditableField.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from 'react'; + +import { EditableField } from '@/ui/editable-field/components/EditableField'; +import { FieldContext } from '@/ui/editable-field/states/FieldContext'; +import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText'; +import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; + +type OwnProps = { + icon?: React.ReactNode; + placeholder?: string; + value: number | null | undefined; + onSubmit?: (newValue: number) => 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 (!internalValue) return; + + try { + const numberValue = parseInt(internalValue); + + if (isNaN(numberValue)) { + throw new Error('Not a number'); + } + + // TODO: find a way to store this better in DB + if (numberValue > 2000000000) { + throw new Error('Number too big'); + } + + onSubmit?.(numberValue); + + setInternalValue(numberValue.toString()); + } catch { + handleCancel(); + } + } + + async function handleCancel() { + setInternalValue(value?.toString()); + } + + return ( + + { + handleChange(newValue); + }} + /> + } + displayModeContent={internalValue} + isDisplayModeContentEmpty={!(internalValue !== '')} + /> + + ); +} diff --git a/front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx new file mode 100644 index 0000000000..d180f10ee7 --- /dev/null +++ b/front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; + +import { EditableField } from '@/ui/editable-field/components/EditableField'; +import { FieldContext } from '@/ui/editable-field/states/FieldContext'; +import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText'; +import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; + +type OwnProps = { + icon?: React.ReactNode; + placeholder?: string; + value: string | null | undefined; + onSubmit?: (newValue: string) => void; +}; + +export function TextEditableField({ + icon, + placeholder, + value, + onSubmit, +}: OwnProps) { + const [internalValue, setInternalValue] = useState(value); + + useEffect(() => { + setInternalValue(value); + }, [value]); + + async function handleChange(newValue: string) { + setInternalValue(newValue); + } + + async function handleSubmit() { + if (!internalValue) return; + + onSubmit?.(internalValue); + } + + async function handleCancel() { + setInternalValue(value); + } + + return ( + + { + handleChange(newValue); + }} + /> + } + displayModeContent={internalValue} + isDisplayModeContentEmpty={!(internalValue !== '')} + /> + + ); +}