diff --git a/front/src/interfaces/company.interface.test.ts b/front/src/interfaces/company.interface.test.ts index 7478fa0ee8..044e69e1b0 100644 --- a/front/src/interfaces/company.interface.test.ts +++ b/front/src/interfaces/company.interface.test.ts @@ -50,7 +50,7 @@ describe('mapCompany', () => { expect(company.name).toBe('ACME'); expect(company.domain_name).toBe('exmaple.com'); expect(company.creationDate).toEqual(now); - expect(company.accountOwner).toBeUndefined(); + expect(company.accountOwner).toBeNull(); expect(company.employees).toBe(10); expect(company.address).toBe( '1 Infinite Loop, 95014 Cupertino, California, USA', diff --git a/front/src/interfaces/company.interface.ts b/front/src/interfaces/company.interface.ts index 631d2b66a5..c6974655ac 100644 --- a/front/src/interfaces/company.interface.ts +++ b/front/src/interfaces/company.interface.ts @@ -54,7 +54,7 @@ export const mapCompany = (company: GraphqlQueryCompany): Company => ({ email: company.account_owner.email, displayName: company.account_owner.displayName, } - : undefined, + : null, creationDate: new Date(company.created_at), opportunities: [], }); diff --git a/front/src/interfaces/person.interface.test.ts b/front/src/interfaces/person.interface.test.ts index dad7124cb1..0022372008 100644 --- a/front/src/interfaces/person.interface.test.ts +++ b/front/src/interfaces/person.interface.test.ts @@ -44,7 +44,6 @@ describe('mapPerson', () => { name: '', icon: '', }, - countryCode: '', }); expect(person.firstname).toBe('John'); }); diff --git a/front/src/interfaces/person.interface.ts b/front/src/interfaces/person.interface.ts index 249849fc07..e9f7397018 100644 --- a/front/src/interfaces/person.interface.ts +++ b/front/src/interfaces/person.interface.ts @@ -7,12 +7,11 @@ export type Person = { lastname: string; picture?: string; email: string; - company: PartialCompany; + company: PartialCompany | null; phone: string; creationDate: Date; - pipe: Pipe; + pipe: Pipe | null; city: string; - countryCode: string; }; export type GraphqlQueryPerson = { @@ -45,7 +44,10 @@ export type GraphqlMutationPerson = { }; export const mapPerson = (person: GraphqlQueryPerson): Person => ({ - ...person, + id: person.id, + email: person.email, + phone: person.phone, + city: person.city, firstname: person.firstname, lastname: person.lastname, creationDate: new Date(person.created_at), @@ -54,12 +56,13 @@ export const mapPerson = (person: GraphqlQueryPerson): Person => ({ id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', icon: '💰', }, - company: { - id: person.company.id, - name: person.company.name, - domain_name: person.company.domain_name, - }, - countryCode: 'FR', + company: person.company + ? { + id: person.company.id, + name: person.company.name, + domain_name: person.company.domain_name, + } + : null, }); export const mapGqlPerson = (person: Person): GraphqlMutationPerson => ({ @@ -67,6 +70,6 @@ export const mapGqlPerson = (person: Person): GraphqlMutationPerson => ({ firstname: person.firstname, lastname: person.lastname, created_at: person.creationDate.toUTCString(), - company_id: person.company.id, + company_id: person.company?.id, __typename: 'People', }); diff --git a/front/src/pages/companies/__stories__/Companies.stories.tsx b/front/src/pages/companies/__stories__/Companies.stories.tsx index 7c106c7275..7f3bcff0cb 100644 --- a/front/src/pages/companies/__stories__/Companies.stories.tsx +++ b/front/src/pages/companies/__stories__/Companies.stories.tsx @@ -28,6 +28,20 @@ const mocks = [ }, }, }, + { + request: { + query: GET_COMPANIES, + variables: { + orderBy: [{ created_at: 'desc' }], + where: {}, + }, + }, + result: { + data: { + companies: mockData, + }, + }, + }, ]; export const CompaniesDefault = () => ( diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx index 54e85a7912..72096340ce 100644 --- a/front/src/pages/people/People.tsx +++ b/front/src/pages/people/People.tsx @@ -1,17 +1,19 @@ import { FaRegUser, FaList } from 'react-icons/fa'; import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; import Table from '../../components/table/Table'; +import { v4 as uuidv4 } from 'uuid'; import styled from '@emotion/styled'; import { availableFilters, - peopleColumns, availableSorts, + usePeopleColumns, } from './people-table'; -import { mapPerson } from '../../interfaces/person.interface'; -import { useCallback, useState } from 'react'; +import { Person, mapPerson } from '../../interfaces/person.interface'; +import { useCallback, useEffect, useState } from 'react'; import { PeopleSelectedSortType, defaultOrderBy, + insertPerson, usePeopleQuery, } from '../../services/people'; import { useSearch } from '../../services/search/search'; @@ -32,6 +34,7 @@ function People() { const [orderBy, setOrderBy] = useState(defaultOrderBy); const [where, setWhere] = useState({}); const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch(); + const [internalData, setInternalData] = useState>([]); const updateSorts = useCallback((sorts: Array) => { setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy); @@ -44,11 +47,34 @@ function People() { [], ); - const addEmptyRow = useCallback(() => { - console.log('add row'); - }, []); + const { data, loading, refetch } = usePeopleQuery(orderBy, where); - const { data } = usePeopleQuery(orderBy, where); + useEffect(() => { + if (!loading) { + if (data) { + setInternalData(data.people.map(mapPerson)); + } + } + }, [loading, setInternalData, data]); + + const addEmptyRow = useCallback(() => { + const newCompany: Person = { + id: uuidv4(), + firstname: '', + lastname: '', + email: '', + phone: '', + company: null, + pipe: null, + creationDate: new Date(), + city: '', + }; + insertPerson(newCompany); + setInternalData([newCompany, ...internalData]); + refetch(); + }, [internalData, setInternalData, refetch]); + + const peopleColumns = usePeopleColumns(); return ( { } diff --git a/front/src/pages/people/__stories__/People.stories.tsx b/front/src/pages/people/__stories__/People.stories.tsx index b39ae841a4..d8f9c228c5 100644 --- a/front/src/pages/people/__stories__/People.stories.tsx +++ b/front/src/pages/people/__stories__/People.stories.tsx @@ -29,6 +29,20 @@ const mocks = [ }, }, }, + { + request: { + query: GET_PEOPLE, + variables: { + orderBy: [{ created_at: 'desc' }], + where: {}, + }, + }, + result: { + data: { + people: mockData, + }, + }, + }, { request: { query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters diff --git a/front/src/pages/people/__tests__/People.test.tsx b/front/src/pages/people/__tests__/People.test.tsx index dadb19b7cb..b698a6f486 100644 --- a/front/src/pages/people/__tests__/People.test.tsx +++ b/front/src/pages/people/__tests__/People.test.tsx @@ -83,3 +83,24 @@ it('Checks people email edit is updating data', async () => { expect(getByText('john@linkedin.c')).toBeInTheDocument(); }); }); + +it('Checks insert data is appending a new line', async () => { + const { getByText, getByTestId, container } = render(); + + await waitFor(() => { + expect(getByText('John Doe')).toBeDefined(); + }); + const tableRows = container.querySelectorAll('table tbody tr'); + + expect(tableRows.length).toBe(4); + + act(() => { + fireEvent.click(getByTestId('add-button')); + }); + + await waitFor(() => { + const tableRows = container.querySelectorAll('table tbody tr'); + + expect(tableRows.length).toBe(5); + }); +}); diff --git a/front/src/pages/people/people-table.tsx b/front/src/pages/people/people-table.tsx index b2766b864d..9ec0bff23a 100644 --- a/front/src/pages/people/people-table.tsx +++ b/front/src/pages/people/people-table.tsx @@ -38,6 +38,7 @@ import EditableFullName from '../../components/table/editable-cell/EditableFullN import EditableDate from '../../components/table/editable-cell/EditableDate'; import EditableRelation from '../../components/table/editable-cell/EditableRelation'; import { updatePerson } from '../../services/people'; +import { useMemo } from 'react'; export const availableSorts = [ { @@ -242,135 +243,150 @@ export const availableFilters = [ ] satisfies FilterType[]; const columnHelper = createColumnHelper(); -export const peopleColumns = [ - columnHelper.accessor('id', { - header: () => ( - - ), - cell: (props) => ( - - ), - }), - columnHelper.accessor('firstname', { - header: () => } />, - cell: (props) => ( - { - const person = props.row.original; - person.firstname = firstName; - person.lastname = lastName; - updatePerson(person); - }} - /> - ), - }), - columnHelper.accessor('email', { - header: () => } />, - cell: (props) => ( - { - const person = props.row.original; - person.email = value; - updatePerson(person); - }} - /> - ), - }), - columnHelper.accessor('company', { - header: () => ( - } /> - ), - cell: (props) => ( - - relation={props.row.original.company} - searchPlaceholder="Company" - ChipComponent={CompanyChip} - chipComponentPropsMapper={( - company: PartialCompany, - ): CompanyChipPropsType => { - return { - name: company.name, - picture: `https://www.google.com/s2/favicons?domain=${company.domain_name}&sz=256`, - }; - }} - changeHandler={(relation: PartialCompany) => { - const person = props.row.original; - person.company.id = relation.id; - updatePerson(person); - }} - searchFilter={ - { - key: 'company_name', - label: 'Company', - icon: , - whereTemplate: () => { - return {}; - }, - searchQuery: SEARCH_COMPANY_QUERY, - searchTemplate: (searchInput: string) => ({ - name: { _ilike: `%${searchInput}%` }, - }), - searchResultMapper: (company: GraphqlQueryCompany) => ({ - displayValue: company.name, - value: { - id: company.id, + +export const usePeopleColumns = () => { + return useMemo(() => { + return [ + columnHelper.accessor('id', { + header: () => ( + + ), + cell: (props) => ( + + ), + }), + columnHelper.accessor('firstname', { + header: () => } />, + cell: (props) => ( + { + const person = props.row.original; + person.firstname = firstName; + person.lastname = lastName; + updatePerson(person); + }} + /> + ), + }), + columnHelper.accessor('email', { + header: () => } />, + cell: (props) => ( + { + const person = props.row.original; + person.email = value; + updatePerson(person); + }} + /> + ), + }), + columnHelper.accessor('company', { + header: () => ( + } /> + ), + cell: (props) => ( + + relation={props.row.original.company} + searchPlaceholder="Company" + ChipComponent={CompanyChip} + chipComponentPropsMapper={( + company: PartialCompany, + ): CompanyChipPropsType => { + return { name: company.name, - domain_name: company.domain_name, - }, - }), - operands: [], - } satisfies FilterType - } - /> - ), - }), - columnHelper.accessor('phone', { - header: () => } />, - cell: (props) => ( - { - const person = props.row.original; - person.phone = value; - updatePerson(person); - }} - /> - ), - }), - columnHelper.accessor('creationDate', { - header: () => } />, - cell: (props) => ( - { - const person = props.row.original; - person.creationDate = value; - updatePerson(person); - }} - /> - ), - }), - columnHelper.accessor('city', { - header: () => } />, - cell: (props) => ( - { - const person = props.row.original; - person.city = value; - updatePerson(person); - }} - /> - ), - }), -]; + picture: `https://www.google.com/s2/favicons?domain=${company.domain_name}&sz=256`, + }; + }} + changeHandler={(relation: PartialCompany) => { + const person = props.row.original; + if (person.company) { + person.company.id = relation.id; + } else { + person.company = { + id: relation.id, + name: relation.name, + domain_name: relation.domain_name, + }; + } + updatePerson(person); + }} + searchFilter={ + { + key: 'company_name', + label: 'Company', + icon: , + whereTemplate: () => { + return {}; + }, + searchQuery: SEARCH_COMPANY_QUERY, + searchTemplate: (searchInput: string) => ({ + name: { _ilike: `%${searchInput}%` }, + }), + searchResultMapper: (company: GraphqlQueryCompany) => ({ + displayValue: company.name, + value: { + id: company.id, + name: company.name, + domain_name: company.domain_name, + }, + }), + operands: [], + } satisfies FilterType + } + /> + ), + }), + columnHelper.accessor('phone', { + header: () => } />, + cell: (props) => ( + { + const person = props.row.original; + person.phone = value; + updatePerson(person); + }} + /> + ), + }), + columnHelper.accessor('creationDate', { + header: () => ( + } /> + ), + cell: (props) => ( + { + const person = props.row.original; + person.creationDate = value; + updatePerson(person); + }} + /> + ), + }), + columnHelper.accessor('city', { + header: () => } />, + cell: (props) => ( + { + const person = props.row.original; + person.city = value; + updatePerson(person); + }} + /> + ), + }), + ]; + }, []); +}; diff --git a/front/src/services/people/update.ts b/front/src/services/people/update.ts index f88755b131..f82a7cfb3d 100644 --- a/front/src/services/people/update.ts +++ b/front/src/services/people/update.ts @@ -44,6 +44,48 @@ export const UPDATE_PERSON = gql` } `; +export const INSERT_PERSON = gql` + mutation InsertPerson( + $id: uuid + $firstname: String + $lastname: String + $phone: String + $city: String + $company_id: uuid + $email: String + $created_at: timestamptz + ) { + insert_people( + objects: { + id: $id + firstname: $firstname + lastname: $lastname + phone: $phone + city: $city + company_id: $company_id + email: $email + created_at: $created_at + } + ) { + affected_rows + returning { + city + company { + domain_name + name + id + } + email + firstname + id + lastname + phone + created_at + } + } + } +`; + export async function updatePerson( person: Person, ): Promise> { @@ -53,3 +95,14 @@ export async function updatePerson( }); return result; } + +export async function insertPerson( + person: Person, +): Promise> { + const result = await apiClient.mutate({ + mutation: INSERT_PERSON, + variables: mapGqlPerson(person), + }); + + return result; +} diff --git a/hasura/metadata/databases/default/tables/public_people.yaml b/hasura/metadata/databases/default/tables/public_people.yaml index 677793b94c..bf531f60c9 100644 --- a/hasura/metadata/databases/default/tables/public_people.yaml +++ b/hasura/metadata/databases/default/tables/public_people.yaml @@ -14,6 +14,8 @@ insert_permissions: check: workspace_id: _eq: x-hasura-workspace-id + set: + workspace_id: x-hasura-Workspace-Id columns: - city - email