feat: change column visibility on add (#1174)

* feat: change column visibility on add

* refactor: extract views business logic from table
This commit is contained in:
Thaïs
2023-08-11 21:38:20 +02:00
committed by GitHub
parent e61c263b1a
commit 3978ef4edb
27 changed files with 353 additions and 466 deletions

View File

@ -1,137 +0,0 @@
import { useCallback, useState } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import { useRecoilValue } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon';
import {
hiddenViewFieldsState,
visibleViewFieldsState,
} from '@/ui/table/states/viewFieldsState';
import { useUpdateViewFieldMutation } from '~/generated/graphql';
import { GET_VIEW_FIELDS } from '../queries/select';
import { OptionsDropdownSection } from './OptionsDropdownSection';
type OptionsDropdownButtonProps = {
HotkeyScope: FiltersHotkeyScope;
};
enum Option {
Properties = 'Properties',
}
export const OptionsDropdownButton = ({
HotkeyScope,
}: OptionsDropdownButtonProps) => {
const theme = useTheme();
const [isUnfolded, setIsUnfolded] = useState(false);
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
undefined,
);
const visibleFields = useRecoilValue(visibleViewFieldsState);
const hiddenFields = useRecoilValue(hiddenViewFieldsState);
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const handleViewFieldVisibilityChange = useCallback(
(viewFieldId: string, nextIsVisible: boolean) => {
updateViewFieldMutation({
variables: {
data: { isVisible: nextIsVisible },
where: { id: viewFieldId },
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
[updateViewFieldMutation],
);
const renderFieldActions = useCallback(
(viewField: ViewFieldDefinition<ViewFieldMetadata>) =>
// Do not allow hiding last visible column
!viewField.isVisible || visibleFields.length > 1 ? (
<IconButton
icon={
viewField.isVisible ? (
<IconMinus size={theme.icon.size.sm} />
) : (
<IconPlus size={theme.icon.size.sm} />
)
}
onClick={() =>
handleViewFieldVisibilityChange(viewField.id, !viewField.isVisible)
}
/>
) : undefined,
[handleViewFieldVisibilityChange, theme.icon.size.sm, visibleFields.length],
);
const resetSelectedOption = useCallback(() => {
setSelectedOption(undefined);
}, []);
return (
<DropdownButton
label="Options"
isActive={false}
isUnfolded={isUnfolded}
onIsUnfoldedChange={setIsUnfolded}
HotkeyScope={HotkeyScope}
>
{!selectedOption && (
<>
<DropdownMenuHeader>View settings</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<DropdownMenuItem
onClick={() => setSelectedOption(Option.Properties)}
>
<IconTag size={theme.icon.size.md} />
Properties
</DropdownMenuItem>
</DropdownMenuItemsContainer>
</>
)}
{selectedOption === Option.Properties && (
<>
<DropdownMenuHeader
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
onClick={resetSelectedOption}
>
Properties
</DropdownMenuHeader>
<DropdownMenuSeparator />
<OptionsDropdownSection
renderActions={renderFieldActions}
title="Visible"
viewFields={visibleFields}
/>
{hiddenFields.length > 0 && (
<>
<DropdownMenuSeparator />
<OptionsDropdownSection
renderActions={renderFieldActions}
title="Hidden"
viewFields={hiddenFields}
/>
</>
)}
</>
)}
</DropdownButton>
);
};

View File

@ -1,46 +0,0 @@
import { cloneElement } from 'react';
import { useTheme } from '@emotion/react';
import {
DropdownMenuItem,
DropdownMenuItemProps,
} from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSubheader } from '@/ui/dropdown/components/DropdownMenuSubheader';
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
type OptionsDropdownSectionProps = {
renderActions: (
viewField: ViewFieldDefinition<ViewFieldMetadata>,
) => DropdownMenuItemProps['actions'];
title: string;
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
};
export const OptionsDropdownSection = ({
renderActions,
title,
viewFields,
}: OptionsDropdownSectionProps) => {
const theme = useTheme();
return (
<>
<DropdownMenuSubheader>{title}</DropdownMenuSubheader>
<DropdownMenuItemsContainer>
{viewFields.map((viewField) => (
<DropdownMenuItem actions={renderActions(viewField)}>
{viewField.columnIcon &&
cloneElement(viewField.columnIcon, {
size: theme.icon.size.md,
})}
{viewField.columnLabel}
</DropdownMenuItem>
))}
</DropdownMenuItemsContainer>
</>
);
};

View File

@ -0,0 +1,146 @@
import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldTextMetadata,
} from '@/ui/editable-field/types/ViewField';
import {
tableColumnsByIdState,
tableColumnsState,
} from '@/ui/table/states/tableColumnsState';
import { currentViewIdState } from '@/views/states/currentViewIdState';
import {
SortOrder,
useCreateViewFieldsMutation,
useGetViewFieldsQuery,
useUpdateViewFieldMutation,
} from '~/generated/graphql';
import { GET_VIEW_FIELDS } from '../queries/select';
const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = {
type: 'text',
placeHolder: '',
fieldName: '',
};
export const toViewFieldInput = (
objectName: 'company' | 'person',
viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>,
) => ({
fieldName: viewFieldDefinition.columnLabel,
index: viewFieldDefinition.columnOrder,
isVisible: viewFieldDefinition.isVisible ?? true,
objectName,
sizeInPx: viewFieldDefinition.columnSize,
});
export const useTableViewFields = ({
objectName,
viewFieldDefinitions,
}: {
objectName: 'company' | 'person';
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
}) => {
const currentViewId = useRecoilValue(currentViewIdState);
const setColumns = useSetRecoilState(tableColumnsState);
const columnsById = useRecoilValue(tableColumnsByIdState);
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const createViewFields = useCallback(
(columns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
if (!columns.length) return;
return createViewFieldsMutation({
variables: {
data: columns.map((column) => ({
...toViewFieldInput(objectName, column),
viewId: currentViewId,
})),
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
[createViewFieldsMutation, currentViewId, objectName],
);
const updateViewFields = useCallback(
(columns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
if (!columns.length) return;
return Promise.all(
columns.map((column) =>
updateViewFieldMutation({
variables: {
data: {
isVisible: column.isVisible,
sizeInPx: column.columnSize,
},
where: { id: column.id },
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
}),
),
);
},
[updateViewFieldMutation],
);
useGetViewFieldsQuery({
variables: {
orderBy: { index: SortOrder.Asc },
where: {
objectName: { equals: objectName },
viewId: { equals: currentViewId ?? null },
},
},
onCompleted: (data) => {
if (data.viewFields.length) {
setColumns(
data.viewFields.map<ViewFieldDefinition<ViewFieldMetadata>>(
(viewField) => ({
...(viewFieldDefinitions.find(
({ columnLabel }) => viewField.fieldName === columnLabel,
) || { metadata: DEFAULT_VIEW_FIELD_METADATA }),
id: viewField.id,
columnLabel: viewField.fieldName,
columnOrder: viewField.index,
columnSize: viewField.sizeInPx,
isVisible: viewField.isVisible,
}),
),
);
return;
}
// Populate if empty
createViewFields(viewFieldDefinitions);
},
});
const handleColumnsChange = useCallback(
async (nextColumns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
const viewFieldsToCreate = nextColumns.filter(
(nextColumn) => !columnsById[nextColumn.id],
);
await createViewFields(viewFieldsToCreate);
const viewFieldsToUpdate = nextColumns.filter(
(nextColumn) =>
columnsById[nextColumn.id] &&
(columnsById[nextColumn.id].columnSize !== nextColumn.columnSize ||
columnsById[nextColumn.id].isVisible !== nextColumn.isVisible),
);
await updateViewFields(viewFieldsToUpdate);
},
[columnsById, createViewFields, updateViewFields],
);
return { handleColumnsChange };
};

View File

@ -1,17 +1,5 @@
import { gql } from '@apollo/client';
export const CREATE_VIEW_FIELD = gql`
mutation CreateViewField($data: ViewFieldCreateInput!) {
createOneViewField(data: $data) {
id
fieldName
isVisible
sizeInPx
index
}
}
`;
export const CREATE_VIEW_FIELDS = gql`
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
createManyViewField(data: $data) {