Lucas/refactored table state with recoil (#149)
* Fixed ActionBar paddings and added transition on button hover * Added recoil library for state management * Refactor table state with recoil : - Removed table internal states - Added refetchQueries to plug apollo store directly into tables - Added an action bar component that manages itself - Use recoil state and selector for row selection - Refactored Companies and People tables * Moved hook * Cleaned some files * Fix bug infinite re-compute table row selection --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { FaList } from 'react-icons/fa';
|
||||
import styled from '@emotion/styled';
|
||||
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
|
||||
@ -6,11 +6,10 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
CompaniesSelectedSortType,
|
||||
defaultOrderBy,
|
||||
deleteCompanies,
|
||||
insertCompany,
|
||||
useCompaniesQuery,
|
||||
} from '../../services/api/companies';
|
||||
import Table from '../../components/table/Table';
|
||||
import { EntityTable } from '../../components/table/EntityTable';
|
||||
import {
|
||||
Company,
|
||||
mapToCompany,
|
||||
@ -21,13 +20,14 @@ import {
|
||||
reduceSortsToOrderBy,
|
||||
} from '../../components/table/table-header/helpers';
|
||||
import { CompanyOrderByWithRelationInput as 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';
|
||||
import { TbBuilding } from 'react-icons/tb';
|
||||
import { EntityTableActionBar } from '../../components/table/action-bar/EntityTableActionBar';
|
||||
import { TableActionBarButtonDeleteCompanies } from './table/TableActionBarButtonDeleteCompanies';
|
||||
|
||||
const StyledCompaniesContainer = styled.div`
|
||||
display: flex;
|
||||
@ -37,8 +37,6 @@ const StyledCompaniesContainer = styled.div`
|
||||
function Companies() {
|
||||
const [orderBy, setOrderBy] = useState<Companies_Order_By[]>(defaultOrderBy);
|
||||
const [where, setWhere] = useState<BoolExpType<Company>>({});
|
||||
const [internalData, setInternalData] = useState<Array<Company>>([]);
|
||||
const [selectedRowIds, setSelectedRowIds] = useState<Array<string>>([]);
|
||||
|
||||
const updateSorts = useCallback((sorts: Array<CompaniesSelectedSortType>) => {
|
||||
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
|
||||
@ -51,17 +49,11 @@ function Companies() {
|
||||
[],
|
||||
);
|
||||
|
||||
const { data, loading, refetch } = useCompaniesQuery(orderBy, where);
|
||||
const { data } = useCompaniesQuery(orderBy, where);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
if (data) {
|
||||
setInternalData(data.companies.map(mapToCompany));
|
||||
}
|
||||
}
|
||||
}, [loading, setInternalData, data]);
|
||||
const companies = data?.companies.map(mapToCompany) ?? [];
|
||||
|
||||
const addEmptyRow = useCallback(async () => {
|
||||
async function handleAddButtonClick() {
|
||||
const newCompany: Company = {
|
||||
id: uuidv4(),
|
||||
name: '',
|
||||
@ -73,36 +65,22 @@ function Companies() {
|
||||
accountOwner: null,
|
||||
__typename: 'companies',
|
||||
};
|
||||
await insertCompany(newCompany);
|
||||
setInternalData([newCompany, ...internalData]);
|
||||
refetch();
|
||||
}, [internalData, setInternalData, refetch]);
|
||||
|
||||
const deleteRows = useCallback(async () => {
|
||||
await deleteCompanies(selectedRowIds);
|
||||
setInternalData([
|
||||
...internalData.filter((row) => !selectedRowIds.includes(row.id)),
|
||||
]);
|
||||
refetch();
|
||||
if (tableRef.current) {
|
||||
tableRef.current.resetRowSelection();
|
||||
}
|
||||
}, [internalData, selectedRowIds, refetch]);
|
||||
await insertCompany(newCompany);
|
||||
}
|
||||
|
||||
const companiesColumns = useCompaniesColumns();
|
||||
const tableRef = useRef<{ resetRowSelection: () => void }>();
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title="Companies"
|
||||
icon={<TbBuilding size={16} />}
|
||||
onAddButtonClick={addEmptyRow}
|
||||
onAddButtonClick={handleAddButtonClick}
|
||||
>
|
||||
<>
|
||||
<StyledCompaniesContainer>
|
||||
<Table
|
||||
ref={tableRef}
|
||||
data={internalData}
|
||||
<EntityTable
|
||||
data={companies}
|
||||
columns={companiesColumns}
|
||||
viewName="All Companies"
|
||||
viewIcon={<FaList />}
|
||||
@ -110,10 +88,11 @@ function Companies() {
|
||||
availableFilters={availableFilters}
|
||||
onSortsUpdate={updateSorts}
|
||||
onFiltersUpdate={updateFilters}
|
||||
onRowSelectionChange={setSelectedRowIds}
|
||||
/>
|
||||
</StyledCompaniesContainer>
|
||||
{selectedRowIds.length > 0 && <ActionBar onDeleteClick={deleteRows} />}
|
||||
<EntityTableActionBar>
|
||||
<TableActionBarButtonDeleteCompanies />
|
||||
</EntityTableActionBar>
|
||||
</>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { TbTrash } from 'react-icons/tb';
|
||||
import { EntityTableActionBarButton } from '../../../components/table/action-bar/EntityTableActionBarButton';
|
||||
import { useDeleteCompaniesMutation } from '../../../generated/graphql';
|
||||
import { selectedRowIdsState } from '../../../modules/ui/tables/states/selectedRowIdsState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useResetTableRowSelection } from '../../../modules/ui/tables/hooks/useResetTableRowSelection';
|
||||
|
||||
export function TableActionBarButtonDeleteCompanies() {
|
||||
const selectedRowIds = useRecoilValue(selectedRowIdsState);
|
||||
|
||||
const resetRowSelection = useResetTableRowSelection();
|
||||
|
||||
const [deleteCompanies] = useDeleteCompaniesMutation({
|
||||
refetchQueries: ['GetCompanies'],
|
||||
});
|
||||
|
||||
async function handleDeleteClick() {
|
||||
await deleteCompanies({
|
||||
variables: {
|
||||
ids: selectedRowIds,
|
||||
},
|
||||
});
|
||||
|
||||
resetRowSelection();
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTableActionBarButton
|
||||
label="Delete"
|
||||
icon={<TbTrash size={16} />}
|
||||
onClick={handleDeleteClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { FaList } from 'react-icons/fa';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
|
||||
import Table from '../../components/table/Table';
|
||||
import { EntityTable } from '../../components/table/EntityTable';
|
||||
|
||||
import {
|
||||
Person,
|
||||
@ -13,7 +13,6 @@ import {
|
||||
import {
|
||||
PeopleSelectedSortType,
|
||||
defaultOrderBy,
|
||||
deletePeople,
|
||||
insertPerson,
|
||||
usePeopleQuery,
|
||||
} from '../../services/api/people';
|
||||
@ -21,13 +20,14 @@ 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';
|
||||
import { TbUser } from 'react-icons/tb';
|
||||
import { EntityTableActionBar } from '../../components/table/action-bar/EntityTableActionBar';
|
||||
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
|
||||
|
||||
const StyledPeopleContainer = styled.div`
|
||||
display: flex;
|
||||
@ -38,8 +38,6 @@ const StyledPeopleContainer = styled.div`
|
||||
function People() {
|
||||
const [orderBy, setOrderBy] = useState(defaultOrderBy);
|
||||
const [where, setWhere] = useState<BoolExpType<Person>>({});
|
||||
const [internalData, setInternalData] = useState<Array<Person>>([]);
|
||||
const [selectedRowIds, setSelectedRowIds] = useState<Array<string>>([]);
|
||||
|
||||
const updateSorts = useCallback((sorts: Array<PeopleSelectedSortType>) => {
|
||||
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
|
||||
@ -52,17 +50,11 @@ function People() {
|
||||
[],
|
||||
);
|
||||
|
||||
const { data, loading, refetch } = usePeopleQuery(orderBy, where);
|
||||
const { data } = usePeopleQuery(orderBy, where);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
if (data) {
|
||||
setInternalData(data.people.map(mapToPerson));
|
||||
}
|
||||
}
|
||||
}, [loading, setInternalData, data]);
|
||||
const people = data?.people.map(mapToPerson) ?? [];
|
||||
|
||||
const addEmptyRow = useCallback(async () => {
|
||||
async function handleAddButtonClick() {
|
||||
const newPerson: Person = {
|
||||
__typename: 'people',
|
||||
id: uuidv4(),
|
||||
@ -75,36 +67,22 @@ function People() {
|
||||
creationDate: new Date(),
|
||||
city: '',
|
||||
};
|
||||
|
||||
await insertPerson(newPerson);
|
||||
setInternalData([newPerson, ...internalData]);
|
||||
refetch();
|
||||
}, [internalData, setInternalData, refetch]);
|
||||
}
|
||||
|
||||
const deleteRows = useCallback(async () => {
|
||||
await deletePeople(selectedRowIds);
|
||||
setInternalData([
|
||||
...internalData.filter((row) => !selectedRowIds.includes(row.id)),
|
||||
]);
|
||||
refetch();
|
||||
if (tableRef.current) {
|
||||
tableRef.current.resetRowSelection();
|
||||
}
|
||||
}, [internalData, selectedRowIds, refetch]);
|
||||
|
||||
const tableRef = useRef<{ resetRowSelection: () => void }>();
|
||||
const peopleColumns = usePeopleColumns();
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title="People"
|
||||
icon={<TbUser size={16} />}
|
||||
onAddButtonClick={addEmptyRow}
|
||||
onAddButtonClick={handleAddButtonClick}
|
||||
>
|
||||
<>
|
||||
<StyledPeopleContainer>
|
||||
<Table
|
||||
ref={tableRef}
|
||||
data={internalData}
|
||||
<EntityTable
|
||||
data={people}
|
||||
columns={peopleColumns}
|
||||
viewName="All People"
|
||||
viewIcon={<FaList />}
|
||||
@ -112,10 +90,11 @@ function People() {
|
||||
availableFilters={availableFilters}
|
||||
onSortsUpdate={updateSorts}
|
||||
onFiltersUpdate={updateFilters}
|
||||
onRowSelectionChange={setSelectedRowIds}
|
||||
/>
|
||||
</StyledPeopleContainer>
|
||||
{selectedRowIds.length > 0 && <ActionBar onDeleteClick={deleteRows} />}
|
||||
<EntityTableActionBar>
|
||||
<TableActionBarButtonDeletePeople />
|
||||
</EntityTableActionBar>
|
||||
</>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { TbTrash } from 'react-icons/tb';
|
||||
import { EntityTableActionBarButton } from '../../../components/table/action-bar/EntityTableActionBarButton';
|
||||
import { useDeletePeopleMutation } from '../../../generated/graphql';
|
||||
import { selectedRowIdsState } from '../../../modules/ui/tables/states/selectedRowIdsState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useResetTableRowSelection } from '../../../modules/ui/tables/hooks/useResetTableRowSelection';
|
||||
|
||||
export function TableActionBarButtonDeletePeople() {
|
||||
const selectedRowIds = useRecoilValue(selectedRowIdsState);
|
||||
|
||||
const resetRowSelection = useResetTableRowSelection();
|
||||
|
||||
const [deletePeople] = useDeletePeopleMutation({
|
||||
refetchQueries: ['GetPeople'],
|
||||
});
|
||||
|
||||
async function handleDeleteClick() {
|
||||
await deletePeople({
|
||||
variables: {
|
||||
ids: selectedRowIds,
|
||||
},
|
||||
});
|
||||
|
||||
resetRowSelection();
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTableActionBarButton
|
||||
label="Delete"
|
||||
icon={<TbTrash size={16} />}
|
||||
onClick={handleDeleteClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user