Added two editable fields on company board card (#738)

This commit is contained in:
Lucas Bordeau 2023-07-18 21:02:45 +02:00 committed by GitHub
parent 9378677744
commit 84018efc7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 238 additions and 23 deletions

View File

@ -10,8 +10,8 @@ import { BoardCardContext } from '@/pipeline/states/BoardCardContext';
import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState'; import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState';
import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState'; import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState';
import { BoardCardEditableFieldDate } from '@/ui/board/card-field/components/BoardCardEditableFieldDate'; 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 { ChipVariant } from '@/ui/chip/components/EntityChip';
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
import { IconCurrencyDollar } from '@/ui/icon'; import { IconCurrencyDollar } from '@/ui/icon';
import { IconCalendarEvent } from '@/ui/icon'; import { IconCalendarEvent } from '@/ui/icon';
import { Checkbox } from '@/ui/input/components/Checkbox'; import { Checkbox } from '@/ui/input/components/Checkbox';
@ -22,6 +22,8 @@ import {
} from '~/generated/graphql'; } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
import { CompanyAccountOwnerEditableField } from '../editable-field/components/CompanyAccountOwnerEditableField';
import { CompanyChip } from './CompanyChip'; import { CompanyChip } from './CompanyChip';
const StyledBoardCard = styled.div<{ selected: boolean }>` const StyledBoardCard = styled.div<{ selected: boolean }>`
@ -143,19 +145,18 @@ export function CompanyBoardCard() {
<Checkbox checked={selected} onChange={handleCheckboxChange} /> <Checkbox checked={selected} onChange={handleCheckboxChange} />
</StyledBoardCardHeader> </StyledBoardCardHeader>
<StyledBoardCardBody> <StyledBoardCardBody>
<span> <NumberEditableField
<IconCurrencyDollar size={theme.icon.size.md} /> icon={<IconCurrencyDollar />}
<BoardCardEditableFieldText placeholder="Opportunity amount"
value={pipelineProgress.amount?.toString() || ''} value={pipelineProgress.amount}
placeholder="Opportunity amount" onSubmit={(value) =>
onChange={(value) => handleCardUpdate({
handleCardUpdate({ ...pipelineProgress,
...pipelineProgress, amount: value,
amount: parseInt(value), })
}) }
} />
/> <CompanyAccountOwnerEditableField company={company} />
</span>
<span> <span>
<IconCalendarEvent size={theme.icon.size.md} /> <IconCalendarEvent size={theme.icon.size.md} />
<BoardCardEditableFieldDate <BoardCardEditableFieldDate

View File

@ -1,5 +1,4 @@
import { PersonChip } from '@/people/components/PersonChip'; import { PersonChip } from '@/people/components/PersonChip';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { EditableField } from '@/ui/editable-field/components/EditableField'; import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext'; import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconUserCircle } from '@/ui/icon'; import { IconUserCircle } from '@/ui/icon';
@ -23,12 +22,6 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
customEditHotkeyScope={{ customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker, scope: RelationPickerHotkeyScope.RelationPicker,
}} }}
parentHotkeyScope={{
scope: PageHotkeyScope.CompanyShowPage,
customScopes: {
goto: true,
},
}}
iconLabel={<IconUserCircle />} iconLabel={<IconUserCircle />}
editModeContent={ editModeContent={
<CompanyAccountOwnerPickerFieldEditMode company={company} /> <CompanyAccountOwnerPickerFieldEditMode company={company} />
@ -44,6 +37,7 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
<></> <></>
) )
} }
isDisplayModeContentEmpty={!company.accountOwner}
/> />
</RecoilScope> </RecoilScope>
</RecoilScope> </RecoilScope>

View File

@ -1,8 +1,8 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField'; 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 { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { EditableFieldEditModeDate } from '@/ui/editable-field/variants/components/EditableFieldEditModeDate';
import { IconCalendar } from '@/ui/icon'; import { IconCalendar } from '@/ui/icon';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { Company, useUpdateCompanyMutation } from '~/generated/graphql'; import { Company, useUpdateCompanyMutation } from '~/generated/graphql';

View File

@ -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<PipelineProgress, 'id' | 'amount'>;
};
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 (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconCurrencyDollar />}
editModeContent={
<InplaceInputText
placeholder={'Amount'}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
/>
</RecoilScope>
);
}

View File

@ -2,7 +2,7 @@ import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate'; import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
import { parseDate } from '~/utils/date-utils'; import { parseDate } from '~/utils/date-utils';
import { useEditableField } from '../../hooks/useEditableField'; import { useEditableField } from '../hooks/useEditableField';
type OwnProps = { type OwnProps = {
value: string; value: string;

View File

@ -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 (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
editModeContent={
<InplaceInputText
placeholder={placeholder ?? ''}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}

View File

@ -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 (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
editModeContent={
<InplaceInputText
placeholder={placeholder ?? ''}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}