Files
twenty_crm/front/src/modules/ui/layout/board/components/BoardOptionsDropdownContent.tsx
Charles Bochet 685d342170 Migrate view field to new data model - Part 2 (#2270)
* Migrate view field to new data model

* Migrate view fields to new model
2023-10-28 19:13:48 +02:00

261 lines
8.7 KiB
TypeScript

import { useContext, useRef, useState } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import {
IconBaselineDensitySmall,
IconChevronLeft,
IconLayoutKanban,
IconPlus,
IconTag,
} from '@/ui/display/icon';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate';
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState';
import { useBoardCardFields } from '../hooks/useBoardCardFields';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardColumnsState } from '../states/boardColumnsState';
import { isCompactViewEnabledState } from '../states/isCompactViewEnabledState';
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector';
import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
export type BoardOptionsDropdownContentProps = {
customHotkeyScope: HotkeyScope;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
};
type BoardOptionsMenu = 'fields' | 'stage-creation' | 'stages';
type ColumnForCreate = {
id: string;
colorCode: ThemeColor;
position: number;
title: string;
};
export const BoardOptionsDropdownContent = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownContentProps) => {
const { setViewEditMode, createView, currentViewId } = useView();
const { viewEditMode, currentView } = useViewInternalStates();
const { BoardRecoilScopeContext } = useContext(BoardContext);
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
const stageInputRef = useRef<HTMLInputElement>(null);
const viewEditInputRef = useRef<HTMLInputElement>(null);
const [currentMenu, setCurrentMenu] = useState<
BoardOptionsMenu | undefined
>();
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
const [isCompactViewEnabled, setIsCompactViewEnabled] = useRecoilState(
isCompactViewEnabledState,
);
const hiddenBoardCardFields = useRecoilScopedValue(
hiddenBoardCardFieldsScopedSelector,
BoardRecoilScopeContext,
);
const hasHiddenFields = hiddenBoardCardFields.length > 0;
const visibleBoardCardFields = useRecoilScopedValue(
visibleBoardCardFieldsScopedSelector,
BoardRecoilScopeContext,
);
const hasVisibleFields = visibleBoardCardFields.length > 0;
const handleStageSubmit = () => {
if (currentMenu !== 'stage-creation' || !stageInputRef?.current?.value)
return;
const columnToCreate: ColumnForCreate = {
id: v4(),
colorCode: 'gray',
position: boardColumns.length,
title: stageInputRef.current.value,
};
setBoardColumns((previousBoardColumns) => [
...previousBoardColumns,
columnToCreate,
]);
onStageAdd?.(columnToCreate);
};
const handleViewNameSubmit = useRecoilCallback(
({ set, snapshot }) =>
async () => {
const viewEditMode = snapshot
.getLoadable(viewEditModeScopedState({ scopeId: boardRecoilScopeId }))
.getValue();
if (!viewEditMode) {
return;
}
const boardCardFields = await snapshot.getPromise(
boardCardFieldsScopedState(boardRecoilScopeId),
);
const isCreateMode = viewEditMode === 'create';
const name = viewEditInputRef.current?.value;
if (isCreateMode && name) {
await createView(name);
set(savedBoardCardFieldsFamilyState(currentViewId), boardCardFields);
}
},
[boardRecoilScopeId, createView, currentViewId],
);
const resetMenu = () => setCurrentMenu(undefined);
const handleMenuNavigate = (menu: BoardOptionsMenu) => {
handleViewNameSubmit();
setCurrentMenu(menu);
};
const { handleFieldVisibilityChange } = useBoardCardFields();
const { closeDropdown } = useDropdown();
useScopedHotkeys(
Key.Escape,
() => {
setViewEditMode('none');
closeDropdown();
},
customHotkeyScope.scope,
);
useScopedHotkeys(
Key.Enter,
() => {
handleStageSubmit();
handleViewNameSubmit();
closeDropdown();
},
customHotkeyScope.scope,
);
return (
<>
{!currentMenu && (
<>
{viewEditMode && (
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={
viewEditMode === 'create'
? ''
: viewEditMode === 'edit'
? currentView?.name
: ''
}
/>
)}
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemNavigate
onClick={() => handleMenuNavigate('fields')}
LeftIcon={IconTag}
text="Fields"
/>
<MenuItemNavigate
onClick={() => handleMenuNavigate('stages')}
LeftIcon={IconLayoutKanban}
text="Stages"
/>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconBaselineDensitySmall}
onToggleChange={setIsCompactViewEnabled}
toggled={isCompactViewEnabled}
text="Compact view"
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</>
)}
{currentMenu === 'stages' && (
<>
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
Stages
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => setCurrentMenu('stage-creation')}
LeftIcon={IconPlus}
text="Add stage"
/>
</DropdownMenuItemsContainer>
</>
)}
{currentMenu === 'stage-creation' && (
<DropdownMenuSearchInput
autoFocus
placeholder="New stage"
ref={stageInputRef}
/>
)}
{currentMenu === 'fields' && (
<>
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
Fields
</DropdownMenuHeader>
<DropdownMenuSeparator />
{hasVisibleFields && (
<ViewFieldsVisibilityDropdownSection
title="Visible"
fields={visibleBoardCardFields}
onVisibilityChange={handleFieldVisibilityChange}
isDraggable={true}
/>
)}
{hasVisibleFields && hasHiddenFields && <DropdownMenuSeparator />}
{hasHiddenFields && (
<ViewFieldsVisibilityDropdownSection
title="Hidden"
fields={hiddenBoardCardFields}
onVisibilityChange={handleFieldVisibilityChange}
isDraggable={false}
/>
)}
</>
)}
</>
);
};