From 8f88605f325b71003fe4e361b88b2c7c7d4edf8e Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Sat, 27 May 2023 08:41:26 +0200 Subject: [PATCH] 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 --- front/package-lock.json | 25 +++++++ front/package.json | 1 + .../people/PeopleCompanyCreateCell.tsx | 2 +- .../table/{Table.tsx => EntityTable.tsx} | 68 +++++++------------ .../components/table/action-bar/ActionBar.tsx | 39 ----------- .../table/action-bar/EntityTableActionBar.tsx | 36 ++++++++++ ...ton.tsx => EntityTableActionBarButton.tsx} | 5 +- .../__stories__/ActionBar.stories.tsx | 30 -------- .../action-bar/__tests__/ActionBar.test.tsx | 17 ----- front/src/index.tsx | 17 +++-- .../hooks/useListenClickOutsideArrayOfRef.ts | 2 +- .../tables/hooks/useResetTableRowSelection.ts | 16 +++++ .../ui/tables/states/rowSelectionState.ts | 7 ++ .../ui/tables/states/selectedRowIdsState.ts | 13 ++++ front/src/pages/companies/Companies.tsx | 51 ++++---------- .../TableActionBarButtonDeleteCompanies.tsx | 34 ++++++++++ front/src/pages/people/People.tsx | 51 ++++---------- .../TableActionBarButtonDeletePeople.tsx | 34 ++++++++++ front/src/services/api/companies/update.ts | 1 + front/src/services/api/people/update.ts | 1 + 20 files changed, 238 insertions(+), 212 deletions(-) rename front/src/components/table/{Table.tsx => EntityTable.tsx} (78%) delete mode 100644 front/src/components/table/action-bar/ActionBar.tsx create mode 100644 front/src/components/table/action-bar/EntityTableActionBar.tsx rename front/src/components/table/action-bar/{ActionBarButton.tsx => EntityTableActionBarButton.tsx} (85%) delete mode 100644 front/src/components/table/action-bar/__stories__/ActionBar.stories.tsx delete mode 100644 front/src/components/table/action-bar/__tests__/ActionBar.test.tsx rename front/src/modules/ui/{ => common}/hooks/useListenClickOutsideArrayOfRef.ts (94%) create mode 100644 front/src/modules/ui/tables/hooks/useResetTableRowSelection.ts create mode 100644 front/src/modules/ui/tables/states/rowSelectionState.ts create mode 100644 front/src/modules/ui/tables/states/selectedRowIdsState.ts create mode 100644 front/src/pages/companies/table/TableActionBarButtonDeleteCompanies.tsx create mode 100644 front/src/pages/people/table/TableActionBarButtonDeletePeople.tsx diff --git a/front/package-lock.json b/front/package-lock.json index ce171b419..855f6d477 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -25,6 +25,7 @@ "react-hotkeys-hook": "^4.4.0", "react-icons": "^4.8.0", "react-router-dom": "^6.4.4", + "recoil": "^0.7.7", "uuid": "^9.0.0", "web-vitals": "^2.1.4" }, @@ -15893,6 +15894,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -25407,6 +25413,25 @@ "node": ">= 0.10" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", diff --git a/front/package.json b/front/package.json index eee236ec2..1cfca8c7a 100644 --- a/front/package.json +++ b/front/package.json @@ -20,6 +20,7 @@ "react-hotkeys-hook": "^4.4.0", "react-icons": "^4.8.0", "react-router-dom": "^6.4.4", + "recoil": "^0.7.7", "uuid": "^9.0.0", "web-vitals": "^2.1.4" }, diff --git a/front/src/components/people/PeopleCompanyCreateCell.tsx b/front/src/components/people/PeopleCompanyCreateCell.tsx index 7fd618eba..553fa2991 100644 --- a/front/src/components/people/PeopleCompanyCreateCell.tsx +++ b/front/src/components/people/PeopleCompanyCreateCell.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from 'react'; import { DoubleTextInput } from '../inputs/DoubleTextInput'; -import { useListenClickOutsideArrayOfRef } from '../../modules/ui/hooks/useListenClickOutsideArrayOfRef'; +import { useListenClickOutsideArrayOfRef } from '../../modules/ui/common/hooks/useListenClickOutsideArrayOfRef'; import { useHotkeys } from 'react-hotkeys-hook'; import { CellBaseContainer } from '../editable-cell/CellBaseContainer'; import { CellEditModeContainer } from '../editable-cell/CellEditModeContainer'; diff --git a/front/src/components/table/Table.tsx b/front/src/components/table/EntityTable.tsx similarity index 78% rename from front/src/components/table/Table.tsx rename to front/src/components/table/EntityTable.tsx index bf15efacf..df8b10e10 100644 --- a/front/src/components/table/Table.tsx +++ b/front/src/components/table/EntityTable.tsx @@ -13,12 +13,9 @@ import { SelectedFilterType, } from '../../interfaces/filters/interface'; import { SortType, SelectedSortType } from '../../interfaces/sorts/interface'; - -declare module 'react' { - function forwardRef( - render: (props: P, ref: React.Ref) => React.ReactElement | null, - ): (props: P & React.RefAttributes) => React.ReactElement | null; -} +import { useRecoilState } from 'recoil'; +import { currentRowSelectionState } from '../../modules/ui/tables/states/rowSelectionState'; +import { useResetTableRowSelection } from '../../modules/ui/tables/hooks/useResetTableRowSelection'; type OwnProps< TData extends { id: string; __typename: 'companies' | 'people' }, @@ -87,52 +84,41 @@ const StyledTableScrollableContainer = styled.div` flex: 1; `; -const Table = < +export function EntityTable< TData extends { id: string; __typename: 'companies' | 'people' }, SortField, ->( - { - data, - columns, - viewName, - viewIcon, - availableSorts, - availableFilters, - onSortsUpdate, - onFiltersUpdate, - onRowSelectionChange, - }: OwnProps, - ref: React.ForwardedRef<{ resetRowSelection: () => void } | undefined>, -) => { - const [internalRowSelection, setInternalRowSelection] = React.useState({}); +>({ + data, + columns, + viewName, + viewIcon, + availableSorts, + availableFilters, + onSortsUpdate, + onFiltersUpdate, +}: OwnProps) { + const [currentRowSelection, setCurrentRowSelection] = useRecoilState( + currentRowSelectionState, + ); + + const resetTableRowSelection = useResetTableRowSelection(); + + React.useEffect(() => { + resetTableRowSelection(); + }, [resetTableRowSelection]); const table = useReactTable({ data, columns, state: { - rowSelection: internalRowSelection, + rowSelection: currentRowSelection, }, getCoreRowModel: getCoreRowModel(), enableRowSelection: true, - onRowSelectionChange: setInternalRowSelection, + onRowSelectionChange: setCurrentRowSelection, getRowId: (row) => row.id, }); - const selectedRows = table.getSelectedRowModel().rows; - - React.useEffect(() => { - const selectedRowIds = selectedRows.map((row) => row.original.id); - onRowSelectionChange && onRowSelectionChange(selectedRowIds); - }, [onRowSelectionChange, selectedRows]); - - React.useImperativeHandle(ref, () => { - return { - resetRowSelection: () => { - table.resetRowSelection(); - }, - }; - }); - return ( ); -}; - -export default React.forwardRef(Table); +} diff --git a/front/src/components/table/action-bar/ActionBar.tsx b/front/src/components/table/action-bar/ActionBar.tsx deleted file mode 100644 index 06ab69e03..000000000 --- a/front/src/components/table/action-bar/ActionBar.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import styled from '@emotion/styled'; -import ActionBarButton from './ActionBarButton'; -import { TbTrash } from 'react-icons/tb'; - -type OwnProps = { - onDeleteClick: () => void; -}; - -const StyledContainer = styled.div` - display: flex; - position: absolute; - z-index: 1; - height: 48px; - bottom: 38px; - background: ${(props) => props.theme.secondaryBackground}; - align-items: center; - padding-left: ${(props) => props.theme.spacing(4)}; - padding-right: ${(props) => props.theme.spacing(4)}; - color: ${(props) => props.theme.red}; - left: 50%; - transform: translateX(-50%); - - border-radius: 8px; - border: 1px solid ${(props) => props.theme.primaryBorder}; -`; - -function ActionBar({ onDeleteClick }: OwnProps) { - return ( - - } - onClick={onDeleteClick} - /> - - ); -} - -export default ActionBar; diff --git a/front/src/components/table/action-bar/EntityTableActionBar.tsx b/front/src/components/table/action-bar/EntityTableActionBar.tsx new file mode 100644 index 000000000..70b322bdf --- /dev/null +++ b/front/src/components/table/action-bar/EntityTableActionBar.tsx @@ -0,0 +1,36 @@ +import styled from '@emotion/styled'; +import React from 'react'; +import { useRecoilValue } from 'recoil'; +import { selectedRowIdsState } from '../../../modules/ui/tables/states/selectedRowIdsState'; + +type OwnProps = { + children: React.ReactNode | React.ReactNode[]; +}; + +const StyledContainer = styled.div` + display: flex; + position: absolute; + z-index: 1; + height: 48px; + bottom: 38px; + background: ${(props) => props.theme.secondaryBackground}; + align-items: center; + padding-left: ${(props) => props.theme.spacing(2)}; + padding-right: ${(props) => props.theme.spacing(2)}; + color: ${(props) => props.theme.red}; + left: 50%; + transform: translateX(-50%); + + border-radius: 8px; + border: 1px solid ${(props) => props.theme.primaryBorder}; +`; + +export function EntityTableActionBar({ children }: OwnProps) { + const selectedRowIds = useRecoilValue(selectedRowIdsState); + + if (selectedRowIds.length === 0) { + return <>; + } + + return {children}; +} diff --git a/front/src/components/table/action-bar/ActionBarButton.tsx b/front/src/components/table/action-bar/EntityTableActionBarButton.tsx similarity index 85% rename from front/src/components/table/action-bar/ActionBarButton.tsx rename to front/src/components/table/action-bar/EntityTableActionBarButton.tsx index 46d5d629b..dbd1386af 100644 --- a/front/src/components/table/action-bar/ActionBarButton.tsx +++ b/front/src/components/table/action-bar/EntityTableActionBarButton.tsx @@ -16,6 +16,7 @@ const StyledButton = styled.div` padding: ${(props) => props.theme.spacing(2)}; border-radius: 4px; + transition: background 0.1s ease; &:hover { background: ${(props) => props.theme.tertiaryBackground}; @@ -27,7 +28,7 @@ const StyledButtonLabel = styled.div` font-weight: 500; `; -function ActionBarButton({ label, icon, onClick }: OwnProps) { +export function EntityTableActionBarButton({ label, icon, onClick }: OwnProps) { return ( {icon} @@ -35,5 +36,3 @@ function ActionBarButton({ label, icon, onClick }: OwnProps) { ); } - -export default ActionBarButton; diff --git a/front/src/components/table/action-bar/__stories__/ActionBar.stories.tsx b/front/src/components/table/action-bar/__stories__/ActionBar.stories.tsx deleted file mode 100644 index 10e1a9c13..000000000 --- a/front/src/components/table/action-bar/__stories__/ActionBar.stories.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import ActionBar from '../ActionBar'; -import { ThemeProvider } from '@emotion/react'; -import { lightTheme } from '../../../../layout/styles/themes'; -import { StoryFn } from '@storybook/react'; - -const component = { - title: 'ActionBar', - component: ActionBar, -}; - -type OwnProps = { - onDeleteClick: () => void; -}; - -export default component; - -const Template: StoryFn = (args: OwnProps) => { - return ( - - - - ); -}; - -export const ActionBarStory = Template.bind({}); -ActionBarStory.args = { - onDeleteClick: () => { - console.log('deleted'); - }, -}; diff --git a/front/src/components/table/action-bar/__tests__/ActionBar.test.tsx b/front/src/components/table/action-bar/__tests__/ActionBar.test.tsx deleted file mode 100644 index 9f6f4f158..000000000 --- a/front/src/components/table/action-bar/__tests__/ActionBar.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { fireEvent, render } from '@testing-library/react'; - -import { ActionBarStory } from '../__stories__/ActionBar.stories'; -import { act } from 'react-dom/test-utils'; - -it('Checks the ActionBar editing event bubbles up', async () => { - const deleteFunc = jest.fn(() => null); - const { getByText } = render(); - - expect(getByText('Delete')).toBeInTheDocument(); - - act(() => { - fireEvent.click(getByText('Delete')); - }); - - expect(deleteFunc).toHaveBeenCalled(); -}); diff --git a/front/src/index.tsx b/front/src/index.tsx index b57549182..399a9083f 100644 --- a/front/src/index.tsx +++ b/front/src/index.tsx @@ -7,18 +7,21 @@ import { ApolloProvider } from '@apollo/client'; import '@emotion/react'; import { ThemeType } from './layout/styles/themes'; import { apiClient } from './apollo'; +import { RecoilRoot } from 'recoil'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, ); root.render( - - - - - - - , + + + + + + + + + , ); declare module '@emotion/react' { diff --git a/front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts b/front/src/modules/ui/common/hooks/useListenClickOutsideArrayOfRef.ts similarity index 94% rename from front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts rename to front/src/modules/ui/common/hooks/useListenClickOutsideArrayOfRef.ts index 37b3aa1c4..b902bd5fe 100644 --- a/front/src/modules/ui/hooks/useListenClickOutsideArrayOfRef.ts +++ b/front/src/modules/ui/common/hooks/useListenClickOutsideArrayOfRef.ts @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { isDefined } from '../../utils/type-guards/isDefined'; +import { isDefined } from '../../../utils/type-guards/isDefined'; export function useListenClickOutsideArrayOfRef( arrayOfRef: Array>, diff --git a/front/src/modules/ui/tables/hooks/useResetTableRowSelection.ts b/front/src/modules/ui/tables/hooks/useResetTableRowSelection.ts new file mode 100644 index 000000000..139051f34 --- /dev/null +++ b/front/src/modules/ui/tables/hooks/useResetTableRowSelection.ts @@ -0,0 +1,16 @@ +import { useSetRecoilState } from 'recoil'; +import { currentRowSelectionState } from '../states/rowSelectionState'; +import { useCallback, useMemo } from 'react'; + +export function useResetTableRowSelection() { + const setCurrentRowSelectionState = useSetRecoilState( + currentRowSelectionState, + ); + + return useCallback( + function resetCurrentRowSelection() { + setCurrentRowSelectionState({}); + }, + [setCurrentRowSelectionState], + ); +} diff --git a/front/src/modules/ui/tables/states/rowSelectionState.ts b/front/src/modules/ui/tables/states/rowSelectionState.ts new file mode 100644 index 000000000..101bc9c12 --- /dev/null +++ b/front/src/modules/ui/tables/states/rowSelectionState.ts @@ -0,0 +1,7 @@ +import { RowSelectionState } from '@tanstack/react-table'; +import { atom } from 'recoil'; + +export const currentRowSelectionState = atom({ + key: 'ui/table-row-selection-state', + default: {}, +}); diff --git a/front/src/modules/ui/tables/states/selectedRowIdsState.ts b/front/src/modules/ui/tables/states/selectedRowIdsState.ts new file mode 100644 index 000000000..bdf3c606e --- /dev/null +++ b/front/src/modules/ui/tables/states/selectedRowIdsState.ts @@ -0,0 +1,13 @@ +import { selector } from 'recoil'; +import { currentRowSelectionState } from './rowSelectionState'; + +export const selectedRowIdsState = selector({ + key: 'ui/table-selected-row-ids', + get: ({ get }) => { + const currentRowSelection = get(currentRowSelectionState); + + return Object.keys(currentRowSelection).filter( + (key) => currentRowSelection[key] === true, + ); + }, +}); diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx index dfcce99d5..7e9346c47 100644 --- a/front/src/pages/companies/Companies.tsx +++ b/front/src/pages/companies/Companies.tsx @@ -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(defaultOrderBy); const [where, setWhere] = useState>({}); - const [internalData, setInternalData] = useState>([]); - const [selectedRowIds, setSelectedRowIds] = useState>([]); const updateSorts = useCallback((sorts: Array) => { 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 ( } - onAddButtonClick={addEmptyRow} + onAddButtonClick={handleAddButtonClick} > <> - } @@ -110,10 +88,11 @@ function Companies() { availableFilters={availableFilters} onSortsUpdate={updateSorts} onFiltersUpdate={updateFilters} - onRowSelectionChange={setSelectedRowIds} /> - {selectedRowIds.length > 0 && } + + + ); diff --git a/front/src/pages/companies/table/TableActionBarButtonDeleteCompanies.tsx b/front/src/pages/companies/table/TableActionBarButtonDeleteCompanies.tsx new file mode 100644 index 000000000..fb8fa0f3a --- /dev/null +++ b/front/src/pages/companies/table/TableActionBarButtonDeleteCompanies.tsx @@ -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 ( + } + onClick={handleDeleteClick} + /> + ); +} diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx index e1dd484e0..05181b159 100644 --- a/front/src/pages/people/People.tsx +++ b/front/src/pages/people/People.tsx @@ -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>({}); - const [internalData, setInternalData] = useState>([]); - const [selectedRowIds, setSelectedRowIds] = useState>([]); const updateSorts = useCallback((sorts: Array) => { 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 ( } - onAddButtonClick={addEmptyRow} + onAddButtonClick={handleAddButtonClick} > <> -
} @@ -112,10 +90,11 @@ function People() { availableFilters={availableFilters} onSortsUpdate={updateSorts} onFiltersUpdate={updateFilters} - onRowSelectionChange={setSelectedRowIds} /> - {selectedRowIds.length > 0 && } + + + ); diff --git a/front/src/pages/people/table/TableActionBarButtonDeletePeople.tsx b/front/src/pages/people/table/TableActionBarButtonDeletePeople.tsx new file mode 100644 index 000000000..0d5c73ca0 --- /dev/null +++ b/front/src/pages/people/table/TableActionBarButtonDeletePeople.tsx @@ -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 ( + } + onClick={handleDeleteClick} + /> + ); +} diff --git a/front/src/services/api/companies/update.ts b/front/src/services/api/companies/update.ts index 682452061..416e7b9e2 100644 --- a/front/src/services/api/companies/update.ts +++ b/front/src/services/api/companies/update.ts @@ -94,6 +94,7 @@ export async function insertCompany( const result = await apiClient.mutate({ mutation: INSERT_COMPANY, variables: mapToGqlCompany(company), + refetchQueries: ['GetCompanies'], }); return result; diff --git a/front/src/services/api/people/update.ts b/front/src/services/api/people/update.ts index 5aa485bcf..37cbcca66 100644 --- a/front/src/services/api/people/update.ts +++ b/front/src/services/api/people/update.ts @@ -106,6 +106,7 @@ export async function insertPerson( const result = await apiClient.mutate({ mutation: INSERT_PERSON, variables: mapToGqlPerson(person), + refetchQueries: ['GetPeople'], }); return result;