feat: reorder kanban columns (#1699)
* kaban header options * gql codegn * moveColumn hook refactor * BoardColumnContext addition * saved board columns state * db call hook update * lint fix * state change first db call second * handleMoveTableColumn call * codegen lint fix * useMoveViewColumns hook * useBoardColumns db call addition * boardColumns state change from BoardHeader --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -899,6 +899,177 @@ export type CompanyWhereUniqueInput = {
|
|||||||
id?: InputMaybe<Scalars['String']>;
|
id?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum Currency {
|
||||||
|
Aed = 'AED',
|
||||||
|
Afn = 'AFN',
|
||||||
|
All = 'ALL',
|
||||||
|
Amd = 'AMD',
|
||||||
|
Ang = 'ANG',
|
||||||
|
Aoa = 'AOA',
|
||||||
|
Ars = 'ARS',
|
||||||
|
Aud = 'AUD',
|
||||||
|
Awg = 'AWG',
|
||||||
|
Azn = 'AZN',
|
||||||
|
Bam = 'BAM',
|
||||||
|
Bbd = 'BBD',
|
||||||
|
Bdt = 'BDT',
|
||||||
|
Bgn = 'BGN',
|
||||||
|
Bhd = 'BHD',
|
||||||
|
Bif = 'BIF',
|
||||||
|
Bmd = 'BMD',
|
||||||
|
Bnd = 'BND',
|
||||||
|
Bob = 'BOB',
|
||||||
|
Bov = 'BOV',
|
||||||
|
Brl = 'BRL',
|
||||||
|
Bsd = 'BSD',
|
||||||
|
Btn = 'BTN',
|
||||||
|
Bwp = 'BWP',
|
||||||
|
Byn = 'BYN',
|
||||||
|
Bzd = 'BZD',
|
||||||
|
Cad = 'CAD',
|
||||||
|
Cdf = 'CDF',
|
||||||
|
Chf = 'CHF',
|
||||||
|
Clf = 'CLF',
|
||||||
|
Clp = 'CLP',
|
||||||
|
Cny = 'CNY',
|
||||||
|
Cop = 'COP',
|
||||||
|
Cou = 'COU',
|
||||||
|
Crc = 'CRC',
|
||||||
|
Cuc = 'CUC',
|
||||||
|
Cup = 'CUP',
|
||||||
|
Cve = 'CVE',
|
||||||
|
Czk = 'CZK',
|
||||||
|
Djf = 'DJF',
|
||||||
|
Dkk = 'DKK',
|
||||||
|
Dop = 'DOP',
|
||||||
|
Dzd = 'DZD',
|
||||||
|
Egp = 'EGP',
|
||||||
|
Ern = 'ERN',
|
||||||
|
Etb = 'ETB',
|
||||||
|
Eur = 'EUR',
|
||||||
|
Fjd = 'FJD',
|
||||||
|
Fkp = 'FKP',
|
||||||
|
Gbp = 'GBP',
|
||||||
|
Gel = 'GEL',
|
||||||
|
Ghs = 'GHS',
|
||||||
|
Gip = 'GIP',
|
||||||
|
Gmd = 'GMD',
|
||||||
|
Gnf = 'GNF',
|
||||||
|
Gtq = 'GTQ',
|
||||||
|
Gyd = 'GYD',
|
||||||
|
Hkd = 'HKD',
|
||||||
|
Hnl = 'HNL',
|
||||||
|
Hrk = 'HRK',
|
||||||
|
Htg = 'HTG',
|
||||||
|
Huf = 'HUF',
|
||||||
|
Idr = 'IDR',
|
||||||
|
Ils = 'ILS',
|
||||||
|
Inr = 'INR',
|
||||||
|
Iqd = 'IQD',
|
||||||
|
Irr = 'IRR',
|
||||||
|
Isk = 'ISK',
|
||||||
|
Jmd = 'JMD',
|
||||||
|
Jod = 'JOD',
|
||||||
|
Jpy = 'JPY',
|
||||||
|
Kes = 'KES',
|
||||||
|
Kgs = 'KGS',
|
||||||
|
Khr = 'KHR',
|
||||||
|
Kmf = 'KMF',
|
||||||
|
Kpw = 'KPW',
|
||||||
|
Krw = 'KRW',
|
||||||
|
Kwd = 'KWD',
|
||||||
|
Kyd = 'KYD',
|
||||||
|
Kzt = 'KZT',
|
||||||
|
Lak = 'LAK',
|
||||||
|
Lbp = 'LBP',
|
||||||
|
Lkr = 'LKR',
|
||||||
|
Lrd = 'LRD',
|
||||||
|
Lsl = 'LSL',
|
||||||
|
Lyd = 'LYD',
|
||||||
|
Mad = 'MAD',
|
||||||
|
Mdl = 'MDL',
|
||||||
|
Mga = 'MGA',
|
||||||
|
Mkd = 'MKD',
|
||||||
|
Mmk = 'MMK',
|
||||||
|
Mnt = 'MNT',
|
||||||
|
Mop = 'MOP',
|
||||||
|
Mro = 'MRO',
|
||||||
|
Mru = 'MRU',
|
||||||
|
Mur = 'MUR',
|
||||||
|
Mvr = 'MVR',
|
||||||
|
Mwk = 'MWK',
|
||||||
|
Mxn = 'MXN',
|
||||||
|
Mxv = 'MXV',
|
||||||
|
Myr = 'MYR',
|
||||||
|
Mzn = 'MZN',
|
||||||
|
Nad = 'NAD',
|
||||||
|
Ngn = 'NGN',
|
||||||
|
Nio = 'NIO',
|
||||||
|
Nok = 'NOK',
|
||||||
|
Npr = 'NPR',
|
||||||
|
Nzd = 'NZD',
|
||||||
|
Omr = 'OMR',
|
||||||
|
Pab = 'PAB',
|
||||||
|
Pen = 'PEN',
|
||||||
|
Pgk = 'PGK',
|
||||||
|
Php = 'PHP',
|
||||||
|
Pkr = 'PKR',
|
||||||
|
Pln = 'PLN',
|
||||||
|
Pyg = 'PYG',
|
||||||
|
Qar = 'QAR',
|
||||||
|
Ron = 'RON',
|
||||||
|
Rsd = 'RSD',
|
||||||
|
Rub = 'RUB',
|
||||||
|
Rwf = 'RWF',
|
||||||
|
Sar = 'SAR',
|
||||||
|
Sbd = 'SBD',
|
||||||
|
Scr = 'SCR',
|
||||||
|
Sdd = 'SDD',
|
||||||
|
Sdg = 'SDG',
|
||||||
|
Sek = 'SEK',
|
||||||
|
Sgd = 'SGD',
|
||||||
|
Shp = 'SHP',
|
||||||
|
Sll = 'SLL',
|
||||||
|
Sos = 'SOS',
|
||||||
|
Srd = 'SRD',
|
||||||
|
Ssp = 'SSP',
|
||||||
|
Std = 'STD',
|
||||||
|
Stn = 'STN',
|
||||||
|
Svc = 'SVC',
|
||||||
|
Syp = 'SYP',
|
||||||
|
Szl = 'SZL',
|
||||||
|
Thb = 'THB',
|
||||||
|
Tjs = 'TJS',
|
||||||
|
Tmm = 'TMM',
|
||||||
|
Tmt = 'TMT',
|
||||||
|
Tnd = 'TND',
|
||||||
|
Top = 'TOP',
|
||||||
|
Try = 'TRY',
|
||||||
|
Ttd = 'TTD',
|
||||||
|
Twd = 'TWD',
|
||||||
|
Tzs = 'TZS',
|
||||||
|
Uah = 'UAH',
|
||||||
|
Ugx = 'UGX',
|
||||||
|
Usd = 'USD',
|
||||||
|
Uyu = 'UYU',
|
||||||
|
Uzs = 'UZS',
|
||||||
|
Vef = 'VEF',
|
||||||
|
Ves = 'VES',
|
||||||
|
Vnd = 'VND',
|
||||||
|
Vuv = 'VUV',
|
||||||
|
Wst = 'WST',
|
||||||
|
Xaf = 'XAF',
|
||||||
|
Xcd = 'XCD',
|
||||||
|
Xof = 'XOF',
|
||||||
|
Xpf = 'XPF',
|
||||||
|
Xsu = 'XSU',
|
||||||
|
Xua = 'XUA',
|
||||||
|
Yer = 'YER',
|
||||||
|
Zar = 'ZAR',
|
||||||
|
Zmw = 'ZMW',
|
||||||
|
Zwl = 'ZWL'
|
||||||
|
}
|
||||||
|
|
||||||
export type DateTimeFilter = {
|
export type DateTimeFilter = {
|
||||||
equals?: InputMaybe<Scalars['DateTime']>;
|
equals?: InputMaybe<Scalars['DateTime']>;
|
||||||
gt?: InputMaybe<Scalars['DateTime']>;
|
gt?: InputMaybe<Scalars['DateTime']>;
|
||||||
@ -942,6 +1113,13 @@ export type EnumColorSchemeFilter = {
|
|||||||
notIn?: InputMaybe<Array<ColorScheme>>;
|
notIn?: InputMaybe<Array<ColorScheme>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EnumCurrencyFilter = {
|
||||||
|
equals?: InputMaybe<Currency>;
|
||||||
|
in?: InputMaybe<Array<Currency>>;
|
||||||
|
not?: InputMaybe<NestedEnumCurrencyFilter>;
|
||||||
|
notIn?: InputMaybe<Array<Currency>>;
|
||||||
|
};
|
||||||
|
|
||||||
export type EnumPipelineProgressableTypeFilter = {
|
export type EnumPipelineProgressableTypeFilter = {
|
||||||
equals?: InputMaybe<PipelineProgressableType>;
|
equals?: InputMaybe<PipelineProgressableType>;
|
||||||
in?: InputMaybe<Array<PipelineProgressableType>>;
|
in?: InputMaybe<Array<PipelineProgressableType>>;
|
||||||
@ -1492,6 +1670,13 @@ export type NestedEnumColorSchemeFilter = {
|
|||||||
notIn?: InputMaybe<Array<ColorScheme>>;
|
notIn?: InputMaybe<Array<ColorScheme>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NestedEnumCurrencyFilter = {
|
||||||
|
equals?: InputMaybe<Currency>;
|
||||||
|
in?: InputMaybe<Array<Currency>>;
|
||||||
|
not?: InputMaybe<NestedEnumCurrencyFilter>;
|
||||||
|
notIn?: InputMaybe<Array<Currency>>;
|
||||||
|
};
|
||||||
|
|
||||||
export type NestedEnumPipelineProgressableTypeFilter = {
|
export type NestedEnumPipelineProgressableTypeFilter = {
|
||||||
equals?: InputMaybe<PipelineProgressableType>;
|
equals?: InputMaybe<PipelineProgressableType>;
|
||||||
in?: InputMaybe<Array<PipelineProgressableType>>;
|
in?: InputMaybe<Array<PipelineProgressableType>>;
|
||||||
@ -1795,6 +1980,7 @@ export type PersonWhereUniqueInput = {
|
|||||||
export type Pipeline = {
|
export type Pipeline = {
|
||||||
__typename?: 'Pipeline';
|
__typename?: 'Pipeline';
|
||||||
createdAt: Scalars['DateTime'];
|
createdAt: Scalars['DateTime'];
|
||||||
|
currency: Currency;
|
||||||
icon: Scalars['String'];
|
icon: Scalars['String'];
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID'];
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
@ -1814,6 +2000,7 @@ export type PipelineCreateNestedOneWithoutPipelineStagesInput = {
|
|||||||
|
|
||||||
export type PipelineOrderByWithRelationInput = {
|
export type PipelineOrderByWithRelationInput = {
|
||||||
createdAt?: InputMaybe<SortOrder>;
|
createdAt?: InputMaybe<SortOrder>;
|
||||||
|
currency?: InputMaybe<SortOrder>;
|
||||||
icon?: InputMaybe<SortOrder>;
|
icon?: InputMaybe<SortOrder>;
|
||||||
id?: InputMaybe<SortOrder>;
|
id?: InputMaybe<SortOrder>;
|
||||||
name?: InputMaybe<SortOrder>;
|
name?: InputMaybe<SortOrder>;
|
||||||
@ -2000,6 +2187,7 @@ export type PipelineRelationFilter = {
|
|||||||
|
|
||||||
export enum PipelineScalarFieldEnum {
|
export enum PipelineScalarFieldEnum {
|
||||||
CreatedAt = 'createdAt',
|
CreatedAt = 'createdAt',
|
||||||
|
Currency = 'currency',
|
||||||
DeletedAt = 'deletedAt',
|
DeletedAt = 'deletedAt',
|
||||||
Icon = 'icon',
|
Icon = 'icon',
|
||||||
Id = 'id',
|
Id = 'id',
|
||||||
@ -2141,6 +2329,7 @@ export type PipelineWhereInput = {
|
|||||||
NOT?: InputMaybe<Array<PipelineWhereInput>>;
|
NOT?: InputMaybe<Array<PipelineWhereInput>>;
|
||||||
OR?: InputMaybe<Array<PipelineWhereInput>>;
|
OR?: InputMaybe<Array<PipelineWhereInput>>;
|
||||||
createdAt?: InputMaybe<DateTimeFilter>;
|
createdAt?: InputMaybe<DateTimeFilter>;
|
||||||
|
currency?: InputMaybe<EnumCurrencyFilter>;
|
||||||
icon?: InputMaybe<StringFilter>;
|
icon?: InputMaybe<StringFilter>;
|
||||||
id?: InputMaybe<StringFilter>;
|
id?: InputMaybe<StringFilter>;
|
||||||
name?: InputMaybe<StringFilter>;
|
name?: InputMaybe<StringFilter>;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useContext, useState } from 'react';
|
import { useCallback, useContext, useState } from 'react';
|
||||||
|
|
||||||
import { NewButton } from '@/ui/board/components/NewButton';
|
import { NewButton } from '@/ui/board/components/NewButton';
|
||||||
import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext';
|
import { BoardColumnContext } from '@/ui/board/contexts/BoardColumnContext';
|
||||||
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
@ -14,7 +14,9 @@ import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompany
|
|||||||
|
|
||||||
export const NewCompanyProgressButton = () => {
|
export const NewCompanyProgressButton = () => {
|
||||||
const [isCreatingCard, setIsCreatingCard] = useState(false);
|
const [isCreatingCard, setIsCreatingCard] = useState(false);
|
||||||
const pipelineStageId = useContext(BoardColumnIdContext);
|
const column = useContext(BoardColumnContext);
|
||||||
|
|
||||||
|
const pipelineStageId = column?.columnDefinition.id || '';
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useRecoilCallback } from 'recoil';
|
|||||||
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
||||||
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
|
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
|
||||||
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
|
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
|
||||||
|
import { savedBoardColumnsState } from '@/ui/board/states/savedBoardColumnsState';
|
||||||
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
||||||
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
|
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
|
||||||
import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor';
|
import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor';
|
||||||
@ -114,11 +115,10 @@ export const useUpdateCompanyBoard = () =>
|
|||||||
index: pipelineStage.index ?? 0,
|
index: pipelineStage.index ?? 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
if (currentBoardColumns.length === 0) {
|
||||||
if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) {
|
|
||||||
set(boardColumnsState, newBoardColumns);
|
set(boardColumnsState, newBoardColumns);
|
||||||
|
set(savedBoardColumnsState, newBoardColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const boardColumn of newBoardColumns) {
|
for (const boardColumn of newBoardColumns) {
|
||||||
const boardCardIds = pipelineProgresses
|
const boardCardIds = pipelineProgresses
|
||||||
.filter(
|
.filter(
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { Tag } from '@/ui/tag/components/Tag';
|
import { Tag } from '@/ui/tag/components/Tag';
|
||||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
|
||||||
|
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
||||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||||
|
|
||||||
import { BoardColumnMenu } from './BoardColumnMenu';
|
import { BoardColumnMenu } from './BoardColumnMenu';
|
||||||
@ -53,28 +53,24 @@ const StyledNumChildren = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type BoardColumnProps = {
|
export type BoardColumnProps = {
|
||||||
color?: ThemeColor;
|
|
||||||
title: string;
|
|
||||||
onDelete?: (id: string) => void;
|
onDelete?: (id: string) => void;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
totalAmount?: number;
|
totalAmount?: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
isFirstColumn: boolean;
|
|
||||||
numChildren: number;
|
numChildren: number;
|
||||||
stageId: string;
|
stageId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BoardColumn = ({
|
export const BoardColumn = ({
|
||||||
color,
|
|
||||||
title,
|
|
||||||
onDelete,
|
onDelete,
|
||||||
onTitleEdit,
|
onTitleEdit,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
children,
|
children,
|
||||||
isFirstColumn,
|
|
||||||
numChildren,
|
numChildren,
|
||||||
stageId,
|
stageId,
|
||||||
}: BoardColumnProps) => {
|
}: BoardColumnProps) => {
|
||||||
|
const boardColumn = useContext(BoardColumnContext);
|
||||||
|
|
||||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] =
|
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] =
|
||||||
React.useState(false);
|
React.useState(false);
|
||||||
|
|
||||||
@ -95,10 +91,18 @@ export const BoardColumn = ({
|
|||||||
setIsBoardColumnMenuOpen(false);
|
setIsBoardColumnMenuOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!boardColumn) return <></>;
|
||||||
|
|
||||||
|
const { isFirstColumn, columnDefinition } = boardColumn;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||||
<StyledHeader onClick={handleTitleClick}>
|
<StyledHeader>
|
||||||
<Tag color={color ?? 'gray'} text={title} />
|
<Tag
|
||||||
|
onClick={handleTitleClick}
|
||||||
|
color={columnDefinition.colorCode ?? 'gray'}
|
||||||
|
text={columnDefinition.title}
|
||||||
|
/>
|
||||||
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
|
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
|
||||||
<StyledNumChildren>{numChildren}</StyledNumChildren>
|
<StyledNumChildren>{numChildren}</StyledNumChildren>
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
@ -107,8 +111,6 @@ export const BoardColumn = ({
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onTitleEdit={onTitleEdit}
|
onTitleEdit={onTitleEdit}
|
||||||
title={title}
|
|
||||||
color={color ?? 'gray'}
|
|
||||||
stageId={stageId}
|
stageId={stageId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useContext, useRef, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
@ -7,50 +7,53 @@ import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProg
|
|||||||
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
|
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { IconPencil, IconPlus, IconTrash } from '@/ui/icon';
|
import {
|
||||||
|
IconArrowLeft,
|
||||||
|
IconArrowRight,
|
||||||
|
IconPencil,
|
||||||
|
IconPlus,
|
||||||
|
IconTrash,
|
||||||
|
} from '@/ui/icon';
|
||||||
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
||||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
||||||
|
import { useBoardColumns } from '../hooks/useBoardColumns';
|
||||||
import { boardColumnsState } from '../states/boardColumnsState';
|
import { boardColumnsState } from '../states/boardColumnsState';
|
||||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||||
|
|
||||||
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||||
|
|
||||||
const StyledMenuContainer = styled.div`
|
const StyledMenuContainer = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type BoardColumnMenuProps = {
|
||||||
color: ThemeColor;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onDelete?: (id: string) => void;
|
onDelete?: (id: string) => void;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
stageId: string;
|
stageId: string;
|
||||||
title: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Menu = 'actions' | 'add' | 'title';
|
type Menu = 'actions' | 'add' | 'title';
|
||||||
|
|
||||||
export const BoardColumnMenu = ({
|
export const BoardColumnMenu = ({
|
||||||
color,
|
|
||||||
onClose,
|
onClose,
|
||||||
onDelete,
|
onDelete,
|
||||||
onTitleEdit,
|
onTitleEdit,
|
||||||
stageId,
|
stageId,
|
||||||
title,
|
}: BoardColumnMenuProps) => {
|
||||||
}: OwnProps) => {
|
|
||||||
const [currentMenu, setCurrentMenu] = useState('actions');
|
const [currentMenu, setCurrentMenu] = useState('actions');
|
||||||
|
const column = useContext(BoardColumnContext);
|
||||||
|
|
||||||
const [, setBoardColumns] = useRecoilState(boardColumnsState);
|
const [, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||||
|
|
||||||
@ -58,6 +61,7 @@ export const BoardColumnMenu = ({
|
|||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const createCompanyProgress = useCreateCompanyProgress();
|
const createCompanyProgress = useCreateCompanyProgress();
|
||||||
|
const { handleMoveBoardColumn } = useBoardColumns();
|
||||||
|
|
||||||
const handleCompanySelected = (
|
const handleCompanySelected = (
|
||||||
selectedCompany: EntityForSelect | null | undefined,
|
selectedCompany: EntityForSelect | null | undefined,
|
||||||
@ -125,6 +129,26 @@ export const BoardColumnMenu = ({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!column) return <></>;
|
||||||
|
|
||||||
|
const { isFirstColumn, isLastColumn, columnDefinition } = column;
|
||||||
|
|
||||||
|
const handleColumnMoveLeft = () => {
|
||||||
|
closeMenu();
|
||||||
|
if (isFirstColumn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleMoveBoardColumn('left', columnDefinition);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleColumnMoveRight = () => {
|
||||||
|
closeMenu();
|
||||||
|
if (isLastColumn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleMoveBoardColumn('right', columnDefinition);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||||
<StyledDropdownMenu data-select-disable>
|
<StyledDropdownMenu data-select-disable>
|
||||||
@ -135,6 +159,16 @@ export const BoardColumnMenu = ({
|
|||||||
LeftIcon={IconPencil}
|
LeftIcon={IconPencil}
|
||||||
text="Rename"
|
text="Rename"
|
||||||
/>
|
/>
|
||||||
|
<MenuItem
|
||||||
|
LeftIcon={IconArrowLeft}
|
||||||
|
onClick={handleColumnMoveLeft}
|
||||||
|
text="Move left"
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
LeftIcon={IconArrowRight}
|
||||||
|
onClick={handleColumnMoveRight}
|
||||||
|
text="Move right"
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
LeftIcon={IconTrash}
|
LeftIcon={IconTrash}
|
||||||
@ -149,10 +183,10 @@ export const BoardColumnMenu = ({
|
|||||||
)}
|
)}
|
||||||
{currentMenu === 'title' && (
|
{currentMenu === 'title' && (
|
||||||
<BoardColumnEditTitleMenu
|
<BoardColumnEditTitleMenu
|
||||||
color={color}
|
color={columnDefinition.colorCode ?? 'gray'}
|
||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
onTitleEdit={onTitleEdit}
|
onTitleEdit={onTitleEdit}
|
||||||
title={title}
|
title={columnDefinition.title}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentMenu === 'add' && (
|
{currentMenu === 'add' && (
|
||||||
|
|||||||
@ -12,9 +12,12 @@ import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
|
|||||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||||
|
|
||||||
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
|
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
|
||||||
|
import { boardColumnsState } from '../states/boardColumnsState';
|
||||||
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
|
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
|
||||||
|
import { savedBoardColumnsState } from '../states/savedBoardColumnsState';
|
||||||
import { canPersistBoardCardFieldsScopedFamilySelector } from '../states/selectors/canPersistBoardCardFieldsScopedFamilySelector';
|
import { canPersistBoardCardFieldsScopedFamilySelector } from '../states/selectors/canPersistBoardCardFieldsScopedFamilySelector';
|
||||||
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
import { canPersistBoardColumnsSelector } from '../states/selectors/canPersistBoardColumnsSelector';
|
||||||
|
import type { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||||
import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
|
import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
|
||||||
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
|
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
|
||||||
|
|
||||||
@ -47,6 +50,8 @@ export const BoardHeader = ({ className, onStageAdd }: BoardHeaderProps) => {
|
|||||||
viewId: currentViewId,
|
viewId: currentViewId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const canPersistBoardColumns = useRecoilValue(canPersistBoardColumnsSelector);
|
||||||
|
|
||||||
const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
|
const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
|
||||||
boardCardFieldsScopedState,
|
boardCardFieldsScopedState,
|
||||||
BoardRecoilScopeContext,
|
BoardRecoilScopeContext,
|
||||||
@ -55,7 +60,15 @@ export const BoardHeader = ({ className, onStageAdd }: BoardHeaderProps) => {
|
|||||||
savedBoardCardFieldsFamilyState(currentViewId),
|
savedBoardCardFieldsFamilyState(currentViewId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleViewBarReset = () => setBoardCardFields(savedBoardCardFields);
|
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||||
|
const [, setSavedBoardColumns] = useRecoilState(savedBoardColumnsState);
|
||||||
|
|
||||||
|
const savedBoardColumns = useRecoilValue(savedBoardColumnsState);
|
||||||
|
|
||||||
|
const handleViewBarReset = () => {
|
||||||
|
setBoardCardFields(savedBoardCardFields);
|
||||||
|
setBoardColumns(savedBoardColumns);
|
||||||
|
};
|
||||||
|
|
||||||
const handleViewSelect = useRecoilCallback(
|
const handleViewSelect = useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
@ -75,16 +88,21 @@ export const BoardHeader = ({ className, onStageAdd }: BoardHeaderProps) => {
|
|||||||
if (canPersistBoardCardFields) {
|
if (canPersistBoardCardFields) {
|
||||||
setSavedBoardCardFields(boardCardFields);
|
setSavedBoardCardFields(boardCardFields);
|
||||||
}
|
}
|
||||||
|
if (canPersistBoardColumns) {
|
||||||
|
setSavedBoardColumns(boardColumns);
|
||||||
|
}
|
||||||
|
|
||||||
await onCurrentViewSubmit?.();
|
await onCurrentViewSubmit?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const canPersistView = canPersistBoardCardFields || canPersistBoardColumns;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
|
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
|
||||||
<ViewBarContext.Provider
|
<ViewBarContext.Provider
|
||||||
value={{
|
value={{
|
||||||
...viewBarContextProps,
|
...viewBarContextProps,
|
||||||
canPersistViewFields: canPersistBoardCardFields,
|
canPersistViewFields: canPersistView,
|
||||||
onCurrentViewSubmit: handleCurrentViewSubmit,
|
onCurrentViewSubmit: handleCurrentViewSubmit,
|
||||||
onViewBarReset: handleViewBarReset,
|
onViewBarReset: handleViewBarReset,
|
||||||
onViewSelect: handleViewSelect,
|
onViewSelect: handleViewSelect,
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import { useCallback, useRef } from 'react';
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
|
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
|
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { BoardHeader } from '@/ui/board/components/BoardHeader';
|
import { BoardHeader } from '@/ui/board/components/BoardHeader';
|
||||||
import { StyledBoard } from '@/ui/board/components/StyledBoard';
|
import { StyledBoard } from '@/ui/board/components/StyledBoard';
|
||||||
import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext';
|
import { BoardColumnContext } from '@/ui/board/contexts/BoardColumnContext';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
@ -54,7 +54,7 @@ export const EntityBoard = ({
|
|||||||
onColumnDelete,
|
onColumnDelete,
|
||||||
onEditColumnTitle,
|
onEditColumnTitle,
|
||||||
}: EntityBoardProps) => {
|
}: EntityBoardProps) => {
|
||||||
const [boardColumns] = useRecoilState(boardColumnsState);
|
const boardColumns = useRecoilValue(boardColumnsState);
|
||||||
const setCardSelected = useSetCardSelected();
|
const setCardSelected = useSetCardSelected();
|
||||||
|
|
||||||
const [updatePipelineProgressStage] =
|
const [updatePipelineProgressStage] =
|
||||||
@ -151,19 +151,26 @@ export const EntityBoard = ({
|
|||||||
<StyledBoard ref={boardRef}>
|
<StyledBoard ref={boardRef}>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
{sortedBoardColumns.map((column) => (
|
{sortedBoardColumns.map((column) => (
|
||||||
<BoardColumnIdContext.Provider value={column.id} key={column.id}>
|
<BoardColumnContext.Provider
|
||||||
|
key={column.id}
|
||||||
|
value={{
|
||||||
|
id: column.id,
|
||||||
|
columnDefinition: column,
|
||||||
|
isFirstColumn: column.index === 0,
|
||||||
|
isLastColumn: column.index === sortedBoardColumns.length - 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<RecoilScope
|
<RecoilScope
|
||||||
CustomRecoilScopeContext={BoardColumnRecoilScopeContext}
|
CustomRecoilScopeContext={BoardColumnRecoilScopeContext}
|
||||||
key={column.id}
|
key={column.id}
|
||||||
>
|
>
|
||||||
<EntityBoardColumn
|
<EntityBoardColumn
|
||||||
boardOptions={boardOptions}
|
boardOptions={boardOptions}
|
||||||
column={column}
|
|
||||||
onDelete={onColumnDelete}
|
onDelete={onColumnDelete}
|
||||||
onTitleEdit={onEditColumnTitle}
|
onTitleEdit={onEditColumnTitle}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
</BoardColumnIdContext.Provider>
|
</BoardColumnContext.Provider>
|
||||||
))}
|
))}
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</StyledBoard>
|
</StyledBoard>
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { BoardColumn } from '@/ui/board/components/BoardColumn';
|
import { BoardColumn } from '@/ui/board/components/BoardColumn';
|
||||||
import { BoardCardIdContext } from '@/ui/board/contexts/BoardCardIdContext';
|
import { BoardCardIdContext } from '@/ui/board/contexts/BoardCardIdContext';
|
||||||
import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext';
|
import { BoardColumnContext } from '@/ui/board/contexts/BoardColumnContext';
|
||||||
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState';
|
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState';
|
||||||
@ -29,13 +28,21 @@ const StyledColumnCardsContainer = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
type BoardColumnCardsContainerProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
droppableProvided: DroppableProvided;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EntityBoardColumnProps = {
|
||||||
|
boardOptions: BoardOptions;
|
||||||
|
onDelete?: (columnId: string) => void;
|
||||||
|
onTitleEdit: (columnId: string, title: string, color: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
const BoardColumnCardsContainer = ({
|
const BoardColumnCardsContainer = ({
|
||||||
children,
|
children,
|
||||||
droppableProvided,
|
droppableProvided,
|
||||||
}: {
|
}: BoardColumnCardsContainerProps) => {
|
||||||
children: React.ReactNode;
|
|
||||||
droppableProvided: DroppableProvided;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnCardsContainer
|
<StyledColumnCardsContainer
|
||||||
ref={droppableProvided?.innerRef}
|
ref={droppableProvided?.innerRef}
|
||||||
@ -49,39 +56,34 @@ const BoardColumnCardsContainer = ({
|
|||||||
|
|
||||||
export const EntityBoardColumn = ({
|
export const EntityBoardColumn = ({
|
||||||
boardOptions,
|
boardOptions,
|
||||||
column,
|
|
||||||
onDelete,
|
onDelete,
|
||||||
onTitleEdit,
|
onTitleEdit,
|
||||||
}: {
|
}: EntityBoardColumnProps) => {
|
||||||
boardOptions: BoardOptions;
|
const column = useContext(BoardColumnContext);
|
||||||
column: BoardColumnDefinition;
|
|
||||||
onDelete?: (columnId: string) => void;
|
const boardColumnId = column?.id || '';
|
||||||
onTitleEdit: (columnId: string, title: string, color: string) => void;
|
|
||||||
}) => {
|
|
||||||
const boardColumnId = useContext(BoardColumnIdContext) ?? '';
|
|
||||||
|
|
||||||
const boardColumnTotal = useRecoilValue(
|
const boardColumnTotal = useRecoilValue(
|
||||||
boardColumnTotalsFamilySelector(column.id),
|
boardColumnTotalsFamilySelector(boardColumnId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const cardIds = useRecoilValue(
|
const cardIds = useRecoilValue(
|
||||||
boardCardIdsByColumnIdFamilyState(boardColumnId ?? ''),
|
boardCardIdsByColumnIdFamilyState(boardColumnId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTitleEdit = (title: string, color: string) => {
|
const handleTitleEdit = (title: string, color: string) => {
|
||||||
onTitleEdit(boardColumnId, title, color);
|
onTitleEdit(boardColumnId, title, color);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!column) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable droppableId={column.id}>
|
<Droppable droppableId={column.id}>
|
||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<BoardColumn
|
<BoardColumn
|
||||||
onTitleEdit={handleTitleEdit}
|
onTitleEdit={handleTitleEdit}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
title={column.title}
|
|
||||||
color={column.colorCode}
|
|
||||||
totalAmount={boardColumnTotal}
|
totalAmount={boardColumnTotal}
|
||||||
isFirstColumn={column.index === 0}
|
|
||||||
numChildren={cardIds.length}
|
numChildren={cardIds.length}
|
||||||
stageId={column.id}
|
stageId={column.id}
|
||||||
>
|
>
|
||||||
|
|||||||
12
front/src/modules/ui/board/contexts/BoardColumnContext.ts
Normal file
12
front/src/modules/ui/board/contexts/BoardColumnContext.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||||
|
|
||||||
|
type BoardColumn = {
|
||||||
|
id: string;
|
||||||
|
columnDefinition: BoardColumnDefinition;
|
||||||
|
isFirstColumn: boolean;
|
||||||
|
isLastColumn: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BoardColumnContext = createContext<BoardColumn | null>(null);
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export const BoardColumnIdContext = createContext<string | null>(null);
|
|
||||||
53
front/src/modules/ui/board/hooks/useBoardColumns.ts
Normal file
53
front/src/modules/ui/board/hooks/useBoardColumns.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
|
||||||
|
import { useUpdatePipelineStageMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { boardColumnsState } from '../states/boardColumnsState';
|
||||||
|
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||||
|
|
||||||
|
export const useBoardColumns = () => {
|
||||||
|
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||||
|
|
||||||
|
const { handleColumnMove } = useMoveViewColumns();
|
||||||
|
|
||||||
|
const [updatePipelineStageMutation] = useUpdatePipelineStageMutation();
|
||||||
|
|
||||||
|
const updatedPipelineStages = (stages: BoardColumnDefinition[]) => {
|
||||||
|
if (!stages.length) return;
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
stages.map((stage) =>
|
||||||
|
updatePipelineStageMutation({
|
||||||
|
variables: {
|
||||||
|
data: {
|
||||||
|
index: stage.index,
|
||||||
|
},
|
||||||
|
id: stage.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistBoardColumns = async () => {
|
||||||
|
await updatedPipelineStages(boardColumns);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMoveBoardColumn = (
|
||||||
|
direction: 'left' | 'right',
|
||||||
|
column: BoardColumnDefinition,
|
||||||
|
) => {
|
||||||
|
const currentColumnArrayIndex = boardColumns.findIndex(
|
||||||
|
(tableColumn) => tableColumn.id === column.id,
|
||||||
|
);
|
||||||
|
const columns = handleColumnMove(
|
||||||
|
direction,
|
||||||
|
currentColumnArrayIndex,
|
||||||
|
boardColumns,
|
||||||
|
);
|
||||||
|
setBoardColumns(columns);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleMoveBoardColumn, persistBoardColumns };
|
||||||
|
};
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||||
|
|
||||||
|
export const savedBoardColumnsState = atom<BoardColumnDefinition[]>({
|
||||||
|
key: 'savedBoardColumnsState',
|
||||||
|
default: [],
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { selector } from 'recoil';
|
||||||
|
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
|
import { boardColumnsState } from '../boardColumnsState';
|
||||||
|
import { savedBoardColumnsState } from '../savedBoardColumnsState';
|
||||||
|
|
||||||
|
export const canPersistBoardColumnsSelector = selector<boolean>({
|
||||||
|
key: 'canPersistBoardCardFieldsScopedFamilySelector',
|
||||||
|
get: ({ get }) =>
|
||||||
|
!isDeeplyEqual(get(boardColumnsState), get(savedBoardColumnsState)),
|
||||||
|
});
|
||||||
@ -22,11 +22,8 @@ export const TableColumnDropdownMenu = ({
|
|||||||
isLastColumn,
|
isLastColumn,
|
||||||
primaryColumnKey,
|
primaryColumnKey,
|
||||||
}: EntityTableHeaderOptionsProps) => {
|
}: EntityTableHeaderOptionsProps) => {
|
||||||
const {
|
const { handleColumnVisibilityChange, handleMoveTableColumn } =
|
||||||
handleColumnVisibilityChange,
|
useTableColumns();
|
||||||
handleColumnLeftMove,
|
|
||||||
handleColumnRightMove,
|
|
||||||
} = useTableColumns();
|
|
||||||
|
|
||||||
const { closeDropdownButton } = useDropdownButton({
|
const { closeDropdownButton } = useDropdownButton({
|
||||||
dropdownId: ColumnHeadDropdownId,
|
dropdownId: ColumnHeadDropdownId,
|
||||||
@ -37,7 +34,7 @@ export const TableColumnDropdownMenu = ({
|
|||||||
if (isFirstColumn) {
|
if (isFirstColumn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleColumnLeftMove(column);
|
handleMoveTableColumn('left', column);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnMoveRight = () => {
|
const handleColumnMoveRight = () => {
|
||||||
@ -45,7 +42,7 @@ export const TableColumnDropdownMenu = ({
|
|||||||
if (isLastColumn) {
|
if (isLastColumn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleColumnRightMove(column);
|
handleMoveTableColumn('right', column);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnVisibility = () => {
|
const handleColumnVisibility = () => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
|||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||||
|
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
|
||||||
|
|
||||||
import { TableContext } from '../contexts/TableContext';
|
import { TableContext } from '../contexts/TableContext';
|
||||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
@ -32,6 +33,8 @@ export const useTableColumns = () => {
|
|||||||
TableRecoilScopeContext,
|
TableRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { handleColumnMove } = useMoveViewColumns();
|
||||||
|
|
||||||
const handleColumnsChange = useCallback(
|
const handleColumnsChange = useCallback(
|
||||||
async (columns: ColumnDefinition<ViewFieldMetadata>[]) => {
|
async (columns: ColumnDefinition<ViewFieldMetadata>[]) => {
|
||||||
setSavedTableColumns(columns);
|
setSavedTableColumns(columns);
|
||||||
@ -71,54 +74,28 @@ export const useTableColumns = () => {
|
|||||||
[tableColumnsByKey, tableColumns, handleColumnsChange],
|
[tableColumnsByKey, tableColumns, handleColumnsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleColumnMove = useCallback(
|
const handleMoveTableColumn = useCallback(
|
||||||
async (direction: string, column: ColumnDefinition<ViewFieldMetadata>) => {
|
(
|
||||||
|
direction: 'left' | 'right',
|
||||||
|
column: ColumnDefinition<ViewFieldMetadata>,
|
||||||
|
) => {
|
||||||
const currentColumnArrayIndex = tableColumns.findIndex(
|
const currentColumnArrayIndex = tableColumns.findIndex(
|
||||||
(tableColumn) => tableColumn.key === column.key,
|
(tableColumn) => tableColumn.key === column.key,
|
||||||
);
|
);
|
||||||
const targetColumnArrayIndex =
|
const columns = handleColumnMove(
|
||||||
direction === 'left'
|
direction,
|
||||||
? currentColumnArrayIndex - 1
|
currentColumnArrayIndex,
|
||||||
: currentColumnArrayIndex + 1;
|
tableColumns,
|
||||||
|
);
|
||||||
|
|
||||||
if (currentColumnArrayIndex >= 0) {
|
setTableColumns(columns);
|
||||||
const currentColumn = tableColumns[currentColumnArrayIndex];
|
|
||||||
const targetColumn = tableColumns[targetColumnArrayIndex];
|
|
||||||
|
|
||||||
const newTableColumns = [...tableColumns];
|
|
||||||
newTableColumns[currentColumnArrayIndex] = {
|
|
||||||
...targetColumn,
|
|
||||||
index: currentColumn.index,
|
|
||||||
};
|
|
||||||
newTableColumns[targetColumnArrayIndex] = {
|
|
||||||
...currentColumn,
|
|
||||||
index: targetColumn.index,
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleColumnsChange(newTableColumns);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[tableColumns, handleColumnsChange],
|
[tableColumns, setTableColumns, handleColumnMove],
|
||||||
);
|
|
||||||
|
|
||||||
const handleColumnLeftMove = useCallback(
|
|
||||||
(column: ColumnDefinition<ViewFieldMetadata>) => {
|
|
||||||
handleColumnMove('left', column);
|
|
||||||
},
|
|
||||||
[handleColumnMove],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleColumnRightMove = useCallback(
|
|
||||||
(column: ColumnDefinition<ViewFieldMetadata>) => {
|
|
||||||
handleColumnMove('right', column);
|
|
||||||
},
|
|
||||||
[handleColumnMove],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleColumnVisibilityChange,
|
handleColumnVisibilityChange,
|
||||||
handleColumnLeftMove,
|
handleMoveTableColumn,
|
||||||
handleColumnRightMove,
|
|
||||||
handleColumnReorder,
|
handleColumnReorder,
|
||||||
handleColumnsChange,
|
handleColumnsChange,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { availableBoardCardFieldsScopedState } from '@/ui/board/states/available
|
|||||||
import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState';
|
import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState';
|
||||||
import { savedBoardCardFieldsFamilyState } from '@/ui/board/states/savedBoardCardFieldsFamilyState';
|
import { savedBoardCardFieldsFamilyState } from '@/ui/board/states/savedBoardCardFieldsFamilyState';
|
||||||
import { savedBoardCardFieldsByKeyFamilySelector } from '@/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector';
|
import { savedBoardCardFieldsByKeyFamilySelector } from '@/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector';
|
||||||
import {
|
import type {
|
||||||
ViewFieldDefinition,
|
ViewFieldDefinition,
|
||||||
ViewFieldMetadata,
|
ViewFieldMetadata,
|
||||||
} from '@/ui/editable-field/types/ViewField';
|
} from '@/ui/editable-field/types/ViewField';
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { RecoilScopeContext } from '@/types/RecoilScopeContext';
|
import { RecoilScopeContext } from '@/types/RecoilScopeContext';
|
||||||
|
import { useBoardColumns } from '@/ui/board/hooks/useBoardColumns';
|
||||||
import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState';
|
import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState';
|
||||||
import {
|
import {
|
||||||
ViewFieldDefinition,
|
ViewFieldDefinition,
|
||||||
@ -50,6 +51,8 @@ export const useBoardViews = ({
|
|||||||
RecoilScopeContext,
|
RecoilScopeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { persistBoardColumns } = useBoardColumns();
|
||||||
|
|
||||||
const { createViewFilters, persistFilters } = useViewFilters({
|
const { createViewFilters, persistFilters } = useViewFilters({
|
||||||
skipFetch: isFetchingViews,
|
skipFetch: isFetchingViews,
|
||||||
RecoilScopeContext,
|
RecoilScopeContext,
|
||||||
@ -62,6 +65,7 @@ export const useBoardViews = ({
|
|||||||
|
|
||||||
const submitCurrentView = async () => {
|
const submitCurrentView = async () => {
|
||||||
await persistCardFields();
|
await persistCardFields();
|
||||||
|
await persistBoardColumns();
|
||||||
await persistFilters();
|
await persistFilters();
|
||||||
await persistSorts();
|
await persistSorts();
|
||||||
};
|
};
|
||||||
|
|||||||
35
front/src/modules/views/hooks/useMoveViewColumns.ts
Normal file
35
front/src/modules/views/hooks/useMoveViewColumns.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export const useMoveViewColumns = () => {
|
||||||
|
const handleColumnMove = <T extends { index: number }>(
|
||||||
|
direction: 'left' | 'right',
|
||||||
|
currentArrayindex: number,
|
||||||
|
targetArray: T[],
|
||||||
|
) => {
|
||||||
|
const targetArrayIndex =
|
||||||
|
direction === 'left' ? currentArrayindex - 1 : currentArrayindex + 1;
|
||||||
|
const targetArraySize = targetArray.length - 1;
|
||||||
|
if (
|
||||||
|
currentArrayindex >= 0 &&
|
||||||
|
targetArrayIndex >= 0 &&
|
||||||
|
currentArrayindex <= targetArraySize &&
|
||||||
|
targetArrayIndex <= targetArraySize
|
||||||
|
) {
|
||||||
|
const currentEntity = targetArray[currentArrayindex];
|
||||||
|
const targetEntity = targetArray[targetArrayIndex];
|
||||||
|
const newArray = [...targetArray];
|
||||||
|
|
||||||
|
newArray[currentArrayindex] = {
|
||||||
|
...targetEntity,
|
||||||
|
index: currentEntity.index,
|
||||||
|
};
|
||||||
|
newArray[targetArrayIndex] = {
|
||||||
|
...currentEntity,
|
||||||
|
index: targetEntity.index,
|
||||||
|
};
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleColumnMove };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user