Companies table (#79)
* Add columns to companies: * account_owner_id * employees * address Add foreign key constraint companies_account_owner_id_fkey to auth.users.id * Add select permissions to: * account_owner_id * employees * address Add relationship between companies and auth.users. * Update Companies interface to include: * account_owner_id * employees * address Opportunity is expected to be replace by actual opportunity in a separate PR. * Add GetCompanies query * Add initial companies table * Update test to use mock apollo provider * Update to match changed company column names * Add company interface mapping tests * Test entire object * Add test for companies being rendered in table. * Add test for sorting reduce. * Fix prettier errors
This commit is contained in:
@ -1,10 +1,45 @@
|
||||
import { faBuildings } from '@fortawesome/pro-regular-svg-icons';
|
||||
import { faBuildings, faList } from '@fortawesome/pro-regular-svg-icons';
|
||||
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState, useCallback } from 'react';
|
||||
import {
|
||||
CompaniesSelectedSortType,
|
||||
defaultOrderBy,
|
||||
reduceSortsToOrderBy,
|
||||
useCompaniesQuery,
|
||||
} from '../../services/companies';
|
||||
import Table from '../../components/table/Table';
|
||||
import { mapCompany } from '../../interfaces/company.interface';
|
||||
import { companiesColumns, sortsAvailable } from './companies-table';
|
||||
|
||||
const StyledCompaniesContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
function Companies() {
|
||||
const [, setSorts] = useState([] as Array<CompaniesSelectedSortType>);
|
||||
const [orderBy, setOrderBy] = useState(defaultOrderBy);
|
||||
|
||||
const updateSorts = useCallback((sorts: Array<CompaniesSelectedSortType>) => {
|
||||
setSorts(sorts);
|
||||
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
|
||||
}, []);
|
||||
|
||||
const { data } = useCompaniesQuery(orderBy);
|
||||
|
||||
return (
|
||||
<WithTopBarContainer title="Companies" icon={faBuildings}>
|
||||
<></>
|
||||
<StyledCompaniesContainer>
|
||||
<Table
|
||||
data={data ? data.companies.map(mapCompany) : []}
|
||||
columns={companiesColumns}
|
||||
viewName="All Companies"
|
||||
viewIcon={faList}
|
||||
onSortsUpdate={updateSorts}
|
||||
availableSorts={sortsAvailable}
|
||||
/>
|
||||
</StyledCompaniesContainer>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import Companies from '../Companies';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../../../layout/styles/themes';
|
||||
import { GET_COMPANIES } from '../../../services/companies';
|
||||
import { defaultData } from './mock-data';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
const component = {
|
||||
title: 'Companies',
|
||||
@ -10,10 +13,28 @@ const component = {
|
||||
|
||||
export default component;
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: GET_COMPANIES,
|
||||
variables: {
|
||||
orderBy: [{ name: 'asc' }],
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
companies: defaultData,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const CompaniesDefault = () => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<Companies />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<Companies />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
17
front/src/pages/companies/__stories__/mock-data.ts
Normal file
17
front/src/pages/companies/__stories__/mock-data.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
|
||||
|
||||
export const defaultData: Array<GraphqlQueryCompany> = [
|
||||
{
|
||||
id: 'f121ab32-fac4-4b8c-9a3d-150c877319c2',
|
||||
name: 'ACME',
|
||||
domain_name: 'example.com',
|
||||
account_owner: {
|
||||
id: '91510aa5-ede6-451f-8029-a7fa69e4bad6',
|
||||
email: 'john@example.com',
|
||||
displayName: 'John Doe',
|
||||
},
|
||||
employees: 10,
|
||||
address: '1 Infinity Loop, 95014 Cupertino, California',
|
||||
created_at: new Date().toUTCString(),
|
||||
},
|
||||
];
|
||||
@ -1,10 +1,15 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
|
||||
import { CompaniesDefault } from '../__stories__/Companies.stories';
|
||||
|
||||
it('Checks the Companies page render', () => {
|
||||
it('Checks the Companies page render', async () => {
|
||||
const { getByTestId } = render(<CompaniesDefault />);
|
||||
|
||||
const title = getByTestId('top-bar-title');
|
||||
expect(title).toHaveTextContent('Companies');
|
||||
|
||||
await waitFor(() => {
|
||||
const row = getByTestId('row-id-0');
|
||||
expect(row).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
116
front/src/pages/companies/companies-table.tsx
Normal file
116
front/src/pages/companies/companies-table.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { Company } from '../../interfaces/company.interface';
|
||||
import { OrderByFields } from '../../services/companies';
|
||||
import ColumnHead from '../../components/table/ColumnHead';
|
||||
import HorizontalyAlignedContainer from '../../layout/containers/HorizontalyAlignedContainer';
|
||||
import Checkbox from '../../components/form/Checkbox';
|
||||
import CompanyChip from '../../components/chips/CompanyChip';
|
||||
import EditableCell from '../../components/table/EditableCell';
|
||||
import PipeChip from '../../components/chips/PipeChip';
|
||||
import { faCalendar } from '@fortawesome/pro-regular-svg-icons';
|
||||
import ClickableCell from '../../components/table/ClickableCell';
|
||||
import PersonChip from '../../components/chips/PersonChip';
|
||||
import { SortType } from '../../components/table/table-header/interface';
|
||||
|
||||
export const sortsAvailable = [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
icon: undefined,
|
||||
},
|
||||
{
|
||||
key: 'domain_name',
|
||||
label: 'Domain',
|
||||
icon: undefined,
|
||||
},
|
||||
] satisfies Array<SortType<OrderByFields>>;
|
||||
|
||||
const columnHelper = createColumnHelper<Company>();
|
||||
export const companiesColumns = [
|
||||
columnHelper.accessor('name', {
|
||||
header: () => <ColumnHead viewName="Name" />,
|
||||
cell: (props) => (
|
||||
<HorizontalyAlignedContainer>
|
||||
<Checkbox
|
||||
id={`company-selected-${props.row.original.id}`}
|
||||
name={`company-selected-${props.row.original.id}`}
|
||||
/>
|
||||
<CompanyChip
|
||||
name={props.row.original.name}
|
||||
picture={`https://www.google.com/s2/favicons?domain=${props.row.original.domain_name}&sz=256`}
|
||||
/>
|
||||
</HorizontalyAlignedContainer>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('employees', {
|
||||
header: () => <ColumnHead viewName="Employees" />,
|
||||
cell: (props) => (
|
||||
<EditableCell
|
||||
content={props.row.original.employees.toFixed(0)}
|
||||
changeHandler={(value) => {
|
||||
const company = props.row.original;
|
||||
company.employees = parseInt(value);
|
||||
// TODO: update company
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('domain_name', {
|
||||
header: () => <ColumnHead viewName="URL" />,
|
||||
cell: (props) => (
|
||||
<EditableCell
|
||||
content={props.row.original.domain_name}
|
||||
changeHandler={(value) => {
|
||||
const company = props.row.original;
|
||||
company.domain_name = value;
|
||||
// TODO: update company
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('address', {
|
||||
header: () => <ColumnHead viewName="Address" />,
|
||||
cell: (props) => (
|
||||
<EditableCell
|
||||
content={props.row.original.address}
|
||||
changeHandler={(value) => {
|
||||
const company = props.row.original;
|
||||
company.address = value;
|
||||
// TODO: update company
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('opportunities', {
|
||||
header: () => <ColumnHead viewName="Opportunities" />,
|
||||
cell: (props) => (
|
||||
<HorizontalyAlignedContainer>
|
||||
{props.row.original.opportunities.map((opportunity) => (
|
||||
<PipeChip name={opportunity.name} picture={opportunity.icon} />
|
||||
))}
|
||||
</HorizontalyAlignedContainer>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('creationDate', {
|
||||
header: () => <ColumnHead viewName="Creation" viewIcon={faCalendar} />,
|
||||
cell: (props) => (
|
||||
<ClickableCell href="#">
|
||||
{new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(props.row.original.creationDate)}
|
||||
</ClickableCell>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('accountOwner', {
|
||||
header: () => <ColumnHead viewName="Account Owner" />,
|
||||
cell: (props) => (
|
||||
<HorizontalyAlignedContainer>
|
||||
<PersonChip
|
||||
name={`${props.row.original.accountOwner.first_name} ${props.row.original.accountOwner.last_name}`}
|
||||
/>
|
||||
</HorizontalyAlignedContainer>
|
||||
),
|
||||
}),
|
||||
];
|
||||
Reference in New Issue
Block a user