Skip to content

Commit

Permalink
feat: add table columns
Browse files Browse the repository at this point in the history
Closes #879
  • Loading branch information
thaisguigon committed Aug 2, 2023
1 parent 3850b1f commit a56d06c
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 54 deletions.
59 changes: 59 additions & 0 deletions front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ export type Mutation = {
createOneCompany: Company;
createOnePerson: Person;
createOnePipelineProgress: PipelineProgress;
createOneViewField: ViewField;
deleteCurrentWorkspace: Workspace;
deleteManyActivities: AffectedRows;
deleteManyCompany: AffectedRows;
Expand Down Expand Up @@ -966,6 +967,11 @@ export type MutationCreateOnePipelineProgressArgs = {
};


export type MutationCreateOneViewFieldArgs = {
data: ViewFieldCreateInput;
};


export type MutationDeleteManyActivitiesArgs = {
where?: InputMaybe<ActivityWhereInput>;
};
Expand Down Expand Up @@ -2083,6 +2089,15 @@ export type ViewField = {
sizeInPx: Scalars['Int'];
};

export type ViewFieldCreateInput = {
fieldName: Scalars['String'];
id?: InputMaybe<Scalars['String']>;
index: Scalars['Int'];
isVisible: Scalars['Boolean'];
objectName: Scalars['String'];
sizeInPx: Scalars['Int'];
};

export type ViewFieldCreateManyInput = {
fieldName: Scalars['String'];
id?: InputMaybe<Scalars['String']>;
Expand Down Expand Up @@ -2656,6 +2671,13 @@ 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;
}>;
Expand Down Expand Up @@ -5074,6 +5096,43 @@ 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) {
Expand Down
12 changes: 12 additions & 0 deletions front/src/modules/companies/constants/companyViewFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ export const companyViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
placeHolder: 'LinkedIn URL',
},
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
{
id: 'twitter',
columnLabel: 'Twitter',
columnIcon: <IconBrandLinkedin />,
columnSize: 170,
columnOrder: 6,
metadata: {
type: 'url',
fieldName: 'twitterUrl',
placeHolder: 'Twitter URL',
},
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
{
id: 'address',
columnLabel: 'Address',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ export function CompanyTableMockData() {
const setEntityTableDimensions = useSetRecoilState(
entityTableDimensionsState,
);
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
const setViewFieldsState = useSetRecoilState(viewFieldsFamilyState);
const setEntityTableData = useSetEntityTableData();

setEntityTableData(mockedCompaniesData, []);

useEffect(() => {
setViewFields(companyViewFields);
setViewFieldsState({
objectName: 'company',
viewFields: companyViewFields,
});
setEntityTableDimensions((prevState) => ({
...prevState,
numberOfColumns: companyViewFields.length,
}));
}, [setEntityTableDimensions, setViewFields]);
}, [setEntityTableDimensions, setViewFieldsState]);

return <></>;
}
2 changes: 1 addition & 1 deletion front/src/modules/ui/button/components/ButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const StyledButtonGroupContainer = styled.div`

type ButtonGroupProps = Pick<ButtonProps, 'variant' | 'size'> & {
children: React.ReactElement[];
};
} & Omit<React.ComponentProps<'div'>, 'children'>;

export function ButtonGroup({ children, variant, size }: ButtonGroupProps) {
return (
Expand Down
10 changes: 6 additions & 4 deletions front/src/modules/ui/button/components/IconButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import React, { type ComponentProps } from 'react';
import styled from '@emotion/styled';

import { IconButtonSize, IconButtonVariant } from './IconButton';
import type { IconButtonSize, IconButtonVariant } from './IconButton';

const StyledIconButtonGroupContainer = styled.div`
align-items: flex-start;
background: ${({ theme }) => theme.background.transparent.primary};
Expand All @@ -15,15 +16,16 @@ type IconButtonGroupProps = {
variant: IconButtonVariant;
size: IconButtonSize;
children: React.ReactElement[];
};
} & Omit<ComponentProps<'div'>, 'children'>;

export function IconButtonGroup({
children,
variant,
size,
...props
}: IconButtonGroupProps) {
return (
<StyledIconButtonGroupContainer>
<StyledIconButtonGroupContainer {...props}>
{React.Children.map(children, (child) =>
React.cloneElement(child, {
...(variant ? { variant } : {}),
Expand Down
58 changes: 41 additions & 17 deletions front/src/modules/ui/table/components/EntityTable.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { useCallback, useRef } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';

import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
import { useUpdateViewFieldMutation } from '~/generated/graphql';
import { GET_VIEW_FIELDS } from '@/views/queries/select';
import {
useCreateViewFieldMutation,
useUpdateViewFieldMutation,
} from '~/generated/graphql';

import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { toViewFieldInput } from '../hooks/useLoadView';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext';
import { viewFieldsFamilyState } from '../states/viewFieldsState';
import {
addableViewFieldDefinitionsFamilyState,
viewFieldsFamilyState,
} from '../states/viewFieldsState';
import { TableHeader } from '../table-header/components/TableHeader';
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';

import { EntityTableBody } from './EntityTableBody';
import { EntityTableHeader } from './EntityTableHeader';
Expand Down Expand Up @@ -40,8 +50,7 @@ const StyledTable = styled.table`
border-right-color: transparent;
}
:last-of-type {
min-width: 0;
width: 100%;
width: 32px;
}
}
Expand All @@ -61,8 +70,7 @@ const StyledTable = styled.table`
border-right-color: transparent;
}
:last-of-type {
min-width: 0;
width: 100%;
width: 32px;
}
}
`;
Expand Down Expand Up @@ -102,9 +110,12 @@ export function EntityTable<SortField>({
onSortsUpdate,
useUpdateEntityMutation,
}: OwnProps<SortField>) {
const viewFields = useRecoilValue(viewFieldsFamilyState);
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
const { objectName, viewFields } = useRecoilValue(viewFieldsFamilyState);
const addableViewFieldDefinitions = useRecoilValue(
addableViewFieldDefinitionsFamilyState,
);

const [createViewFieldMutation] = useCreateViewFieldMutation();
const [updateViewFieldMutation] = useUpdateViewFieldMutation();

const tableBodyRef = useRef<HTMLDivElement>(null);
Expand All @@ -122,21 +133,32 @@ export function EntityTable<SortField>({

const handleColumnResize = useCallback(
(resizedFieldId: string, width: number) => {
setViewFields((previousViewFields) =>
previousViewFields.map((viewField) =>
viewField.id === resizedFieldId
? { ...viewField, columnSize: width }
: viewField,
),
);
updateViewFieldMutation({
variables: {
data: { sizeInPx: width },
where: { id: resizedFieldId },
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
[updateViewFieldMutation],
);

const handleAddViewField = useCallback(
async (viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>) => {
if (!objectName) return;

createViewFieldMutation({
variables: {
data: toViewFieldInput(objectName, {
...viewFieldDefinition,
columnOrder: viewFields.length + 1,
}),
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
[setViewFields, updateViewFieldMutation],
[createViewFieldMutation, objectName, viewFields.length],
);

return (
Expand All @@ -153,6 +175,8 @@ export function EntityTable<SortField>({
{viewFields.length > 0 && (
<StyledTable>
<EntityTableHeader
addableViewFieldDefinitions={addableViewFieldDefinitions}
onAddViewField={handleAddViewField}
onColumnResize={handleColumnResize}
viewFields={viewFields}
/>
Expand Down
88 changes: 88 additions & 0 deletions front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useRef } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';

import { IconButton } from '@/ui/button/components/IconButton';
import { IconButtonGroup } from '@/ui/button/components/IconButtonGroup';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { IconPlus } from '@/ui/icon';
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';

import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';

const StyledColumnMenu = styled(DropdownMenu)`
font-weight: ${({ theme }) => theme.font.weight.regular};
position: absolute;
right: 0;
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;

const StyledIconButtonGroup = styled(IconButtonGroup)`
display: none;
position: absolute;
right: ${({ theme }) => theme.spacing(1)};
`;
const styledIconButtonGroupClassName = 'column-menu-item-icon-button-group';

const StyledColumnMenuItem = styled(DropdownMenuItem)`
position: relative;
&:hover {
.${styledIconButtonGroupClassName} {
display: flex;
}
}
`;

type EntityTableColumnMenuProps = {
onAddViewField: (
viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>,
) => void;
onClickOutside?: () => void;
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
};

export const EntityTableColumnMenu = ({
onAddViewField,
onClickOutside = () => undefined,
viewFieldDefinitions,
}: EntityTableColumnMenuProps) => {
const ref = useRef<HTMLDivElement>(null);
const theme = useTheme();

useListenClickOutside({
refs: [ref],
callback: onClickOutside,
});

return (
<StyledColumnMenu ref={ref}>
<DropdownMenuItemsContainer>
{viewFieldDefinitions.map((viewFieldDefinition) => (
<StyledColumnMenuItem key={viewFieldDefinition.id}>
{viewFieldDefinition.columnIcon &&
React.cloneElement(viewFieldDefinition.columnIcon, {
size: theme.icon.size.md,
})}
{viewFieldDefinition.columnLabel}
<StyledIconButtonGroup
className={styledIconButtonGroupClassName}
variant="transparent"
size="small"
>
{[
<IconButton
key={`${viewFieldDefinition.id}-add`}
icon={<IconPlus size={theme.icon.size.sm} />}
onClick={() => onAddViewField(viewFieldDefinition)}
/>,
]}
</StyledIconButtonGroup>
</StyledColumnMenuItem>
))}
</DropdownMenuItemsContainer>
</StyledColumnMenu>
);
};
Loading

0 comments on commit a56d06c

Please sign in to comment.