diff --git a/front/src/modules/companies/__stories__/Board.stories.tsx b/front/src/modules/companies/__stories__/Board.stories.tsx index c35d9b02f..1a51ab974 100644 --- a/front/src/modules/companies/__stories__/Board.stories.tsx +++ b/front/src/modules/companies/__stories__/Board.stories.tsx @@ -3,7 +3,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { EntityBoard } from '@/ui/board/components/EntityBoard'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { SortOrder } from '~/generated/graphql'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; @@ -17,13 +16,7 @@ const meta: Meta = { decorators: [ (Story) => ( - + diff --git a/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx b/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx index 254758da6..5e4406b74 100644 --- a/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx +++ b/front/src/modules/companies/__stories__/CompanyBoardCard.stories.tsx @@ -5,7 +5,6 @@ import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard'; import { BoardCardIdContext } from '@/ui/board/contexts/BoardCardIdContext'; import { BoardColumnRecoilScopeContext } from '@/ui/board/states/recoil-scope-contexts/BoardColumnRecoilScopeContext'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { SortOrder } from '~/generated/graphql'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { mockedPipelineProgressData } from '~/testing/mock-data/pipeline-progress'; @@ -19,13 +18,7 @@ const meta: Meta = { decorators: [ (Story) => ( - + diff --git a/front/src/modules/companies/components/HooksCompanyBoard.tsx b/front/src/modules/companies/components/HooksCompanyBoard.tsx index c0fe83874..5fcf55285 100644 --- a/front/src/modules/companies/components/HooksCompanyBoard.tsx +++ b/front/src/modules/companies/components/HooksCompanyBoard.tsx @@ -10,11 +10,11 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; +import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector'; import { turnFilterIntoWhereClause } from '@/ui/view-bar/utils/turnFilterIntoWhereClause'; import { Pipeline, PipelineProgressableType, - PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By, useGetCompaniesQuery, useGetPipelineProgressQuery, useGetPipelinesQuery, @@ -25,13 +25,7 @@ import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds'; import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns'; import { CompanyBoardRecoilScopeContext } from '../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext'; -export function HooksCompanyBoard({ - orderBy, -}: { - orderBy: PipelineProgresses_Order_By[]; - setActionBar?: () => void; - setContextMenu?: () => void; -}) { +export function HooksCompanyBoard() { const setFieldsDefinitionsState = useSetRecoilState( viewFieldsDefinitionsState, ); @@ -71,6 +65,10 @@ export function HooksCompanyBoard({ ?.map((pipelineStage) => pipelineStage.id) .flat(); + const sortsOrderBy = useRecoilScopedValue( + sortsOrderByScopedSelector, + CompanyBoardRecoilScopeContext, + ); const whereFilters = useMemo(() => { return { AND: [ @@ -86,7 +84,7 @@ export function HooksCompanyBoard({ useGetPipelineProgressQuery({ variables: { where: whereFilters, - orderBy, + orderBy: sortsOrderBy, }, onCompleted: (data) => { const pipelineProgresses = data?.findManyPipelineProgress || []; diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index 417885fac..50156d646 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -12,7 +12,6 @@ import { filtersWhereScopedSelector } from '@/ui/view-bar/states/selectors/filte import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector'; import { useTableViews } from '@/views/hooks/useTableViews'; import { - SortOrder, UpdateOneCompanyMutationVariables, useGetCompaniesQuery, useUpdateOneCompanyMutation, @@ -55,16 +54,14 @@ export function CompanyTable() { getRequestResultKey="companies" useGetRequest={useGetCompaniesQuery} getRequestOptimisticEffect={getCompaniesOptimisticEffect} - orderBy={ - sortsOrderBy.length ? sortsOrderBy : [{ createdAt: SortOrder.Desc }] - } + orderBy={sortsOrderBy} whereFilters={filtersWhere} filterDefinitionArray={companiesFilters} setContextMenuEntries={setContextMenuEntries} setActionBarEntries={setActionBarEntries} /> diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx index 3a6e91da9..3ae0032da 100644 --- a/front/src/modules/people/table/components/PeopleTable.tsx +++ b/front/src/modules/people/table/components/PeopleTable.tsx @@ -12,7 +12,6 @@ import { filtersWhereScopedSelector } from '@/ui/view-bar/states/selectors/filte import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector'; import { useTableViews } from '@/views/hooks/useTableViews'; import { - SortOrder, UpdateOnePersonMutationVariables, useGetPeopleQuery, useUpdateOnePersonMutation, @@ -54,16 +53,14 @@ export function PeopleTable() { getRequestResultKey="people" useGetRequest={useGetPeopleQuery} getRequestOptimisticEffect={getPeopleOptimisticEffect} - orderBy={ - sortsOrderBy.length ? sortsOrderBy : [{ createdAt: SortOrder.Desc }] - } + orderBy={sortsOrderBy} whereFilters={filtersWhere} filterDefinitionArray={peopleFilters} setContextMenuEntries={setContextMenuEntries} setActionBarEntries={setActionBarEntries} /> = ComponentProps<'div'> & { viewName: string; viewIcon?: ReactNode; availableSorts?: Array>; - onSortsUpdate?: (sorts: Array>) => void; onStageAdd?: (boardColumn: BoardColumnDefinition) => void; context: Context; }; @@ -44,33 +37,10 @@ export function BoardHeader({ viewName, viewIcon, availableSorts, - onSortsUpdate, onStageAdd, context, ...props }: OwnProps) { - const [sorts, innerSetSorts] = useState>>( - [], - ); - - const sortSelect = useCallback( - (newSort: SelectedSortType) => { - const newSorts = updateSortOrFilterByKey(sorts, newSort); - innerSetSorts(newSorts); - onSortsUpdate && onSortsUpdate(newSorts); - }, - [onSortsUpdate, sorts], - ); - - const sortUnselect = useCallback( - (sortKey: string) => { - const newSorts = sorts.filter((sort) => sort.key !== sortKey); - innerSetSorts(newSorts); - onSortsUpdate && onSortsUpdate(newSorts); - }, - [onSortsUpdate, sorts], - ); - return ( ({ /> context={context} - isSortSelected={sorts.length > 0} availableSorts={availableSorts || []} - onSortSelect={sortSelect} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} /> ({ /> } - bottomComponent={ - { - innerSetSorts([]); - onSortsUpdate?.([]); - }} - /> - } + bottomComponent={} /> ); } - -function updateSortOrFilterByKey( - sorts: Readonly, - newSort: SortOrFilter, -): SortOrFilter[] { - const newSorts = [...sorts]; - const existingSortIndex = sorts.findIndex((sort) => sort.key === newSort.key); - - if (existingSortIndex !== -1) { - newSorts[existingSortIndex] = newSort; - } else { - newSorts.push(newSort); - } - - return newSorts; -} diff --git a/front/src/modules/ui/board/components/EntityBoard.tsx b/front/src/modules/ui/board/components/EntityBoard.tsx index 2fc91e44d..6bb1f7165 100644 --- a/front/src/modules/ui/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/board/components/EntityBoard.tsx @@ -17,10 +17,8 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; -import { SelectedSortType } from '@/ui/view-bar/types/interface'; import { PipelineProgress, - PipelineProgressOrderByWithRelationInput, PipelineStage, useUpdateOnePipelineProgressStageMutation, } from '~/generated/graphql'; @@ -51,15 +49,11 @@ export function EntityBoard({ onColumnAdd, onColumnDelete, onEditColumnTitle, - updateSorts, }: { boardOptions: BoardOptions; onColumnAdd?: (boardColumn: BoardColumnDefinition) => void; onColumnDelete?: (boardColumnId: string) => void; onEditColumnTitle: (columnId: string, title: string, color: string) => void; - updateSorts: ( - sorts: Array>, - ) => void; }) { const [boardColumns] = useRecoilState(boardColumnsState); const setCardSelected = useSetCardSelected(); @@ -140,7 +134,6 @@ export function EntityBoard({ viewName="All opportunities" viewIcon={} availableSorts={boardOptions.sorts} - onSortsUpdate={updateSorts} onStageAdd={onColumnAdd} context={CompanyBoardRecoilScopeContext} /> diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx index addb6f75a..ec9d17ada 100644 --- a/front/src/modules/ui/table/components/EntityTable.tsx +++ b/front/src/modules/ui/table/components/EntityTable.tsx @@ -8,15 +8,16 @@ import { useListenClickOutsideByClassName, } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; -import { SortType } from '@/ui/view-bar/types/interface'; -import type { View } from '@/ui/view-bar/types/View'; import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext'; import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus'; import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; import { useResetTableRowSelection } from '../hooks/useResetTableRowSelection'; import { useSetRowSelectedState } from '../hooks/useSetRowSelectedState'; -import { TableHeader } from '../table-header/components/TableHeader'; +import { + TableHeader, + type TableHeaderProps, +} from '../table-header/components/TableHeader'; import { TableHotkeyScope } from '../types/TableHotkeyScope'; import { EntityTableBody } from './EntityTableBody'; @@ -85,21 +86,22 @@ const StyledTableContainer = styled.div` `; type OwnProps = { - viewName: string; - viewIcon?: React.ReactNode; - availableSorts?: Array>; - onViewsChange?: (views: View[]) => void; - onViewSubmit?: () => void; - onImport?: () => void; updateEntityMutation: any; -}; +} & Pick< + TableHeaderProps, + | 'availableSorts' + | 'defaultViewName' + | 'onImport' + | 'onViewsChange' + | 'onViewSubmit' +>; export function EntityTable({ - viewName, availableSorts, + defaultViewName, + onImport, onViewsChange, onViewSubmit, - onImport, updateEntityMutation, }: OwnProps) { const tableBodyRef = useRef(null); @@ -139,11 +141,11 @@ export function EntityTable({
diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx index 2a8d5db6e..5afba3002 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx @@ -2,6 +2,8 @@ import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import type { View } from '@/ui/view-bar/types/View'; +import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey'; + import { TableOptionsDropdownButton } from './TableOptionsDropdownButton'; import { TableOptionsDropdownContent } from './TableOptionsDropdownContent'; @@ -20,7 +22,7 @@ export function TableOptionsDropdown({ } dropdownHotkeyScope={customHotkeyScope} - dropdownKey="options" + dropdownKey={TableOptionsDropdownKey} dropdownComponents={ ( undefined, diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx index 773dee500..9c464d795 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -1,151 +1,90 @@ import { useCallback } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; 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 { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; -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 { ViewBar, type ViewBarProps } from '@/ui/view-bar/components/ViewBar'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; -import { canPersistFiltersScopedFamilySelector } from '@/ui/view-bar/states/selectors/canPersistFiltersScopedFamilySelector'; -import { canPersistSortsScopedFamilySelector } from '@/ui/view-bar/states/selectors/canPersistSortsScopedFamilySelector'; -import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; -import { FiltersHotkeyScope } from '@/ui/view-bar/types/FiltersHotkeyScope'; -import { SelectedSortType, SortType } from '@/ui/view-bar/types/interface'; -import type { View } from '@/ui/view-bar/types/View'; -import { ViewsHotkeyScope } from '@/ui/view-bar/types/ViewsHotkeyScope'; import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown'; -import { TableUpdateViewButtonGroup } from '../../options/components/TableUpdateViewButtonGroup'; -import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; +import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState'; import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector'; +import { tableColumnsScopedState } from '../../states/tableColumnsScopedState'; +import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey'; import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; -type OwnProps = { - viewName: string; - availableSorts?: Array>; - onViewsChange?: (views: View[]) => void; - onViewSubmit?: () => void; +export type TableHeaderProps = { onImport?: () => void; -}; +} & Pick< + ViewBarProps, + 'availableSorts' | 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' +>; export function TableHeader({ - viewName, - availableSorts, + onImport, onViewsChange, onViewSubmit, - onImport, -}: OwnProps) { + ...props +}: TableHeaderProps) { const tableScopeId = useContextScopeId(TableRecoilScopeContext); const currentViewId = useRecoilScopedValue( currentViewIdScopedState, TableRecoilScopeContext, ); - const [sorts, setSorts] = useRecoilScopedState[]>( - sortsScopedState, - TableRecoilScopeContext, - ); const canPersistTableColumns = useRecoilValue( canPersistTableColumnsScopedFamilySelector([tableScopeId, currentViewId]), ); - const canPersistFilters = useRecoilValue( - canPersistFiltersScopedFamilySelector([tableScopeId, currentViewId]), + const tableColumns = useRecoilScopedValue( + tableColumnsScopedState, + TableRecoilScopeContext, + ); + const setSavedTableColumns = useSetRecoilState( + savedTableColumnsFamilyState(currentViewId), ); - const canPersistSorts = useRecoilValue( - canPersistSortsScopedFamilySelector([tableScopeId, currentViewId]), + const handleViewSelect = useRecoilCallback( + ({ set, snapshot }) => + async (viewId: string) => { + const savedTableColumns = await snapshot.getPromise( + savedTableColumnsFamilyState(viewId), + ); + set(tableColumnsScopedState(tableScopeId), savedTableColumns); + }, + [tableScopeId], ); - const sortSelect = useCallback( - (newSort: SelectedSortType) => { - const newSorts = updateSortOrFilterByKey(sorts, newSort); - setSorts(newSorts); - }, - [setSorts, sorts], - ); + const handleViewSubmit = async () => { + if (canPersistTableColumns) setSavedTableColumns(tableColumns); - const sortUnselect = useCallback( - (sortKey: string) => { - const newSorts = sorts.filter((sort) => sort.key !== sortKey); - setSorts(newSorts); - }, - [setSorts, sorts], + await onViewSubmit?.(); + }; + + const OptionsDropdownButton = useCallback( + () => ( + + ), + [onImport, onViewsChange], ); return ( - - } - displayBottomBorder={false} - rightComponent={ - <> - - - context={TableRecoilScopeContext} - isSortSelected={sorts.length > 0} - availableSorts={availableSorts || []} - onSortSelect={sortSelect} - HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} - isPrimaryButton - /> - - - } - bottomComponent={ - setSorts([])} - hasFilterButton - rightComponent={ - - } - /> - } + ); } - -function updateSortOrFilterByKey( - sorts: Readonly, - newSort: SortOrFilter, -): SortOrFilter[] { - const newSorts = [...sorts]; - const existingSortIndex = sorts.findIndex((sort) => sort.key === newSort.key); - - if (existingSortIndex !== -1) { - newSorts[existingSortIndex] = newSort; - } else { - newSorts.push(newSort); - } - - return newSorts; -} diff --git a/front/src/modules/ui/table/types/TableOptionsDropdownKey.ts b/front/src/modules/ui/table/types/TableOptionsDropdownKey.ts new file mode 100644 index 000000000..2821a2b4a --- /dev/null +++ b/front/src/modules/ui/table/types/TableOptionsDropdownKey.ts @@ -0,0 +1 @@ +export const TableOptionsDropdownKey = 'table-options'; diff --git a/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx b/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx index ba878757e..d03047b0b 100644 --- a/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx @@ -5,15 +5,15 @@ import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/Style import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { IconChevronDown } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { sortsScopedState } from '../states/sortsScopedState'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { SelectedSortType, SortType } from '../types/interface'; import DropdownButton from './DropdownButton'; -type OwnProps = { - isSortSelected: boolean; - onSortSelect: (sort: SelectedSortType) => void; +export type SortDropdownButtonProps = { availableSorts: SortType[]; HotkeyScope: FiltersHotkeyScope; context: Context; @@ -23,21 +23,37 @@ type OwnProps = { const options: Array['order']> = ['asc', 'desc']; export function SortDropdownButton({ - isSortSelected, + context, availableSorts, - onSortSelect, HotkeyScope, -}: OwnProps) { +}: SortDropdownButtonProps) { const [isUnfolded, setIsUnfolded] = useState(false); const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); const [selectedSortDirection, setSelectedSortDirection] = useState['order']>('asc'); + const [sorts, setSorts] = useRecoilScopedState[]>( + sortsScopedState, + context, + ); + + const isSortSelected = sorts.length > 0; + const onSortItemSelect = useCallback( (sort: SortType) => { - onSortSelect({ ...sort, order: selectedSortDirection }); + const newSort = { ...sort, order: selectedSortDirection }; + const sortIndex = sorts.findIndex((sort) => sort.key === newSort.key); + const newSorts = [...sorts]; + + if (sortIndex !== -1) { + newSorts[sortIndex] = newSort; + } else { + newSorts.push(newSort); + } + + setSorts(newSorts); }, - [onSortSelect, selectedSortDirection], + [selectedSortDirection, setSorts, sorts], ); const resetState = useCallback(() => { @@ -46,12 +62,8 @@ export function SortDropdownButton({ }, []); function handleIsUnfoldedChange(newIsUnfolded: boolean) { - if (newIsUnfolded) { - setIsUnfolded(true); - } else { - setIsUnfolded(false); - resetState(); - } + setIsUnfolded(newIsUnfolded); + if (!newIsUnfolded) resetState(); } function handleAddSort(sort: SortType) { diff --git a/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx b/front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx similarity index 63% rename from front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx rename to front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx index 8030597c8..f2b5c960a 100644 --- a/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx +++ b/front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { type Context, useCallback, useState } from 'react'; import styled from '@emotion/styled'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; @@ -6,7 +6,6 @@ import { Key } from 'ts-key-enum'; import { Button } from '@/ui/button/components/Button'; import { ButtonGroup } from '@/ui/button/components/ButtonGroup'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; -import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconChevronDown, IconPlus } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; @@ -22,101 +21,72 @@ import { canPersistSortsScopedFamilySelector } from '@/ui/view-bar/states/select import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState'; -import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; -import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState'; -import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector'; -import { tableColumnsScopedState } from '../../states/tableColumnsScopedState'; - const StyledContainer = styled.div` display: inline-flex; margin-right: ${({ theme }) => theme.spacing(2)}; position: relative; `; -type TableUpdateViewButtonGroupProps = { - onViewSubmit?: () => void; +export type UpdateViewButtonGroupProps = { + canPersistViewFields?: boolean; HotkeyScope: string; + onViewEditModeChange?: () => void; + onViewSubmit?: () => void | Promise; + scopeContext: Context; }; -export const TableUpdateViewButtonGroup = ({ - onViewSubmit, +export const UpdateViewButtonGroup = ({ + canPersistViewFields, HotkeyScope, -}: TableUpdateViewButtonGroupProps) => { + onViewEditModeChange, + onViewSubmit, + scopeContext, +}: UpdateViewButtonGroupProps) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const tableScopeId = useContextScopeId(TableRecoilScopeContext); + const recoilScopeId = useContextScopeId(scopeContext); const currentViewId = useRecoilScopedValue( currentViewIdScopedState, - TableRecoilScopeContext, + scopeContext, ); - const tableColumns = useRecoilScopedValue( - tableColumnsScopedState, - TableRecoilScopeContext, - ); - const setSavedColumns = useSetRecoilState( - savedTableColumnsFamilyState(currentViewId), - ); - const canPersistColumns = useRecoilValue( - canPersistTableColumnsScopedFamilySelector([tableScopeId, currentViewId]), - ); - - const filters = useRecoilScopedValue( - filtersScopedState, - TableRecoilScopeContext, - ); + const filters = useRecoilScopedValue(filtersScopedState, scopeContext); const setSavedFilters = useSetRecoilState( savedFiltersFamilyState(currentViewId), ); const canPersistFilters = useRecoilValue( - canPersistFiltersScopedFamilySelector([tableScopeId, currentViewId]), + canPersistFiltersScopedFamilySelector([recoilScopeId, currentViewId]), ); - const sorts = useRecoilScopedValue(sortsScopedState, TableRecoilScopeContext); + const sorts = useRecoilScopedValue(sortsScopedState, scopeContext); const setSavedSorts = useSetRecoilState(savedSortsFamilyState(currentViewId)); const canPersistSorts = useRecoilValue( - canPersistSortsScopedFamilySelector([tableScopeId, currentViewId]), + canPersistSortsScopedFamilySelector([recoilScopeId, currentViewId]), ); const setViewEditMode = useSetRecoilState(viewEditModeState); - const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ - key: 'options', - }); - const handleArrowDownButtonClick = useCallback(() => { setIsDropdownOpen((previousIsOpen) => !previousIsOpen); }, []); const handleCreateViewButtonClick = useCallback(() => { setViewEditMode({ mode: 'create', viewId: undefined }); - openOptionsDropdownButton(); + onViewEditModeChange?.(); setIsDropdownOpen(false); - }, [setViewEditMode, openOptionsDropdownButton]); + }, [setViewEditMode, onViewEditModeChange]); const handleDropdownClose = useCallback(() => { setIsDropdownOpen(false); }, []); - const handleViewSubmit = useCallback(async () => { - if (canPersistColumns) setSavedColumns(tableColumns); + const handleViewSubmit = async () => { if (canPersistFilters) setSavedFilters(filters); if (canPersistSorts) setSavedSorts(sorts); - await Promise.resolve(onViewSubmit?.()); - }, [ - canPersistColumns, - canPersistFilters, - canPersistSorts, - filters, - onViewSubmit, - setSavedColumns, - setSavedFilters, - setSavedSorts, - sorts, - tableColumns, - ]); + await onViewSubmit?.(); + }; useScopedHotkeys( [Key.Enter, Key.Escape], @@ -132,7 +102,7 @@ export const TableUpdateViewButtonGroup = ({ title="Update view" disabled={ !currentViewId || - (!canPersistColumns && !canPersistFilters && !canPersistSorts) + (!canPersistViewFields && !canPersistFilters && !canPersistSorts) } onClick={handleViewSubmit} /> diff --git a/front/src/modules/ui/view-bar/components/ViewBar.tsx b/front/src/modules/ui/view-bar/components/ViewBar.tsx new file mode 100644 index 000000000..ec8e3fd3d --- /dev/null +++ b/front/src/modules/ui/view-bar/components/ViewBar.tsx @@ -0,0 +1,120 @@ +import { ComponentProps, type ComponentType, type Context } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; +import { TopBar } from '@/ui/top-bar/TopBar'; +import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; +import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; + +import { currentViewIdScopedState } from '../states/currentViewIdScopedState'; +import { canPersistFiltersScopedFamilySelector } from '../states/selectors/canPersistFiltersScopedFamilySelector'; +import { canPersistSortsScopedFamilySelector } from '../states/selectors/canPersistSortsScopedFamilySelector'; +import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; +import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope'; + +import { FilterDropdownButton } from './FilterDropdownButton'; +import { + SortDropdownButton, + SortDropdownButtonProps, +} from './SortDropdownButton'; +import { + UpdateViewButtonGroup, + UpdateViewButtonGroupProps, +} from './UpdateViewButtonGroup'; +import ViewBarDetails from './ViewBarDetails'; +import { + ViewsDropdownButton, + ViewsDropdownButtonProps, +} from './ViewsDropdownButton'; + +export type ViewBarProps = ComponentProps<'div'> & { + canPersistViewFields?: boolean; + OptionsDropdownButton: ComponentType; + optionsDropdownKey: string; + scopeContext: Context; +} & Pick< + ViewsDropdownButtonProps, + 'defaultViewName' | 'onViewsChange' | 'onViewSelect' + > & + Pick, 'availableSorts'> & + Pick; + +export const ViewBar = ({ + availableSorts, + canPersistViewFields, + defaultViewName, + onViewsChange, + onViewSelect, + onViewSubmit, + OptionsDropdownButton, + optionsDropdownKey, + scopeContext, + ...props +}: ViewBarProps) => { + const recoilScopeId = useContextScopeId(scopeContext); + + const currentViewId = useRecoilScopedValue( + currentViewIdScopedState, + scopeContext, + ); + const canPersistFilters = useRecoilValue( + canPersistFiltersScopedFamilySelector([recoilScopeId, currentViewId]), + ); + const canPersistSorts = useRecoilValue( + canPersistSortsScopedFamilySelector([recoilScopeId, currentViewId]), + ); + + const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ + key: optionsDropdownKey, + }); + + return ( + + } + displayBottomBorder={false} + rightComponent={ + <> + + + context={scopeContext} + availableSorts={availableSorts} + HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} + isPrimaryButton + /> + + + } + bottomComponent={ + + } + /> + } + /> + ); +}; diff --git a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx index fdd5a353e..98403e247 100644 --- a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx +++ b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx @@ -13,6 +13,7 @@ import { useRemoveFilter } from '../hooks/useRemoveFilter'; import { availableFiltersScopedState } from '../states/availableFiltersScopedState'; import { filtersScopedState } from '../states/filtersScopedState'; import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedState'; +import { sortsScopedState } from '../states/sortsScopedState'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { SelectedSortType } from '../types/interface'; import { getOperandLabelShort } from '../utils/getOperandLabel'; @@ -20,12 +21,9 @@ import { getOperandLabelShort } from '../utils/getOperandLabel'; import { FilterDropdownButton } from './FilterDropdownButton'; import SortOrFilterChip from './SortOrFilterChip'; -type OwnProps = { +type OwnProps = { canPersistView?: boolean; context: Context; - sorts: Array>; - onRemoveSort: (sortId: SelectedSortType['key']) => void; - onCancelClick: () => void; hasFilterButton?: boolean; rightComponent?: ReactNode; }; @@ -101,24 +99,25 @@ const StyledAddFilterContainer = styled.div` function ViewBarDetails({ canPersistView, context, - sorts, - onRemoveSort, - onCancelClick, hasFilterButton = false, rightComponent, -}: OwnProps) { +}: OwnProps) { const theme = useTheme(); const [filters, setFilters] = useRecoilScopedState( filtersScopedState, context, ); - const [availableFilters] = useRecoilScopedState( availableFiltersScopedState, context, ); + const [sorts, setSorts] = useRecoilScopedState[]>( + sortsScopedState, + context, + ); + const [isViewBarExpanded] = useRecoilScopedState( isViewBarExpandedScopedState, context, @@ -139,9 +138,14 @@ function ViewBarDetails({ function handleCancelClick() { setFilters([]); - onCancelClick(); + setSorts([]); } + const handleSortRemove = (sortKey: string) => + setSorts((previousSorts) => + previousSorts.filter((sort) => sort.key !== sortKey), + ); + const shouldExpandViewBar = canPersistView || ((filtersWithDefinition.length || sorts.length) && isViewBarExpanded); @@ -166,7 +170,7 @@ function ViewBarDetails({ : IconArrowNarrowUp } isSort - onRemove={() => onRemoveSort(sort.key)} + onRemove={() => handleSortRemove(sort.key)} /> ); })} diff --git a/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx b/front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx similarity index 81% rename from front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx rename to front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx index ca94ad614..c25eb2505 100644 --- a/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx @@ -1,11 +1,16 @@ -import { type MouseEvent, useCallback, useEffect, useState } from 'react'; +import { + type Context, + type MouseEvent, + useCallback, + useEffect, + useState, +} from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; -import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconChevronDown, IconList, @@ -32,10 +37,6 @@ import type { View } from '@/ui/view-bar/types/View'; import { ViewsHotkeyScope } from '@/ui/view-bar/types/ViewsHotkeyScope'; import { assertNotNull } from '~/utils/assert'; -import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; -import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState'; -import { tableColumnsScopedState } from '../../states/tableColumnsScopedState'; - const StyledBoldDropdownMenuItemsContainer = styled( StyledDropdownMenuItemsContainer, )` @@ -69,37 +70,39 @@ const StyledViewName = styled.span` white-space: nowrap; `; -type TableViewsDropdownButtonProps = { +export type ViewsDropdownButtonProps = { defaultViewName: string; HotkeyScope: ViewsHotkeyScope; - onViewsChange?: (views: View[]) => void; + onViewEditModeChange?: () => void; + onViewsChange?: (views: View[]) => void | Promise; + onViewSelect?: (viewId: string) => void | Promise; + scopeContext: Context; }; -export const TableViewsDropdownButton = ({ +export const ViewsDropdownButton = ({ defaultViewName, HotkeyScope, + onViewEditModeChange, onViewsChange, -}: TableViewsDropdownButtonProps) => { + onViewSelect, + scopeContext, +}: ViewsDropdownButtonProps) => { const theme = useTheme(); const [isUnfolded, setIsUnfolded] = useState(false); - const tableScopeId = useContextScopeId(TableRecoilScopeContext); - - const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ - key: 'options', - }); + const recoilScopeId = useContextScopeId(scopeContext); const [, setCurrentViewId] = useRecoilScopedState( currentViewIdScopedState, - TableRecoilScopeContext, + scopeContext, ); const currentView = useRecoilScopedValue( currentViewScopedSelector, - TableRecoilScopeContext, + scopeContext, ); const [views, setViews] = useRecoilScopedState( viewsScopedState, - TableRecoilScopeContext, + scopeContext, ); const setViewEditMode = useSetRecoilState(viewEditModeState); @@ -111,9 +114,7 @@ export const TableViewsDropdownButton = ({ const handleViewSelect = useRecoilCallback( ({ set, snapshot }) => async (viewId: string) => { - const savedColumns = await snapshot.getPromise( - savedTableColumnsFamilyState(viewId), - ); + await onViewSelect?.(viewId); const savedFilters = await snapshot.getPromise( savedFiltersFamilyState(viewId), ); @@ -121,29 +122,28 @@ export const TableViewsDropdownButton = ({ savedSortsFamilyState(viewId), ); - set(tableColumnsScopedState(tableScopeId), savedColumns); - set(filtersScopedState(tableScopeId), savedFilters); - set(sortsScopedState(tableScopeId), savedSorts); - set(currentViewIdScopedState(tableScopeId), viewId); + set(filtersScopedState(recoilScopeId), savedFilters); + set(sortsScopedState(recoilScopeId), savedSorts); + set(currentViewIdScopedState(recoilScopeId), viewId); setIsUnfolded(false); }, - [tableScopeId], + [onViewSelect, recoilScopeId], ); const handleAddViewButtonClick = useCallback(() => { setViewEditMode({ mode: 'create', viewId: undefined }); - openOptionsDropdownButton(); + onViewEditModeChange?.(); setIsUnfolded(false); - }, [setViewEditMode, openOptionsDropdownButton]); + }, [setViewEditMode, onViewEditModeChange]); const handleEditViewButtonClick = useCallback( (event: MouseEvent, viewId: string) => { event.stopPropagation(); setViewEditMode({ mode: 'edit', viewId }); - openOptionsDropdownButton(); + onViewEditModeChange?.(); setIsUnfolded(false); }, - [setViewEditMode, openOptionsDropdownButton], + [setViewEditMode, onViewEditModeChange], ); const handleDeleteViewButtonClick = useCallback( @@ -155,7 +155,7 @@ export const TableViewsDropdownButton = ({ const nextViews = views.filter((view) => view.id !== viewId); setViews(nextViews); - await Promise.resolve(onViewsChange?.(nextViews)); + await onViewsChange?.(nextViews); setIsUnfolded(false); }, [currentView?.id, onViewsChange, setCurrentViewId, setViews, views], diff --git a/front/src/modules/ui/view-bar/states/selectors/sortsOrderByScopedSelector.ts b/front/src/modules/ui/view-bar/states/selectors/sortsOrderByScopedSelector.ts index dc2e2e8b0..3f93406eb 100644 --- a/front/src/modules/ui/view-bar/states/selectors/sortsOrderByScopedSelector.ts +++ b/front/src/modules/ui/view-bar/states/selectors/sortsOrderByScopedSelector.ts @@ -1,12 +1,16 @@ import { selectorFamily } from 'recoil'; +import { SortOrder } from '~/generated/graphql'; + import { reduceSortsToOrderBy } from '../../helpers'; import { sortsScopedState } from '../sortsScopedState'; export const sortsOrderByScopedSelector = selectorFamily({ key: 'sortsOrderByScopedSelector', get: - (param: string) => - ({ get }) => - reduceSortsToOrderBy(get(sortsScopedState(param))), + (scopeId: string) => + ({ get }) => { + const orderBy = reduceSortsToOrderBy(get(sortsScopedState(scopeId))); + return orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]; + }, }); diff --git a/front/src/modules/views/hooks/useViews.ts b/front/src/modules/views/hooks/useViews.ts index aa211a328..383e462e5 100644 --- a/front/src/modules/views/hooks/useViews.ts +++ b/front/src/modules/views/hooks/useViews.ts @@ -113,7 +113,8 @@ export const useViews = ({ const viewToCreate = nextViews.find((nextView) => !viewsById[nextView.id]); if (viewToCreate) { await createView(viewToCreate); - return refetch(); + await refetch(); + return; } const viewToUpdate = nextViews.find( @@ -122,7 +123,8 @@ export const useViews = ({ ); if (viewToUpdate) { await updateView(viewToUpdate); - return refetch(); + await refetch(); + return; } const nextViewIds = nextViews.map((nextView) => nextView.id); @@ -131,7 +133,7 @@ export const useViews = ({ ); if (viewIdToDelete) await deleteView(viewIdToDelete); - return refetch(); + await refetch(); }; return { handleViewsChange, isFetchingViews: loading }; diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index 59d7ffdd6..6a75bee85 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -1,4 +1,3 @@ -import { useCallback, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; @@ -16,13 +15,7 @@ import { PageBody } from '@/ui/layout/components/PageBody'; import { PageContainer } from '@/ui/layout/components/PageContainer'; import { PageHeader } from '@/ui/layout/components/PageHeader'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -import { reduceSortsToOrderBy } from '@/ui/view-bar/helpers'; -import { SelectedSortType } from '@/ui/view-bar/types/interface'; -import { - PipelineProgressOrderByWithRelationInput, - SortOrder, - useUpdatePipelineStageMutation, -} from '~/generated/graphql'; +import { useUpdatePipelineStageMutation } from '~/generated/graphql'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; const StyledPageHeader = styled(PageHeader)` @@ -33,23 +26,6 @@ const StyledPageHeader = styled(PageHeader)` export function Opportunities() { const theme = useTheme(); - const [orderBy, setOrderBy] = useState< - PipelineProgressOrderByWithRelationInput[] - >([{ createdAt: SortOrder.Asc }]); - - const updateSorts = useCallback( - ( - sorts: Array>, - ) => { - setOrderBy( - sorts.length - ? reduceSortsToOrderBy(sorts) - : [{ createdAt: SortOrder.Asc }], - ); - }, - [], - ); - const { handlePipelineStageAdd, handlePipelineStageDelete } = usePipelineStages(); @@ -91,10 +67,9 @@ export function Opportunities() { - +