mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-28 23:03:41 +03:00
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:
parent
73e9104b16
commit
725a46adfa
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
),
|
||||
};
|
@ -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]} />,
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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
|
||||
|
@ -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)};
|
||||
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user