Feature/edit name from show page (#806)

* Enable company name edition from page

* Enable editing persons as well

* Add styling for titles

* Better manage style with inheritance

* Add stories for poeple editable fields

* Remove failing test

* Revert "Remove failing test"

This reverts commit 02cdeeba64276a26f93cf4af94f5857e47d36fff.

* Fix test

* Update name

* Fix location

* Rename tests

* Fix stories
This commit is contained in:
Emilien Chauvet 2023-07-21 15:44:42 -07:00 committed by GitHub
parent 73e9104b16
commit 725a46adfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 251 additions and 7 deletions

View File

@ -0,0 +1,63 @@
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';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
type OwnProps = {
company: Pick<Company, 'id' | 'name'>;
};
export function CompanyNameEditableField({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(company.name);
const [updateCompany] = useUpdateOneCompanyMutation();
useEffect(() => {
setInternalValue(company.name);
}, [company.name]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
name: internalValue ?? '',
},
},
});
}
async function handleCancel() {
setInternalValue(company.name);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
editModeContent={
<InplaceInputText
placeholder={'Name'}
autoFocus
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue ?? ''}
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,22 @@
import { BrowserRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
import { mockedPeopleData } from '~/testing/mock-data/people';
import { PeopleCompanyEditableField } from '../../editable-field/components/PeopleCompanyEditableField';
const meta: Meta<typeof PeopleCompanyEditableField> = {
title: 'Modules/People/EditableFields/PeopleCompanyEditableField',
component: PeopleCompanyEditableField,
};
export default meta;
type Story = StoryObj<typeof PeopleCompanyEditableField>;
export const Default: Story = {
render: () => (
<BrowserRouter>
<PeopleCompanyEditableField people={mockedPeopleData[0]} />
</BrowserRouter>
),
};

View File

@ -0,0 +1,17 @@
import type { Meta, StoryObj } from '@storybook/react';
import { mockedPeopleData } from '~/testing/mock-data/people';
import { PeopleFullNameEditableField } from '../../editable-field/components/PeopleFullNameEditableField';
const meta: Meta<typeof PeopleFullNameEditableField> = {
title: 'Modules/People/EditableFields/PeopleFullNameEditableField',
component: PeopleFullNameEditableField,
};
export default meta;
type Story = StoryObj<typeof PeopleFullNameEditableField>;
export const Default: Story = {
render: () => <PeopleFullNameEditableField people={mockedPeopleData[0]} />,
};

View File

@ -0,0 +1,77 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { Person, useUpdateOnePersonMutation } from '~/generated/graphql';
import { InplaceInputDoubleText } from '../../../ui/inplace-input/components/InplaceInputDoubleText';
type OwnProps = {
people: Pick<Person, 'id' | 'firstName' | 'lastName'>;
};
export function PeopleFullNameEditableField({ people }: OwnProps) {
const [internalValueFirstName, setInternalValueFirstName] = useState(
people.firstName,
);
const [internalValueLastName, setInternalValueLastName] = useState(
people.lastName,
);
const [updatePeople] = useUpdateOnePersonMutation();
useEffect(() => {
setInternalValueFirstName(people.firstName);
setInternalValueLastName(people.lastName);
}, [people.firstName, people.lastName]);
async function handleChange(
newValueFirstName: string,
newValueLastName: string,
) {
setInternalValueFirstName(newValueFirstName);
setInternalValueLastName(newValueLastName);
}
async function handleSubmit() {
await updatePeople({
variables: {
where: {
id: people.id,
},
data: {
firstName: internalValueFirstName ?? '',
lastName: internalValueLastName ?? '',
},
},
});
}
async function handleCancel() {
setInternalValueFirstName(people.firstName);
setInternalValueLastName(people.lastName);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
editModeContent={
<InplaceInputDoubleText
firstValuePlaceholder={'First name'}
secondValuePlaceholder={'Last name'}
firstValue={internalValueFirstName ?? ''}
secondValue={internalValueLastName ?? ''}
onChange={handleChange}
/>
}
displayModeContent={`${internalValueFirstName} ${internalValueLastName}`}
isDisplayModeContentEmpty={
!(internalValueFirstName !== '') && !(internalValueLastName !== '')
}
/>
</RecoilScope>
);
}

View File

@ -51,8 +51,8 @@ export const EditableFieldNormalModeOuterContainer = styled.div<
export const EditableFieldNormalModeInnerContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
font-size: 'inherit';
font-weight: 'inherit';
height: fit-content;

View File

@ -0,0 +1,51 @@
import { ChangeEvent } from 'react';
import styled from '@emotion/styled';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
type OwnProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
onChange: (firstValue: string, secondValue: string) => void;
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
& > input:last-child {
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
padding-left: ${({ theme }) => theme.spacing(2)};
}
`;
export function InplaceInputDoubleText({
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
onChange,
}: OwnProps) {
return (
<StyledContainer>
<InplaceInputTextEditMode
autoFocus
placeholder={firstValuePlaceholder}
value={firstValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value, secondValue);
}}
/>
<InplaceInputTextEditMode
placeholder={secondValuePlaceholder}
value={secondValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(firstValue, event.target.value);
}}
/>
</StyledContainer>
);
}

View File

@ -16,6 +16,7 @@ type OwnProps = {
logoOrAvatar?: string;
title: string;
date: string;
renderTitleEditComponent?: () => JSX.Element;
};
const StyledShowPageSummaryCard = styled.div`
@ -45,7 +46,6 @@ const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
max-width: 100%;
`;
@ -65,6 +65,7 @@ export function ShowPageSummaryCard({
logoOrAvatar,
title,
date,
renderTitleEditComponent,
}: OwnProps) {
const beautifiedCreatedAt =
date !== '' ? beautifyPastDateRelativeToNow(date) : '';
@ -82,7 +83,11 @@ export function ShowPageSummaryCard({
/>
<StyledInfoContainer>
<StyledTitle>
<OverflowingTextWithTooltip text={title} />
{renderTitleEditComponent ? (
renderTitleEditComponent()
) : (
<OverflowingTextWithTooltip text={title} />
)}
</StyledTitle>
<StyledDate id={dateElementId}>
Added {beautifiedCreatedAt} ago

View File

@ -15,9 +15,8 @@ export const textInputStyle = (props: { theme: ThemeType }) =>
border: none;
color: ${props.theme.font.color.primary};
font-family: ${props.theme.font.family};
font-size: ${props.theme.font.size.md};
font-weight: ${props.theme.font.weight.regular};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${props.theme.spacing(0)} ${props.theme.spacing(2)};

View File

@ -17,6 +17,8 @@ import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSu
import { CommentableType } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
import { CompanyNameEditableField } from '../../modules/companies/editable-field/components/CompanyNameEditableField';
export function CompanyShow() {
const companyId = useParams().companyId ?? '';
@ -39,6 +41,9 @@ export function CompanyShow() {
logoOrAvatar={getLogoUrlFromDomainName(company?.domainName ?? '')}
title={company?.name ?? 'No name'}
date={company?.createdAt ?? ''}
renderTitleEditComponent={() => (
<CompanyNameEditableField company={company} />
)}
/>
<PropertyBox extraPadding={true}>
<CompanyDomainNameEditableField company={company} />

View File

@ -11,6 +11,8 @@ import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPag
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
import { CommentableType } from '~/generated/graphql';
import { PeopleFullNameEditableField } from '../../modules/people/editable-field/components/PeopleFullNameEditableField';
export function PersonShow() {
const personId = useParams().personId ?? '';
@ -31,6 +33,9 @@ export function PersonShow() {
id={person?.id}
title={person?.displayName ?? 'No name'}
date={person?.createdAt ?? ''}
renderTitleEditComponent={() =>
person ? <PeopleFullNameEditableField people={person} /> : <></>
}
/>
{person && <PersonPropertyBox person={person} />}
</ShowPageLeftContainer>