Refactor fast follow on column move feature (#1665)
* Refactor fast follow on column move feature * Fix lint
This commit is contained in:
@ -5,11 +5,8 @@ export {
|
|||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconArchive,
|
IconArchive,
|
||||||
IconArrowBack,
|
IconArrowBack,
|
||||||
IconArrowNarrowDown,
|
|
||||||
IconArrowNarrowLeft,
|
|
||||||
IconArrowNarrowRight,
|
|
||||||
IconArrowNarrowUp,
|
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconArrowUp,
|
IconArrowUp,
|
||||||
IconArrowUpRight,
|
IconArrowUpRight,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||||
|
|
||||||
import { ColumnHeaderDropdownId } from '../constants/ColumnHeaderDropdownId';
|
import { ColumnHeadDropdownId } from '../constants/ColumnHeadDropdownId';
|
||||||
import { ColumnDefinition } from '../types/ColumnDefinition';
|
import { ColumnDefinition } from '../types/ColumnDefinition';
|
||||||
|
|
||||||
import { EntityTableHeaderOptions } from './EntityTableHeaderOptions';
|
import { EntityTableHeaderOptions } from './EntityTableHeaderOptions';
|
||||||
@ -49,7 +49,7 @@ export const ColumnHead = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { openDropdownButton } = useDropdownButton({
|
const { openDropdownButton } = useDropdownButton({
|
||||||
dropdownId: ColumnHeaderDropdownId,
|
dropdownId: ColumnHeadDropdownId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -166,21 +166,22 @@ export const EntityTableHeader = () => {
|
|||||||
</th>
|
</th>
|
||||||
|
|
||||||
{visibleTableColumns.map((column, index) => (
|
{visibleTableColumns.map((column, index) => (
|
||||||
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
|
<StyledColumnHeaderCell
|
||||||
<StyledColumnHeaderCell
|
key={column.key}
|
||||||
key={column.key}
|
isResizing={resizedFieldKey === column.key}
|
||||||
isResizing={resizedFieldKey === column.key}
|
columnWidth={Math.max(
|
||||||
columnWidth={Math.max(
|
tableColumnsByKey[column.key].size +
|
||||||
tableColumnsByKey[column.key].size +
|
(resizedFieldKey === column.key ? resizeFieldOffset : 0),
|
||||||
(resizedFieldKey === column.key ? resizeFieldOffset : 0),
|
COLUMN_MIN_WIDTH,
|
||||||
COLUMN_MIN_WIDTH,
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
|
||||||
<ColumnHead
|
<ColumnHead
|
||||||
column={column}
|
column={column}
|
||||||
isFirstColumn={index === 0}
|
isFirstColumn={index === 0}
|
||||||
isLastColumn={index === visibleTableColumns.length - 1}
|
isLastColumn={index === visibleTableColumns.length - 1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledResizeHandler
|
<StyledResizeHandler
|
||||||
className="cursor-col-resize"
|
className="cursor-col-resize"
|
||||||
role="separator"
|
role="separator"
|
||||||
@ -188,8 +189,8 @@ export const EntityTableHeader = () => {
|
|||||||
setResizedFieldKey(column.key);
|
setResizedFieldKey(column.key);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</StyledColumnHeaderCell>
|
</RecoilScope>
|
||||||
</RecoilScope>
|
</StyledColumnHeaderCell>
|
||||||
))}
|
))}
|
||||||
<th>
|
<th>
|
||||||
{hiddenTableColumns.length > 0 && (
|
{hiddenTableColumns.length > 0 && (
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
||||||
@ -6,14 +5,10 @@ import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'
|
|||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||||
import {
|
import { IconArrowLeft, IconArrowRight, IconEyeOff } from '@/ui/icon';
|
||||||
IconArrowNarrowLeft,
|
|
||||||
IconArrowNarrowRight,
|
|
||||||
IconEyeOff,
|
|
||||||
} from '@/ui/icon';
|
|
||||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
|
||||||
import { ColumnHeaderDropdownId } from '../constants/ColumnHeaderDropdownId';
|
import { ColumnHeadDropdownId } from '../constants/ColumnHeadDropdownId';
|
||||||
import { useTableColumns } from '../hooks/useTableColumns';
|
import { useTableColumns } from '../hooks/useTableColumns';
|
||||||
import { ColumnDefinition } from '../types/ColumnDefinition';
|
import { ColumnDefinition } from '../types/ColumnDefinition';
|
||||||
|
|
||||||
@ -33,8 +28,6 @@ export const EntityTableHeaderOptions = ({
|
|||||||
isFirstColumn,
|
isFirstColumn,
|
||||||
isLastColumn,
|
isLastColumn,
|
||||||
}: EntityTableHeaderOptionsProps) => {
|
}: EntityTableHeaderOptionsProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleColumnVisibilityChange,
|
handleColumnVisibilityChange,
|
||||||
handleColumnLeftMove,
|
handleColumnLeftMove,
|
||||||
@ -42,19 +35,23 @@ export const EntityTableHeaderOptions = ({
|
|||||||
} = useTableColumns();
|
} = useTableColumns();
|
||||||
|
|
||||||
const { closeDropdownButton } = useDropdownButton({
|
const { closeDropdownButton } = useDropdownButton({
|
||||||
dropdownId: ColumnHeaderDropdownId,
|
dropdownId: ColumnHeadDropdownId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleColumnMoveLeft = () => {
|
const handleColumnMoveLeft = () => {
|
||||||
closeDropdownButton();
|
closeDropdownButton();
|
||||||
if (isFirstColumn) return;
|
if (isFirstColumn) {
|
||||||
else handleColumnLeftMove(column);
|
return;
|
||||||
|
}
|
||||||
|
handleColumnLeftMove(column);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnMoveRight = () => {
|
const handleColumnMoveRight = () => {
|
||||||
closeDropdownButton();
|
closeDropdownButton();
|
||||||
if (isLastColumn) return;
|
if (isLastColumn) {
|
||||||
else handleColumnRightMove(column);
|
return;
|
||||||
|
}
|
||||||
|
handleColumnRightMove(column);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnVisibility = () => {
|
const handleColumnVisibility = () => {
|
||||||
@ -64,26 +61,22 @@ export const EntityTableHeaderOptions = ({
|
|||||||
return (
|
return (
|
||||||
<StyledDropdownContainer>
|
<StyledDropdownContainer>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
dropdownId={ColumnHeaderDropdownId}
|
dropdownId={ColumnHeadDropdownId}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<StyledDropdownMenu>
|
<StyledDropdownMenu>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
LeftIcon={() => (
|
LeftIcon={IconArrowLeft}
|
||||||
<IconArrowNarrowLeft size={theme.icon.size.md} />
|
|
||||||
)}
|
|
||||||
onClick={handleColumnMoveLeft}
|
onClick={handleColumnMoveLeft}
|
||||||
text="Move left"
|
text="Move left"
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
LeftIcon={() => (
|
LeftIcon={IconArrowRight}
|
||||||
<IconArrowNarrowRight size={theme.icon.size.md} />
|
|
||||||
)}
|
|
||||||
onClick={handleColumnMoveRight}
|
onClick={handleColumnMoveRight}
|
||||||
text="Move right"
|
text="Move right"
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
LeftIcon={() => <IconEyeOff size={theme.icon.size.md} />}
|
LeftIcon={IconEyeOff}
|
||||||
onClick={handleColumnVisibility}
|
onClick={handleColumnVisibility}
|
||||||
text="Hide"
|
text="Hide"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const ColumnHeadDropdownId = 'table-head-options';
|
||||||
@ -1 +0,0 @@
|
|||||||
export const ColumnHeaderDropdownId = 'table-header-options';
|
|
||||||
@ -51,29 +51,29 @@ export const useTableColumns = () => {
|
|||||||
|
|
||||||
const handleColumnMove = useCallback(
|
const handleColumnMove = useCallback(
|
||||||
(direction: string, column: ColumnDefinition<ViewFieldMetadata>) => {
|
(direction: string, column: ColumnDefinition<ViewFieldMetadata>) => {
|
||||||
const tableColumnIndex = tableColumns.findIndex(
|
const currentColumnArrayIndex = tableColumns.findIndex(
|
||||||
(tableColumn) => tableColumn.key === column.key,
|
(tableColumn) => tableColumn.key === column.key,
|
||||||
);
|
);
|
||||||
if (tableColumnIndex >= 0) {
|
const targetColumnArrayIndex =
|
||||||
const currentColumn = tableColumns[tableColumnIndex];
|
direction === 'left'
|
||||||
const targetColumn =
|
? currentColumnArrayIndex - 1
|
||||||
direction === 'left'
|
: currentColumnArrayIndex + 1;
|
||||||
? tableColumns[tableColumnIndex - 1]
|
|
||||||
: tableColumns[tableColumnIndex + 1];
|
|
||||||
const updatedColumns = tableColumns
|
|
||||||
.map((tableColumn) => {
|
|
||||||
switch (tableColumn.key) {
|
|
||||||
case targetColumn.key:
|
|
||||||
return { ...tableColumn, index: currentColumn.index };
|
|
||||||
case currentColumn.key:
|
|
||||||
return { ...tableColumn, index: targetColumn.index };
|
|
||||||
default:
|
|
||||||
return tableColumn;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sort((columnA, columnB) => columnA.index - columnB.index);
|
|
||||||
|
|
||||||
setTableColumns(updatedColumns);
|
if (currentColumnArrayIndex >= 0) {
|
||||||
|
const currentColumn = tableColumns[currentColumnArrayIndex];
|
||||||
|
const targetColumn = tableColumns[targetColumnArrayIndex];
|
||||||
|
|
||||||
|
const newTableColumns = [...tableColumns];
|
||||||
|
newTableColumns[currentColumnArrayIndex] = {
|
||||||
|
...targetColumn,
|
||||||
|
index: currentColumn.index,
|
||||||
|
};
|
||||||
|
newTableColumns[targetColumnArrayIndex] = {
|
||||||
|
...currentColumn,
|
||||||
|
index: targetColumn.index,
|
||||||
|
};
|
||||||
|
|
||||||
|
setTableColumns(newTableColumns);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[tableColumns, setTableColumns],
|
[tableColumns, setTableColumns],
|
||||||
|
|||||||
@ -164,6 +164,7 @@ export const useTableViewFields = ({
|
|||||||
(column) =>
|
(column) =>
|
||||||
savedTableColumnsByKey[column.key] &&
|
savedTableColumnsByKey[column.key] &&
|
||||||
(savedTableColumnsByKey[column.key].size !== column.size ||
|
(savedTableColumnsByKey[column.key].size !== column.size ||
|
||||||
|
savedTableColumnsByKey[column.key].index !== column.index ||
|
||||||
savedTableColumnsByKey[column.key].isVisible !== column.isVisible),
|
savedTableColumnsByKey[column.key].isVisible !== column.isVisible),
|
||||||
);
|
);
|
||||||
await updateViewFields(viewFieldsToUpdate);
|
await updateViewFields(viewFieldsToUpdate);
|
||||||
@ -178,5 +179,5 @@ export const useTableViewFields = ({
|
|||||||
updateViewFields,
|
updateViewFields,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { createViewFields, persistColumns, updateViewFields };
|
return { createViewFields, persistColumns };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -41,12 +41,11 @@ export const useTableViews = ({
|
|||||||
type: ViewType.Table,
|
type: ViewType.Table,
|
||||||
RecoilScopeContext: TableRecoilScopeContext,
|
RecoilScopeContext: TableRecoilScopeContext,
|
||||||
});
|
});
|
||||||
const { createViewFields, persistColumns, updateViewFields } =
|
const { createViewFields, persistColumns } = useTableViewFields({
|
||||||
useTableViewFields({
|
objectId,
|
||||||
objectId,
|
columnDefinitions,
|
||||||
columnDefinitions,
|
skipFetch: isFetchingViews,
|
||||||
skipFetch: isFetchingViews,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const { createViewFilters, persistFilters } = useViewFilters({
|
const { createViewFilters, persistFilters } = useViewFilters({
|
||||||
RecoilScopeContext: TableRecoilScopeContext,
|
RecoilScopeContext: TableRecoilScopeContext,
|
||||||
@ -62,7 +61,6 @@ export const useTableViews = ({
|
|||||||
await persistColumns();
|
await persistColumns();
|
||||||
await persistFilters();
|
await persistFilters();
|
||||||
await persistSorts();
|
await persistSorts();
|
||||||
await updateViewFields(tableColumns);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { createView, deleteView, submitCurrentView, updateView };
|
return { createView, deleteView, submitCurrentView, updateView };
|
||||||
|
|||||||
@ -46,17 +46,16 @@ export const FilterByName: Story = {
|
|||||||
delay: 200,
|
delay: 200,
|
||||||
});
|
});
|
||||||
|
|
||||||
await sleep(50);
|
await sleep(200);
|
||||||
|
|
||||||
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
|
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
|
||||||
expect(await canvas.findByText('Aircall')).toBeInTheDocument();
|
expect(await canvas.findByText('Aircall')).toBeInTheDocument();
|
||||||
await sleep(50);
|
expect(await canvas.queryAllByText('Qonto')).toStrictEqual([]);
|
||||||
await expect(canvas.queryAllByText('Qonto')).toStrictEqual([]);
|
|
||||||
|
|
||||||
const accountOwnerFilter = canvas.getAllByText('Name').find((item) => {
|
const nameFilter = canvas.getAllByText('Name').find((item) => {
|
||||||
return item.parentElement?.textContent?.includes('Name: Air');
|
return item.parentElement?.textContent?.includes('Name: Air');
|
||||||
});
|
});
|
||||||
expect(accountOwnerFilter).toBeInTheDocument();
|
expect(nameFilter).toBeInTheDocument();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -280,7 +280,7 @@ export const SelectRelationWithKeys: Story = {
|
|||||||
|
|
||||||
await userEvent.type(relationInput, '{enter}');
|
await userEvent.type(relationInput, '{enter}');
|
||||||
|
|
||||||
await sleep(50);
|
await sleep(200);
|
||||||
|
|
||||||
const allAirbns = await canvas.findAllByText('Aircall');
|
const allAirbns = await canvas.findAllByText('Aircall');
|
||||||
expect(allAirbns.length).toBe(1);
|
expect(allAirbns.length).toBe(1);
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "viewFields" ALTER COLUMN "index" SET DATA TYPE DOUBLE PRECISION;
|
||||||
Reference in New Issue
Block a user