diff --git a/front/src/components/table/Table.tsx b/front/src/components/table/Table.tsx index 061c8c25c..73e606b08 100644 --- a/front/src/components/table/Table.tsx +++ b/front/src/components/table/Table.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import styled from '@emotion/styled'; import { ColumnDef, @@ -7,15 +8,10 @@ import { useReactTable, } from '@tanstack/react-table'; import TableHeader from './table-header/TableHeader'; -import styled from '@emotion/styled'; import { FilterConfigType, SelectedFilterType, } from '../../interfaces/filters/interface'; -import { - SearchableType, - SearchConfigType, -} from '../../interfaces/search/interface'; import { SortType, SelectedSortType } from '../../interfaces/sorts/interface'; declare module 'react' { @@ -34,19 +30,8 @@ type OwnProps< viewIcon?: React.ReactNode; availableSorts?: Array>; availableFilters?: FilterConfigType[]; - filterSearchResults?: { - results: { - render: (value: SearchableType) => string; - value: SearchableType; - }[]; - loading: boolean; - }; onSortsUpdate?: (sorts: Array>) => void; onFiltersUpdate?: (sorts: Array>) => void; - onFilterSearch?: ( - filter: SearchConfigType | null, - searchValue: string, - ) => void; onRowSelectionChange?: (rowSelection: string[]) => void; }; @@ -112,10 +97,8 @@ const Table = < viewIcon, availableSorts, availableFilters, - filterSearchResults, onSortsUpdate, onFiltersUpdate, - onFilterSearch, onRowSelectionChange, }: OwnProps, ref: React.ForwardedRef<{ resetRowSelection: () => void } | undefined>, @@ -156,10 +139,8 @@ const Table = < viewIcon={viewIcon} availableSorts={availableSorts} availableFilters={availableFilters as FilterConfigType[]} - filterSearchResults={filterSearchResults} onSortsUpdate={onSortsUpdate} onFiltersUpdate={onFiltersUpdate} - onFilterSearch={onFilterSearch} /> diff --git a/front/src/components/table/table-header/FilterDropdownButton.tsx b/front/src/components/table/table-header/FilterDropdownButton.tsx index 314c579fd..ba4a2a990 100644 --- a/front/src/components/table/table-header/FilterDropdownButton.tsx +++ b/front/src/components/table/table-header/FilterDropdownButton.tsx @@ -7,31 +7,19 @@ import { SelectedFilterType, } from '../../../interfaces/filters/interface'; import { - SearchConfigType, - SearchableType, -} from '../../../interfaces/search/interface'; + SearchResultsType, + useSearch, +} from '../../../services/api/search/search'; +import { SearchableType } from '../../../interfaces/search/interface'; type OwnProps = { isFilterSelected: boolean; availableFilters: FilterConfigType[]; - filterSearchResults?: { - results: { - render: (value: SearchableType) => string; - value: SearchableType; - }[]; - loading: boolean; - }; onFilterSelect: (filter: SelectedFilterType) => void; - onFilterSearch: ( - filter: SearchConfigType | null, - searchValue: string, - ) => void; }; export const FilterDropdownButton = ({ availableFilters, - filterSearchResults, - onFilterSearch, onFilterSelect, isFilterSelected, }: OwnProps) => { @@ -47,12 +35,14 @@ export const FilterDropdownButton = ({ FilterOperandType | undefined >(undefined); + const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch(); + const resetState = useCallback(() => { setIsOptionUnfolded(false); setSelectedFilter(undefined); setSelectedFilterOperand(undefined); - onFilterSearch(null, ''); - }, [onFilterSearch]); + setFilterSearch(null); + }, [setFilterSearch]); const renderSelectOptionItems = selectedFilter?.operands.map( (filterOperand, index) => ( @@ -69,7 +59,7 @@ export const FilterDropdownButton = ({ ); const renderSearchResults = ( - filterSearchResults: NonNullable['filterSearchResults']>, + filterSearchResults: SearchResultsType, selectedFilter: FilterConfigType, selectedFilterOperand: FilterOperandType, ) => { @@ -108,7 +98,8 @@ export const FilterDropdownButton = ({ onClick={() => { setSelectedFilter(filter); setSelectedFilterOperand(filter.operands[0]); - onFilterSearch(filter.searchConfig, ''); + setFilterSearch(filter.searchConfig); + setSearchInput(''); }} > {filter.icon} @@ -134,9 +125,10 @@ export const FilterDropdownButton = ({ ) => - onFilterSearch(selectedFilter.searchConfig, event.target.value) - } + onChange={(event: ChangeEvent) => { + setFilterSearch(selectedFilter.searchConfig); + setSearchInput(event.target.value); + }} /> {filterSearchResults && diff --git a/front/src/components/table/table-header/TableHeader.tsx b/front/src/components/table/table-header/TableHeader.tsx index 38b00aa87..d1d2e1bd8 100644 --- a/front/src/components/table/table-header/TableHeader.tsx +++ b/front/src/components/table/table-header/TableHeader.tsx @@ -9,10 +9,6 @@ import { FilterConfigType, SelectedFilterType, } from '../../../interfaces/filters/interface'; -import { - SearchableType, - SearchConfigType, -} from '../../../interfaces/search/interface'; import { SortType, SelectedSortType, @@ -23,19 +19,8 @@ type OwnProps = { viewIcon?: ReactNode; availableSorts?: Array>; availableFilters?: FilterConfigType[]; - filterSearchResults?: { - results: { - render: (value: SearchableType) => string; - value: SearchableType; - }[]; - loading: boolean; - }; onSortsUpdate?: (sorts: Array>) => void; onFiltersUpdate?: (sorts: Array>) => void; - onFilterSearch?: ( - filter: SearchConfigType | null, - searchValue: string, - ) => void; }; const StyledContainer = styled.div` @@ -79,10 +64,8 @@ function TableHeader({ viewIcon, availableSorts, availableFilters, - filterSearchResults, onSortsUpdate, onFiltersUpdate, - onFilterSearch, }: OwnProps) { const [sorts, innerSetSorts] = useState>>( [], @@ -128,13 +111,6 @@ function TableHeader({ [onFiltersUpdate, filters], ); - const filterSearch = useCallback( - (filter: SearchConfigType | null, searchValue: string) => { - onFilterSearch && onFilterSearch(filter, searchValue); - }, - [onFilterSearch], - ); - return ( @@ -146,9 +122,7 @@ function TableHeader({ 0} availableFilters={availableFilters || []} - filterSearchResults={filterSearchResults} onFilterSelect={filterSelect} - onFilterSearch={filterSearch} /> isSortSelected={sorts.length > 0} diff --git a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx index d570d448b..f5a5fdb93 100644 --- a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx +++ b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx @@ -3,13 +3,10 @@ import { lightTheme } from '../../../../layout/styles/themes'; import { FilterDropdownButton } from '../FilterDropdownButton'; import styled from '@emotion/styled'; import { useCallback, useState } from 'react'; -import { - SEARCH_PEOPLE_QUERY, - useSearch, -} from '../../../../services/api/search/search'; +import { SEARCH_PEOPLE_QUERY } from '../../../../services/api/search/search'; import { MockedProvider } from '@apollo/client/testing'; import { mockData } from '../../../../pages/people/__tests__/__data__/mock-data'; -import { availableFilters } from '../../../../pages/people/people-table'; +import { availableFilters } from '../../../../pages/people/people-filters'; import { Person } from '../../../../interfaces/entities/person.interface'; import { FilterableFieldsType, @@ -90,7 +87,6 @@ const InnerRegularFilterDropdownButton = ({ setFilter: setFilters, }: OwnProps) => { const [, innerSetFilters] = useState>(); - const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch(); const outerSetFilters = useCallback( (filter: SelectedFilterType) => { @@ -105,11 +101,6 @@ const InnerRegularFilterDropdownButton = ({ availableFilters={availableFilters} isFilterSelected={true} onFilterSelect={outerSetFilters} - filterSearchResults={filterSearchResults} - onFilterSearch={(filter, searchValue) => { - setSearhInput(searchValue); - setFilterSearch(filter); - }} /> ); diff --git a/front/src/components/table/table-header/__stories__/TableHeader.stories.tsx b/front/src/components/table/table-header/__stories__/TableHeader.stories.tsx index 891604ad1..ce55b9dc6 100644 --- a/front/src/components/table/table-header/__stories__/TableHeader.stories.tsx +++ b/front/src/components/table/table-header/__stories__/TableHeader.stories.tsx @@ -3,11 +3,28 @@ import { ThemeProvider } from '@emotion/react'; import { lightTheme } from '../../../../layout/styles/themes'; import { FaRegBuilding, FaCalendar } from 'react-icons/fa'; import { SortType } from '../../../../interfaces/sorts/interface'; +import { MockedProvider } from '@apollo/client/testing'; +import { EMPTY_QUERY } from '../../../../services/api/search/search'; const component = { title: 'TableHeader', component: TableHeader, }; +const mocks = [ + { + request: { + query: EMPTY_QUERY, + variables: { + where: undefined, + }, + }, + result: { + data: { + searchResults: [], + }, + }, + }, +]; export default component; @@ -21,12 +38,14 @@ export const RegularTableHeader = () => { }, ]; return ( - - } - availableSorts={availableSorts} - /> - + + + } + availableSorts={availableSorts} + /> + + ); }; diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx index 0c2a87831..a568d8f6d 100644 --- a/front/src/pages/companies/Companies.tsx +++ b/front/src/pages/companies/Companies.tsx @@ -1,7 +1,7 @@ -import { FaRegBuilding, FaList } from 'react-icons/fa'; -import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; -import styled from '@emotion/styled'; import { useState, useCallback, useEffect, useRef } from 'react'; +import { FaRegBuilding, FaList } from 'react-icons/fa'; +import styled from '@emotion/styled'; +import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; import { v4 as uuidv4 } from 'uuid'; import { CompaniesSelectedSortType, @@ -15,22 +15,18 @@ import { Company, mapToCompany, } from '../../interfaces/entities/company.interface'; -import { - useCompaniesColumns, - availableFilters, - availableSorts, -} from './companies-table'; + import { reduceFiltersToWhere, reduceSortsToOrderBy, } from '../../components/table/table-header/helpers'; -import { - Companies_Bool_Exp, - Companies_Order_By, -} from '../../generated/graphql'; -import { useSearch } from '../../services/api/search/search'; +import { Companies_Order_By } from '../../generated/graphql'; import ActionBar from '../../components/table/action-bar/ActionBar'; import { SelectedFilterType } from '../../interfaces/filters/interface'; +import { BoolExpType } from '../../interfaces/entities/generic.interface'; +import { useCompaniesColumns } from './companies-columns'; +import { availableSorts } from './companies-sorts'; +import { availableFilters } from './companies-filters'; const StyledCompaniesContainer = styled.div` display: flex; @@ -39,12 +35,10 @@ const StyledCompaniesContainer = styled.div` function Companies() { const [orderBy, setOrderBy] = useState(defaultOrderBy); - const [where, setWhere] = useState({}); + const [where, setWhere] = useState>({}); const [internalData, setInternalData] = useState>([]); const [selectedRowIds, setSelectedRowIds] = useState>([]); - const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch(); - const updateSorts = useCallback((sorts: Array) => { setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy); }, []); @@ -113,13 +107,8 @@ function Companies() { viewIcon={} availableSorts={availableSorts} availableFilters={availableFilters} - filterSearchResults={filterSearchResults} onSortsUpdate={updateSorts} onFiltersUpdate={updateFilters} - onFilterSearch={(filter, searchValue) => { - setSearhInput(searchValue); - setFilterSearch(filter); - }} onRowSelectionChange={setSelectedRowIds} /> diff --git a/front/src/pages/companies/companies-table.tsx b/front/src/pages/companies/companies-columns.tsx similarity index 69% rename from front/src/pages/companies/companies-table.tsx rename to front/src/pages/companies/companies-columns.tsx index 398604d86..5794d0ddb 100644 --- a/front/src/pages/companies/companies-table.tsx +++ b/front/src/pages/companies/companies-columns.tsx @@ -1,12 +1,4 @@ -import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { - Company, - mapToCompany, -} from '../../interfaces/entities/company.interface'; -import { updateCompany } from '../../services/api/companies'; -import ColumnHead from '../../components/table/ColumnHead'; -import CompanyChip from '../../components/chips/CompanyChip'; -import EditableText from '../../components/editable-cell/EditableText'; +import { useMemo } from 'react'; import { FaRegBuilding, FaCalendar, @@ -14,126 +6,27 @@ import { FaMapPin, FaRegUser, FaUsers, - FaBuilding, } from 'react-icons/fa'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; + +import { SEARCH_USER_QUERY } from '../../services/api/search/search'; +import { SearchConfigType } from '../../interfaces/search/interface'; + +import { Company } from '../../interfaces/entities/company.interface'; +import { updateCompany } from '../../services/api/companies'; +import { User, mapToUser } from '../../interfaces/entities/user.interface'; + +import ColumnHead from '../../components/table/ColumnHead'; +import Checkbox from '../../components/form/Checkbox'; +import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox'; +import EditableDate from '../../components/editable-cell/EditableDate'; +import EditableRelation from '../../components/editable-cell/EditableRelation'; +import EditableChip from '../../components/editable-cell/EditableChip'; +import EditableText from '../../components/editable-cell/EditableText'; import PersonChip, { PersonChipPropsType, } from '../../components/chips/PersonChip'; -import EditableChip from '../../components/editable-cell/EditableChip'; -import { Companies_Order_By } from '../../generated/graphql'; -import { - SEARCH_COMPANY_QUERY, - SEARCH_USER_QUERY, -} from '../../services/api/search/search'; -import EditableDate from '../../components/editable-cell/EditableDate'; -import EditableRelation from '../../components/editable-cell/EditableRelation'; -import { User, mapToUser } from '../../interfaces/entities/user.interface'; -import { useMemo } from 'react'; -import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox'; -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 = [ - { - key: 'name', - label: 'Name', - icon: , - _type: 'default_sort', - }, - { - key: 'employees', - label: 'Employees', - icon: , - _type: 'default_sort', - }, - { - key: 'domain_name', - label: 'Url', - icon: , - _type: 'default_sort', - }, - { - key: 'address', - label: 'Address', - icon: , - _type: 'default_sort', - }, - { - key: 'created_at', - label: 'Creation', - icon: , - _type: 'default_sort', - }, -] satisfies Array>; - -export const availableFilters = [ - { - key: 'company_name', - label: 'Company', - icon: , - searchConfig: { - query: SEARCH_COMPANY_QUERY, - template: (searchInput) => ({ - name: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (company) => ({ - render: (company) => company.name, - value: mapToCompany(company), - }), - }, - selectedValueRender: (company) => company.name || '', - operands: [ - { - label: 'Equal', - id: 'equal', - whereTemplate: (company) => ({ - name: { _eq: company.name }, - }), - }, - { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (company) => ({ - _not: { name: { _eq: company.name } }, - }), - }, - ], - } satisfies FilterConfigType, - { - key: 'company_domain_name', - label: 'Url', - icon: , - searchConfig: { - query: SEARCH_COMPANY_QUERY, - template: (searchInput) => ({ - name: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (company) => ({ - render: (company) => company.domainName, - value: mapToCompany(company), - }), - }, - selectedValueRender: (company) => company.domainName || '', - operands: [ - { - label: 'Equal', - id: 'equal', - whereTemplate: (company) => ({ - domain_name: { _eq: company.domainName }, - }), - }, - { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (company) => ({ - _not: { domain_name: { _eq: company.domainName } }, - }), - }, - ], - } satisfies FilterConfigType, -]; +import CompanyChip from '../../components/chips/CompanyChip'; const columnHelper = createColumnHelper(); diff --git a/front/src/pages/companies/companies-filters.tsx b/front/src/pages/companies/companies-filters.tsx new file mode 100644 index 000000000..a964b43d5 --- /dev/null +++ b/front/src/pages/companies/companies-filters.tsx @@ -0,0 +1,74 @@ +import { + Company, + mapToCompany, +} from '../../interfaces/entities/company.interface'; +import { FaLink, FaBuilding } from 'react-icons/fa'; +import { SEARCH_COMPANY_QUERY } from '../../services/api/search/search'; +import { FilterConfigType } from '../../interfaces/filters/interface'; + +export const availableFilters = [ + { + key: 'company_name', + label: 'Company', + icon: , + searchConfig: { + query: SEARCH_COMPANY_QUERY, + template: (searchInput) => ({ + name: { _ilike: `%${searchInput}%` }, + }), + resultMapper: (company) => ({ + render: (company) => company.name, + value: mapToCompany(company), + }), + }, + selectedValueRender: (company) => company.name || '', + operands: [ + { + label: 'Equal', + id: 'equal', + whereTemplate: (company) => ({ + name: { _eq: company.name }, + }), + }, + { + label: 'Not equal', + id: 'not-equal', + whereTemplate: (company) => ({ + _not: { name: { _eq: company.name } }, + }), + }, + ], + } satisfies FilterConfigType, + { + key: 'company_domain_name', + label: 'Url', + icon: , + searchConfig: { + query: SEARCH_COMPANY_QUERY, + template: (searchInput) => ({ + name: { _ilike: `%${searchInput}%` }, + }), + resultMapper: (company) => ({ + render: (company) => company.domainName, + value: mapToCompany(company), + }), + }, + selectedValueRender: (company) => company.domainName || '', + operands: [ + { + label: 'Equal', + id: 'equal', + whereTemplate: (company) => ({ + domain_name: { _eq: company.domainName }, + }), + }, + { + label: 'Not equal', + id: 'not-equal', + whereTemplate: (company) => ({ + _not: { domain_name: { _eq: company.domainName } }, + }), + }, + ], + } satisfies FilterConfigType, +]; diff --git a/front/src/pages/companies/companies-sorts.tsx b/front/src/pages/companies/companies-sorts.tsx new file mode 100644 index 000000000..8b2ba982c --- /dev/null +++ b/front/src/pages/companies/companies-sorts.tsx @@ -0,0 +1,42 @@ +import { + FaCalendar, + FaLink, + FaMapPin, + FaUsers, + FaBuilding, +} from 'react-icons/fa'; +import { Companies_Order_By } from '../../generated/graphql'; +import { SortType } from '../../interfaces/sorts/interface'; + +export const availableSorts = [ + { + key: 'name', + label: 'Name', + icon: , + _type: 'default_sort', + }, + { + key: 'employees', + label: 'Employees', + icon: , + _type: 'default_sort', + }, + { + key: 'domain_name', + label: 'Url', + icon: , + _type: 'default_sort', + }, + { + key: 'address', + label: 'Address', + icon: , + _type: 'default_sort', + }, + { + key: 'created_at', + label: 'Creation', + icon: , + _type: 'default_sort', + }, +] satisfies Array>; diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx index 9121170b3..de22d563a 100644 --- a/front/src/pages/people/People.tsx +++ b/front/src/pages/people/People.tsx @@ -1,18 +1,15 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; 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, - availableSorts, - usePeopleColumns, -} from './people-table'; + +import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; +import Table from '../../components/table/Table'; + import { Person, mapToPerson, } from '../../interfaces/entities/person.interface'; -import { useCallback, useEffect, useRef, useState } from 'react'; import { PeopleSelectedSortType, defaultOrderBy, @@ -20,14 +17,16 @@ import { insertPerson, usePeopleQuery, } from '../../services/api/people'; -import { useSearch } from '../../services/api/search/search'; -import { People_Bool_Exp } from '../../generated/graphql'; import { reduceFiltersToWhere, reduceSortsToOrderBy, } from '../../components/table/table-header/helpers'; import ActionBar from '../../components/table/action-bar/ActionBar'; import { SelectedFilterType } from '../../interfaces/filters/interface'; +import { BoolExpType } from '../../interfaces/entities/generic.interface'; +import { usePeopleColumns } from './people-columns'; +import { availableSorts } from './people-sorts'; +import { availableFilters } from './people-filters'; const StyledPeopleContainer = styled.div` display: flex; @@ -37,8 +36,7 @@ const StyledPeopleContainer = styled.div` function People() { const [orderBy, setOrderBy] = useState(defaultOrderBy); - const [where, setWhere] = useState({}); - const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch(); + const [where, setWhere] = useState>({}); const [internalData, setInternalData] = useState>([]); const [selectedRowIds, setSelectedRowIds] = useState>([]); @@ -111,13 +109,8 @@ function People() { viewIcon={} availableSorts={availableSorts} availableFilters={availableFilters} - filterSearchResults={filterSearchResults} onSortsUpdate={updateSorts} onFiltersUpdate={updateFilters} - onFilterSearch={(filter, searchValue) => { - setSearchInput(searchValue); - setFilterSearch(filter); - }} onRowSelectionChange={setSelectedRowIds} /> diff --git a/front/src/pages/people/__tests__/people-filter.test.ts b/front/src/pages/people/__tests__/people-filter.test.ts index 451ca7316..f821045b7 100644 --- a/front/src/pages/people/__tests__/people-filter.test.ts +++ b/front/src/pages/people/__tests__/people-filter.test.ts @@ -1,4 +1,4 @@ -import { cityFilter } from '../people-table'; +import { cityFilter } from '../people-filters'; describe('PeopleFilter', () => { it(`should render the filter ${cityFilter.key}`, () => { @@ -11,8 +11,9 @@ describe('PeopleFilter', () => { lastname: 'Doe', phone: '0123456789', creationDate: new Date(), - pipe: null, + pipes: [], company: null, + __typename: 'people', }), ).toMatchSnapshot(); }); diff --git a/front/src/pages/people/people-table.tsx b/front/src/pages/people/people-columns.tsx similarity index 54% rename from front/src/pages/people/people-table.tsx rename to front/src/pages/people/people-columns.tsx index bd777f415..7b363ff73 100644 --- a/front/src/pages/people/people-table.tsx +++ b/front/src/pages/people/people-columns.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { FaRegBuilding, FaCalendar, @@ -5,233 +6,30 @@ import { FaRegUser, FaMapPin, FaPhone, - FaUser, - FaBuilding, } from 'react-icons/fa'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import ColumnHead from '../../components/table/ColumnHead'; -import Checkbox from '../../components/form/Checkbox'; -import CompanyChip, { - CompanyChipPropsType, -} from '../../components/chips/CompanyChip'; -import { - Person, - mapToPerson, -} from '../../interfaces/entities/person.interface'; -import EditableText from '../../components/editable-cell/EditableText'; -import { Order_By, People_Order_By } from '../../generated/graphql'; -import { - SEARCH_COMPANY_QUERY, - SEARCH_PEOPLE_QUERY, -} from '../../services/api/search/search'; + +import { SEARCH_COMPANY_QUERY } from '../../services/api/search/search'; +import { SearchConfigType } from '../../interfaces/search/interface'; + import { Company, mapToCompany, } from '../../interfaces/entities/company.interface'; +import { Person } from '../../interfaces/entities/person.interface'; +import { updatePerson } from '../../services/api/people'; + +import ColumnHead from '../../components/table/ColumnHead'; +import Checkbox from '../../components/form/Checkbox'; +import { SelectAllCheckbox } from '../../components/table/SelectAllCheckbox'; import EditablePhone from '../../components/editable-cell/EditablePhone'; import EditableFullName from '../../components/editable-cell/EditableFullName'; import EditableDate from '../../components/editable-cell/EditableDate'; +import EditableText from '../../components/editable-cell/EditableText'; import EditableRelation from '../../components/editable-cell/EditableRelation'; -import { updatePerson } from '../../services/api/people'; -import { useMemo } from 'react'; -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 = [ - { - key: 'fullname', - label: 'People', - icon: , - _type: 'custom_sort', - orderByTemplate: (order: Order_By) => ({ - firstname: order, - lastname: order, - }), - }, - { - key: 'company_name', - label: 'Company', - icon: , - _type: 'custom_sort', - orderByTemplate: (order: Order_By) => ({ company: { name: order } }), - }, - { - key: 'email', - label: 'Email', - icon: , - _type: 'default_sort', - }, - { - key: 'phone', - label: 'Phone', - icon: , - _type: 'default_sort', - }, - { - key: 'created_at', - label: 'Created at', - icon: , - _type: 'default_sort', - }, - { - key: 'city', - label: 'City', - icon: , - _type: 'default_sort', - }, -] satisfies Array>; - -export const fullnameFilter = { - key: 'fullname', - label: 'People', - icon: , - searchConfig: { - query: SEARCH_PEOPLE_QUERY, - template: (searchInput: string) => ({ - _or: [ - { firstname: { _ilike: `%${searchInput}%` } }, - { lastname: { _ilike: `%${searchInput}%` } }, - ], - }), - resultMapper: (person) => ({ - render: (person) => `${person.firstname} ${person.lastname}`, - value: mapToPerson(person), - }), - }, - selectedValueRender: (person) => `${person.firstname} ${person.lastname}`, - operands: [ - { - label: 'Equal', - id: 'equal', - whereTemplate: (person) => ({ - _and: [ - { firstname: { _eq: `${person.firstname}` } }, - { lastname: { _eq: `${person.lastname}` } }, - ], - }), - }, - { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (person) => ({ - _not: { - _and: [ - { firstname: { _eq: `${person.firstname}` } }, - { lastname: { _eq: `${person.lastname}` } }, - ], - }, - }), - }, - ], -} satisfies FilterConfigType; - -export const companyFilter = { - key: 'company_name', - label: 'Company', - icon: , - searchConfig: { - query: SEARCH_COMPANY_QUERY, - template: (searchInput: string) => ({ - name: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (data) => ({ - value: mapToCompany(data), - render: (company) => company.name, - }), - }, - selectedValueRender: (company) => company.name || '', - operands: [ - { - label: 'Equal', - id: 'equal', - whereTemplate: (company) => ({ - company: { name: { _eq: company.name } }, - }), - }, - { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (company) => ({ - _not: { company: { name: { _eq: company.name } } }, - }), - }, - ], -} satisfies FilterConfigType; - -export const emailFilter = { - key: 'email', - label: 'Email', - icon: , - searchConfig: { - query: SEARCH_PEOPLE_QUERY, - template: (searchInput: string) => ({ - email: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (person) => ({ - render: (person) => person.email, - value: mapToPerson(person), - }), - }, - operands: [ - { - label: 'Equal', - id: 'equal', - whereTemplate: (person) => ({ - email: { _eq: person.email }, - }), - }, - { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (person) => ({ - _not: { email: { _eq: person.email } }, - }), - }, - ], - selectedValueRender: (person) => person.email || '', -} satisfies FilterConfigType; - -export const cityFilter = { - key: 'city', - label: 'City', - icon: , - searchConfig: { - query: SEARCH_PEOPLE_QUERY, - template: (searchInput: string) => ({ - city: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (person) => ({ - render: (person) => person.city, - value: mapToPerson(person), - }), - }, - operands: [ - { - label: 'Equal', - id: 'equal', - whereTemplate: (person) => ({ - city: { _eq: person.city }, - }), - }, - { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (person) => ({ - _not: { city: { _eq: person.city } }, - }), - }, - ], - selectedValueRender: (person) => person.email || '', -} satisfies FilterConfigType; - -export const availableFilters = [ - fullnameFilter, - companyFilter, - emailFilter, - cityFilter, -] satisfies FilterConfigType[]; +import CompanyChip, { + CompanyChipPropsType, +} from '../../components/chips/CompanyChip'; const columnHelper = createColumnHelper(); diff --git a/front/src/pages/people/people-filters.tsx b/front/src/pages/people/people-filters.tsx new file mode 100644 index 000000000..2e9be22ac --- /dev/null +++ b/front/src/pages/people/people-filters.tsx @@ -0,0 +1,164 @@ +import { FaEnvelope, FaMapPin, FaUser, FaBuilding } from 'react-icons/fa'; +import { + Person, + mapToPerson, +} from '../../interfaces/entities/person.interface'; +import { + SEARCH_COMPANY_QUERY, + SEARCH_PEOPLE_QUERY, +} from '../../services/api/search/search'; +import { + Company, + mapToCompany, +} from '../../interfaces/entities/company.interface'; +import { FilterConfigType } from '../../interfaces/filters/interface'; + +export const fullnameFilter = { + key: 'fullname', + label: 'People', + icon: , + searchConfig: { + query: SEARCH_PEOPLE_QUERY, + template: (searchInput: string) => ({ + _or: [ + { firstname: { _ilike: `%${searchInput}%` } }, + { lastname: { _ilike: `%${searchInput}%` } }, + ], + }), + resultMapper: (person) => ({ + render: (person) => `${person.firstname} ${person.lastname}`, + value: mapToPerson(person), + }), + }, + selectedValueRender: (person) => `${person.firstname} ${person.lastname}`, + operands: [ + { + label: 'Equal', + id: 'equal', + whereTemplate: (person) => ({ + _and: [ + { firstname: { _eq: `${person.firstname}` } }, + { lastname: { _eq: `${person.lastname}` } }, + ], + }), + }, + { + label: 'Not equal', + id: 'not-equal', + whereTemplate: (person) => ({ + _not: { + _and: [ + { firstname: { _eq: `${person.firstname}` } }, + { lastname: { _eq: `${person.lastname}` } }, + ], + }, + }), + }, + ], +} satisfies FilterConfigType; + +export const companyFilter = { + key: 'company_name', + label: 'Company', + icon: , + searchConfig: { + query: SEARCH_COMPANY_QUERY, + template: (searchInput: string) => ({ + name: { _ilike: `%${searchInput}%` }, + }), + resultMapper: (data) => ({ + value: mapToCompany(data), + render: (company) => company.name, + }), + }, + selectedValueRender: (company) => company.name || '', + operands: [ + { + label: 'Equal', + id: 'equal', + whereTemplate: (company) => ({ + company: { name: { _eq: company.name } }, + }), + }, + { + label: 'Not equal', + id: 'not-equal', + whereTemplate: (company) => ({ + _not: { company: { name: { _eq: company.name } } }, + }), + }, + ], +} satisfies FilterConfigType; + +export const emailFilter = { + key: 'email', + label: 'Email', + icon: , + searchConfig: { + query: SEARCH_PEOPLE_QUERY, + template: (searchInput: string) => ({ + email: { _ilike: `%${searchInput}%` }, + }), + resultMapper: (person) => ({ + render: (person) => person.email, + value: mapToPerson(person), + }), + }, + operands: [ + { + label: 'Equal', + id: 'equal', + whereTemplate: (person) => ({ + email: { _eq: person.email }, + }), + }, + { + label: 'Not equal', + id: 'not-equal', + whereTemplate: (person) => ({ + _not: { email: { _eq: person.email } }, + }), + }, + ], + selectedValueRender: (person) => person.email || '', +} satisfies FilterConfigType; + +export const cityFilter = { + key: 'city', + label: 'City', + icon: , + searchConfig: { + query: SEARCH_PEOPLE_QUERY, + template: (searchInput: string) => ({ + city: { _ilike: `%${searchInput}%` }, + }), + resultMapper: (person) => ({ + render: (person) => person.city, + value: mapToPerson(person), + }), + }, + operands: [ + { + label: 'Equal', + id: 'equal', + whereTemplate: (person) => ({ + city: { _eq: person.city }, + }), + }, + { + label: 'Not equal', + id: 'not-equal', + whereTemplate: (person) => ({ + _not: { city: { _eq: person.city } }, + }), + }, + ], + selectedValueRender: (person) => person.email || '', +} satisfies FilterConfigType; + +export const availableFilters = [ + fullnameFilter, + companyFilter, + emailFilter, + cityFilter, +] satisfies FilterConfigType[]; diff --git a/front/src/pages/people/people-sorts.tsx b/front/src/pages/people/people-sorts.tsx new file mode 100644 index 000000000..31c3f5600 --- /dev/null +++ b/front/src/pages/people/people-sorts.tsx @@ -0,0 +1,54 @@ +import { + FaRegBuilding, + FaCalendar, + FaEnvelope, + FaRegUser, + FaMapPin, + FaPhone, +} from 'react-icons/fa'; +import { Order_By, People_Order_By } from '../../generated/graphql'; +import { SortType } from '../../interfaces/sorts/interface'; + +export const availableSorts = [ + { + key: 'fullname', + label: 'People', + icon: , + _type: 'custom_sort', + orderByTemplate: (order: Order_By) => ({ + firstname: order, + lastname: order, + }), + }, + { + key: 'company_name', + label: 'Company', + icon: , + _type: 'custom_sort', + orderByTemplate: (order: Order_By) => ({ company: { name: order } }), + }, + { + key: 'email', + label: 'Email', + icon: , + _type: 'default_sort', + }, + { + key: 'phone', + label: 'Phone', + icon: , + _type: 'default_sort', + }, + { + key: 'created_at', + label: 'Created at', + icon: , + _type: 'default_sort', + }, + { + key: 'city', + label: 'City', + icon: , + _type: 'default_sort', + }, +] satisfies Array>; diff --git a/front/src/services/api/search/search.ts b/front/src/services/api/search/search.ts index ae09d7ec2..9aa216a4e 100644 --- a/front/src/services/api/search/search.ts +++ b/front/src/services/api/search/search.ts @@ -29,7 +29,7 @@ export const SEARCH_USER_QUERY = gql` } `; -const EMPTY_QUERY = gql` +export const EMPTY_QUERY = gql` query EmptyQuery { _ } @@ -58,14 +58,16 @@ const debounce = ( }; }; +export type SearchResultsType = { + results: { + render: (value: T) => string; + value: T; + }[]; + loading: boolean; +}; + export const useSearch = (): [ - { - results: { - render: (value: T) => string; - value: T; - }[]; - loading: boolean; - }, + SearchResultsType, React.Dispatch>, React.Dispatch | null>>, ] => { @@ -87,18 +89,15 @@ export const useSearch = (): [ ); }, [searchConfig, searchInput]); - const searchFilterQueryResults = useQuery( - searchConfig?.query || EMPTY_QUERY, - { - variables: { - where, - limit: 5, - }, - skip: !searchConfig, + const searchQueryResults = useQuery(searchConfig?.query || EMPTY_QUERY, { + variables: { + where, + limit: 5, }, - ); + skip: !searchConfig, + }); - const searchFilterResults = useMemo<{ + const searchResults = useMemo<{ results: { render: (value: T) => string; value: any }[]; loading: boolean; }>(() => { @@ -108,7 +107,7 @@ export const useSearch = (): [ results: [], }; } - if (searchFilterQueryResults.loading) { + if (searchQueryResults.loading) { return { loading: true, results: [], @@ -116,11 +115,11 @@ export const useSearch = (): [ } return { loading: false, - results: searchFilterQueryResults.data.searchResults.map( + results: searchQueryResults.data.searchResults.map( searchConfig.resultMapper, ), }; - }, [searchConfig, searchFilterQueryResults]); + }, [searchConfig, searchQueryResults]); - return [searchFilterResults, debouncedsetSearchInput, setSearchConfig]; + return [searchResults, debouncedsetSearchInput, setSearchConfig]; };