Restructure project (#124)

This commit is contained in:
Charles Bochet
2023-05-17 22:31:16 +02:00
committed by GitHub
parent baca6150f5
commit 434e020846
76 changed files with 295 additions and 304 deletions

View File

@ -7,9 +7,9 @@ import AppLayout from './layout/AppLayout';
import { Routes, Route, Navigate } from 'react-router-dom'; import { Routes, Route, Navigate } from 'react-router-dom';
import RequireAuth from './components/auth/RequireAuth'; import RequireAuth from './components/auth/RequireAuth';
import Opportunities from './pages/opportunities/Opportunities'; import Opportunities from './pages/opportunities/Opportunities';
import { User, mapToUser } from './interfaces/user.interface'; import { User, mapToUser } from './interfaces/entities/user.interface';
import { useGetCurrentUserQuery } from './services/users'; import { useGetCurrentUserQuery } from './services/api/users';
import { getUserIdFromToken } from './services/AuthService'; import { getUserIdFromToken } from './services/auth/AuthService';
function App() { function App() {
const [user, setUser] = useState<User | undefined>(undefined); const [user, setUser] = useState<User | undefined>(undefined);

View File

@ -1,8 +1,8 @@
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import App from '../App'; import App from '../App';
import { GET_CURRENT_USER } from '../services/users'; import { GET_CURRENT_USER } from '../services/api/users';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { GET_PEOPLE } from '../services/people'; import { GET_PEOPLE } from '../services/api/people';
const component = { const component = {
title: 'App', title: 'App',

View File

@ -8,7 +8,7 @@ import {
import { setContext } from '@apollo/client/link/context'; import { setContext } from '@apollo/client/link/context';
import { RestLink } from 'apollo-link-rest'; import { RestLink } from 'apollo-link-rest';
import { onError } from '@apollo/client/link/error'; import { onError } from '@apollo/client/link/error';
import { refreshAccessToken } from './services/AuthService'; import { refreshAccessToken } from './services/auth/AuthService';
const apiLink = createHttpLink({ const apiLink = createHttpLink({
uri: `${process.env.REACT_APP_API_URL}/v1/graphql`, uri: `${process.env.REACT_APP_API_URL}/v1/graphql`,

View File

@ -1,6 +1,6 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { hasAccessToken } from '../../services/AuthService'; import { hasAccessToken } from '../../services/auth/AuthService';
function RequireAuth({ children }: { children: JSX.Element }): JSX.Element { function RequireAuth({ children }: { children: JSX.Element }): JSX.Element {
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Pipe } from '../../interfaces/pipe.interface'; import { Pipe } from '../../interfaces/entities/pipe.interface';
type OwnProps = { type OwnProps = {
opportunity: Pipe; opportunity: Pipe;

View File

@ -1,6 +1,6 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ReactElement, useRef } from 'react'; import { ReactElement, useRef } from 'react';
import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter'; import { useOutsideAlerter } from '../../hooks/useOutsideAlerter';
type OwnProps = { type OwnProps = {
editModeContent: ReactElement; editModeContent: ReactElement;

View File

@ -1,9 +1,9 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { forwardRef, useState } from 'react'; import { forwardRef, useState } from 'react';
import EditableCellWrapper from './EditableCellWrapper'; import EditableCellWrapper from './EditableCellWrapper';
import DatePicker from '../../form/DatePicker'; import DatePicker from '../form/DatePicker';
import { CalendarContainer } from 'react-datepicker'; import { CalendarContainer } from 'react-datepicker';
import { modalBackground } from '../../../layout/styles/themes'; import { modalBackground } from '../../layout/styles/themes';
export type EditableDateProps = { export type EditableDateProps = {
value: Date; value: Date;

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ChangeEvent, useRef, useState } from 'react'; import { ChangeEvent, useRef, useState } from 'react';
import EditableCellWrapper from './EditableCellWrapper'; import EditableCellWrapper from './EditableCellWrapper';
import PersonChip from '../../chips/PersonChip'; import PersonChip from '../chips/PersonChip';
type OwnProps = { type OwnProps = {
firstname: string; firstname: string;

View File

@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { ChangeEvent, MouseEvent, useRef, useState } from 'react'; import { ChangeEvent, MouseEvent, useRef, useState } from 'react';
import EditableCellWrapper from './EditableCellWrapper'; import EditableCellWrapper from './EditableCellWrapper';
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js'; import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js';
import Link from '../../link/Link'; import Link from '../link/Link';
type OwnProps = { type OwnProps = {
placeholder?: string; placeholder?: string;

View File

@ -1,8 +1,11 @@
import { ChangeEvent, ComponentType, useState } from 'react'; import { ChangeEvent, ComponentType, useState } from 'react';
import EditableCellWrapper from './EditableCellWrapper'; import EditableCellWrapper from './EditableCellWrapper';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useSearch } from '../../../services/search/search'; import { useSearch } from '../../services/api/search/search';
import { SearchConfigType, SearchableType } from '../table-header/interface'; import {
SearchConfigType,
SearchableType,
} from '../../interfaces/search/interface';
const StyledEditModeContainer = styled.div` const StyledEditModeContainer = styled.div`
width: 200px; width: 200px;

View File

@ -1,8 +1,8 @@
import EditableChip, { EditableChipProps } from '../EditableChip'; import EditableChip, { EditableChipProps } from '../EditableChip';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
import CompanyChip from '../../../chips/CompanyChip'; import CompanyChip from '../../chips/CompanyChip';
const component = { const component = {
title: 'EditableChip', title: 'EditableChip',

View File

@ -1,6 +1,6 @@
import EditableDate, { EditableDateProps } from '../EditableDate'; import EditableDate, { EditableDateProps } from '../EditableDate';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
const component = { const component = {

View File

@ -1,6 +1,6 @@
import EditableFullName from '../EditableFullName'; import EditableFullName from '../EditableFullName';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';

View File

@ -1,6 +1,6 @@
import EditablePhone from '../EditablePhone'; import EditablePhone from '../EditablePhone';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';

View File

@ -1,16 +1,16 @@
import EditableRelation, { EditableRelationProps } from '../EditableRelation'; import EditableRelation, { EditableRelationProps } from '../EditableRelation';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
import CompanyChip, { CompanyChipPropsType } from '../../../chips/CompanyChip'; import CompanyChip, { CompanyChipPropsType } from '../../chips/CompanyChip';
import { import {
Company, Company,
mapToCompany, mapToCompany,
} from '../../../../interfaces/company.interface'; } from '../../../interfaces/entities/company.interface';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { SEARCH_COMPANY_QUERY } from '../../../../services/search/search'; import { SEARCH_COMPANY_QUERY } from '../../../services/api/search/search';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { SearchConfigType } from '../../table-header/interface'; import { SearchConfigType } from '../../../interfaces/search/interface';
const component = { const component = {
title: 'editable-cell/EditableRelation', title: 'editable-cell/EditableRelation',

View File

@ -1,6 +1,6 @@
import EditableText from '../EditableText'; import EditableText from '../EditableText';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
const component = { const component = {

View File

@ -1,7 +1,7 @@
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import { EditableChipStory } from '../__stories__/EditableChip.stories'; import { EditableChipStory } from '../__stories__/EditableChip.stories';
import CompanyChip from '../../../chips/CompanyChip'; import CompanyChip from '../../chips/CompanyChip';
it('Checks the EditableChip editing event bubbles up', async () => { it('Checks the EditableChip editing event bubbles up', async () => {
const func = jest.fn(() => null); const func = jest.fn(() => null);

View File

@ -1,11 +1,11 @@
import { fireEvent, render, waitFor } from '@testing-library/react'; import { fireEvent, render, waitFor } from '@testing-library/react';
import { EditableRelationStory } from '../__stories__/EditableRelation.stories'; import { EditableRelationStory } from '../__stories__/EditableRelation.stories';
import { CompanyChipPropsType } from '../../../chips/CompanyChip'; import { CompanyChipPropsType } from '../../chips/CompanyChip';
import { EditableRelationProps } from '../EditableRelation'; import { EditableRelationProps } from '../EditableRelation';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { Company } from '../../../../interfaces/company.interface'; import { Company } from '../../../interfaces/company.interface';
it('Checks the EditableRelation editing event bubbles up', async () => { it('Checks the EditableRelation editing event bubbles up', async () => {
const func = jest.fn(() => null); const func = jest.fn(() => null);

View File

@ -1,55 +0,0 @@
import styled from '@emotion/styled';
import * as React from 'react';
import { Link } from 'react-router-dom';
type OwnProps = {
href: string;
children?: React.ReactNode;
};
const StyledClickable = styled.div`
position: relative;
box-sizing: border-box;
height: 32px;
display: flex;
align-items: center;
::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
border: 1px solid ${(props) => props.theme.text20};
box-sizing: border-box;
border-radius: 4px;
pointer-events: none;
display: none;
}
:hover::before {
display: block;
}
a {
color: inherit;
text-decoration: none;
}
`;
const Container = styled.span`
padding-left: ${(props) => props.theme.spacing(2)};
`;
function ClickableCell({ href, children }: OwnProps) {
return (
<StyledClickable>
<Link to={href}>
<Container>{children}</Container>
</Link>
</StyledClickable>
);
}
export default ClickableCell;

View File

@ -10,12 +10,13 @@ import TableHeader from './table-header/TableHeader';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import {
FilterConfigType, FilterConfigType,
SearchConfigType,
SearchableType,
SelectedFilterType, SelectedFilterType,
SelectedSortType, } from '../../interfaces/filters/interface';
SortType, import {
} from './table-header/interface'; SearchableType,
SearchConfigType,
} from '../../interfaces/search/interface';
import { SortType, SelectedSortType } from '../../interfaces/sorts/interface';
declare module 'react' { declare module 'react' {
function forwardRef<T, P = object>( function forwardRef<T, P = object>(

View File

@ -4,10 +4,12 @@ import {
FilterConfigType, FilterConfigType,
FilterOperandType, FilterOperandType,
FilterableFieldsType, FilterableFieldsType,
SelectedFilterType,
} from '../../../interfaces/filters/interface';
import {
SearchConfigType, SearchConfigType,
SearchableType, SearchableType,
SelectedFilterType, } from '../../../interfaces/search/interface';
} from './interface';
type OwnProps<TData extends FilterableFieldsType> = { type OwnProps<TData extends FilterableFieldsType> = {
isFilterSelected: boolean; isFilterSelected: boolean;

View File

@ -4,8 +4,8 @@ import { FaArrowDown, FaArrowUp } from 'react-icons/fa';
import { import {
FilterableFieldsType, FilterableFieldsType,
SelectedFilterType, SelectedFilterType,
SelectedSortType, } from '../../../interfaces/filters/interface';
} from './interface'; import { SelectedSortType } from '../../../interfaces/sorts/interface';
type OwnProps<SortField, TData extends FilterableFieldsType> = { type OwnProps<SortField, TData extends FilterableFieldsType> = {
sorts: Array<SelectedSortType<SortField>>; sorts: Array<SelectedSortType<SortField>>;

View File

@ -1,6 +1,9 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import DropdownButton from './DropdownButton'; import DropdownButton from './DropdownButton';
import { SelectedSortType, SortType } from './interface'; import {
SelectedSortType,
SortType,
} from '../../../interfaces/sorts/interface';
type OwnProps<SortField> = { type OwnProps<SortField> = {
isSortSelected: boolean; isSortSelected: boolean;

View File

@ -1,17 +1,22 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import {
FilterConfigType,
FilterableFieldsType,
SearchConfigType,
SearchableType,
SelectedFilterType,
SelectedSortType,
SortType,
} from './interface';
import { ReactNode, useCallback, useState } from 'react'; import { ReactNode, useCallback, useState } from 'react';
import { SortDropdownButton } from './SortDropdownButton'; import { SortDropdownButton } from './SortDropdownButton';
import { FilterDropdownButton } from './FilterDropdownButton'; import { FilterDropdownButton } from './FilterDropdownButton';
import SortAndFilterBar from './SortAndFilterBar'; import SortAndFilterBar from './SortAndFilterBar';
import {
FilterableFieldsType,
FilterConfigType,
SelectedFilterType,
} from '../../../interfaces/filters/interface';
import {
SearchableType,
SearchConfigType,
} from '../../../interfaces/search/interface';
import {
SortType,
SelectedSortType,
} from '../../../interfaces/sorts/interface';
type OwnProps<SortField, TData extends FilterableFieldsType> = { type OwnProps<SortField, TData extends FilterableFieldsType> = {
viewName: string; viewName: string;

View File

@ -2,16 +2,19 @@ import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../../layout/styles/themes';
import { FilterDropdownButton } from '../FilterDropdownButton'; import { FilterDropdownButton } from '../FilterDropdownButton';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { FilterableFieldsType, SelectedFilterType } from '../interface';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { import {
SEARCH_PEOPLE_QUERY, SEARCH_PEOPLE_QUERY,
useSearch, useSearch,
} from '../../../../services/search/search'; } from '../../../../services/api/search/search';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { mockData } from '../../../../pages/people/__tests__/__data__/mock-data'; import { mockData } from '../../../../pages/people/__tests__/__data__/mock-data';
import { availableFilters } from '../../../../pages/people/people-table'; import { availableFilters } from '../../../../pages/people/people-table';
import { Person } from '../../../../interfaces/person.interface'; import { Person } from '../../../../interfaces/entities/person.interface';
import {
FilterableFieldsType,
SelectedFilterType,
} from '../../../../interfaces/filters/interface';
const component = { const component = {
title: 'FilterDropdownButton', title: 'FilterDropdownButton',

View File

@ -2,8 +2,8 @@ import SortAndFilterBar from '../SortAndFilterBar';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../../layout/styles/themes';
import { FaArrowDown } from 'react-icons/fa'; import { FaArrowDown } from 'react-icons/fa';
import { SelectedFilterType } from '../interface'; import { Person } from '../../../../interfaces/entities/person.interface';
import { Person } from '../../../../interfaces/person.interface'; import { SelectedFilterType } from '../../../../interfaces/filters/interface';
const component = { const component = {
title: 'SortAndFilterBar', title: 'SortAndFilterBar',

View File

@ -1,4 +1,3 @@
import { SortType } from '../interface';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../../layout/styles/themes';
import { import {
@ -12,6 +11,7 @@ import {
import { SortDropdownButton } from '../SortDropdownButton'; import { SortDropdownButton } from '../SortDropdownButton';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Order_By, People_Order_By } from '../../../../generated/graphql'; import { Order_By, People_Order_By } from '../../../../generated/graphql';
import { SortType } from '../../../../interfaces/sorts/interface';
const component = { const component = {
title: 'SortDropdownButton', title: 'SortDropdownButton',

View File

@ -2,7 +2,7 @@ import TableHeader from '../TableHeader';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../../layout/styles/themes';
import { FaRegBuilding, FaCalendar } from 'react-icons/fa'; import { FaRegBuilding, FaCalendar } from 'react-icons/fa';
import { SortType } from '../interface'; import { SortType } from '../../../../interfaces/sorts/interface';
const component = { const component = {
title: 'TableHeader', title: 'TableHeader',

View File

@ -1,11 +1,11 @@
import { Order_By } from '../../../generated/graphql'; import { Order_By } from '../../../generated/graphql';
import { BoolExpType } from '../../../interfaces/entities/generic.interface';
import { import {
BoolExpType,
FilterWhereType,
FilterableFieldsType, FilterableFieldsType,
FilterWhereType,
SelectedFilterType, SelectedFilterType,
SelectedSortType, } from '../../../interfaces/filters/interface';
} from './interface'; import { SelectedSortType } from '../../../interfaces/sorts/interface';
export const reduceFiltersToWhere = < export const reduceFiltersToWhere = <
ValueType extends FilterableFieldsType, ValueType extends FilterableFieldsType,

View File

@ -1,124 +0,0 @@
import { DocumentNode } from 'graphql';
import { ReactNode } from 'react';
import {
Companies_Bool_Exp,
Order_By,
People_Bool_Exp,
Users_Bool_Exp,
} from '../../../generated/graphql';
import {
Company,
GraphqlQueryCompany,
} from '../../../interfaces/company.interface';
import {
GraphqlQueryPerson,
Person,
} from '../../../interfaces/person.interface';
import { GraphqlQueryUser, User } from '../../../interfaces/user.interface';
export type SortType<OrderByTemplate> =
| {
_type: 'default_sort';
label: string;
key: keyof OrderByTemplate & string;
icon?: ReactNode;
}
| {
_type: 'custom_sort';
label: string;
key: string;
icon?: ReactNode;
orderByTemplate: (order: Order_By) => OrderByTemplate;
};
export type SelectedSortType<OrderByTemplate> = SortType<OrderByTemplate> & {
order: 'asc' | 'desc';
};
type AnyEntity = {
id: string;
__typename: string;
} & Record<string, any>;
export type FilterableFieldsType = Person | Company;
export type FilterWhereType = Person | Company | User | AnyEntity;
type FilterConfigGqlType<WhereType> = WhereType extends Company
? GraphqlQueryCompany
: WhereType extends Person
? GraphqlQueryPerson
: WhereType extends User
? GraphqlQueryUser
: never;
export type BoolExpType<T> = T extends Company
? Companies_Bool_Exp
: T extends Person
? People_Bool_Exp
: T extends User
? Users_Bool_Exp
: never;
export type FilterConfigType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType = any,
> = {
key: string;
label: string;
icon: ReactNode;
operands: FilterOperandType<FilteredType, WhereType>[];
searchConfig: WhereType extends SearchableType
? SearchConfigType<WhereType>
: null;
selectedValueRender: (selected: WhereType) => string;
};
export type SearchableType = Person | Company | User;
export type SearchConfigType<SearchType extends SearchableType> = {
query: DocumentNode;
template: (
searchInput: string,
) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp;
resultMapper: (data: FilterConfigGqlType<SearchType>) => {
value: SearchType;
render: (value: SearchType) => ReactNode;
};
};
export type FilterOperandType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType = AnyEntity,
> =
| FilterOperandExactMatchType<FilteredType, WhereType>
| FilterOperandComparativeType<FilteredType, WhereType>;
type FilterOperandExactMatchType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType,
> = {
label: 'Equal' | 'Not equal';
id: 'equal' | 'not-equal';
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
};
type FilterOperandComparativeType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType,
> = {
label: 'Like' | 'Not like' | 'Include';
id: 'like' | 'not_like' | 'include';
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
};
export type SelectedFilterType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType = AnyEntity,
> = {
key: string;
value: WhereType;
displayValue: string;
label: string;
icon: ReactNode;
operand: FilterOperandType<FilteredType, WhereType>;
};

View File

@ -0,0 +1,29 @@
import {
Companies_Bool_Exp,
People_Bool_Exp,
Users_Bool_Exp,
} from '../../generated/graphql';
import { Company, GraphqlQueryCompany } from './company.interface';
import { GraphqlQueryPerson, Person } from './person.interface';
import { GraphqlQueryUser, User } from './user.interface';
export type AnyEntity = {
id: string;
__typename: string;
} & Record<string, any>;
export type GqlType<T> = T extends Company
? GraphqlQueryCompany
: T extends Person
? GraphqlQueryPerson
: T extends User
? GraphqlQueryUser
: never;
export type BoolExpType<T> = T extends Company
? Companies_Bool_Exp
: T extends Person
? People_Bool_Exp
: T extends User
? Users_Bool_Exp
: never;

View File

@ -0,0 +1,60 @@
import { ReactNode } from 'react';
import { SearchConfigType, SearchableType } from '../search/interface';
import { Person } from '../entities/person.interface';
import { Company } from '../entities/company.interface';
import { User } from '../entities/user.interface';
import { AnyEntity, BoolExpType } from '../entities/generic.interface';
export type FilterableFieldsType = Person | Company;
export type FilterWhereType = Person | Company | User | AnyEntity;
export type FilterConfigType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType = any,
> = {
key: string;
label: string;
icon: ReactNode;
operands: FilterOperandType<FilteredType, WhereType>[];
searchConfig: WhereType extends SearchableType
? SearchConfigType<WhereType>
: null;
selectedValueRender: (selected: WhereType) => string;
};
export type FilterOperandType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType = AnyEntity,
> =
| FilterOperandExactMatchType<FilteredType, WhereType>
| FilterOperandComparativeType<FilteredType, WhereType>;
type FilterOperandExactMatchType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType,
> = {
label: 'Equal' | 'Not equal';
id: 'equal' | 'not-equal';
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
};
type FilterOperandComparativeType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType,
> = {
label: 'Like' | 'Not like' | 'Include';
id: 'like' | 'not_like' | 'include';
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
};
export type SelectedFilterType<
FilteredType extends FilterableFieldsType,
WhereType extends FilterWhereType = AnyEntity,
> = {
key: string;
value: WhereType;
displayValue: string;
label: string;
icon: ReactNode;
operand: FilterOperandType<FilteredType, WhereType>;
};

View File

@ -0,0 +1,24 @@
import { DocumentNode } from 'graphql';
import { ReactNode } from 'react';
import {
Companies_Bool_Exp,
People_Bool_Exp,
Users_Bool_Exp,
} from '../../generated/graphql';
import { Person } from '../entities/person.interface';
import { Company } from '../entities/company.interface';
import { User } from '../entities/user.interface';
import { GqlType } from '../entities/generic.interface';
export type SearchableType = Person | Company | User;
export type SearchConfigType<SearchType extends SearchableType> = {
query: DocumentNode;
template: (
searchInput: string,
) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp;
resultMapper: (data: GqlType<SearchType>) => {
value: SearchType;
render: (value: SearchType) => ReactNode;
};
};

View File

@ -0,0 +1,21 @@
import { ReactNode } from 'react';
import { Order_By } from '../../generated/graphql';
export type SortType<OrderByTemplate> =
| {
_type: 'default_sort';
label: string;
key: keyof OrderByTemplate & string;
icon?: ReactNode;
}
| {
_type: 'custom_sort';
label: string;
key: string;
icon?: ReactNode;
orderByTemplate: (order: Order_By) => OrderByTemplate;
};
export type SelectedSortType<OrderByTemplate> = SortType<OrderByTemplate> & {
order: 'asc' | 'desc';
};

View File

@ -1,7 +1,7 @@
import Navbar from './navbar/Navbar'; import Navbar from './navbar/Navbar';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { User } from '../interfaces/user.interface'; import { User } from '../interfaces/entities/user.interface';
import { lightTheme } from './styles/themes'; import { lightTheme } from './styles/themes';
const StyledLayout = styled.div` const StyledLayout = styled.div`

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useMatch, useResolvedPath } from 'react-router-dom'; import { useMatch, useResolvedPath } from 'react-router-dom';
import { User } from '../../interfaces/user.interface'; import { User } from '../../interfaces/entities/user.interface';
import { Workspace } from '../../interfaces/workspace.interface'; import { Workspace } from '../../interfaces/entities/workspace.interface';
import NavItem from './NavItem'; import NavItem from './NavItem';
import NavTitle from './NavTitle'; import NavTitle from './NavTitle';
import WorkspaceContainer from './WorkspaceContainer'; import WorkspaceContainer from './WorkspaceContainer';

View File

@ -1,5 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Workspace } from '../../interfaces/workspace.interface'; import { Workspace } from '../../interfaces/entities/workspace.interface';
type OwnProps = { type OwnProps = {
workspace: Workspace; workspace: Workspace;

View File

@ -1,6 +1,6 @@
import { useSearchParams, useNavigate } from 'react-router-dom'; import { useSearchParams, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { refreshAccessToken } from '../../services/AuthService'; import { refreshAccessToken } from '../../services/auth/AuthService';
function Callback() { function Callback() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { hasAccessToken } from '../../services/AuthService'; import { hasAccessToken } from '../../services/auth/AuthService';
function Login() { function Login() {
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -9,9 +9,12 @@ import {
deleteCompanies, deleteCompanies,
insertCompany, insertCompany,
useCompaniesQuery, useCompaniesQuery,
} from '../../services/companies'; } from '../../services/api/companies';
import Table from '../../components/table/Table'; import Table from '../../components/table/Table';
import { Company, mapToCompany } from '../../interfaces/company.interface'; import {
Company,
mapToCompany,
} from '../../interfaces/entities/company.interface';
import { import {
useCompaniesColumns, useCompaniesColumns,
availableFilters, availableFilters,
@ -25,9 +28,9 @@ import {
Companies_Bool_Exp, Companies_Bool_Exp,
Companies_Order_By, Companies_Order_By,
} from '../../generated/graphql'; } from '../../generated/graphql';
import { SelectedFilterType } from '../../components/table/table-header/interface'; import { useSearch } from '../../services/api/search/search';
import { useSearch } from '../../services/search/search';
import ActionBar from '../../components/table/action-bar/ActionBar'; import ActionBar from '../../components/table/action-bar/ActionBar';
import { SelectedFilterType } from '../../interfaces/filters/interface';
const StyledCompaniesContainer = styled.div` const StyledCompaniesContainer = styled.div`
display: flex; display: flex;

View File

@ -2,11 +2,11 @@ import { MemoryRouter } from 'react-router-dom';
import Companies from '../Companies'; import Companies from '../Companies';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { GET_COMPANIES } from '../../../services/companies'; import { GET_COMPANIES } from '../../../services/api/companies';
import { mockData } from '../__tests__/__data__/mock-data'; import { mockData } from '../__tests__/__data__/mock-data';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { SEARCH_COMPANY_QUERY } from '../../../services/search/search'; import { SEARCH_COMPANY_QUERY } from '../../../services/api/search/search';
import { mockCompanySearchData } from '../../../services/search/__data__/mock-search-data'; import { mockCompanySearchData } from '../../../services/api/search/__data__/mock-search-data';
const component = { const component = {
title: 'Companies', title: 'Companies',

View File

@ -5,11 +5,11 @@ import { act } from 'react-dom/test-utils';
import { import {
GraphqlMutationCompany, GraphqlMutationCompany,
GraphqlQueryCompany, GraphqlQueryCompany,
} from '../../../interfaces/company.interface'; } from '../../../interfaces/entities/company.interface';
jest.mock('../../../apollo', () => { jest.mock('../../../apollo', () => {
const companyInterface = jest.requireActual( const companyInterface = jest.requireActual(
'../../../interfaces/company.interface', '../../../interfaces/entities/company.interface',
); );
return { return {
apiClient: { apiClient: {

View File

@ -1,9 +1,12 @@
import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { CellContext, createColumnHelper } from '@tanstack/react-table';
import { Company, mapToCompany } from '../../interfaces/company.interface'; import {
import { updateCompany } from '../../services/companies'; Company,
mapToCompany,
} from '../../interfaces/entities/company.interface';
import { updateCompany } from '../../services/api/companies';
import ColumnHead from '../../components/table/ColumnHead'; import ColumnHead from '../../components/table/ColumnHead';
import CompanyChip from '../../components/chips/CompanyChip'; import CompanyChip from '../../components/chips/CompanyChip';
import EditableText from '../../components/table/editable-cell/EditableText'; import EditableText from '../../components/editable-cell/EditableText';
import { import {
FaRegBuilding, FaRegBuilding,
FaCalendar, FaCalendar,
@ -16,23 +19,21 @@ import {
import PersonChip, { import PersonChip, {
PersonChipPropsType, PersonChipPropsType,
} from '../../components/chips/PersonChip'; } from '../../components/chips/PersonChip';
import EditableChip from '../../components/table/editable-cell/EditableChip'; import EditableChip from '../../components/editable-cell/EditableChip';
import {
FilterConfigType,
SearchConfigType,
SortType,
} from '../../components/table/table-header/interface';
import { Companies_Order_By } from '../../generated/graphql'; import { Companies_Order_By } from '../../generated/graphql';
import { import {
SEARCH_COMPANY_QUERY, SEARCH_COMPANY_QUERY,
SEARCH_USER_QUERY, SEARCH_USER_QUERY,
} from '../../services/search/search'; } from '../../services/api/search/search';
import EditableDate from '../../components/table/editable-cell/EditableDate'; import EditableDate from '../../components/editable-cell/EditableDate';
import EditableRelation from '../../components/table/editable-cell/EditableRelation'; import EditableRelation from '../../components/editable-cell/EditableRelation';
import { User, mapToUser } from '../../interfaces/user.interface'; import { User, mapToUser } from '../../interfaces/entities/user.interface';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox'; import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox';
import Checkbox from '../../components/form/Checkbox'; import Checkbox from '../../components/form/Checkbox';
import { SortType } from '../../interfaces/sorts/interface';
import { FilterConfigType } from '../../interfaces/filters/interface';
import { SearchConfigType } from '../../interfaces/search/interface';
export const availableSorts = [ export const availableSorts = [
{ {

View File

@ -8,7 +8,10 @@ import {
availableSorts, availableSorts,
usePeopleColumns, usePeopleColumns,
} from './people-table'; } from './people-table';
import { Person, mapToPerson } from '../../interfaces/person.interface'; import {
Person,
mapToPerson,
} from '../../interfaces/entities/person.interface';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { import {
PeopleSelectedSortType, PeopleSelectedSortType,
@ -16,15 +19,15 @@ import {
deletePeople, deletePeople,
insertPerson, insertPerson,
usePeopleQuery, usePeopleQuery,
} from '../../services/people'; } from '../../services/api/people';
import { useSearch } from '../../services/search/search'; import { useSearch } from '../../services/api/search/search';
import { People_Bool_Exp } from '../../generated/graphql'; import { People_Bool_Exp } from '../../generated/graphql';
import { SelectedFilterType } from '../../components/table/table-header/interface';
import { import {
reduceFiltersToWhere, reduceFiltersToWhere,
reduceSortsToOrderBy, reduceSortsToOrderBy,
} from '../../components/table/table-header/helpers'; } from '../../components/table/table-header/helpers';
import ActionBar from '../../components/table/action-bar/ActionBar'; import ActionBar from '../../components/table/action-bar/ActionBar';
import { SelectedFilterType } from '../../interfaces/filters/interface';
const StyledPeopleContainer = styled.div` const StyledPeopleContainer = styled.div`
display: flex; display: flex;

View File

@ -4,8 +4,8 @@ import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes'; import { lightTheme } from '../../../layout/styles/themes';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { mockData } from '../__tests__/__data__/mock-data'; import { mockData } from '../__tests__/__data__/mock-data';
import { GET_PEOPLE } from '../../../services/people'; import { GET_PEOPLE } from '../../../services/api/people';
import { SEARCH_PEOPLE_QUERY } from '../../../services/search/search'; import { SEARCH_PEOPLE_QUERY } from '../../../services/api/search/search';
const component = { const component = {
title: 'People', title: 'People',

View File

@ -5,11 +5,11 @@ import { act } from 'react-dom/test-utils';
import { import {
GraphqlMutationPerson, GraphqlMutationPerson,
GraphqlQueryPerson, GraphqlQueryPerson,
} from '../../../interfaces/person.interface'; } from '../../../interfaces/entities/person.interface';
jest.mock('../../../apollo', () => { jest.mock('../../../apollo', () => {
const personInterface = jest.requireActual( const personInterface = jest.requireActual(
'../../../interfaces/person.interface', '../../../interfaces/entities/person.interface',
); );
return { return {
apiClient: { apiClient: {

View File

@ -14,26 +14,30 @@ import Checkbox from '../../components/form/Checkbox';
import CompanyChip, { import CompanyChip, {
CompanyChipPropsType, CompanyChipPropsType,
} from '../../components/chips/CompanyChip'; } from '../../components/chips/CompanyChip';
import { Person, mapToPerson } from '../../interfaces/person.interface';
import EditableText from '../../components/table/editable-cell/EditableText';
import { import {
FilterConfigType, Person,
SearchConfigType, mapToPerson,
SortType, } from '../../interfaces/entities/person.interface';
} from '../../components/table/table-header/interface'; import EditableText from '../../components/editable-cell/EditableText';
import { Order_By, People_Order_By } from '../../generated/graphql'; import { Order_By, People_Order_By } from '../../generated/graphql';
import { import {
SEARCH_COMPANY_QUERY, SEARCH_COMPANY_QUERY,
SEARCH_PEOPLE_QUERY, SEARCH_PEOPLE_QUERY,
} from '../../services/search/search'; } from '../../services/api/search/search';
import { Company, mapToCompany } from '../../interfaces/company.interface'; import {
import EditablePhone from '../../components/table/editable-cell/EditablePhone'; Company,
import EditableFullName from '../../components/table/editable-cell/EditableFullName'; mapToCompany,
import EditableDate from '../../components/table/editable-cell/EditableDate'; } from '../../interfaces/entities/company.interface';
import EditableRelation from '../../components/table/editable-cell/EditableRelation'; import EditablePhone from '../../components/editable-cell/EditablePhone';
import { updatePerson } from '../../services/people'; import EditableFullName from '../../components/editable-cell/EditableFullName';
import EditableDate from '../../components/editable-cell/EditableDate';
import EditableRelation from '../../components/editable-cell/EditableRelation';
import { updatePerson } from '../../services/api/people';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox'; import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox';
import { SortType } from '../../interfaces/sorts/interface';
import { FilterConfigType } from '../../interfaces/filters/interface';
import { SearchConfigType } from '../../interfaces/search/interface';
export const availableSorts = [ export const availableSorts = [
{ {

View File

@ -1,4 +1,4 @@
import { reduceSortsToOrderBy } from '../../../components/table/table-header/helpers'; import { reduceSortsToOrderBy } from '../../../../components/table/table-header/helpers';
import { CompaniesSelectedSortType } from '../select'; import { CompaniesSelectedSortType } from '../select';
describe('reduceSortsToOrderBy', () => { describe('reduceSortsToOrderBy', () => {

View File

@ -3,9 +3,9 @@ import {
Order_By, Order_By,
Companies_Order_By, Companies_Order_By,
Companies_Bool_Exp, Companies_Bool_Exp,
} from '../../generated/graphql'; } from '../../../generated/graphql';
import { GraphqlQueryCompany } from '../../interfaces/company.interface'; import { GraphqlQueryCompany } from '../../../interfaces/entities/company.interface';
import { SelectedSortType } from '../../components/table/table-header/interface'; import { SelectedSortType } from '../../../interfaces/sorts/interface';
export type CompaniesSelectedSortType = SelectedSortType<Companies_Order_By>; export type CompaniesSelectedSortType = SelectedSortType<Companies_Order_By>;

View File

@ -1,6 +1,9 @@
import { FetchResult, gql } from '@apollo/client'; import { FetchResult, gql } from '@apollo/client';
import { Company, mapToGqlCompany } from '../../interfaces/company.interface'; import {
import { apiClient } from '../../apollo'; Company,
mapToGqlCompany,
} from '../../../interfaces/entities/company.interface';
import { apiClient } from '../../../apollo';
export const UPDATE_COMPANY = gql` export const UPDATE_COMPANY = gql`
mutation UpdateCompany( mutation UpdateCompany(

View File

@ -1,4 +1,4 @@
import { reduceSortsToOrderBy } from '../../../components/table/table-header/helpers'; import { reduceSortsToOrderBy } from '../../../../components/table/table-header/helpers';
import { PeopleSelectedSortType } from '../select'; import { PeopleSelectedSortType } from '../select';
describe('reduceSortsToOrderBy', () => { describe('reduceSortsToOrderBy', () => {

View File

@ -1,12 +1,12 @@
import { import {
GraphqlMutationPerson, GraphqlMutationPerson,
GraphqlQueryPerson, GraphqlQueryPerson,
} from '../../../interfaces/person.interface'; } from '../../../../interfaces/entities/person.interface';
import { updatePerson } from '../update'; import { updatePerson } from '../update';
jest.mock('../../../apollo', () => { jest.mock('../../../../apollo', () => {
const personInterface = jest.requireActual( const personInterface = jest.requireActual(
'../../../interfaces/person.interface', '../../../../interfaces/entities/person.interface',
); );
return { return {
apiClient: { apiClient: {
@ -31,6 +31,7 @@ it('updates a person', async () => {
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
name: 'ACME', name: 'ACME',
domainName: 'example.com', domainName: 'example.com',
__typename: 'companies',
}, },
phone: '+1 (555) 123-4567', phone: '+1 (555) 123-4567',
pipes: [ pipes: [
@ -42,6 +43,7 @@ it('updates a person', async () => {
], ],
creationDate: new Date(), creationDate: new Date(),
city: 'San Francisco', city: 'San Francisco',
__typename: 'people',
}); });
expect(result.data).toBeDefined(); expect(result.data).toBeDefined();
result.data && expect(result.data.email).toBe('john@example.com'); result.data && expect(result.data.email).toBe('john@example.com');

View File

@ -1,11 +1,11 @@
import { QueryResult, gql, useQuery } from '@apollo/client'; import { QueryResult, gql, useQuery } from '@apollo/client';
import { GraphqlQueryPerson } from '../../interfaces/person.interface'; import { GraphqlQueryPerson } from '../../../interfaces/entities/person.interface';
import { import {
Order_By, Order_By,
People_Bool_Exp, People_Bool_Exp,
People_Order_By, People_Order_By,
} from '../../generated/graphql'; } from '../../../generated/graphql';
import { SelectedSortType } from '../../components/table/table-header/interface'; import { SelectedSortType } from '../../../interfaces/sorts/interface';
export type PeopleSelectedSortType = SelectedSortType<People_Order_By>; export type PeopleSelectedSortType = SelectedSortType<People_Order_By>;

View File

@ -1,6 +1,9 @@
import { FetchResult, gql } from '@apollo/client'; import { FetchResult, gql } from '@apollo/client';
import { Person, mapToGqlPerson } from '../../interfaces/person.interface'; import {
import { apiClient } from '../../apollo'; Person,
mapToGqlPerson,
} from '../../../interfaces/entities/person.interface';
import { apiClient } from '../../../apollo';
export const UPDATE_PERSON = gql` export const UPDATE_PERSON = gql`
mutation UpdatePeople( mutation UpdatePeople(

View File

@ -3,7 +3,7 @@ import { useMemo, useState } from 'react';
import { import {
SearchConfigType, SearchConfigType,
SearchableType, SearchableType,
} from '../../components/table/table-header/interface'; } from '../../../interfaces/search/interface';
export const SEARCH_PEOPLE_QUERY = gql` export const SEARCH_PEOPLE_QUERY = gql`
query SearchQuery($where: people_bool_exp, $limit: Int) { query SearchQuery($where: people_bool_exp, $limit: Int) {

View File

@ -1,5 +1,5 @@
import { QueryResult, gql, useQuery } from '@apollo/client'; import { QueryResult, gql, useQuery } from '@apollo/client';
import { GraphqlQueryUser } from '../../interfaces/user.interface'; import { GraphqlQueryUser } from '../../../interfaces/entities/user.interface';
export const GET_CURRENT_USER = gql` export const GET_CURRENT_USER = gql`
query GetCurrentUser($uuid: uuid) { query GetCurrentUser($uuid: uuid) {