Reorganize frontend and install Craco to alias modules (#190)
This commit is contained in:
@ -0,0 +1,63 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CellCommentChip } from '@/comments/components/comments/CellCommentChip';
|
||||
import { EditableDoubleText } from '@/ui/components/editable-cell/EditableDoubleText';
|
||||
|
||||
import { PersonChip } from './PersonChip';
|
||||
|
||||
type OwnProps = {
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
onChange: (firstname: string, lastname: string) => void;
|
||||
};
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function EditablePeopleFullName({
|
||||
firstname,
|
||||
lastname,
|
||||
onChange,
|
||||
}: OwnProps) {
|
||||
const [firstnameValue, setFirstnameValue] = useState(firstname);
|
||||
const [lastnameValue, setLastnameValue] = useState(lastname);
|
||||
|
||||
function handleDoubleTextChange(
|
||||
firstValue: string,
|
||||
secondValue: string,
|
||||
): void {
|
||||
setFirstnameValue(firstValue);
|
||||
setLastnameValue(secondValue);
|
||||
|
||||
onChange(firstValue, secondValue);
|
||||
}
|
||||
|
||||
function handleCommentClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
console.log('comment clicked');
|
||||
}
|
||||
|
||||
return (
|
||||
<EditableDoubleText
|
||||
firstValue={firstnameValue}
|
||||
secondValue={lastnameValue}
|
||||
firstValuePlaceholder="First name"
|
||||
secondValuePlaceholder="Last name"
|
||||
onChange={handleDoubleTextChange}
|
||||
nonEditModeContent={
|
||||
<>
|
||||
<StyledDiv>
|
||||
<PersonChip name={firstname + ' ' + lastname} />
|
||||
</StyledDiv>
|
||||
<CellCommentChip count={12} onClick={handleCommentClick} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
113
front/src/modules/people/components/PeopleCompanyCell.tsx
Normal file
113
front/src/modules/people/components/PeopleCompanyCell.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import CompanyChip, {
|
||||
CompanyChipPropsType,
|
||||
} from '@/companies/components/CompanyChip';
|
||||
import {
|
||||
Company,
|
||||
mapToCompany,
|
||||
} from '@/companies/interfaces/company.interface';
|
||||
import { SearchConfigType } from '@/search/interfaces/interface';
|
||||
import { SEARCH_COMPANY_QUERY } from '@/search/services/search';
|
||||
import { EditableRelation } from '@/ui/components/editable-cell/EditableRelation';
|
||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||
import {
|
||||
QueryMode,
|
||||
useInsertCompanyMutation,
|
||||
useUpdatePeopleMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { mapToGqlPerson, Person } from '../interfaces/person.interface';
|
||||
|
||||
import { PeopleCompanyCreateCell } from './PeopleCompanyCreateCell';
|
||||
|
||||
export type OwnProps = {
|
||||
people: Person;
|
||||
};
|
||||
|
||||
export function PeopleCompanyCell({ people }: OwnProps) {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [insertCompany] = useInsertCompanyMutation();
|
||||
const [updatePeople] = useUpdatePeopleMutation();
|
||||
const [initialCompanyName, setInitialCompanyName] = useState('');
|
||||
|
||||
async function handleCompanyCreate(
|
||||
companyName: string,
|
||||
companyDomainName: string,
|
||||
) {
|
||||
const newCompanyId = v4();
|
||||
|
||||
try {
|
||||
await insertCompany({
|
||||
variables: {
|
||||
id: newCompanyId,
|
||||
name: companyName,
|
||||
domainName: companyDomainName,
|
||||
address: '',
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
await updatePeople({
|
||||
variables: {
|
||||
...mapToGqlPerson(people),
|
||||
companyId: newCompanyId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// TODO: handle error better
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setIsCreating(false);
|
||||
}
|
||||
|
||||
// TODO: should be replaced with search context
|
||||
function handleChangeSearchInput(searchInput: string) {
|
||||
setInitialCompanyName(searchInput);
|
||||
}
|
||||
|
||||
return isCreating ? (
|
||||
<PeopleCompanyCreateCell
|
||||
initialCompanyName={initialCompanyName}
|
||||
onCreate={handleCompanyCreate}
|
||||
/>
|
||||
) : (
|
||||
<EditableRelation<Company, CompanyChipPropsType>
|
||||
relation={people.company}
|
||||
searchPlaceholder="Company"
|
||||
ChipComponent={CompanyChip}
|
||||
chipComponentPropsMapper={(company): CompanyChipPropsType => {
|
||||
return {
|
||||
name: company.name || '',
|
||||
picture: getLogoUrlFromDomainName(company.domainName),
|
||||
};
|
||||
}}
|
||||
onChange={async (relation) => {
|
||||
await updatePeople({
|
||||
variables: {
|
||||
...mapToGqlPerson(people),
|
||||
companyId: relation.id,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onChangeSearchInput={handleChangeSearchInput}
|
||||
searchConfig={
|
||||
{
|
||||
query: SEARCH_COMPANY_QUERY,
|
||||
template: (searchInput: string) => ({
|
||||
name: { contains: `%${searchInput}%`, mode: QueryMode.Insensitive },
|
||||
}),
|
||||
resultMapper: (company) => ({
|
||||
render: (company) => company.name,
|
||||
value: mapToCompany(company),
|
||||
}),
|
||||
} satisfies SearchConfigType<Company>
|
||||
}
|
||||
onCreate={() => {
|
||||
setIsCreating(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import { CellBaseContainer } from '@/ui/components/editable-cell/CellBaseContainer';
|
||||
import { CellEditModeContainer } from '@/ui/components/editable-cell/CellEditModeContainer';
|
||||
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
|
||||
type OwnProps = {
|
||||
initialCompanyName: string;
|
||||
onCreate: (companyName: string, companyDomainName: string) => void;
|
||||
};
|
||||
|
||||
export function PeopleCompanyCreateCell({
|
||||
initialCompanyName,
|
||||
onCreate,
|
||||
}: OwnProps) {
|
||||
const [companyName, setCompanyName] = useState(initialCompanyName);
|
||||
const [companyDomainName, setCompanyDomainName] = useState('');
|
||||
|
||||
const containerRef = useRef(null);
|
||||
|
||||
useListenClickOutsideArrayOfRef([containerRef], () => {
|
||||
onCreate(companyName, companyDomainName);
|
||||
});
|
||||
|
||||
useHotkeys(
|
||||
'enter, escape',
|
||||
() => {
|
||||
onCreate(companyName, companyDomainName);
|
||||
},
|
||||
{
|
||||
enableOnFormTags: true,
|
||||
enableOnContentEditable: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[containerRef, companyName, companyDomainName, onCreate],
|
||||
);
|
||||
|
||||
function handleDoubleTextChange(leftValue: string, rightValue: string): void {
|
||||
setCompanyDomainName(leftValue);
|
||||
setCompanyName(rightValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellBaseContainer ref={containerRef}>
|
||||
<CellEditModeContainer editModeVerticalPosition="over">
|
||||
<DoubleTextInput
|
||||
leftValue={companyDomainName}
|
||||
rightValue={companyName}
|
||||
leftValuePlaceholder="URL"
|
||||
rightValuePlaceholder="Name"
|
||||
onChange={handleDoubleTextChange}
|
||||
/>
|
||||
</CellEditModeContainer>
|
||||
</CellBaseContainer>
|
||||
);
|
||||
}
|
||||
46
front/src/modules/people/components/PersonChip.tsx
Normal file
46
front/src/modules/people/components/PersonChip.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import * as React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import PersonPlaceholder from './person-placeholder.png';
|
||||
|
||||
export type PersonChipPropsType = {
|
||||
name: string;
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.span`
|
||||
background-color: ${(props) => props.theme.tertiaryBackground};
|
||||
border-radius: ${(props) => props.theme.spacing(1)};
|
||||
color: ${(props) => props.theme.text80};
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: ${(props) => props.theme.spacing(1)};
|
||||
gap: ${(props) => props.theme.spacing(1)};
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
:hover {
|
||||
filter: brightness(95%);
|
||||
}
|
||||
|
||||
img {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border-radius: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
`;
|
||||
|
||||
export function PersonChip({ name, picture }: PersonChipPropsType) {
|
||||
return (
|
||||
<StyledContainer data-testid="person-chip">
|
||||
<img
|
||||
data-testid="person-chip-image"
|
||||
src={picture ? picture.toString() : PersonPlaceholder.toString()}
|
||||
alt="person"
|
||||
/>
|
||||
{name}
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
BIN
front/src/modules/people/components/person-placeholder.png
Normal file
BIN
front/src/modules/people/components/person-placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user