From 8627416d60e75d624d35dcd0b304909698d3584d Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 14 Sep 2023 01:38:11 +0200 Subject: [PATCH] Refator/sorts dropdown (#1568) * WIP * Fixed lint * Ok for sorts * Fixed on dropdown toggle * Fix lint --- .../board/components/CompanyBoard.tsx | 2 - .../components/HooksCompanyBoard.tsx | 7 + .../table/components/CompanyTable.tsx | 10 +- .../table/components/CompanyTableMockData.tsx | 2 +- .../table/components/CompanyTableMockMode.tsx | 2 - .../people/table/components/PeopleTable.tsx | 10 +- .../pipeline/components/PipelineAddButton.tsx | 4 +- .../ui/board/components/BoardHeader.tsx | 16 +- .../board/components/BoardOptionsDropdown.tsx | 2 +- .../components/BoardOptionsDropdownButton.tsx | 2 +- .../BoardOptionsDropdownContent.tsx | 2 +- .../ui/board/components/EntityBoard.tsx | 4 +- .../modules/ui/board/types/BoardOptions.ts | 5 +- .../ui/dropdown/components/DropdownButton.tsx | 11 +- .../ui/dropdown/hooks/useDropdownButton.ts | 16 +- .../components/ShowPageAddButton.tsx | 4 +- .../ui/table/components/EntityTable.tsx | 16 +- ...ityTableData.tsx => EntityTableEffect.tsx} | 12 +- .../table/constants/TableOptionsDropdownId.ts | 2 + .../ui/table/hooks/useSetEntityTableData.ts | 14 +- .../components/TableOptionsDropdown.tsx | 4 +- .../components/TableOptionsDropdownButton.tsx | 4 +- .../TableOptionsDropdownContent.tsx | 4 +- .../table-header/components/TableHeader.tsx | 15 +- .../ui/table/types/TableOptionsDropdownKey.ts | 1 - .../components/AddFilterFromDetailsButton.tsx | 4 +- .../components/FilterDropdownButton.tsx | 5 +- .../components/MultipleFiltersButton.tsx | 4 +- .../MultipleFiltersDropdownButton.tsx | 26 ++- .../MultipleFiltersDropdownContent.tsx | 2 + .../SingleEntityFilterDropdownButton.tsx | 7 +- .../components/SortDropdownButton.tsx | 196 ++++++++++-------- .../ui/view-bar/components/ViewBar.tsx | 22 +- .../ui/view-bar/components/ViewBarDetails.tsx | 13 +- .../ui/view-bar/constants/FilterDropdownId.ts | 1 + .../ui/view-bar/constants/SortDropdownId.ts | 1 + front/src/modules/ui/view-bar/helpers.ts | 17 -- .../states/availableSortsScopedState.ts | 8 + .../view-bar/states/savedSortsFamilyState.ts | 7 +- .../savedSortsByKeyFamilySelector.ts | 9 +- .../selectors/sortsOrderByScopedSelector.ts | 2 +- .../ui/view-bar/states/sortsScopedState.ts | 4 +- .../ui/view-bar/types/FilterDropdownKey.ts | 1 - front/src/modules/ui/view-bar/types/Sort.ts | 8 + .../ui/view-bar/types/SortDefinition.ts | 10 + .../ui/view-bar/types/SortDirection.ts | 3 + .../src/modules/ui/view-bar/utils/helpers.ts | 16 ++ .../src/modules/views/hooks/useBoardViews.ts | 13 +- .../src/modules/views/hooks/useTableViews.ts | 10 +- .../src/modules/views/hooks/useViewFilters.ts | 10 +- front/src/modules/views/hooks/useViewSorts.ts | 43 ++-- front/src/pages/companies/companies-sorts.tsx | 5 +- .../opportunities/opportunities-sorts.tsx | 7 +- front/src/pages/people/people-sorts.tsx | 19 +- front/src/pages/tasks/Tasks.tsx | 4 +- 55 files changed, 339 insertions(+), 309 deletions(-) rename front/src/modules/ui/table/components/{GenericEntityTableData.tsx => EntityTableEffect.tsx} (75%) create mode 100644 front/src/modules/ui/table/constants/TableOptionsDropdownId.ts delete mode 100644 front/src/modules/ui/table/types/TableOptionsDropdownKey.ts create mode 100644 front/src/modules/ui/view-bar/constants/FilterDropdownId.ts create mode 100644 front/src/modules/ui/view-bar/constants/SortDropdownId.ts delete mode 100644 front/src/modules/ui/view-bar/helpers.ts create mode 100644 front/src/modules/ui/view-bar/states/availableSortsScopedState.ts delete mode 100644 front/src/modules/ui/view-bar/types/FilterDropdownKey.ts create mode 100644 front/src/modules/ui/view-bar/types/Sort.ts create mode 100644 front/src/modules/ui/view-bar/types/SortDefinition.ts create mode 100644 front/src/modules/ui/view-bar/types/SortDirection.ts create mode 100644 front/src/modules/ui/view-bar/utils/helpers.ts diff --git a/front/src/modules/companies/board/components/CompanyBoard.tsx b/front/src/modules/companies/board/components/CompanyBoard.tsx index acad1ecb7..d77ff4c17 100644 --- a/front/src/modules/companies/board/components/CompanyBoard.tsx +++ b/front/src/modules/companies/board/components/CompanyBoard.tsx @@ -18,8 +18,6 @@ type CompanyBoardProps = Pick< export const CompanyBoard = ({ ...props }: CompanyBoardProps) => { const { handleViewsChange, handleViewSubmit } = useBoardViews({ - availableFilters: opportunitiesBoardOptions.filters, - availableSorts: opportunitiesBoardOptions.sorts, objectId: 'company', scopeContext: CompanyBoardRecoilScopeContext, fieldDefinitions: pipelineAvailableFieldDefinitions, diff --git a/front/src/modules/companies/components/HooksCompanyBoard.tsx b/front/src/modules/companies/components/HooksCompanyBoard.tsx index dad044062..707ffc742 100644 --- a/front/src/modules/companies/components/HooksCompanyBoard.tsx +++ b/front/src/modules/companies/components/HooksCompanyBoard.tsx @@ -7,6 +7,7 @@ import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; +import { availableSortsScopedState } from '@/ui/view-bar/states/availableSortsScopedState'; 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'; @@ -29,8 +30,14 @@ export function HooksCompanyBoard() { CompanyBoardRecoilScopeContext, ); + const [, setAvailableSorts] = useRecoilScopedState( + availableSortsScopedState, + CompanyBoardRecoilScopeContext, + ); + useEffect(() => { setAvailableFilters(opportunitiesBoardOptions.filters); + setAvailableSorts(opportunitiesBoardOptions.sorts); }); const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState); diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index 47acdc3b9..974891a72 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -4,7 +4,7 @@ import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTab import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries'; import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport'; import { EntityTable } from '@/ui/table/components/EntityTable'; -import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData'; +import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect'; import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -17,7 +17,7 @@ import { useUpdateOneCompanyMutation, } from '~/generated/graphql'; import { companiesFilters } from '~/pages/companies/companies-filters'; -import { availableSorts } from '~/pages/companies/companies-sorts'; +import { companyAvailableSorts } from '~/pages/companies/companies-sorts'; export function CompanyTable() { const sortsOrderBy = useRecoilScopedValue( @@ -33,8 +33,6 @@ export function CompanyTable() { const upsertEntityTableItem = useUpsertEntityTableItem(); const { handleViewsChange, handleViewSubmit } = useTableViews({ - availableFilters: companiesFilters, - availableSorts, objectId: 'company', columnDefinitions: companiesAvailableColumnDefinitions, }); @@ -50,7 +48,7 @@ export function CompanyTable() { return ( <> - { - setEntityTableData(mockedCompaniesData, []); + setEntityTableData(mockedCompaniesData, [], []); setTableColumns(companiesAvailableColumnDefinitions); }, [setEntityTableData, setTableColumns]); diff --git a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx index 43a2b5862..0d699bb43 100644 --- a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx +++ b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx @@ -1,6 +1,5 @@ import { EntityTable } from '@/ui/table/components/EntityTable'; import { useUpdateOneCompanyMutation } from '~/generated/graphql'; -import { availableSorts } from '~/pages/companies/companies-sorts'; import { CompanyTableMockData } from './CompanyTableMockData'; @@ -10,7 +9,6 @@ export function CompanyTableMockMode() { diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx index 5ce3191bc..8ef195a5e 100644 --- a/front/src/modules/people/table/components/PeopleTable.tsx +++ b/front/src/modules/people/table/components/PeopleTable.tsx @@ -4,7 +4,7 @@ import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableC import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries'; import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport'; import { EntityTable } from '@/ui/table/components/EntityTable'; -import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData'; +import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect'; import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -17,7 +17,7 @@ import { useUpdateOnePersonMutation, } from '~/generated/graphql'; import { peopleFilters } from '~/pages/people/people-filters'; -import { availableSorts } from '~/pages/people/people-sorts'; +import { peopleAvailableSorts } from '~/pages/people/people-sorts'; export function PeopleTable() { const sortsOrderBy = useRecoilScopedValue( @@ -34,8 +34,6 @@ export function PeopleTable() { const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport(); const { handleViewsChange, handleViewSubmit } = useTableViews({ - availableFilters: peopleFilters, - availableSorts, objectId: 'person', columnDefinitions: peopleAvailableColumnDefinitions, }); @@ -49,7 +47,7 @@ export function PeopleTable() { return ( <> - = ComponentProps<'div'> & { +export type BoardHeaderProps = ComponentProps<'div'> & { onStageAdd?: (boardColumn: BoardColumnDefinition) => void; } & Pick< - ViewBarProps, - | 'availableSorts' - | 'defaultViewName' - | 'onViewsChange' - | 'onViewSubmit' - | 'scopeContext' + ViewBarProps, + 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' | 'scopeContext' >; -export function BoardHeader({ +export function BoardHeader({ onStageAdd, onViewsChange, onViewSubmit, scopeContext, - availableSorts, defaultViewName, -}: BoardHeaderProps) { +}: BoardHeaderProps) { return ( } dropdownHotkeyScope={customHotkeyScope} - dropdownKey={BoardOptionsDropdownKey} + dropdownId={BoardOptionsDropdownKey} /> ); } diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx index b4e218418..f1f14eca1 100644 --- a/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx +++ b/front/src/modules/ui/board/components/BoardOptionsDropdownButton.tsx @@ -5,7 +5,7 @@ import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey'; export function BoardOptionsDropdownButton() { const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ - key: BoardOptionsDropdownKey, + dropdownId: BoardOptionsDropdownKey, }); function handleClick() { diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx index 6bb25ce3d..662db6129 100644 --- a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx +++ b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx @@ -126,7 +126,7 @@ export function BoardOptionsDropdownContent({ const { handleFieldVisibilityChange } = useBoardCardFields({ scopeContext }); const { closeDropdownButton } = useDropdownButton({ - key: BoardOptionsDropdownKey, + dropdownId: BoardOptionsDropdownKey, }); useScopedHotkeys( diff --git a/front/src/modules/ui/board/components/EntityBoard.tsx b/front/src/modules/ui/board/components/EntityBoard.tsx index 686e05f8b..8f883996c 100644 --- a/front/src/modules/ui/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/board/components/EntityBoard.tsx @@ -22,7 +22,6 @@ 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'; @@ -41,7 +40,7 @@ export type EntityBoardProps = { onEditColumnTitle: (columnId: string, title: string, color: string) => void; scopeContext: Context; } & Pick< - BoardHeaderProps, + BoardHeaderProps, 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' >; @@ -142,7 +141,6 @@ export function EntityBoard({ }>; filters: FilterDefinitionByEntity[]; - sorts: Array>; + sorts: SortDefinition[]; }; diff --git a/front/src/modules/ui/dropdown/components/DropdownButton.tsx b/front/src/modules/ui/dropdown/components/DropdownButton.tsx index c2ef6f12a..eb5214784 100644 --- a/front/src/modules/ui/dropdown/components/DropdownButton.tsx +++ b/front/src/modules/ui/dropdown/components/DropdownButton.tsx @@ -15,31 +15,28 @@ import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/Drop type OwnProps = { buttonComponents: JSX.Element | JSX.Element[]; dropdownComponents: JSX.Element | JSX.Element[]; - dropdownKey: string; + dropdownId: string; hotkey?: { key: Keys; scope: string; }; dropdownHotkeyScope?: HotkeyScope; dropdownPlacement?: Placement; - onDropdownToggle?: (isDropdownOpen: boolean) => void; }; export function DropdownButton({ buttonComponents, dropdownComponents, - dropdownKey, + dropdownId, hotkey, dropdownHotkeyScope, dropdownPlacement = 'bottom-end', - onDropdownToggle, }: OwnProps) { const containerRef = useRef(null); const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } = useDropdownButton({ - key: dropdownKey, - onDropdownToggle, + dropdownId, }); const { refs, floatingStyles } = useFloating({ @@ -63,7 +60,7 @@ export function DropdownButton({ const [dropdownButtonCustomHotkeyScope, setDropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState( dropdownButtonCustomHotkeyScopeScopedFamilyState, - dropdownKey, + dropdownId, DropdownRecoilScopeContext, ); diff --git a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts index 15230cd67..895626182 100644 --- a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts +++ b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts @@ -5,14 +5,7 @@ import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/drop import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState'; import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext'; -// TODO: have a more explicit name than key -export function useDropdownButton({ - key, - onDropdownToggle, -}: { - key: string; - onDropdownToggle?: (isDropdownButtonOpen: boolean) => void; -}) { +export function useDropdownButton({ dropdownId }: { dropdownId: string }) { const { setHotkeyScopeAndMemorizePreviousScope, goBackToPreviousHotkeyScope, @@ -21,20 +14,19 @@ export function useDropdownButton({ const [isDropdownButtonOpen, setIsDropdownButtonOpen] = useRecoilScopedFamilyState( isDropdownButtonOpenScopedFamilyState, - key, + dropdownId, DropdownRecoilScopeContext, ); const [dropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState( dropdownButtonCustomHotkeyScopeScopedFamilyState, - key, + dropdownId, DropdownRecoilScopeContext, ); function closeDropdownButton() { goBackToPreviousHotkeyScope(); setIsDropdownButtonOpen(false); - onDropdownToggle?.(false); } function openDropdownButton() { @@ -46,7 +38,6 @@ export function useDropdownButton({ dropdownButtonCustomHotkeyScope.customScopes, ); } - onDropdownToggle?.(true); } function toggleDropdownButton() { @@ -55,7 +46,6 @@ export function useDropdownButton({ } else { openDropdownButton(); } - onDropdownToggle?.(isDropdownButtonOpen); } return { diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx index 8cd416c40..4c83019e2 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx @@ -22,7 +22,7 @@ export function ShowPageAddButton({ entity: ActivityTargetableEntity; }) { const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({ - key: 'add-show-page', + dropdownId: 'add-show-page', }); const openCreateActivity = useOpenCreateActivityDrawer(); @@ -34,7 +34,7 @@ export function ShowPageAddButton({ return ( = { +type OwnProps = { updateEntityMutation: any; } & Pick< - TableHeaderProps, - | 'availableSorts' - | 'defaultViewName' - | 'onImport' - | 'onViewsChange' - | 'onViewSubmit' + TableHeaderProps, + 'defaultViewName' | 'onImport' | 'onViewsChange' | 'onViewSubmit' >; -export function EntityTable({ - availableSorts, +export function EntityTable({ defaultViewName, onImport, onViewsChange, onViewSubmit, updateEntityMutation, -}: OwnProps) { +}: OwnProps) { const tableBodyRef = useRef(null); const setRowSelectedState = useSetRowSelectedState(); @@ -141,7 +136,6 @@ export function EntityTable({ ; + // TODO: type this and replace with defaultSorts reduce should be applied to defaultSorts in this component not before orderBy?: any; + // TODO: type this and replace with defaultFilters reduce should be applied to defaultFilters in this component not before whereFilters?: any; filterDefinitionArray: FilterDefinition[]; + sortDefinitionArray: SortDefinition[]; setActionBarEntries?: () => void; setContextMenuEntries?: () => void; }) { @@ -36,7 +42,9 @@ export function GenericEntityTableData({ variables: { orderBy, where: whereFilters }, onCompleted: (data: any) => { const entities = data[getRequestResultKey] ?? []; - setEntityTableData(entities, filterDefinitionArray); + + setEntityTableData(entities, filterDefinitionArray, sortDefinitionArray); + registerOptimisticEffect({ variables: { orderBy, where: whereFilters }, definition: getRequestOptimisticEffectDefinition, diff --git a/front/src/modules/ui/table/constants/TableOptionsDropdownId.ts b/front/src/modules/ui/table/constants/TableOptionsDropdownId.ts new file mode 100644 index 000000000..2755b8c91 --- /dev/null +++ b/front/src/modules/ui/table/constants/TableOptionsDropdownId.ts @@ -0,0 +1,2 @@ +// We should either apply the constant all caps case or maybe define a more general enum to store those ids ? +export const TableOptionsDropdownId = 'table-options'; diff --git a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts index 1e200d0e5..1b6a0140b 100644 --- a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts +++ b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts @@ -6,7 +6,9 @@ import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyS import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; +import { availableSortsScopedState } from '@/ui/view-bar/states/availableSortsScopedState'; import { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition'; +import { SortDefinition } from '@/ui/view-bar/types/SortDefinition'; import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState'; import { numberOfTableRowsState } from '../states/numberOfTableRowsState'; @@ -20,7 +22,8 @@ export function useSetEntityTableData() { ({ set, snapshot }) => ( newEntityArray: T[], - filters: FilterDefinition[], + filterDefinitionArray: FilterDefinition[], + sortDefinitionArray: SortDefinition[], ) => { for (const entity of newEntityArray) { const currentEntity = snapshot @@ -46,7 +49,14 @@ export function useSetEntityTableData() { set(numberOfTableRowsState, entityIds.length); - set(availableFiltersScopedState(tableContextScopeId), filters); + set( + availableFiltersScopedState(tableContextScopeId), + filterDefinitionArray, + ); + set( + availableSortsScopedState(tableContextScopeId), + sortDefinitionArray, + ); set(isFetchingEntityTableDataState, false); }, diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx index 5afba3002..4a74a845f 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx @@ -2,7 +2,7 @@ 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 { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId'; import { TableOptionsDropdownButton } from './TableOptionsDropdownButton'; import { TableOptionsDropdownContent } from './TableOptionsDropdownContent'; @@ -22,7 +22,7 @@ export function TableOptionsDropdown({ } dropdownHotkeyScope={customHotkeyScope} - dropdownKey={TableOptionsDropdownKey} + dropdownId={TableOptionsDropdownId} dropdownComponents={ ( 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 aad2b1ac9..ef374ace4 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -8,27 +8,24 @@ import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoi import { ViewBar, ViewBarProps } from '@/ui/view-bar/components/ViewBar'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; +import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId'; import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown'; 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'; -export type TableHeaderProps = { +export type TableHeaderProps = { onImport?: () => void; -} & Pick< - ViewBarProps, - 'availableSorts' | 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' ->; +} & Pick; -export function TableHeader({ +export function TableHeader({ onImport, onViewsChange, onViewSubmit, ...props -}: TableHeaderProps) { +}: TableHeaderProps) { const tableScopeId = useContextScopeId(TableRecoilScopeContext); const currentViewId = useRecoilScopedValue( @@ -84,7 +81,7 @@ export function TableHeader({ customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }} /> } - optionsDropdownKey={TableOptionsDropdownKey} + optionsDropdownKey={TableOptionsDropdownId} scopeContext={TableRecoilScopeContext} /> diff --git a/front/src/modules/ui/table/types/TableOptionsDropdownKey.ts b/front/src/modules/ui/table/types/TableOptionsDropdownKey.ts deleted file mode 100644 index 2821a2b4a..000000000 --- a/front/src/modules/ui/table/types/TableOptionsDropdownKey.ts +++ /dev/null @@ -1 +0,0 @@ -export const TableOptionsDropdownKey = 'table-options'; diff --git a/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx b/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx index 097d8233c..c7fd0f185 100644 --- a/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx +++ b/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx @@ -2,11 +2,11 @@ import { LightButton } from '@/ui/button/components/LightButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconPlus } from '@/ui/icon'; -import { FilterDropdownKey } from '../types/FilterDropdownKey'; +import { FilterDropdownId } from '../constants/FilterDropdownId'; export function AddFilterFromDropdownButton() { const { toggleDropdownButton } = useDropdownButton({ - key: FilterDropdownKey, + dropdownId: FilterDropdownId, }); function handleClick() { diff --git a/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx b/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx index d8b69f87f..e7bfa6a4d 100644 --- a/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx @@ -1,5 +1,6 @@ import { Context } from 'react'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { availableFiltersScopedState } from '../states/availableFiltersScopedState'; @@ -9,12 +10,12 @@ import { SingleEntityFilterDropdownButton } from './SingleEntityFilterDropdownBu type FilterDropdownButtonProps = { context: Context; - hotkeyScope: string; + hotkeyScope: HotkeyScope; }; export function FilterDropdownButton({ - context, hotkeyScope, + context, }: FilterDropdownButtonProps) { const [availableFilters] = useRecoilScopedState( availableFiltersScopedState, diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx index 9e2da82e6..25e8a0aaa 100644 --- a/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx +++ b/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx @@ -1,11 +1,11 @@ import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; -import { FilterDropdownKey } from '../types/FilterDropdownKey'; +import { FilterDropdownId } from '../constants/FilterDropdownId'; export function MultipleFiltersButton() { const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ - key: FilterDropdownKey, + dropdownId: FilterDropdownId, }); function handleClick() { diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx index d108f3bce..667670264 100644 --- a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx @@ -1,24 +1,27 @@ -import { Context, useCallback } from 'react'; +import { Context, useCallback, useEffect } from 'react'; import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { FilterDropdownId } from '../constants/FilterDropdownId'; import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState'; import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState'; import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState'; import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState'; -import { FilterDropdownKey } from '../types/FilterDropdownKey'; import { MultipleFiltersButton } from './MultipleFiltersButton'; import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent'; type MultipleFiltersDropdownButtonProps = { context: Context; - hotkeyScope: string; + hotkeyScope: HotkeyScope; }; export function MultipleFiltersDropdownButton({ context, + hotkeyScope, }: MultipleFiltersDropdownButtonProps) { const [, setIsFilterDropdownOperandSelectUnfolded] = useRecoilScopedState( isFilterDropdownOperandSelectUnfoldedScopedState, @@ -40,6 +43,10 @@ export function MultipleFiltersDropdownButton({ context, ); + const { isDropdownButtonOpen } = useDropdownButton({ + dropdownId: FilterDropdownId, + }); + const resetState = useCallback(() => { setIsFilterDropdownOperandSelectUnfolded(false); setFilterDefinitionUsedInDropdown(null); @@ -51,14 +58,19 @@ export function MultipleFiltersDropdownButton({ setFilterDropdownSearchInput, setIsFilterDropdownOperandSelectUnfolded, ]); + + useEffect(() => { + if (!isDropdownButtonOpen) { + resetState(); + } + }, [isDropdownButtonOpen, resetState]); + return ( } dropdownComponents={} - onDropdownToggle={() => { - resetState(); - }} + dropdownHotkeyScope={hotkeyScope} /> ); } diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx index a2e9ab7fb..5a5252618 100644 --- a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx +++ b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx @@ -39,6 +39,8 @@ export function MultipleFiltersDropdownContent({ context, ); + console.log('filterDefinitionUsedInDropdown', filterDefinitionUsedInDropdown); + return ( <> diff --git a/front/src/modules/ui/view-bar/components/SingleEntityFilterDropdownButton.tsx b/front/src/modules/ui/view-bar/components/SingleEntityFilterDropdownButton.tsx index 4f534f623..509c2832c 100644 --- a/front/src/modules/ui/view-bar/components/SingleEntityFilterDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/SingleEntityFilterDropdownButton.tsx @@ -6,6 +6,7 @@ import styled from '@emotion/styled'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { IconChevronDown } from '@/ui/icon'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState'; import { filterDropdownSearchInputScopedState } from '@/ui/view-bar/states/filterDropdownSearchInputScopedState'; @@ -33,7 +34,7 @@ export function SingleEntityFilterDropdownButton({ hotkeyScope, }: { context: Context; - hotkeyScope: string; + hotkeyScope: HotkeyScope; }) { const theme = useTheme(); @@ -80,10 +81,10 @@ export function SingleEntityFilterDropdownButton({ function handleIsUnfoldedChange(newIsUnfolded: boolean) { if (newIsUnfolded) { - setHotkeyScope(hotkeyScope); + setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes); setIsFilterDropdownUnfolded(true); } else { - setHotkeyScope(hotkeyScope); + setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes); setIsFilterDropdownUnfolded(false); setFilterDropdownSearchInput(''); } diff --git a/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx b/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx index 27e6fa260..b3c44fe24 100644 --- a/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/SortDropdownButton.tsx @@ -1,120 +1,136 @@ import { Context, useCallback, useState } from 'react'; +import { produce } from 'immer'; +import { LightButton } from '@/ui/button/components/LightButton'; +import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconChevronDown } from '@/ui/icon'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { SortDropdownId } from '../constants/SortDropdownId'; +import { availableSortsScopedState } from '../states/availableSortsScopedState'; import { sortsScopedState } from '../states/sortsScopedState'; -import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; -import { SelectedSortType, SortType } from '../types/interface'; +import { SortDefinition } from '../types/SortDefinition'; +import { SORT_DIRECTIONS, SortDirection } from '../types/SortDirection'; -import DropdownButton from './DropdownButton'; - -export type SortDropdownButtonProps = { - availableSorts: SortType[]; - hotkeyScope: FiltersHotkeyScope; +export type SortDropdownButtonProps = { context: Context; + hotkeyScope: HotkeyScope; isPrimaryButton?: boolean; }; -const options: Array['order']> = ['asc', 'desc']; - -export function SortDropdownButton({ - context, - availableSorts, +export function SortDropdownButton({ hotkeyScope, -}: SortDropdownButtonProps) { - const [isUnfolded, setIsUnfolded] = useState(false); - const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); + context, +}: SortDropdownButtonProps) { + const [isSortDirectionMenuUnfolded, setIsSortDirectionMenuUnfolded] = + useState(false); + const [selectedSortDirection, setSelectedSortDirection] = - useState['order']>('asc'); - - const [sorts, setSorts] = useRecoilScopedState[]>( - sortsScopedState, - context, - ); - - const isSortSelected = sorts.length > 0; - - const onSortItemSelect = useCallback( - (sort: SortType) => { - 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); - }, - [selectedSortDirection, setSorts, sorts], - ); + useState('asc'); const resetState = useCallback(() => { - setIsOptionUnfolded(false); + setIsSortDirectionMenuUnfolded(false); setSelectedSortDirection('asc'); }, []); - function handleIsUnfoldedChange(newIsUnfolded: boolean) { - setIsUnfolded(newIsUnfolded); - if (!newIsUnfolded) resetState(); + const [availableSorts] = useRecoilScopedState( + availableSortsScopedState, + context, + ); + + const [sorts, setSorts] = useRecoilScopedState(sortsScopedState, context); + + const isSortSelected = sorts.length > 0; + + const { toggleDropdownButton } = useDropdownButton({ + dropdownId: SortDropdownId, + }); + + function handleButtonClick() { + toggleDropdownButton(); + resetState(); } - function handleAddSort(sort: SortType) { - setIsUnfolded(false); - onSortItemSelect(sort); + function handleAddSort(selectedSortDefinition: SortDefinition) { + toggleDropdownButton(); + + setSorts( + produce(sorts, (existingSortsDraft) => { + const foundExistingSortIndex = existingSortsDraft.findIndex( + (existingSort) => existingSort.key === selectedSortDefinition.key, + ); + + if (foundExistingSortIndex !== -1) { + existingSortsDraft[foundExistingSortIndex].direction = + selectedSortDirection; + } else { + existingSortsDraft.push({ + key: selectedSortDefinition.key, + direction: selectedSortDirection, + definition: selectedSortDefinition, + }); + } + }), + ); } return ( - {isOptionUnfolded ? ( - - {options.map((option, index) => ( - { - setSelectedSortDirection(option); - setIsOptionUnfolded(false); - }} - text={option === 'asc' ? 'Ascending' : 'Descending'} - /> - ))} - - ) : ( - <> - setIsOptionUnfolded(true)} - > - {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} - - - - - {availableSorts.map((sort, index) => ( - handleAddSort(sort)} - LeftIcon={sort.Icon} - text={sort.label} - /> - ))} - - - )} - + dropdownId={SortDropdownId} + dropdownHotkeyScope={hotkeyScope} + buttonComponents={ + + } + dropdownComponents={ + + {isSortDirectionMenuUnfolded ? ( + + {SORT_DIRECTIONS.map((sortOrder, index) => ( + { + setSelectedSortDirection(sortOrder); + setIsSortDirectionMenuUnfolded(false); + }} + text={sortOrder === 'asc' ? 'Ascending' : 'Descending'} + /> + ))} + + ) : ( + <> + setIsSortDirectionMenuUnfolded(true)} + > + {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} + + + + {availableSorts.map((availableSort, index) => ( + handleAddSort(availableSort)} + LeftIcon={availableSort.Icon} + text={availableSort.label} + /> + ))} + + + )} + + } + > ); } diff --git a/front/src/modules/ui/view-bar/components/ViewBar.tsx b/front/src/modules/ui/view-bar/components/ViewBar.tsx index 6eb047283..0d1e7becf 100644 --- a/front/src/modules/ui/view-bar/components/ViewBar.tsx +++ b/front/src/modules/ui/view-bar/components/ViewBar.tsx @@ -7,10 +7,7 @@ import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope'; import { FilterDropdownButton } from './FilterDropdownButton'; -import { - SortDropdownButton, - type SortDropdownButtonProps, -} from './SortDropdownButton'; +import { SortDropdownButton } from './SortDropdownButton'; import { UpdateViewButtonGroup, type UpdateViewButtonGroupProps, @@ -21,7 +18,7 @@ import { type ViewsDropdownButtonProps, } from './ViewsDropdownButton'; -export type ViewBarProps = ComponentProps<'div'> & { +export type ViewBarProps = ComponentProps<'div'> & { optionsDropdownButton: ReactNode; optionsDropdownKey: string; scopeContext: Context; @@ -29,12 +26,10 @@ export type ViewBarProps = ComponentProps<'div'> & { ViewsDropdownButtonProps, 'defaultViewName' | 'onViewsChange' | 'onViewSelect' > & - Pick, 'availableSorts'> & Pick & Pick; -export const ViewBar = ({ - availableSorts, +export const ViewBar = ({ canPersistViewFields, defaultViewName, onReset, @@ -45,9 +40,9 @@ export const ViewBar = ({ optionsDropdownKey, scopeContext, ...props -}: ViewBarProps) => { +}: ViewBarProps) => { const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ - key: optionsDropdownKey, + dropdownId: optionsDropdownKey, }); return ( @@ -67,13 +62,12 @@ export const ViewBar = ({ rightComponent={ <> - + {optionsDropdownButton} diff --git a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx index 853ce4aa3..f31bce1f9 100644 --- a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx +++ b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx @@ -15,7 +15,6 @@ import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedS import { canPersistFiltersScopedFamilySelector } from '../states/selectors/canPersistFiltersScopedFamilySelector'; import { canPersistSortsScopedFamilySelector } from '../states/selectors/canPersistSortsScopedFamilySelector'; import { sortsScopedState } from '../states/sortsScopedState'; -import { SelectedSortType } from '../types/interface'; import { getOperandLabelShort } from '../utils/getOperandLabel'; import { AddFilterFromDropdownButton } from './AddFilterFromDetailsButton'; @@ -97,7 +96,7 @@ const StyledAddFilterContainer = styled.div` z-index: 5; `; -function ViewBarDetails({ +function ViewBarDetails({ canPersistViewFields, context, hasFilterButton = false, @@ -120,10 +119,8 @@ function ViewBarDetails({ canPersistFiltersScopedFamilySelector([recoilScopeId, currentViewId]), ); - const [sorts, setSorts] = useRecoilScopedState[]>( - sortsScopedState, - context, - ); + const [sorts, setSorts] = useRecoilScopedState(sortsScopedState, context); + const canPersistSorts = useRecoilValue( canPersistSortsScopedFamilySelector([recoilScopeId, currentViewId]), ); @@ -177,9 +174,9 @@ function ViewBarDetails({ ( - sorts: SelectedSortType[], -): OrderByTemplate[] => - sorts - .map((sort) => { - const order = sort.order === 'asc' ? Order_By.Asc : Order_By.Desc; - return ( - sort.orderByTemplate?.(order) || [ - { [sort.key]: order } as OrderByTemplate, - ] - ); - }) - .flat(); diff --git a/front/src/modules/ui/view-bar/states/availableSortsScopedState.ts b/front/src/modules/ui/view-bar/states/availableSortsScopedState.ts new file mode 100644 index 000000000..5e89ef372 --- /dev/null +++ b/front/src/modules/ui/view-bar/states/availableSortsScopedState.ts @@ -0,0 +1,8 @@ +import { atomFamily } from 'recoil'; + +import { SortDefinition } from '../types/SortDefinition'; + +export const availableSortsScopedState = atomFamily({ + key: 'availableSortsScopedState', + default: [], +}); diff --git a/front/src/modules/ui/view-bar/states/savedSortsFamilyState.ts b/front/src/modules/ui/view-bar/states/savedSortsFamilyState.ts index ab2be0a87..758efc76e 100644 --- a/front/src/modules/ui/view-bar/states/savedSortsFamilyState.ts +++ b/front/src/modules/ui/view-bar/states/savedSortsFamilyState.ts @@ -1,11 +1,8 @@ import { atomFamily } from 'recoil'; -import type { SelectedSortType } from '../types/interface'; +import { Sort } from '../types/Sort'; -export const savedSortsFamilyState = atomFamily< - SelectedSortType[], - string | undefined ->({ +export const savedSortsFamilyState = atomFamily({ key: 'savedSortsFamilyState', default: [], }); diff --git a/front/src/modules/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector.ts b/front/src/modules/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector.ts index ca3ebd1f4..0475870a4 100644 --- a/front/src/modules/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector.ts +++ b/front/src/modules/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector.ts @@ -1,6 +1,6 @@ import { selectorFamily } from 'recoil'; -import type { SelectedSortType } from '../../types/interface'; +import { Sort } from '../../types/Sort'; import { savedSortsFamilyState } from '../savedSortsFamilyState'; export const savedSortsByKeyFamilySelector = selectorFamily({ @@ -8,7 +8,8 @@ export const savedSortsByKeyFamilySelector = selectorFamily({ get: (viewId: string | undefined) => ({ get }) => - get(savedSortsFamilyState(viewId)).reduce< - Record> - >((result, sort) => ({ ...result, [sort.key]: sort }), {}), + get(savedSortsFamilyState(viewId)).reduce>( + (result, sort) => ({ ...result, [sort.key]: sort }), + {}, + ), }); 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 3f93406eb..a2d31fd09 100644 --- a/front/src/modules/ui/view-bar/states/selectors/sortsOrderByScopedSelector.ts +++ b/front/src/modules/ui/view-bar/states/selectors/sortsOrderByScopedSelector.ts @@ -2,7 +2,7 @@ import { selectorFamily } from 'recoil'; import { SortOrder } from '~/generated/graphql'; -import { reduceSortsToOrderBy } from '../../helpers'; +import { reduceSortsToOrderBy } from '../../utils/helpers'; import { sortsScopedState } from '../sortsScopedState'; export const sortsOrderByScopedSelector = selectorFamily({ diff --git a/front/src/modules/ui/view-bar/states/sortsScopedState.ts b/front/src/modules/ui/view-bar/states/sortsScopedState.ts index 968857aa3..4771f6ffc 100644 --- a/front/src/modules/ui/view-bar/states/sortsScopedState.ts +++ b/front/src/modules/ui/view-bar/states/sortsScopedState.ts @@ -1,8 +1,8 @@ import { atomFamily } from 'recoil'; -import type { SelectedSortType } from '../types/interface'; +import { Sort } from '../types/Sort'; -export const sortsScopedState = atomFamily[], string>({ +export const sortsScopedState = atomFamily({ key: 'sortsScopedState', default: [], }); diff --git a/front/src/modules/ui/view-bar/types/FilterDropdownKey.ts b/front/src/modules/ui/view-bar/types/FilterDropdownKey.ts deleted file mode 100644 index b10e7a802..000000000 --- a/front/src/modules/ui/view-bar/types/FilterDropdownKey.ts +++ /dev/null @@ -1 +0,0 @@ -export const FilterDropdownKey = 'filter'; diff --git a/front/src/modules/ui/view-bar/types/Sort.ts b/front/src/modules/ui/view-bar/types/Sort.ts new file mode 100644 index 000000000..41a28e900 --- /dev/null +++ b/front/src/modules/ui/view-bar/types/Sort.ts @@ -0,0 +1,8 @@ +import { SortDefinition } from './SortDefinition'; +import { SortDirection } from './SortDirection'; + +export type Sort = { + key: string; + direction: SortDirection; + definition: SortDefinition; +}; diff --git a/front/src/modules/ui/view-bar/types/SortDefinition.ts b/front/src/modules/ui/view-bar/types/SortDefinition.ts new file mode 100644 index 000000000..7657bee6d --- /dev/null +++ b/front/src/modules/ui/view-bar/types/SortDefinition.ts @@ -0,0 +1,10 @@ +import { IconComponent } from '@/ui/icon/types/IconComponent'; + +import { SortDirection } from './SortDirection'; + +export type SortDefinition = { + key: string; + label: string; + Icon?: IconComponent; + getOrderByTemplate?: (direction: SortDirection) => any[]; +}; diff --git a/front/src/modules/ui/view-bar/types/SortDirection.ts b/front/src/modules/ui/view-bar/types/SortDirection.ts new file mode 100644 index 000000000..5835c6630 --- /dev/null +++ b/front/src/modules/ui/view-bar/types/SortDirection.ts @@ -0,0 +1,3 @@ +export const SORT_DIRECTIONS = ['asc', 'desc'] as const; + +export type SortDirection = (typeof SORT_DIRECTIONS)[number]; diff --git a/front/src/modules/ui/view-bar/utils/helpers.ts b/front/src/modules/ui/view-bar/utils/helpers.ts new file mode 100644 index 000000000..cf17641db --- /dev/null +++ b/front/src/modules/ui/view-bar/utils/helpers.ts @@ -0,0 +1,16 @@ +import { SortOrder as Order_By } from '~/generated/graphql'; + +import { Sort } from '../types/Sort'; + +export const reduceSortsToOrderBy = (sorts: Sort[]): any[] => + sorts + .map((sort) => { + const direction = sort.direction === 'asc' ? Order_By.Asc : Order_By.Desc; + + if (sort.definition.getOrderByTemplate) { + return sort.definition.getOrderByTemplate(direction); + } else { + return [{ [sort.definition.key]: direction }]; + } + }) + .flat(); diff --git a/front/src/modules/views/hooks/useBoardViews.ts b/front/src/modules/views/hooks/useBoardViews.ts index db808165c..9f01509dc 100644 --- a/front/src/modules/views/hooks/useBoardViews.ts +++ b/front/src/modules/views/hooks/useBoardViews.ts @@ -7,8 +7,6 @@ import type { import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; -import type { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity'; -import type { SortType } from '@/ui/view-bar/types/interface'; import { ViewType } from '~/generated/graphql'; import { useBoardViewFields } from './useBoardViewFields'; @@ -16,15 +14,11 @@ import { useViewFilters } from './useViewFilters'; import { useViews } from './useViews'; import { useViewSorts } from './useViewSorts'; -export const useBoardViews = ({ - availableFilters, - availableSorts, +export const useBoardViews = ({ fieldDefinitions, objectId, scopeContext, }: { - availableFilters: FilterDefinitionByEntity[]; - availableSorts: SortType[]; fieldDefinitions: ViewFieldDefinition[]; objectId: 'company'; scopeContext: Context; @@ -38,19 +32,20 @@ export const useBoardViews = ({ type: ViewType.Pipeline, scopeContext, }); + useBoardViewFields({ objectId, fieldDefinitions, scopeContext, skipFetch: isFetchingViews, }); + const { createViewFilters, persistFilters } = useViewFilters({ - availableFilters, scopeContext, skipFetch: isFetchingViews, }); + const { createViewSorts, persistSorts } = useViewSorts({ - availableSorts, scopeContext, skipFetch: isFetchingViews, }); diff --git a/front/src/modules/views/hooks/useTableViews.ts b/front/src/modules/views/hooks/useTableViews.ts index ffa5fd637..fbe87ec81 100644 --- a/front/src/modules/views/hooks/useTableViews.ts +++ b/front/src/modules/views/hooks/useTableViews.ts @@ -5,8 +5,6 @@ import type { ColumnDefinition } from '@/ui/table/types/ColumnDefinition'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; -import type { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity'; -import type { SortType } from '@/ui/view-bar/types/interface'; import { ViewType } from '~/generated/graphql'; import { useTableViewFields } from './useTableViewFields'; @@ -14,14 +12,10 @@ import { useViewFilters } from './useViewFilters'; import { useViews } from './useViews'; import { useViewSorts } from './useViewSorts'; -export const useTableViews = ({ - availableFilters, - availableSorts, +export const useTableViews = ({ objectId, columnDefinitions, }: { - availableFilters: FilterDefinitionByEntity[]; - availableSorts: SortType[]; objectId: 'company' | 'person'; columnDefinitions: ColumnDefinition[]; }) => { @@ -47,12 +41,10 @@ export const useTableViews = ({ skipFetch: isFetchingViews, }); const { createViewFilters, persistFilters } = useViewFilters({ - availableFilters, scopeContext: TableRecoilScopeContext, skipFetch: isFetchingViews, }); const { createViewSorts, persistSorts } = useViewSorts({ - availableSorts, scopeContext: TableRecoilScopeContext, skipFetch: isFetchingViews, }); diff --git a/front/src/modules/views/hooks/useViewFilters.ts b/front/src/modules/views/hooks/useViewFilters.ts index 0e9a22bee..8fe856346 100644 --- a/front/src/modules/views/hooks/useViewFilters.ts +++ b/front/src/modules/views/hooks/useViewFilters.ts @@ -3,12 +3,12 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; +import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; 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 { savedFiltersByKeyFamilySelector } from '@/ui/view-bar/states/selectors/savedFiltersByKeyFamilySelector'; import type { Filter } from '@/ui/view-bar/types/Filter'; -import type { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity'; import { useCreateViewFiltersMutation, useDeleteViewFiltersMutation, @@ -17,12 +17,10 @@ import { } from '~/generated/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -export const useViewFilters = ({ - availableFilters, +export const useViewFilters = ({ scopeContext, skipFetch, }: { - availableFilters: FilterDefinitionByEntity[]; scopeContext: Context; skipFetch?: boolean; }) => { @@ -34,6 +32,10 @@ export const useViewFilters = ({ filtersScopedState, scopeContext, ); + const [availableFilters] = useRecoilScopedState( + availableFiltersScopedState, + scopeContext, + ); const [, setSavedFilters] = useRecoilState( savedFiltersFamilyState(currentViewId), ); diff --git a/front/src/modules/views/hooks/useViewSorts.ts b/front/src/modules/views/hooks/useViewSorts.ts index 1c9b4e88c..210979ccd 100644 --- a/front/src/modules/views/hooks/useViewSorts.ts +++ b/front/src/modules/views/hooks/useViewSorts.ts @@ -3,11 +3,12 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; +import { availableSortsScopedState } from '@/ui/view-bar/states/availableSortsScopedState'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState'; import { savedSortsByKeyFamilySelector } from '@/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector'; import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; -import type { SelectedSortType, SortType } from '@/ui/view-bar/types/interface'; +import { Sort } from '@/ui/view-bar/types/Sort'; import { useCreateViewSortsMutation, useDeleteViewSortsMutation, @@ -17,12 +18,10 @@ import { } from '~/generated/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -export const useViewSorts = ({ - availableSorts, +export const useViewSorts = ({ scopeContext, skipFetch, }: { - availableSorts: SortType[]; scopeContext: Context; skipFetch?: boolean; }) => { @@ -34,6 +33,10 @@ export const useViewSorts = ({ sortsScopedState, scopeContext, ); + const [availableSorts] = useRecoilScopedState( + availableSortsScopedState, + scopeContext, + ); const [, setSavedSorts] = useRecoilState( savedSortsFamilyState(currentViewId), ); @@ -51,19 +54,21 @@ export const useViewSorts = ({ onCompleted: (data) => { const nextSorts = data.viewSorts .map((viewSort) => { - const availableSort = availableSorts.find( + const foundCorrespondingSortDefinition = availableSorts.find( (sort) => sort.key === viewSort.key, ); - return availableSort - ? { - ...availableSort, - label: viewSort.name, - order: viewSort.direction.toLowerCase(), - } - : undefined; + if (foundCorrespondingSortDefinition) { + return { + key: viewSort.key, + definition: foundCorrespondingSortDefinition, + direction: viewSort.direction.toLowerCase(), + } as Sort; + } else { + return undefined; + } }) - .filter((sort): sort is SelectedSortType => !!sort); + .filter((sort): sort is Sort => !!sort); if (!isDeeplyEqual(sorts, nextSorts)) { setSavedSorts(nextSorts); @@ -77,15 +82,15 @@ export const useViewSorts = ({ const [deleteViewSortsMutation] = useDeleteViewSortsMutation(); const createViewSorts = useCallback( - (sorts: SelectedSortType[], viewId = currentViewId) => { + (sorts: Sort[], viewId = currentViewId) => { if (!viewId || !sorts.length) return; return createViewSortsMutation({ variables: { data: sorts.map((sort) => ({ key: sort.key, - direction: sort.order as ViewSortDirection, - name: sort.label, + direction: sort.direction as ViewSortDirection, + name: sort.definition.label, viewId, })), }, @@ -95,7 +100,7 @@ export const useViewSorts = ({ ); const updateViewSorts = useCallback( - (sorts: SelectedSortType[]) => { + (sorts: Sort[]) => { if (!currentViewId || !sorts.length) return; return Promise.all( @@ -103,7 +108,7 @@ export const useViewSorts = ({ updateViewSortMutation({ variables: { data: { - direction: sort.order as ViewSortDirection, + direction: sort.direction as ViewSortDirection, }, where: { viewId_key: { key: sort.key, viewId: currentViewId }, @@ -141,7 +146,7 @@ export const useViewSorts = ({ const sortsToUpdate = sorts.filter( (sort) => savedSortsByKey[sort.key] && - savedSortsByKey[sort.key].order !== sort.order, + savedSortsByKey[sort.key].direction !== sort.direction, ); await updateViewSorts(sortsToUpdate); diff --git a/front/src/pages/companies/companies-sorts.tsx b/front/src/pages/companies/companies-sorts.tsx index 9a2b0eec1..4bfdde723 100644 --- a/front/src/pages/companies/companies-sorts.tsx +++ b/front/src/pages/companies/companies-sorts.tsx @@ -5,10 +5,9 @@ import { IconMap, IconUsers, } from '@/ui/icon/index'; -import { SortType } from '@/ui/view-bar/types/interface'; -import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql'; +import { SortDefinition } from '@/ui/view-bar/types/SortDefinition'; -export const availableSorts: SortType[] = [ +export const companyAvailableSorts: SortDefinition[] = [ { key: 'name', label: 'Name', diff --git a/front/src/pages/opportunities/opportunities-sorts.tsx b/front/src/pages/opportunities/opportunities-sorts.tsx index 6d826ee19..d794db5be 100644 --- a/front/src/pages/opportunities/opportunities-sorts.tsx +++ b/front/src/pages/opportunities/opportunities-sorts.tsx @@ -1,8 +1,7 @@ import { IconCalendarEvent, IconCurrencyDollar } from '@/ui/icon/index'; -import { SortType } from '@/ui/view-bar/types/interface'; -import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql'; +import { SortDefinition } from '@/ui/view-bar/types/SortDefinition'; -export const opportunitiesSorts = [ +export const opportunitiesSorts: SortDefinition[] = [ { key: 'createdAt', label: 'Creation', @@ -18,4 +17,4 @@ export const opportunitiesSorts = [ label: 'Expected close date', Icon: IconCalendarEvent, }, -] satisfies Array>; +]; diff --git a/front/src/pages/people/people-sorts.tsx b/front/src/pages/people/people-sorts.tsx index 4df17db50..10a47cada 100644 --- a/front/src/pages/people/people-sorts.tsx +++ b/front/src/pages/people/people-sorts.tsx @@ -6,28 +6,27 @@ import { IconPhone, IconUser, } from '@/ui/icon/index'; -import { SortType } from '@/ui/view-bar/types/interface'; -import { - PersonOrderByWithRelationInput as People_Order_By, - SortOrder as Order_By, -} from '~/generated/graphql'; +import { SortDefinition } from '@/ui/view-bar/types/SortDefinition'; +import { SortDirection } from '@/ui/view-bar/types/SortDirection'; -export const availableSorts: SortType[] = [ +export const peopleAvailableSorts: SortDefinition[] = [ { key: 'fullname', label: 'People', Icon: IconUser, - orderByTemplate: (order: Order_By) => [ - { firstName: order }, - { lastName: order }, + getOrderByTemplate: (direction: SortDirection) => [ + { firstName: direction }, + { lastName: direction }, ], }, { key: 'company_name', label: 'Company', Icon: IconBuildingSkyscraper, - orderByTemplate: (order: Order_By) => [{ company: { name: order } }], + getOrderByTemplate: (direction: SortDirection) => [ + { company: { name: direction } }, + ], }, { key: 'email', diff --git a/front/src/pages/tasks/Tasks.tsx b/front/src/pages/tasks/Tasks.tsx index 103f9fe6b..b613bfa99 100644 --- a/front/src/pages/tasks/Tasks.tsx +++ b/front/src/pages/tasks/Tasks.tsx @@ -68,7 +68,9 @@ export function Tasks() { } />