Feat/pagination front (#2387)

* Finished renaming and scope

* wip

* WIP update

* Ok

* Cleaned

* Finished infinite scroll

* Clean

* Fixed V1 tables

* Fix post merge

* Removed ScrollWrapper

* Put back ScrollWrapper

* Put back in the right place
This commit is contained in:
Lucas Bordeau
2023-11-10 12:43:14 +01:00
committed by GitHub
parent e0289ba9f2
commit 9c29c436b9
29 changed files with 630 additions and 158 deletions

View File

@ -1,78 +1,72 @@
import styled from '@emotion/styled';
import { useVirtual } from '@tanstack/react-virtual';
import { useRecoilValue } from 'recoil';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
import { useFindOneObjectMetadataItem } from '@/metadata/hooks/useFindOneObjectMetadataItem';
import { useTableObjects } from '@/metadata/hooks/useTableObjects';
import { isFetchingMoreObjectsFamilyState } from '@/metadata/states/isFetchingMoreObjectsFamilyState';
import { isDefined } from '~/utils/isDefined';
import { RowIdContext } from '../contexts/RowIdContext';
import { RowIndexContext } from '../contexts/RowIndexContext';
import { useRecordTable } from '../hooks/useRecordTable';
import { isFetchingRecordTableDataState } from '../states/isFetchingRecordTableDataState';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { RecordTableRow } from './RecordTableRow';
type SpaceProps = {
top?: number;
bottom?: number;
};
const StyledSpace = styled.td<SpaceProps>`
${({ top }) => top && `padding-top: ${top}px;`}
${({ bottom }) => bottom && `padding-bottom: ${bottom}px;`}
`;
import { RecordTableRow, StyledRow } from './RecordTableRow';
export const RecordTableBody = () => {
const scrollWrapperRef = useScrollWrapperScopedRef();
const { ref: lastTableRowRef, inView: lastTableRowIsVisible } = useInView();
const tableRowIds = useRecoilValue(tableRowIdsState);
const { scopeId: objectNamePlural } = useRecordTable();
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
objectNamePlural,
});
const [isFetchingMoreObjects] = useRecoilState(
isFetchingMoreObjectsFamilyState(foundObjectMetadataItem?.namePlural),
);
const isFetchingRecordTableData = useRecoilValue(
isFetchingRecordTableDataState,
);
const rowVirtualizer = useVirtual({
size: tableRowIds.length,
parentRef: scrollWrapperRef,
overscan: 50,
});
const { fetchMoreObjects } = useTableObjects();
const items = rowVirtualizer.virtualItems;
const paddingTop = items.length > 0 ? items[0].start : 0;
const paddingBottom =
items.length > 0
? rowVirtualizer.totalSize - items[items.length - 1].end
: 0;
useEffect(() => {
if (lastTableRowIsVisible && isDefined(fetchMoreObjects)) {
fetchMoreObjects();
}
}, [lastTableRowIsVisible, fetchMoreObjects]);
const lastRowId = tableRowIds[tableRowIds.length - 1];
if (isFetchingRecordTableData) {
return null;
return <></>;
}
return (
<tbody>
{paddingTop > 0 && (
<tr>
<StyledSpace top={paddingTop} />
</tr>
)}
{items.map((virtualItem) => {
const rowId = tableRowIds[virtualItem.index];
return (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={virtualItem.index}>
<RecordTableRow
key={virtualItem.index}
ref={virtualItem.measureRef}
rowId={rowId}
/>
</RowIndexContext.Provider>
</RowIdContext.Provider>
);
})}
{paddingBottom > 0 && (
<tr>
<StyledSpace bottom={paddingBottom} />
</tr>
{tableRowIds.map((rowId, rowIndex) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={rowIndex}>
<RecordTableRow
key={rowId}
ref={rowId === lastRowId ? lastTableRowRef : undefined}
rowId={rowId}
/>
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
{isFetchingMoreObjects && (
<StyledRow selected={false}>
<td style={{ height: 50 }} colSpan={1000}>
Fetching more...
</td>
</StyledRow>
)}
</tbody>
);

View File

@ -0,0 +1,32 @@
import { useRecoilValue } from 'recoil';
import { RowIdContext } from '../contexts/RowIdContext';
import { RowIndexContext } from '../contexts/RowIndexContext';
import { isFetchingRecordTableDataState } from '../states/isFetchingRecordTableDataState';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { RecordTableRow } from './RecordTableRow';
export const RecordTableBodyV1 = () => {
const tableRowIds = useRecoilValue(tableRowIdsState);
const isFetchingRecordTableData = useRecoilValue(
isFetchingRecordTableDataState,
);
if (isFetchingRecordTableData) {
return <></>;
}
return (
<tbody>
{tableRowIds.map((rowId, rowIndex) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={rowIndex}>
<RecordTableRow key={rowId} rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
</tbody>
);
};

View File

@ -26,8 +26,7 @@ export const RecordTableEffect = ({
}: {
useGetRequest: typeof useGetCompaniesQuery | typeof useGetPeopleQuery;
getRequestResultKey: string;
getRequestOptimisticEffectDefinition: OptimisticEffectDefinition<any>;
getRequestOptimisticEffectDefinition: OptimisticEffectDefinition;
filterDefinitionArray: FilterDefinition[];
sortDefinitionArray: SortDefinition[];
setActionBarEntries?: () => void;

View File

@ -9,7 +9,7 @@ import { useCurrentRowSelected } from '../record-table-row/hooks/useCurrentRowSe
import { CheckboxCell } from './CheckboxCell';
import { RecordTableCell } from './RecordTableCell';
const StyledRow = styled.tr<{ selected: boolean }>`
export const StyledRow = styled.tr<{ selected: boolean }>`
background: ${(props) =>
props.selected ? props.theme.accent.quaternary : 'none'};
`;

View File

@ -0,0 +1,139 @@
import { useRef } from 'react';
import styled from '@emotion/styled';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
useListenClickOutside,
useListenClickOutsideByClassName,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
import { useRecordTable } from '../hooks/useRecordTable';
import { TableHotkeyScope } from '../types/TableHotkeyScope';
import { RecordTableBodyV1 } from './RecordTableBodyV1';
import { RecordTableHeader } from './RecordTableHeader';
const StyledTable = styled.table`
border-collapse: collapse;
border-radius: ${({ theme }) => theme.border.radius.sm};
border-spacing: 0;
margin-left: ${({ theme }) => theme.table.horizontalCellMargin};
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
table-layout: fixed;
width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2);
th {
border: 1px solid ${({ theme }) => theme.border.color.light};
border-collapse: collapse;
color: ${({ theme }) => theme.font.color.tertiary};
padding: 0;
text-align: left;
:last-child {
border-right-color: transparent;
}
:first-of-type {
border-left-color: transparent;
border-right-color: transparent;
}
:last-of-type {
width: 100%;
}
}
td {
border: 1px solid ${({ theme }) => theme.border.color.light};
border-collapse: collapse;
color: ${({ theme }) => theme.font.color.primary};
padding: 0;
text-align: left;
:last-child {
border-right-color: transparent;
}
:first-of-type {
border-left-color: transparent;
border-right-color: transparent;
}
}
`;
const StyledTableWithHeader = styled.div`
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
`;
const StyledTableContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
position: relative;
`;
type RecordTableV1Props = {
updateEntityMutation: (params: any) => void;
};
export const RecordTableV1 = ({ updateEntityMutation }: RecordTableV1Props) => {
const tableBodyRef = useRef<HTMLDivElement>(null);
const {
leaveTableFocus,
setRowSelectedState,
resetTableRowSelection,
useMapKeyboardToSoftFocus,
} = useRecordTable();
useMapKeyboardToSoftFocus();
useListenClickOutside({
refs: [tableBodyRef],
callback: () => {
leaveTableFocus();
},
});
useScopedHotkeys(
'escape',
() => {
resetTableRowSelection();
},
TableHotkeyScope.Table,
);
useListenClickOutsideByClassName({
classNames: ['entity-table-cell'],
excludeClassNames: ['action-bar', 'context-menu'],
callback: () => {
resetTableRowSelection();
},
});
return (
<EntityUpdateMutationContext.Provider value={updateEntityMutation}>
<StyledTableWithHeader>
<StyledTableContainer>
<div ref={tableBodyRef}>
<StyledTable className="entity-table-cell">
<RecordTableHeader />
<RecordTableBodyV1 />
</StyledTable>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={resetTableRowSelection}
onDragSelectionChange={setRowSelectedState}
/>
</div>
</StyledTableContainer>
</StyledTableWithHeader>
</EntityUpdateMutationContext.Provider>
);
};