Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Board options edit and reorder #1858

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ export type PipelineStage = {
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
index?: Maybe<Scalars['Int']['output']>;
isVisible: Scalars['Boolean']['output'];
name: Scalars['String']['output'];
pipeline: Pipeline;
pipelineId: Scalars['String']['output'];
Expand Down
9 changes: 8 additions & 1 deletion front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2303,6 +2303,7 @@ export type PipelineStage = {
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
index?: Maybe<Scalars['Int']>;
isVisible: Scalars['Boolean'];
name: Scalars['String'];
pipeline: Pipeline;
pipelineId: Scalars['String'];
Expand All @@ -2316,6 +2317,7 @@ export type PipelineStageCreateInput = {
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
index?: InputMaybe<Scalars['Int']>;
isVisible: Scalars['Boolean'];
name: Scalars['String'];
pipeline: PipelineCreateNestedOneWithoutPipelineStagesInput;
pipelineProgresses?: InputMaybe<PipelineProgressCreateNestedManyWithoutPipelineStageInput>;
Expand All @@ -2342,6 +2344,7 @@ export type PipelineStageOrderByWithRelationInput = {
createdAt?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>;
index?: InputMaybe<SortOrder>;
isVisible?: InputMaybe<SortOrder>;
name?: InputMaybe<SortOrder>;
pipeline?: InputMaybe<PipelineOrderByWithRelationInput>;
pipelineId?: InputMaybe<SortOrder>;
Expand All @@ -2361,6 +2364,7 @@ export enum PipelineStageScalarFieldEnum {
DeletedAt = 'deletedAt',
Id = 'id',
Index = 'index',
IsVisible = 'isVisible',
Name = 'name',
PipelineId = 'pipelineId',
Type = 'type',
Expand All @@ -2373,6 +2377,7 @@ export type PipelineStageUpdateInput = {
createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>;
index?: InputMaybe<Scalars['Int']>;
isVisible?: InputMaybe<Scalars['Boolean']>;
name?: InputMaybe<Scalars['String']>;
pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineStagesNestedInput>;
pipelineProgresses?: InputMaybe<PipelineProgressUpdateManyWithoutPipelineStageNestedInput>;
Expand All @@ -2398,6 +2403,7 @@ export type PipelineStageWhereInput = {
createdAt?: InputMaybe<DateTimeFilter>;
id?: InputMaybe<StringFilter>;
index?: InputMaybe<IntNullableFilter>;
isVisible?: InputMaybe<BoolFilter>;
name?: InputMaybe<StringFilter>;
pipeline?: InputMaybe<PipelineRelationFilter>;
pipelineId?: InputMaybe<StringFilter>;
Expand Down Expand Up @@ -3996,7 +4002,7 @@ export type GetPipelinesQueryVariables = Exact<{
}>;


export type GetPipelinesQuery = { __typename?: 'Query', findManyPipeline: Array<{ __typename?: 'Pipeline', id: string, name: string, pipelineProgressableType: PipelineProgressableType, pipelineStages?: Array<{ __typename?: 'PipelineStage', id: string, name: string, color: string, index?: number | null }> | null }> };
export type GetPipelinesQuery = { __typename?: 'Query', findManyPipeline: Array<{ __typename?: 'Pipeline', id: string, name: string, pipelineProgressableType: PipelineProgressableType, pipelineStages?: Array<{ __typename?: 'PipelineStage', id: string, name: string, color: string, index?: number | null, isVisible: boolean }> | null }> };

export type SearchActivityQueryVariables = Exact<{
where?: InputMaybe<ActivityWhereInput>;
Expand Down Expand Up @@ -6446,6 +6452,7 @@ export const GetPipelinesDocument = gql`
name
color
index
isVisible
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,15 @@ export const useUpdateCompanyBoard = () =>

return {
id: pipelineStage.id,
key: pipelineStage.id,
title: pipelineStage.name,
name: pipelineStage.name,
colorCode: isThemeColor(pipelineStage.color)
? pipelineStage.color
: undefined,
index: pipelineStage.index ?? 0,
};
isVisible: pipelineStage.isVisible,
} as BoardColumnDefinition;
});
if (currentBoardColumns.length === 0) {
set(boardColumnsState, newBoardColumns);
Expand Down
1 change: 1 addition & 0 deletions front/src/modules/pipeline/graphql/queries/getPipelines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const GET_PIPELINES = gql`
name
color
index
isVisible
}
}
}
Expand Down
1 change: 1 addition & 0 deletions front/src/modules/pipeline/hooks/usePipelineStages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const usePipelineStages = () => {
id: boardColumn.id,
index: boardColumn.index,
name: boardColumn.title,
isVisible: true,
pipeline: { connect: { id: currentPipeline.id } },
type: 'ongoing',
},
Expand Down
13 changes: 8 additions & 5 deletions front/src/modules/ui/data/data-table/hooks/useTableColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ export const useTableColumns = () => {
);

const handleColumnReorder = useCallback(
async (columns: ColumnDefinition<FieldMetadata>[]) => {
const updatedColumns = columns.map((column, index) => ({
...column,
index,
}));
async (columns: ViewFieldForVisibility[]) => {
const updatedColumns = columns.map(
(column, index) =>
({
...column,
index,
} as ColumnDefinition<FieldMetadata>),
);

await handleColumnsChange(updatedColumns);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useCallback, useContext, useRef, useState } from 'react';
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useContext, useRef, useState } from 'react';
import { useRecoilCallback, useRecoilValue, useResetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';

Expand All @@ -9,6 +8,7 @@ import { useUpsertView } from '@/ui/data/view-bar/hooks/useUpsertView';
import { currentViewScopedSelector } from '@/ui/data/view-bar/states/selectors/currentViewScopedSelector';
import { viewsByIdScopedSelector } from '@/ui/data/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState';
import { ViewFieldForVisibility } from '@/ui/data/view-bar/types/ViewFieldForVisibility';
import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/display/icon';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
Expand Down Expand Up @@ -90,20 +90,9 @@ export const TableOptionsDropdownContent = () => {
setCurrentMenu(option);
};

const handleReorderField: OnDragEndResponder = useCallback(
(result) => {
if (!result.destination || result.destination.index === 0) {
return;
}

const reorderFields = Array.from(visibleTableColumns);
const [removed] = reorderFields.splice(result.source.index, 1);
reorderFields.splice(result.destination.index, 0, removed);

handleColumnReorder(reorderFields);
},
[visibleTableColumns, handleColumnReorder],
);
const handleReorderField = (fields: ViewFieldForVisibility[]) => {
handleColumnReorder(fields);
};

const resetMenu = () => setCurrentMenu(undefined);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import {
DropResult,
OnDragEndResponder,
ResponderProvided,
} from '@hello-pangea/dnd';
import styled from '@emotion/styled';
import { DropResult } from '@hello-pangea/dnd';

import { IconMinus, IconPlus } from '@/ui/display/icon';
import { IconMinus, IconPencil, IconPlus } from '@/ui/display/icon';
import { AppTooltip } from '@/ui/display/tooltip/AppTooltip';
import { IconInfoCircle } from '@/ui/input/constants/icons';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
Expand All @@ -20,23 +17,36 @@ import { isDefined } from '~/utils/isDefined';

import { ViewFieldForVisibility } from '../types/ViewFieldForVisibility';

const StyledDraggableWrapper = styled.div`
width: 100%;
`;

type ViewFieldsVisibilityDropdownSectionProps = {
fields: ViewFieldForVisibility[];
onVisibilityChange: (field: ViewFieldForVisibility) => void;
editField?: (field: ViewFieldForVisibility) => void;
title: string;
isDraggable: boolean;
onDragEnd?: OnDragEndResponder;
onDragEnd?: (field: ViewFieldForVisibility[]) => void;
};

export const ViewFieldsVisibilityDropdownSection = ({
fields,
onVisibilityChange,
editField,
title,
isDraggable,
onDragEnd,
}: ViewFieldsVisibilityDropdownSectionProps) => {
const handleOnDrag = (result: DropResult, provided: ResponderProvided) => {
onDragEnd?.(result, provided);
const handleOnDrag = (result: DropResult) => {
if (!result.destination || result.destination.index === 0) {
return;
}
const reorderFields = Array.from(fields);
const [removed] = reorderFields.splice(result.source.index, 1);
reorderFields.splice(result.destination.index, 0, removed);

onDragEnd?.(reorderFields);
};

const [openToolTipIndex, setOpenToolTipIndex] = useState<number>();
Expand All @@ -47,26 +57,30 @@ export const ViewFieldsVisibilityDropdownSection = ({
};

const getIconButtons = (index: number, field: ViewFieldForVisibility) => {
const visibilityIcon = {
Icon: field.isVisible ? IconMinus : IconPlus,
onClick: () => onVisibilityChange(field),
};

const infoTooltipIcon = {
Icon: IconInfoCircle,
onClick: () => handleInfoButtonClick(index),
isActive: openToolTipIndex === index,
};

const editIcon = editField
? [
{
Icon: IconPencil,
onClick: () => editField(field),
},
]
: [];
if (field.infoTooltipContent) {
return [
{
Icon: IconInfoCircle,
onClick: () => handleInfoButtonClick(index),
isActive: openToolTipIndex === index,
},
{
Icon: field.isVisible ? IconMinus : IconPlus,
onClick: () => onVisibilityChange(field),
},
];
return [infoTooltipIcon, ...editIcon, visibilityIcon];
}
if (!field.infoTooltipContent) {
return [
{
Icon: field.isVisible ? IconMinus : IconPlus,
onClick: () => onVisibilityChange(field),
},
];
return [...editIcon, visibilityIcon];
}
};

Expand All @@ -80,7 +94,7 @@ export const ViewFieldsVisibilityDropdownSection = ({
});

return (
<div ref={ref}>
<StyledDraggableWrapper ref={ref}>
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
<DropdownMenuItemsContainer>
{isDraggable ? (
Expand All @@ -89,7 +103,7 @@ export const ViewFieldsVisibilityDropdownSection = ({
draggableItems={
<>
{fields
.filter(({ index, size }) => index !== 0 || !size)
.filter(({ index }) => index !== 0)
.map((field, index) => (
<DraggableItem
key={field.key}
Expand All @@ -99,9 +113,10 @@ export const ViewFieldsVisibilityDropdownSection = ({
<MenuItemDraggable
key={field.key}
LeftIcon={field.Icon}
iconButtons={getIconButtons(index + 1, field)}
isTooltipOpen={openToolTipIndex === index + 1}
iconButtons={getIconButtons(index + 1, field)}
text={field.name}
textColor={field.colorCode}
className={`${title}-draggable-item-tooltip-anchor-${
index + 1
}`}
Expand All @@ -120,6 +135,7 @@ export const ViewFieldsVisibilityDropdownSection = ({
iconButtons={getIconButtons(index, field)}
isTooltipOpen={openToolTipIndex === index}
text={field.name}
textColor={field.colorCode}
className={`${title}-fixed-item-tooltip-anchor-${index}`}
/>
))
Expand All @@ -137,6 +153,6 @@ export const ViewFieldsVisibilityDropdownSection = ({
/>,
document.body,
)}
</div>
</StyledDraggableWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { FieldDefinition } from '@/ui/data/field/types/FieldDefinition';
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
import { ThemeColor } from '@/ui/theme/constants/colors';

export type ViewFieldForVisibility = Pick<
FieldDefinition<FieldMetadata>,
'key' | 'name' | 'Icon' | 'infoTooltipContent'
> & {
isVisible?: boolean;
colorCode?: ThemeColor;
index: number;
size?: number | undefined;
};
2 changes: 1 addition & 1 deletion front/src/modules/ui/display/tag/components/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const StyledTag = styled.h3<{
color: ${({ color, theme }) => theme.tag.text[color]};
display: flex;
flex-direction: row;
font-size: ${({ theme }) => theme.font.size.md};
font-size: ${({ theme }) => theme.font.size.sm};
font-style: normal;
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { createPortal } from 'react-dom';
import styled from '@emotion/styled';
import { v4 as uuidV4 } from 'uuid';

import { ThemeColor } from '@/ui/theme/constants/colors';

import { Tag } from '../tag/components/Tag';

import { AppTooltip } from './AppTooltip';

const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>`
Expand All @@ -21,8 +25,10 @@ const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>`

export const OverflowingTextWithTooltip = ({
text,
textColor,
}: {
text: string | null | undefined;
textColor?: ThemeColor;
}) => {
const textElementId = `title-id-${uuidV4()}`;

Expand All @@ -47,16 +53,28 @@ export const OverflowingTextWithTooltip = ({
event.preventDefault();
};

const OverFlowingText = () => (
<StyledOverflowingText
data-testid="tooltip"
ref={textRef}
id={textElementId}
cursorPointer={isTitleOverflowing}
>
{text}
</StyledOverflowingText>
);

return (
<>
<StyledOverflowingText
data-testid="tooltip"
ref={textRef}
id={textElementId}
cursorPointer={isTitleOverflowing}
>
{text}
</StyledOverflowingText>
{text ? (
textColor ? (
<Tag text={text} color={textColor ?? 'gray'} />
) : (
<OverFlowingText />
)
) : (
<OverFlowingText />
)}
{isTitleOverflowing &&
createPortal(
<div onClick={handleTooltipClick}>
Expand Down
Loading
Loading