diff --git a/front/src/modules/companies/board/components/CompanyBoard.tsx b/front/src/modules/companies/board/components/CompanyBoard.tsx new file mode 100644 index 000000000..f195140e2 --- /dev/null +++ b/front/src/modules/companies/board/components/CompanyBoard.tsx @@ -0,0 +1,40 @@ +import { + EntityBoard, + type EntityBoardProps, +} from '@/ui/board/components/EntityBoard'; +import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar'; +import { EntityBoardContextMenu } from '@/ui/board/components/EntityBoardContextMenu'; +import { useBoardViews } from '@/views/hooks/useBoardViews'; + +import { HooksCompanyBoard } from '../../components/HooksCompanyBoard'; +import { CompanyBoardRecoilScopeContext } from '../../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext'; + +type OwnProps = Pick< + EntityBoardProps, + 'boardOptions' | 'onColumnAdd' | 'onColumnDelete' | 'onEditColumnTitle' +>; + +export const CompanyBoard = ({ boardOptions, ...props }: OwnProps) => { + const { handleViewsChange, handleViewSubmit } = useBoardViews({ + availableFilters: boardOptions.filters, + availableSorts: boardOptions.sorts, + objectId: 'company', + scopeContext: CompanyBoardRecoilScopeContext, + }); + + return ( + <> + + + + + + ); +}; diff --git a/front/src/modules/ui/board/components/BoardHeader.tsx b/front/src/modules/ui/board/components/BoardHeader.tsx index 2572d948b..2ba60c0a5 100644 --- a/front/src/modules/ui/board/components/BoardHeader.tsx +++ b/front/src/modules/ui/board/components/BoardHeader.tsx @@ -1,50 +1,52 @@ -import type { ComponentProps, Context, ReactNode } from 'react'; -import styled from '@emotion/styled'; +import { type ComponentProps, useCallback } from 'react'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; -import { TopBar } from '@/ui/top-bar/TopBar'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { FilterDropdownButton } from '@/ui/view-bar/components/FilterDropdownButton'; -import { SortDropdownButton } from '@/ui/view-bar/components/SortDropdownButton'; -import ViewBarDetails from '@/ui/view-bar/components/ViewBarDetails'; -import { FiltersHotkeyScope } from '@/ui/view-bar/types/FiltersHotkeyScope'; -import { SortType } from '@/ui/view-bar/types/interface'; +import { ViewBar, type ViewBarProps } from '@/ui/view-bar/components/ViewBar'; import type { BoardColumnDefinition } from '../types/BoardColumnDefinition'; +import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey'; import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope'; import { BoardOptionsDropdown } from './BoardOptionsDropdown'; -type OwnProps = ComponentProps<'div'> & { - viewName: string; - viewIcon?: ReactNode; - availableSorts?: Array>; +export type BoardHeaderProps = ComponentProps<'div'> & { onStageAdd?: (boardColumn: BoardColumnDefinition) => void; - context: Context; -}; - -const StyledIcon = styled.div` - display: flex; - margin-left: ${({ theme }) => theme.spacing(1)}; - margin-right: ${({ theme }) => theme.spacing(2)}; - - & > svg { - font-size: ${({ theme }) => theme.icon.size.sm}; - } -`; +} & Pick< + ViewBarProps, + | 'availableSorts' + | 'defaultViewName' + | 'onViewsChange' + | 'onViewSubmit' + | 'scopeContext' + >; export function BoardHeader({ - viewName, - viewIcon, - availableSorts, onStageAdd, - context, + onViewsChange, + scopeContext, ...props -}: OwnProps) { +}: BoardHeaderProps) { + const OptionsDropdownButton = useCallback( + () => ( + + ), + [onStageAdd, onViewsChange, scopeContext], + ); + return ( - diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx index d43525519..8b82455c0 100644 --- a/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx +++ b/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx @@ -1,28 +1,29 @@ import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; -import type { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { BoardColumnDefinition } from '../types/BoardColumnDefinition'; import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey'; import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton'; -import { BoardOptionsDropdownContent } from './BoardOptionsDropdownContent'; +import { + BoardOptionsDropdownContent, + type BoardOptionsDropdownContentProps, +} from './BoardOptionsDropdownContent'; -type BoardOptionsDropdownProps = { - customHotkeyScope: HotkeyScope; - onStageAdd?: (boardColumn: BoardColumnDefinition) => void; -}; +type BoardOptionsDropdownProps = Pick< + BoardOptionsDropdownContentProps, + 'customHotkeyScope' | 'onStageAdd' | 'onViewsChange' | 'scopeContext' +>; export function BoardOptionsDropdown({ customHotkeyScope, - onStageAdd, + ...props }: BoardOptionsDropdownProps) { return ( } dropdownComponents={ } dropdownHotkeyScope={customHotkeyScope} diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx index 69bd1ae0a..cc4e3de00 100644 --- a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx +++ b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx @@ -1,7 +1,7 @@ -import { useRef, useState } from 'react'; +import { type Context, useRef, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; import { v4 } from 'uuid'; @@ -22,14 +22,21 @@ import { MenuItemNavigate } from '@/ui/menu-item/components/MenuItemNavigate'; 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 { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView'; +import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector'; +import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState'; +import type { View } from '@/ui/view-bar/types/View'; import { boardColumnsState } from '../states/boardColumnsState'; import type { BoardColumnDefinition } from '../types/BoardColumnDefinition'; import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey'; -type BoardOptionsDropdownContentProps = { +export type BoardOptionsDropdownContentProps = { customHotkeyScope: HotkeyScope; onStageAdd?: (boardColumn: BoardColumnDefinition) => void; + onViewsChange?: (views: View[]) => void | Promise; + scopeContext: Context; }; const StyledIconSettings = styled(IconSettings)` @@ -51,10 +58,13 @@ type ColumnForCreate = { export function BoardOptionsDropdownContent({ customHotkeyScope, onStageAdd, + onViewsChange, + scopeContext, }: BoardOptionsDropdownContentProps) { const theme = useTheme(); const stageInputRef = useRef(null); + const viewEditInputRef = useRef(null); const [currentMenu, setCurrentMenu] = useState< BoardOptionsMenu | undefined @@ -62,7 +72,8 @@ export function BoardOptionsDropdownContent({ const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState); - const resetMenu = () => setCurrentMenu(undefined); + const viewsById = useRecoilScopedValue(viewsByIdScopedSelector, scopeContext); + const viewEditMode = useRecoilValue(viewEditModeState); const handleStageSubmit = () => { if ( @@ -85,6 +96,23 @@ export function BoardOptionsDropdownContent({ onStageAdd?.(columnToCreate); }; + const { upsertView } = useUpsertView({ + onViewsChange, + scopeContext, + }); + + const handleViewNameSubmit = async () => { + const name = viewEditInputRef.current?.value; + await upsertView(name); + }; + + const resetMenu = () => setCurrentMenu(undefined); + + const handleMenuNavigate = (menu: BoardOptionsMenu) => { + handleViewNameSubmit(); + setCurrentMenu(menu); + }; + const { closeDropdownButton } = useDropdownButton({ key: BoardOptionsDropdownKey, }); @@ -101,6 +129,7 @@ export function BoardOptionsDropdownContent({ Key.Enter, () => { handleStageSubmit(); + handleViewNameSubmit(); closeDropdownButton(); }, customHotkeyScope.scope, @@ -110,14 +139,29 @@ export function BoardOptionsDropdownContent({ {!currentMenu && ( <> - - - Settings - + {!!viewEditMode.mode ? ( + + ) : ( + + + Settings + + )} setCurrentMenu(BoardOptionsMenu.Stages)} + onClick={() => handleMenuNavigate(BoardOptionsMenu.Stages)} LeftIcon={IconLayoutKanban} text="Stages" /> diff --git a/front/src/modules/ui/board/components/EntityBoard.tsx b/front/src/modules/ui/board/components/EntityBoard.tsx index 6bb1f7165..29159b172 100644 --- a/front/src/modules/ui/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/board/components/EntityBoard.tsx @@ -1,17 +1,17 @@ -import { useCallback, useRef } from 'react'; +import { type Context, useCallback, useRef } from 'react'; import { getOperationName } from '@apollo/client/utilities'; -import { useTheme } from '@emotion/react'; 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 { useRecoilState } from 'recoil'; -import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext'; import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; -import { BoardHeader } from '@/ui/board/components/BoardHeader'; +import { + BoardHeader, + type BoardHeaderProps, +} from '@/ui/board/components/BoardHeader'; import { StyledBoard } from '@/ui/board/components/StyledBoard'; import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext'; -import { IconList } from '@/ui/icon'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -22,6 +22,7 @@ import { PipelineStage, useUpdateOnePipelineProgressStageMutation, } from '~/generated/graphql'; +import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql'; import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected'; import { useSetCardSelected } from '../hooks/useSetCardSelected'; @@ -33,6 +34,17 @@ import { BoardOptions } from '../types/BoardOptions'; import { EntityBoardColumn } from './EntityBoardColumn'; +export type EntityBoardProps = { + boardOptions: BoardOptions; + onColumnAdd?: (boardColumn: BoardColumnDefinition) => void; + onColumnDelete?: (boardColumnId: string) => void; + onEditColumnTitle: (columnId: string, title: string, color: string) => void; + scopeContext: Context; +} & Pick< + BoardHeaderProps, + 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' +>; + const StyledWrapper = styled.div` display: flex; flex-direction: column; @@ -46,19 +58,17 @@ const StyledBoardHeader = styled(BoardHeader)` export function EntityBoard({ boardOptions, + defaultViewName, onColumnAdd, onColumnDelete, onEditColumnTitle, -}: { - boardOptions: BoardOptions; - onColumnAdd?: (boardColumn: BoardColumnDefinition) => void; - onColumnDelete?: (boardColumnId: string) => void; - onEditColumnTitle: (columnId: string, title: string, color: string) => void; -}) { + onViewsChange, + onViewSubmit, + scopeContext, +}: EntityBoardProps) { const [boardColumns] = useRecoilState(boardColumnsState); const setCardSelected = useSetCardSelected(); - const theme = useTheme(); const [updatePipelineProgressStage] = useUpdateOnePipelineProgressStageMutation(); @@ -131,11 +141,12 @@ export function EntityBoard({ return (boardColumns?.length ?? 0) > 0 ? ( } + defaultViewName={defaultViewName} availableSorts={boardOptions.sorts} onStageAdd={onColumnAdd} - context={CompanyBoardRecoilScopeContext} + onViewsChange={onViewsChange} + onViewSubmit={onViewSubmit} + scopeContext={scopeContext} /> diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx index fd8eee340..663c6e531 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx @@ -1,7 +1,6 @@ -import { type FormEvent, useCallback, useRef, useState } from 'react'; -import { useRecoilCallback, useRecoilState } from 'recoil'; +import { useRef, useState } from 'react'; +import { useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; -import { v4 } from 'uuid'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; @@ -11,22 +10,14 @@ import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDrop import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; -import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; -import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; -import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; -import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState'; -import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState'; +import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView'; import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector'; -import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState'; -import { viewsScopedState } from '@/ui/view-bar/states/viewsScopedState'; import type { View } from '@/ui/view-bar/types/View'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; -import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState'; import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector'; import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector'; import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey'; @@ -35,31 +26,27 @@ import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; import { TableOptionsDropdownColumnVisibility } from './TableOptionsDropdownSection'; type TableOptionsDropdownButtonProps = { - onViewsChange?: (views: View[]) => void; + onViewsChange?: (views: View[]) => void | Promise; onImport?: () => void; }; -enum Option { - Properties = 'Properties', -} +type TableOptionsMenu = 'properties'; export function TableOptionsDropdownContent({ onViewsChange, onImport, }: TableOptionsDropdownButtonProps) { - const tableScopeId = useContextScopeId(TableRecoilScopeContext); - const { closeDropdownButton } = useDropdownButton({ key: TableOptionsDropdownKey, }); - const [selectedOption, setSelectedOption] = useState