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