mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 09:02:11 +03:00
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:
parent
e61c263b1a
commit
3978ef4edb
@ -3128,13 +3128,6 @@ export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }
|
||||
|
||||
export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } };
|
||||
|
||||
export type CreateViewFieldMutationVariables = Exact<{
|
||||
data: ViewFieldCreateInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateViewFieldMutation = { __typename?: 'Mutation', createOneViewField: { __typename?: 'ViewField', id: string, fieldName: string, isVisible: boolean, sizeInPx: number, index: number } };
|
||||
|
||||
export type CreateViewFieldsMutationVariables = Exact<{
|
||||
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
|
||||
}>;
|
||||
@ -5848,43 +5841,6 @@ export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOp
|
||||
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
|
||||
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
|
||||
export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
||||
export const CreateViewFieldDocument = gql`
|
||||
mutation CreateViewField($data: ViewFieldCreateInput!) {
|
||||
createOneViewField(data: $data) {
|
||||
id
|
||||
fieldName
|
||||
isVisible
|
||||
sizeInPx
|
||||
index
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type CreateViewFieldMutationFn = Apollo.MutationFunction<CreateViewFieldMutation, CreateViewFieldMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateViewFieldMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateViewFieldMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateViewFieldMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [createViewFieldMutation, { data, loading, error }] = useCreateViewFieldMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateViewFieldMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewFieldMutation, CreateViewFieldMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateViewFieldMutation, CreateViewFieldMutationVariables>(CreateViewFieldDocument, options);
|
||||
}
|
||||
export type CreateViewFieldMutationHookResult = ReturnType<typeof useCreateViewFieldMutation>;
|
||||
export type CreateViewFieldMutationResult = Apollo.MutationResult<CreateViewFieldMutation>;
|
||||
export type CreateViewFieldMutationOptions = Apollo.BaseMutationOptions<CreateViewFieldMutation, CreateViewFieldMutationVariables>;
|
||||
export const CreateViewFieldsDocument = gql`
|
||||
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
|
||||
createManyViewField(data: $data) {
|
||||
|
@ -10,6 +10,7 @@ import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { TableContext } from '@/ui/table/states/TableContext';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
@ -24,6 +25,11 @@ import { defaultOrderBy } from '../../queries';
|
||||
export function CompanyTable() {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext);
|
||||
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: 'company',
|
||||
viewFieldDefinitions: companyViewFields,
|
||||
});
|
||||
const { updateSorts } = useViewSorts({
|
||||
availableSorts,
|
||||
Context: TableContext,
|
||||
@ -38,18 +44,17 @@ export function CompanyTable() {
|
||||
return (
|
||||
<>
|
||||
<GenericEntityTableData
|
||||
objectName="company"
|
||||
getRequestResultKey="companies"
|
||||
useGetRequest={useGetCompaniesQuery}
|
||||
orderBy={orderBy.length ? orderBy : defaultOrderBy}
|
||||
whereFilters={whereFilters}
|
||||
viewFieldDefinitions={companyViewFields}
|
||||
filterDefinitionArray={companiesFilters}
|
||||
/>
|
||||
<EntityTable
|
||||
viewName="All Companies"
|
||||
viewIcon={<IconList size={16} />}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onSortsUpdate={currentViewId ? updateSorts : undefined}
|
||||
useUpdateEntityMutation={useUpdateOneCompanyMutation}
|
||||
/>
|
||||
|
@ -2,32 +2,21 @@ import { useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
|
||||
import { viewFieldsState } from '@/ui/table/states/viewFieldsState';
|
||||
import { tableColumnsState } from '@/ui/table/states/tableColumnsState';
|
||||
|
||||
import { companyViewFields } from '../../constants/companyViewFields';
|
||||
|
||||
import { mockedCompaniesData } from './companies-mock-data';
|
||||
|
||||
export function CompanyTableMockData() {
|
||||
const setEntityTableDimensions = useSetRecoilState(
|
||||
entityTableDimensionsState,
|
||||
);
|
||||
const setViewFieldsState = useSetRecoilState(viewFieldsState);
|
||||
const setColumns = useSetRecoilState(tableColumnsState);
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
setEntityTableData(mockedCompaniesData, []);
|
||||
|
||||
useEffect(() => {
|
||||
setViewFieldsState({
|
||||
objectName: 'company',
|
||||
viewFields: companyViewFields,
|
||||
});
|
||||
setEntityTableDimensions((prevState) => ({
|
||||
...prevState,
|
||||
numberOfColumns: companyViewFields.length,
|
||||
}));
|
||||
}, [setEntityTableDimensions, setViewFieldsState]);
|
||||
setColumns(companyViewFields);
|
||||
}, [setColumns]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
|
||||
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
|
||||
import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
|
||||
import { numberOfTableRowsState } from '@/ui/table/states/numberOfTableRowsState';
|
||||
import { TableContext } from '@/ui/table/states/TableContext';
|
||||
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
|
||||
import { currentPageLocationState } from '@/ui/utilities/loading-state/states/currentPageLocationState';
|
||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||
import { GetPeopleQuery } from '~/generated/graphql';
|
||||
import { peopleFilters } from '~/pages/people/people-filters';
|
||||
|
||||
import { peopleFilters } from '../../../pages/people/people-filters';
|
||||
import { availableFiltersScopedState } from '../../ui/filter-n-sort/states/availableFiltersScopedState';
|
||||
import { useResetTableRowSelection } from '../../ui/table/hooks/useResetTableRowSelection';
|
||||
import { entityTableDimensionsState } from '../../ui/table/states/entityTableDimensionsState';
|
||||
import { isFetchingEntityTableDataState } from '../../ui/table/states/isFetchingEntityTableDataState';
|
||||
import { TableContext } from '../../ui/table/states/TableContext';
|
||||
import { tableRowIdsState } from '../../ui/table/states/tableRowIdsState';
|
||||
import { peopleCityFamilyState } from '../states/peopleCityFamilyState';
|
||||
import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState';
|
||||
import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState';
|
||||
@ -124,10 +124,7 @@ export function useSetPeopleEntityTable() {
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
set(entityTableDimensionsState, {
|
||||
numberOfColumns: 10,
|
||||
numberOfRows: peopleIds.length,
|
||||
});
|
||||
set(numberOfTableRowsState, peopleIds.length);
|
||||
|
||||
set(availableFiltersScopedState(tableContextScopeId), peopleFilters);
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { TableContext } from '@/ui/table/states/TableContext';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
@ -24,6 +25,11 @@ import { defaultOrderBy } from '../../queries';
|
||||
export function PeopleTable() {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext);
|
||||
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: 'person',
|
||||
viewFieldDefinitions: peopleViewFields,
|
||||
});
|
||||
const { updateSorts } = useViewSorts({
|
||||
availableSorts,
|
||||
Context: TableContext,
|
||||
@ -38,18 +44,17 @@ export function PeopleTable() {
|
||||
return (
|
||||
<>
|
||||
<GenericEntityTableData
|
||||
objectName="person"
|
||||
getRequestResultKey="people"
|
||||
useGetRequest={useGetPeopleQuery}
|
||||
orderBy={orderBy.length ? orderBy : defaultOrderBy}
|
||||
whereFilters={whereFilters}
|
||||
viewFieldDefinitions={peopleViewFields}
|
||||
filterDefinitionArray={peopleFilters}
|
||||
/>
|
||||
<EntityTable
|
||||
viewName="All People"
|
||||
viewIcon={<IconList size={16} />}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onSortsUpdate={currentViewId ? updateSorts : undefined}
|
||||
useUpdateEntityMutation={useUpdateOnePersonMutation}
|
||||
/>
|
||||
|
@ -2,6 +2,10 @@ import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
@ -90,6 +94,7 @@ type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: React.ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onRowSelectionChange?: (rowSelection: string[]) => void;
|
||||
useUpdateEntityMutation: any;
|
||||
@ -99,6 +104,7 @@ export function EntityTable<SortField>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onSortsUpdate,
|
||||
useUpdateEntityMutation,
|
||||
}: OwnProps<SortField>) {
|
||||
@ -132,11 +138,12 @@ export function EntityTable<SortField>({
|
||||
viewName={viewName}
|
||||
viewIcon={viewIcon}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={onColumnsChange}
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
/>
|
||||
<StyledTableWrapper>
|
||||
<StyledTable>
|
||||
<EntityTableHeader />
|
||||
<EntityTableHeader onColumnsChange={onColumnsChange} />
|
||||
<EntityTableBody />
|
||||
</StyledTable>
|
||||
</StyledTableWrapper>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { cloneElement, ComponentProps, useRef } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
@ -9,32 +10,27 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen
|
||||
import { IconPlus } from '@/ui/icon';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../../editable-field/types/ViewField';
|
||||
import { hiddenTableColumnsState } from '../states/tableColumnsState';
|
||||
|
||||
const StyledColumnMenu = styled(DropdownMenu)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
`;
|
||||
|
||||
type EntityTableColumnMenuProps = {
|
||||
onAddViewField: (
|
||||
viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
) => void;
|
||||
onAddColumn: (columnId: string) => void;
|
||||
onClickOutside?: () => void;
|
||||
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
} & ComponentProps<'div'>;
|
||||
|
||||
export const EntityTableColumnMenu = ({
|
||||
onAddViewField,
|
||||
onAddColumn,
|
||||
onClickOutside = () => undefined,
|
||||
viewFieldDefinitions,
|
||||
...props
|
||||
}: EntityTableColumnMenuProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [ref],
|
||||
callback: onClickOutside,
|
||||
@ -43,21 +39,21 @@ export const EntityTableColumnMenu = ({
|
||||
return (
|
||||
<StyledColumnMenu {...props} ref={ref}>
|
||||
<DropdownMenuItemsContainer>
|
||||
{viewFieldDefinitions.map((viewFieldDefinition) => (
|
||||
{hiddenColumns.map((column) => (
|
||||
<DropdownMenuItem
|
||||
key={viewFieldDefinition.id}
|
||||
key={column.id}
|
||||
actions={
|
||||
<IconButton
|
||||
icon={<IconPlus size={theme.icon.size.sm} />}
|
||||
onClick={() => onAddViewField(viewFieldDefinition)}
|
||||
onClick={() => onAddColumn(column.id)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{viewFieldDefinition.columnIcon &&
|
||||
cloneElement(viewFieldDefinition.columnIcon, {
|
||||
{column.columnIcon &&
|
||||
cloneElement(column.columnIcon, {
|
||||
size: theme.icon.size.md,
|
||||
})}
|
||||
{viewFieldDefinition.columnLabel}
|
||||
{column.columnLabel}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||
@ -11,21 +10,14 @@ import type {
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { IconPlus } from '@/ui/icon';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { GET_VIEW_FIELDS } from '@/views/queries/select';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
useCreateViewFieldMutation,
|
||||
useUpdateViewFieldMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { toViewFieldInput } from '../hooks/useLoadViewFields';
|
||||
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
|
||||
import {
|
||||
addableViewFieldDefinitionsState,
|
||||
columnWidthByViewFieldIdState,
|
||||
viewFieldsState,
|
||||
visibleViewFieldsState,
|
||||
} from '../states/viewFieldsState';
|
||||
hiddenTableColumnsState,
|
||||
tableColumnsByIdState,
|
||||
tableColumnsState,
|
||||
visibleTableColumnsState,
|
||||
} from '../states/tableColumnsState';
|
||||
|
||||
import { ColumnHead } from './ColumnHead';
|
||||
import { EntityTableColumnMenu } from './EntityTableColumnMenu';
|
||||
@ -86,17 +78,18 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
|
||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||
`;
|
||||
|
||||
export function EntityTableHeader() {
|
||||
export type EntityTableHeaderProps = {
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
};
|
||||
|
||||
export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const [{ objectName }, setViewFieldsState] = useRecoilState(viewFieldsState);
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const viewFields = useRecoilValue(visibleViewFieldsState);
|
||||
const columnWidths = useRecoilValue(columnWidthByViewFieldIdState);
|
||||
const addableViewFieldDefinitions = useRecoilValue(
|
||||
addableViewFieldDefinitionsState,
|
||||
);
|
||||
const [columns, setColumns] = useRecoilState(tableColumnsState);
|
||||
const [offset, setOffset] = useRecoilState(resizeFieldOffsetState);
|
||||
const columnsById = useRecoilValue(tableColumnsByIdState);
|
||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||
const visibleColumns = useRecoilValue(visibleTableColumnsState);
|
||||
|
||||
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
||||
number | null
|
||||
@ -104,9 +97,6 @@ export function EntityTableHeader() {
|
||||
const [resizedFieldId, setResizedFieldId] = useState<string | null>(null);
|
||||
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
|
||||
|
||||
const [createViewFieldMutation] = useCreateViewFieldMutation();
|
||||
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||
|
||||
const handleResizeHandlerStart = useCallback((positionX: number) => {
|
||||
setInitialPointerPositionX(positionX);
|
||||
}, []);
|
||||
@ -126,37 +116,28 @@ export function EntityTableHeader() {
|
||||
|
||||
const nextWidth = Math.round(
|
||||
Math.max(
|
||||
columnWidths[resizedFieldId] +
|
||||
columnsById[resizedFieldId].columnSize +
|
||||
snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(),
|
||||
COLUMN_MIN_WIDTH,
|
||||
),
|
||||
);
|
||||
|
||||
if (nextWidth !== columnWidths[resizedFieldId]) {
|
||||
// Optimistic update to avoid "bouncing width" visual effect on resize.
|
||||
setViewFieldsState((previousState) => ({
|
||||
...previousState,
|
||||
viewFields: previousState.viewFields.map((viewField) =>
|
||||
viewField.id === resizedFieldId
|
||||
? { ...viewField, columnSize: nextWidth }
|
||||
: viewField,
|
||||
),
|
||||
}));
|
||||
if (nextWidth !== columnsById[resizedFieldId].columnSize) {
|
||||
const nextColumns = columns.map((column) =>
|
||||
column.id === resizedFieldId
|
||||
? { ...column, columnSize: nextWidth }
|
||||
: column,
|
||||
);
|
||||
|
||||
updateViewFieldMutation({
|
||||
variables: {
|
||||
data: { sizeInPx: nextWidth },
|
||||
where: { id: resizedFieldId },
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||
});
|
||||
setColumns(nextColumns);
|
||||
onColumnsChange?.(nextColumns);
|
||||
}
|
||||
|
||||
set(resizeFieldOffsetState, 0);
|
||||
setInitialPointerPositionX(null);
|
||||
setResizedFieldId(null);
|
||||
},
|
||||
[resizedFieldId, columnWidths, setResizedFieldId],
|
||||
[resizedFieldId, columnsById, setResizedFieldId],
|
||||
);
|
||||
|
||||
useTrackPointer({
|
||||
@ -170,26 +151,18 @@ export function EntityTableHeader() {
|
||||
setIsColumnMenuOpen((previousValue) => !previousValue);
|
||||
}, []);
|
||||
|
||||
const handleAddViewField = useCallback(
|
||||
(viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>) => {
|
||||
const handleAddColumn = useCallback(
|
||||
(columnId: string) => {
|
||||
setIsColumnMenuOpen(false);
|
||||
|
||||
if (!objectName) return;
|
||||
const nextColumns = columns.map((column) =>
|
||||
column.id === columnId ? { ...column, isVisible: true } : column,
|
||||
);
|
||||
|
||||
createViewFieldMutation({
|
||||
variables: {
|
||||
data: {
|
||||
...toViewFieldInput(objectName, {
|
||||
...viewFieldDefinition,
|
||||
columnOrder: viewFields.length + 1,
|
||||
}),
|
||||
view: { connect: { id: currentViewId } },
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||
});
|
||||
setColumns(nextColumns);
|
||||
onColumnsChange?.(nextColumns);
|
||||
},
|
||||
[createViewFieldMutation, currentViewId, objectName, viewFields.length],
|
||||
[columns, onColumnsChange, setColumns],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -205,31 +178,31 @@ export function EntityTableHeader() {
|
||||
<SelectAllCheckbox />
|
||||
</th>
|
||||
|
||||
{viewFields.map((viewField) => (
|
||||
{visibleColumns.map((column) => (
|
||||
<StyledColumnHeaderCell
|
||||
key={viewField.id}
|
||||
isResizing={resizedFieldId === viewField.id}
|
||||
key={column.id}
|
||||
isResizing={resizedFieldId === column.id}
|
||||
columnWidth={Math.max(
|
||||
columnWidths[viewField.id] +
|
||||
(resizedFieldId === viewField.id ? offset : 0),
|
||||
columnsById[column.id].columnSize +
|
||||
(resizedFieldId === column.id ? offset : 0),
|
||||
COLUMN_MIN_WIDTH,
|
||||
)}
|
||||
>
|
||||
<ColumnHead
|
||||
viewName={viewField.columnLabel}
|
||||
viewIcon={viewField.columnIcon}
|
||||
viewName={column.columnLabel}
|
||||
viewIcon={column.columnIcon}
|
||||
/>
|
||||
<StyledResizeHandler
|
||||
className="cursor-col-resize"
|
||||
role="separator"
|
||||
onPointerDown={() => {
|
||||
setResizedFieldId(viewField.id);
|
||||
setResizedFieldId(column.id);
|
||||
}}
|
||||
/>
|
||||
</StyledColumnHeaderCell>
|
||||
))}
|
||||
<th>
|
||||
{addableViewFieldDefinitions.length > 0 && (
|
||||
{hiddenColumns.length > 0 && (
|
||||
<StyledAddIconButtonWrapper>
|
||||
<StyledAddIconButton
|
||||
size="large"
|
||||
@ -238,9 +211,8 @@ export function EntityTableHeader() {
|
||||
/>
|
||||
{isColumnMenuOpen && (
|
||||
<StyledEntityTableColumnMenu
|
||||
onAddViewField={handleAddViewField}
|
||||
onAddColumn={handleAddColumn}
|
||||
onClickOutside={toggleColumnMenu}
|
||||
viewFieldDefinitions={addableViewFieldDefinitions}
|
||||
/>
|
||||
)}
|
||||
</StyledAddIconButtonWrapper>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { visibleTableColumnsState } from '../states/tableColumnsState';
|
||||
import { ViewFieldContext } from '../states/ViewFieldContext';
|
||||
import { visibleViewFieldsState } from '../states/viewFieldsState';
|
||||
|
||||
import { CheckboxCell } from './CheckboxCell';
|
||||
import { EntityTableCell } from './EntityTableCell';
|
||||
@ -13,7 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
`;
|
||||
|
||||
export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
const viewFields = useRecoilValue(visibleViewFieldsState);
|
||||
const columns = useRecoilValue(visibleTableColumnsState);
|
||||
|
||||
return (
|
||||
<StyledRow
|
||||
@ -24,9 +24,9 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
<td>
|
||||
<CheckboxCell />
|
||||
</td>
|
||||
{viewFields.map((viewField, columnIndex) => {
|
||||
{columns.map((column, columnIndex) => {
|
||||
return (
|
||||
<ViewFieldContext.Provider value={viewField} key={viewField.id}>
|
||||
<ViewFieldContext.Provider value={column} key={column.id}>
|
||||
<EntityTableCell cellIndex={columnIndex} />
|
||||
</ViewFieldContext.Provider>
|
||||
);
|
||||
|
@ -1,34 +1,22 @@
|
||||
import { defaultOrderBy } from '@/people/queries';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||
|
||||
import { useLoadViewFields } from '../hooks/useLoadViewFields';
|
||||
|
||||
export function GenericEntityTableData({
|
||||
objectName,
|
||||
useGetRequest,
|
||||
getRequestResultKey,
|
||||
orderBy = defaultOrderBy,
|
||||
whereFilters,
|
||||
viewFieldDefinitions,
|
||||
filterDefinitionArray,
|
||||
}: {
|
||||
objectName: 'company' | 'person';
|
||||
useGetRequest: any;
|
||||
getRequestResultKey: string;
|
||||
orderBy?: any;
|
||||
whereFilters?: any;
|
||||
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
filterDefinitionArray: FilterDefinition[];
|
||||
}) {
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
useLoadViewFields({ objectName, viewFieldDefinitions });
|
||||
|
||||
useGetRequest({
|
||||
variables: { orderBy, where: whereFilters },
|
||||
onCompleted: (data: any) => {
|
||||
|
@ -2,11 +2,6 @@ import { useContext } from 'react';
|
||||
|
||||
import { RowIdContext } from '../states/RowIdContext';
|
||||
|
||||
export type TableDimensions = {
|
||||
numberOfColumns: number;
|
||||
numberOfRows: number;
|
||||
};
|
||||
|
||||
export function useCurrentRowEntityId() {
|
||||
const currentEntityId = useContext(RowIdContext);
|
||||
|
||||
|
@ -1,21 +1,12 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
|
||||
import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
import { useResetTableRowSelection } from './useResetTableRowSelection';
|
||||
|
||||
export type TableDimensions = {
|
||||
numberOfColumns: number;
|
||||
numberOfRows: number;
|
||||
};
|
||||
|
||||
export function useInitializeEntityTable({
|
||||
numberOfColumns,
|
||||
}: {
|
||||
numberOfColumns: number;
|
||||
}) {
|
||||
export function useInitializeEntityTable() {
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||
@ -24,12 +15,9 @@ export function useInitializeEntityTable({
|
||||
resetTableRowSelection();
|
||||
}, [resetTableRowSelection]);
|
||||
|
||||
const [, setTableDimensions] = useRecoilState(entityTableDimensionsState);
|
||||
const setNumberOfTableRows = useSetRecoilState(numberOfTableRowsState);
|
||||
|
||||
useEffect(() => {
|
||||
setTableDimensions({
|
||||
numberOfColumns,
|
||||
numberOfRows: tableRowIds?.length,
|
||||
});
|
||||
}, [tableRowIds, numberOfColumns, setTableDimensions]);
|
||||
setNumberOfTableRows(tableRowIds?.length);
|
||||
}, [tableRowIds, setNumberOfTableRows]);
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { GET_VIEW_FIELDS } from '@/views/queries/select';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
SortOrder,
|
||||
useCreateViewFieldsMutation,
|
||||
useGetViewFieldsQuery,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
|
||||
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 useLoadViewFields = ({
|
||||
objectName,
|
||||
viewFieldDefinitions,
|
||||
}: {
|
||||
objectName: 'company' | 'person';
|
||||
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
}) => {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const setEntityTableDimensions = useSetRecoilState(
|
||||
entityTableDimensionsState,
|
||||
);
|
||||
const setViewFieldsState = useSetRecoilState(viewFieldsState);
|
||||
|
||||
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
|
||||
|
||||
useGetViewFieldsQuery({
|
||||
variables: {
|
||||
orderBy: { index: SortOrder.Asc },
|
||||
where: {
|
||||
objectName: { equals: objectName },
|
||||
viewId: { equals: currentViewId ?? null },
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.viewFields.length) {
|
||||
const viewFields = 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,
|
||||
}));
|
||||
|
||||
setViewFieldsState({ objectName, viewFields });
|
||||
setEntityTableDimensions((prevState) => ({
|
||||
...prevState,
|
||||
numberOfColumns: data.viewFields.length,
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate if empty
|
||||
createViewFieldsMutation({
|
||||
variables: {
|
||||
data: viewFieldDefinitions.map((viewFieldDefinition) => ({
|
||||
...toViewFieldInput(objectName, viewFieldDefinition),
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
@ -1,8 +1,8 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { numberOfTableColumnsSelectorState } from '../states/numberOfTableColumnsSelectorState';
|
||||
import { numberOfTableRowsSelectorState } from '../states/numberOfTableRowsSelectorState';
|
||||
import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
import { numberOfTableColumnsState } from '../states/tableColumnsState';
|
||||
|
||||
import { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
|
||||
|
||||
@ -39,7 +39,7 @@ export function useMoveSoftFocus() {
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableRows = snapshot
|
||||
.getLoadable(numberOfTableRowsSelectorState)
|
||||
.getLoadable(numberOfTableRowsState)
|
||||
.valueOrThrow();
|
||||
|
||||
let newRowNumber = softFocusPosition.row + 1;
|
||||
@ -64,11 +64,11 @@ export function useMoveSoftFocus() {
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableColumns = snapshot
|
||||
.getLoadable(numberOfTableColumnsSelectorState)
|
||||
.getLoadable(numberOfTableColumnsState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableRows = snapshot
|
||||
.getLoadable(numberOfTableRowsSelectorState)
|
||||
.getLoadable(numberOfTableRowsState)
|
||||
.valueOrThrow();
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
@ -112,7 +112,7 @@ export function useMoveSoftFocus() {
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableColumns = snapshot
|
||||
.getLoadable(numberOfTableColumnsSelectorState)
|
||||
.getLoadable(numberOfTableColumnsState)
|
||||
.valueOrThrow();
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
|
@ -3,13 +3,14 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
|
||||
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
|
||||
import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
|
||||
import { TableContext } from '@/ui/table/states/TableContext';
|
||||
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
|
||||
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
|
||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||
|
||||
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
|
||||
import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
|
||||
|
||||
export function useSetEntityTableData() {
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
@ -43,10 +44,7 @@ export function useSetEntityTableData() {
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
set(entityTableDimensionsState, (prevState) => ({
|
||||
...prevState,
|
||||
numberOfRows: entityIds.length,
|
||||
}));
|
||||
set(numberOfTableRowsState, entityIds.length);
|
||||
|
||||
set(availableFiltersScopedState(tableContextScopeId), filters);
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||
@ -16,16 +15,15 @@ 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';
|
||||
hiddenTableColumnsState,
|
||||
tableColumnsState,
|
||||
visibleTableColumnsState,
|
||||
} from '@/ui/table/states/tableColumnsState';
|
||||
|
||||
import { GET_VIEW_FIELDS } from '../queries/select';
|
||||
import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
|
||||
|
||||
import { OptionsDropdownSection } from './OptionsDropdownSection';
|
||||
|
||||
type OptionsDropdownButtonProps = {
|
||||
type TableOptionsDropdownButtonProps = {
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
HotkeyScope: FiltersHotkeyScope;
|
||||
};
|
||||
|
||||
@ -33,51 +31,52 @@ enum Option {
|
||||
Properties = 'Properties',
|
||||
}
|
||||
|
||||
export const OptionsDropdownButton = ({
|
||||
export const TableOptionsDropdownButton = ({
|
||||
onColumnsChange,
|
||||
HotkeyScope,
|
||||
}: OptionsDropdownButtonProps) => {
|
||||
}: TableOptionsDropdownButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const visibleFields = useRecoilValue(visibleViewFieldsState);
|
||||
const hiddenFields = useRecoilValue(hiddenViewFieldsState);
|
||||
const [columns, setColumns] = useRecoilState(tableColumnsState);
|
||||
const visibleColumns = useRecoilValue(visibleTableColumnsState);
|
||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||
|
||||
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||
const handleColumnVisibilityChange = useCallback(
|
||||
(columnId: string, nextIsVisible: boolean) => {
|
||||
const nextColumns = columns.map((column) =>
|
||||
column.id === columnId
|
||||
? { ...column, isVisible: nextIsVisible }
|
||||
: column,
|
||||
);
|
||||
|
||||
const handleViewFieldVisibilityChange = useCallback(
|
||||
(viewFieldId: string, nextIsVisible: boolean) => {
|
||||
updateViewFieldMutation({
|
||||
variables: {
|
||||
data: { isVisible: nextIsVisible },
|
||||
where: { id: viewFieldId },
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||
});
|
||||
setColumns(nextColumns);
|
||||
onColumnsChange?.(nextColumns);
|
||||
},
|
||||
[updateViewFieldMutation],
|
||||
[columns, onColumnsChange, setColumns],
|
||||
);
|
||||
|
||||
const renderFieldActions = useCallback(
|
||||
(viewField: ViewFieldDefinition<ViewFieldMetadata>) =>
|
||||
(column: ViewFieldDefinition<ViewFieldMetadata>) =>
|
||||
// Do not allow hiding last visible column
|
||||
!viewField.isVisible || visibleFields.length > 1 ? (
|
||||
!column.isVisible || visibleColumns.length > 1 ? (
|
||||
<IconButton
|
||||
icon={
|
||||
viewField.isVisible ? (
|
||||
column.isVisible ? (
|
||||
<IconMinus size={theme.icon.size.sm} />
|
||||
) : (
|
||||
<IconPlus size={theme.icon.size.sm} />
|
||||
)
|
||||
}
|
||||
onClick={() =>
|
||||
handleViewFieldVisibilityChange(viewField.id, !viewField.isVisible)
|
||||
handleColumnVisibilityChange(column.id, !column.isVisible)
|
||||
}
|
||||
/>
|
||||
) : undefined,
|
||||
[handleViewFieldVisibilityChange, theme.icon.size.sm, visibleFields.length],
|
||||
[handleColumnVisibilityChange, theme.icon.size.sm, visibleColumns.length],
|
||||
);
|
||||
|
||||
const resetSelectedOption = useCallback(() => {
|
||||
@ -115,18 +114,18 @@ export const OptionsDropdownButton = ({
|
||||
Properties
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<OptionsDropdownSection
|
||||
<TableOptionsDropdownSection
|
||||
renderActions={renderFieldActions}
|
||||
title="Visible"
|
||||
viewFields={visibleFields}
|
||||
columns={visibleColumns}
|
||||
/>
|
||||
{hiddenFields.length > 0 && (
|
||||
{hiddenColumns.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<OptionsDropdownSection
|
||||
<TableOptionsDropdownSection
|
||||
renderActions={renderFieldActions}
|
||||
title="Hidden"
|
||||
viewFields={hiddenFields}
|
||||
columns={hiddenColumns}
|
||||
/>
|
||||
</>
|
||||
)}
|
@ -12,32 +12,32 @@ import {
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
type OptionsDropdownSectionProps = {
|
||||
type TableOptionsDropdownSectionProps = {
|
||||
renderActions: (
|
||||
viewField: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
column: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
) => DropdownMenuItemProps['actions'];
|
||||
title: string;
|
||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
columns: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
};
|
||||
|
||||
export const OptionsDropdownSection = ({
|
||||
export const TableOptionsDropdownSection = ({
|
||||
renderActions,
|
||||
title,
|
||||
viewFields,
|
||||
}: OptionsDropdownSectionProps) => {
|
||||
columns,
|
||||
}: TableOptionsDropdownSectionProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuSubheader>{title}</DropdownMenuSubheader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{viewFields.map((viewField) => (
|
||||
<DropdownMenuItem actions={renderActions(viewField)}>
|
||||
{viewField.columnIcon &&
|
||||
cloneElement(viewField.columnIcon, {
|
||||
{columns.map((column) => (
|
||||
<DropdownMenuItem key={column.id} actions={renderActions(column)}>
|
||||
{column.columnIcon &&
|
||||
cloneElement(column.columnIcon, {
|
||||
size: theme.icon.size.md,
|
||||
})}
|
||||
{viewField.columnLabel}
|
||||
{column.columnLabel}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
@ -2,13 +2,13 @@ import { selector } from 'recoil';
|
||||
|
||||
import { AllRowsSelectedStatus } from '../types/AllRowSelectedStatus';
|
||||
|
||||
import { numberOfTableRowsSelectorState } from './numberOfTableRowsSelectorState';
|
||||
import { numberOfTableRowsState } from './numberOfTableRowsState';
|
||||
import { selectedRowIdsSelector } from './selectedRowIdsSelector';
|
||||
|
||||
export const allRowsSelectedStatusSelector = selector<AllRowsSelectedStatus>({
|
||||
key: 'allRowsSelectedStatusSelector',
|
||||
get: ({ get }) => {
|
||||
const numberOfRows = get(numberOfTableRowsSelectorState);
|
||||
const numberOfRows = get(numberOfTableRowsState);
|
||||
|
||||
const selectedRowIds = get(selectedRowIdsSelector);
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { TableDimensions } from '../hooks/useInitializeEntityTable';
|
||||
|
||||
export const entityTableDimensionsState = atom<TableDimensions>({
|
||||
key: 'entityTableDimensionsState',
|
||||
default: {
|
||||
numberOfRows: 0,
|
||||
numberOfColumns: 0,
|
||||
},
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { selector } from 'recoil';
|
||||
|
||||
import { entityTableDimensionsState } from './entityTableDimensionsState';
|
||||
|
||||
export const numberOfTableColumnsSelectorState = selector<number>({
|
||||
key: 'numberOfTableColumnsState',
|
||||
get: ({ get }) => {
|
||||
const { numberOfColumns } = get(entityTableDimensionsState);
|
||||
|
||||
return numberOfColumns;
|
||||
},
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { selector } from 'recoil';
|
||||
|
||||
import { entityTableDimensionsState } from './entityTableDimensionsState';
|
||||
|
||||
export const numberOfTableRowsSelectorState = selector<number>({
|
||||
key: 'numberOfTableRowsState',
|
||||
get: ({ get }) => {
|
||||
const { numberOfRows } = get(entityTableDimensionsState);
|
||||
|
||||
return numberOfRows;
|
||||
},
|
||||
});
|
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const numberOfTableRowsState = atom<number>({
|
||||
key: 'numberOfTableRowsState',
|
||||
default: 0,
|
||||
});
|
37
front/src/modules/ui/table/states/tableColumnsState.ts
Normal file
37
front/src/modules/ui/table/states/tableColumnsState.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { atom, selector } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
export const tableColumnsState = atom<ViewFieldDefinition<ViewFieldMetadata>[]>(
|
||||
{
|
||||
key: 'tableColumnsState',
|
||||
default: [],
|
||||
},
|
||||
);
|
||||
|
||||
export const tableColumnsByIdState = selector({
|
||||
key: 'tableColumnsByIdState',
|
||||
get: ({ get }) =>
|
||||
get(tableColumnsState).reduce<
|
||||
Record<string, ViewFieldDefinition<ViewFieldMetadata>>
|
||||
>((result, column) => ({ ...result, [column.id]: column }), {}),
|
||||
});
|
||||
|
||||
export const numberOfTableColumnsState = selector<number>({
|
||||
key: 'numberOfTableColumnsState',
|
||||
get: ({ get }) => get(tableColumnsState).length,
|
||||
});
|
||||
|
||||
export const visibleTableColumnsState = selector({
|
||||
key: 'visibleTableColumnsState',
|
||||
get: ({ get }) => get(tableColumnsState).filter((column) => column.isVisible),
|
||||
});
|
||||
|
||||
export const hiddenTableColumnsState = selector({
|
||||
key: 'hiddenTableColumnsState',
|
||||
get: ({ get }) =>
|
||||
get(tableColumnsState).filter((column) => !column.isVisible),
|
||||
});
|
@ -1,61 +0,0 @@
|
||||
import { atom, selector } from 'recoil';
|
||||
|
||||
import { companyViewFields } from '@/companies/constants/companyViewFields';
|
||||
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
export const viewFieldsState = atom<{
|
||||
objectName: 'company' | 'person' | '';
|
||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
}>({
|
||||
key: 'viewFieldsState',
|
||||
default: { objectName: '', viewFields: [] },
|
||||
});
|
||||
|
||||
export const columnWidthByViewFieldIdState = selector({
|
||||
key: 'columnWidthByViewFieldIdState',
|
||||
get: ({ get }) =>
|
||||
get(viewFieldsState).viewFields.reduce<Record<string, number>>(
|
||||
(result, viewField) => ({
|
||||
...result,
|
||||
[viewField.id]: viewField.columnSize,
|
||||
}),
|
||||
{},
|
||||
),
|
||||
});
|
||||
|
||||
export const addableViewFieldDefinitionsState = selector({
|
||||
key: 'addableViewFieldDefinitionsState',
|
||||
get: ({ get }) => {
|
||||
const { objectName, viewFields } = get(viewFieldsState);
|
||||
|
||||
if (!objectName) return [];
|
||||
|
||||
const existingColumnLabels = viewFields.map(
|
||||
(viewField) => viewField.columnLabel,
|
||||
);
|
||||
const viewFieldDefinitions =
|
||||
objectName === 'company' ? companyViewFields : peopleViewFields;
|
||||
|
||||
return viewFieldDefinitions.filter(
|
||||
(viewFieldDefinition) =>
|
||||
!existingColumnLabels.includes(viewFieldDefinition.columnLabel),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const visibleViewFieldsState = selector({
|
||||
key: 'visibleViewFieldsState',
|
||||
get: ({ get }) =>
|
||||
get(viewFieldsState).viewFields.filter((viewField) => viewField.isVisible),
|
||||
});
|
||||
|
||||
export const hiddenViewFieldsState = selector({
|
||||
key: 'hiddenViewFieldsState',
|
||||
get: ({ get }) =>
|
||||
get(viewFieldsState).viewFields.filter((viewField) => !viewField.isVisible),
|
||||
});
|
@ -1,15 +1,19 @@
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { FilterDropdownButton } from '@/ui/filter-n-sort/components/FilterDropdownButton';
|
||||
import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
|
||||
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
|
||||
import { sortScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { TableOptionsDropdownButton } from '@/ui/table/options/components/TableOptionsDropdownButton';
|
||||
import { TopBar } from '@/ui/top-bar/TopBar';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { OptionsDropdownButton } from '@/views/components/OptionsDropdownButton';
|
||||
|
||||
import { TableContext } from '../../states/TableContext';
|
||||
|
||||
@ -17,6 +21,7 @@ type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
};
|
||||
|
||||
@ -34,6 +39,7 @@ export function TableHeader<SortField>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onSortsUpdate,
|
||||
}: OwnProps<SortField>) {
|
||||
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
|
||||
@ -79,7 +85,8 @@ export function TableHeader<SortField>({
|
||||
onSortSelect={sortSelect}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
<OptionsDropdownButton
|
||||
<TableOptionsDropdownButton
|
||||
onColumnsChange={onColumnsChange}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
</>
|
||||
|
146
front/src/modules/views/hooks/useTableViewFields.ts
Normal file
146
front/src/modules/views/hooks/useTableViewFields.ts
Normal 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 };
|
||||
};
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user