Feat/account owner picker (#359)

* Added account owner picker

* Regenerated graphql files

* Fixed pickers staying in edit mode with a new generic hook

* Fixed lint
This commit is contained in:
Lucas Bordeau
2023-06-22 19:47:04 +02:00
committed by GitHub
parent cd70209502
commit 4a2797c491
8 changed files with 147 additions and 66 deletions

View File

@ -1359,16 +1359,6 @@ export type User = {
workspaceMember?: Maybe<WorkspaceMember>; workspaceMember?: Maybe<WorkspaceMember>;
}; };
export type UserCompaniesArgs = {
cursor?: InputMaybe<CompanyWhereUniqueInput>;
distinct?: InputMaybe<Array<CompanyScalarFieldEnum>>;
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput>>;
skip?: InputMaybe<Scalars['Int']>;
take?: InputMaybe<Scalars['Int']>;
where?: InputMaybe<CompanyWhereInput>;
};
export type UserCreateNestedOneWithoutCommentsInput = { export type UserCreateNestedOneWithoutCommentsInput = {
connect?: InputMaybe<UserWhereUniqueInput>; connect?: InputMaybe<UserWhereUniqueInput>;
}; };
@ -1527,6 +1517,7 @@ export type WorkspaceMember = {
user: User; user: User;
userId: Scalars['String']; userId: Scalars['String'];
workspace: Workspace; workspace: Workspace;
workspaceId: Scalars['String'];
}; };
export type CreateCommentMutationVariables = Exact<{ export type CreateCommentMutationVariables = Exact<{
@ -1706,6 +1697,7 @@ export type SearchPeopleQuery = { __typename?: 'Query', searchResults: Array<{ _
export type SearchUserQueryVariables = Exact<{ export type SearchUserQueryVariables = Exact<{
where?: InputMaybe<UserWhereInput>; where?: InputMaybe<UserWhereInput>;
limit?: InputMaybe<Scalars['Int']>; limit?: InputMaybe<Scalars['Int']>;
orderBy?: InputMaybe<Array<UserOrderByWithRelationInput> | UserOrderByWithRelationInput>;
}>; }>;
@ -2575,8 +2567,8 @@ export type SearchPeopleQueryHookResult = ReturnType<typeof useSearchPeopleQuery
export type SearchPeopleLazyQueryHookResult = ReturnType<typeof useSearchPeopleLazyQuery>; export type SearchPeopleLazyQueryHookResult = ReturnType<typeof useSearchPeopleLazyQuery>;
export type SearchPeopleQueryResult = Apollo.QueryResult<SearchPeopleQuery, SearchPeopleQueryVariables>; export type SearchPeopleQueryResult = Apollo.QueryResult<SearchPeopleQuery, SearchPeopleQueryVariables>;
export const SearchUserDocument = gql` export const SearchUserDocument = gql`
query SearchUser($where: UserWhereInput, $limit: Int) { query SearchUser($where: UserWhereInput, $limit: Int, $orderBy: [UserOrderByWithRelationInput!]) {
searchResults: findManyUser(where: $where, take: $limit) { searchResults: findManyUser(where: $where, take: $limit, orderBy: $orderBy) {
id id
email email
displayName displayName
@ -2598,6 +2590,7 @@ export const SearchUserDocument = gql`
* variables: { * variables: {
* where: // value for 'where' * where: // value for 'where'
* limit: // value for 'limit' * limit: // value for 'limit'
* orderBy: // value for 'orderBy'
* }, * },
* }); * });
*/ */

View File

@ -0,0 +1,26 @@
import { PersonChip } from '@/people/components/PersonChip';
import { EditableCellV2 } from '@/ui/components/editable-cell/EditableCellV2';
import { Company, User } from '~/generated/graphql';
import { CompanyAccountOwnerPicker } from './CompanyAccountOwnerPicker';
export type OwnProps = {
company: Pick<Company, 'id'> & {
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
};
};
export function CompanyAccountOwnerCell({ company }: OwnProps) {
return (
<EditableCellV2
editModeContent={<CompanyAccountOwnerPicker company={company} />}
nonEditModeContent={
company.accountOwner?.displayName ? (
<PersonChip name={company.accountOwner?.displayName ?? ''} />
) : (
<></>
)
}
/>
);
}

View File

@ -0,0 +1,67 @@
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
import { useCloseEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
import {
Company,
User,
useSearchUserQuery,
useUpdateCompanyMutation,
} from '~/generated/graphql';
export type OwnProps = {
company: Pick<Company, 'id'> & {
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
};
};
type UserForSelect = EntityForSelect & {
entityType: Entity.User;
};
export function CompanyAccountOwnerPicker({ company }: OwnProps) {
const [searchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const [updateCompany] = useUpdateCompanyMutation();
const closeEditableCell = useCloseEditableCell();
const companies = useFilteredSearchEntityQuery({
queryHook: useSearchUserQuery,
selectedIds: [company?.accountOwner?.id ?? ''],
searchFilter: searchFilter,
mappingFunction: (user) => ({
entityType: Entity.User,
id: user.id,
name: user.displayName,
avatarType: 'rounded',
}),
orderByField: 'displayName',
searchOnFields: ['displayName'],
});
async function handleEntitySelected(selectedUser: UserForSelect) {
await updateCompany({
variables: {
...company,
accountOwnerId: selectedUser.id,
},
});
closeEditableCell();
}
return (
<SingleEntitySelect
onEntitySelected={handleEntitySelected}
entities={{
entitiesToSelect: companies.entitiesToSelect,
selectedEntity: companies.selectedEntities[0],
}}
/>
);
}

View File

@ -1,11 +1,9 @@
import { useRecoilState } from 'recoil';
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect'; import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery'; import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState'; import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
import { useCloseEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState'; import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
import { getLogoUrlFromDomainName } from '@/utils/utils'; import { getLogoUrlFromDomainName } from '@/utils/utils';
import { import {
CommentableType, CommentableType,
@ -26,9 +24,8 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
relationPickerSearchFilterScopedState, relationPickerSearchFilterScopedState,
); );
const [updatePeople] = useUpdatePeopleMutation(); const [updatePeople] = useUpdatePeopleMutation();
const [, setIsSomeInputInEditMode] = useRecoilState(
isSomeInputInEditModeState, const closeEditableCell = useCloseEditableCell();
);
const companies = useFilteredSearchEntityQuery({ const companies = useFilteredSearchEntityQuery({
queryHook: useSearchCompanyQuery, queryHook: useSearchCompanyQuery,
@ -46,14 +43,14 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
}); });
async function handleEntitySelected(entity: any) { async function handleEntitySelected(entity: any) {
setIsSomeInputInEditMode(false);
await updatePeople({ await updatePeople({
variables: { variables: {
...people, ...people,
companyId: entity.id, companyId: entity.id,
}, },
}); });
closeEditableCell();
} }
function handleCreate() { function handleCreate() {

View File

@ -1,3 +1,12 @@
import { CommentableType, PipelineProgressableType } from '~/generated/graphql'; import { CommentableType, PipelineProgressableType } from '~/generated/graphql';
export type EntityTypeForSelect = CommentableType | PipelineProgressableType; export enum Entity {
Company = 'Company',
Person = 'Person',
User = 'User',
}
export type EntityTypeForSelect =
| CommentableType
| PipelineProgressableType
| Entity;

View File

@ -28,8 +28,16 @@ export const SEARCH_PEOPLE_QUERY = gql`
`; `;
export const SEARCH_USER_QUERY = gql` export const SEARCH_USER_QUERY = gql`
query SearchUser($where: UserWhereInput, $limit: Int) { query SearchUser(
searchResults: findManyUser(where: $where, take: $limit) { $where: UserWhereInput
$limit: Int
$orderBy: [UserOrderByWithRelationInput!]
) {
searchResults: findManyUser(
where: $where
take: $limit
orderBy: $orderBy
) {
id id
email email
displayName displayName

View File

@ -0,0 +1,19 @@
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
import { isEditModeScopedState } from '../states/isEditModeScopedState';
export function useCloseEditableCell() {
const [, setIsSomeInputInEditMode] = useRecoilState(
isSomeInputInEditModeState,
);
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
return useCallback(() => {
setIsSomeInputInEditMode(false);
setIsEditMode(false);
}, [setIsEditMode, setIsSomeInputInEditMode]);
}

View File

@ -1,17 +1,12 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { createColumnHelper } from '@tanstack/react-table'; import { createColumnHelper } from '@tanstack/react-table';
import { CompanyAccountOwnerCell } from '@/companies/components/CompanyAccountOwnerCell';
import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell'; import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEditableNameCell';
import {
PersonChip,
PersonChipPropsType,
} from '@/people/components/PersonChip';
import { SearchConfigType } from '@/search/interfaces/interface';
import { SEARCH_USER_QUERY } from '@/search/services/search';
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate'; import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
import { EditableRelation } from '@/ui/components/editable-cell/types/EditableRelation';
import { EditableText } from '@/ui/components/editable-cell/types/EditableText'; import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
import { ColumnHead } from '@/ui/components/table/ColumnHead'; import { ColumnHead } from '@/ui/components/table/ColumnHead';
import { RecoilScope } from '@/ui/hooks/RecoilScope';
import { import {
IconBuildingSkyscraper, IconBuildingSkyscraper,
IconCalendarEvent, IconCalendarEvent,
@ -23,7 +18,6 @@ import {
import { getCheckBoxColumn } from '@/ui/tables/utils/getCheckBoxColumn'; import { getCheckBoxColumn } from '@/ui/tables/utils/getCheckBoxColumn';
import { import {
GetCompaniesQuery, GetCompaniesQuery,
QueryMode,
useUpdateCompanyMutation, useUpdateCompanyMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -149,41 +143,9 @@ export const useCompaniesColumns = () => {
/> />
), ),
cell: (props) => ( cell: (props) => (
<EditableRelation<any, PersonChipPropsType> <RecoilScope>
relation={props.row.original.accountOwner} <CompanyAccountOwnerCell company={props.row.original} />
searchPlaceholder="Account Owner" </RecoilScope>
ChipComponent={PersonChip}
chipComponentPropsMapper={(
accountOwner: any,
): PersonChipPropsType => {
return {
name: accountOwner.displayName || '',
};
}}
onChange={(relation: any) => {
updateCompany({
variables: {
...props.row.original,
accountOwnerId: relation.id,
},
});
}}
searchConfig={
{
query: SEARCH_USER_QUERY,
template: (searchInput: string) => ({
displayName: {
contains: `%${searchInput}%`,
mode: QueryMode.Insensitive,
},
}),
resultMapper: (accountOwner: any) => ({
render: (accountOwner: any) => accountOwner.displayName,
value: accountOwner,
}),
} satisfies SearchConfigType
}
/>
), ),
}), }),
]; ];