Refator/sorts dropdown (#1568)

* WIP

* Fixed lint

* Ok for sorts

* Fixed on dropdown toggle

* Fix lint
This commit is contained in:
Lucas Bordeau
2023-09-14 01:38:11 +02:00
committed by GitHub
parent a392a81994
commit 8627416d60
55 changed files with 339 additions and 309 deletions

View File

@ -18,8 +18,6 @@ type CompanyBoardProps = Pick<
export const CompanyBoard = ({ ...props }: CompanyBoardProps) => { export const CompanyBoard = ({ ...props }: CompanyBoardProps) => {
const { handleViewsChange, handleViewSubmit } = useBoardViews({ const { handleViewsChange, handleViewSubmit } = useBoardViews({
availableFilters: opportunitiesBoardOptions.filters,
availableSorts: opportunitiesBoardOptions.sorts,
objectId: 'company', objectId: 'company',
scopeContext: CompanyBoardRecoilScopeContext, scopeContext: CompanyBoardRecoilScopeContext,
fieldDefinitions: pipelineAvailableFieldDefinitions, fieldDefinitions: pipelineAvailableFieldDefinitions,

View File

@ -7,6 +7,7 @@ import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; 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 { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector'; import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector';
import { turnFilterIntoWhereClause } from '@/ui/view-bar/utils/turnFilterIntoWhereClause'; import { turnFilterIntoWhereClause } from '@/ui/view-bar/utils/turnFilterIntoWhereClause';
@ -29,8 +30,14 @@ export function HooksCompanyBoard() {
CompanyBoardRecoilScopeContext, CompanyBoardRecoilScopeContext,
); );
const [, setAvailableSorts] = useRecoilScopedState(
availableSortsScopedState,
CompanyBoardRecoilScopeContext,
);
useEffect(() => { useEffect(() => {
setAvailableFilters(opportunitiesBoardOptions.filters); setAvailableFilters(opportunitiesBoardOptions.filters);
setAvailableSorts(opportunitiesBoardOptions.sorts);
}); });
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState); const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);

View File

@ -4,7 +4,7 @@ import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTab
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries'; import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport'; import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
import { EntityTable } from '@/ui/table/components/EntityTable'; 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 { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@ -17,7 +17,7 @@ import {
useUpdateOneCompanyMutation, useUpdateOneCompanyMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { companiesFilters } from '~/pages/companies/companies-filters'; import { companiesFilters } from '~/pages/companies/companies-filters';
import { availableSorts } from '~/pages/companies/companies-sorts'; import { companyAvailableSorts } from '~/pages/companies/companies-sorts';
export function CompanyTable() { export function CompanyTable() {
const sortsOrderBy = useRecoilScopedValue( const sortsOrderBy = useRecoilScopedValue(
@ -33,8 +33,6 @@ export function CompanyTable() {
const upsertEntityTableItem = useUpsertEntityTableItem(); const upsertEntityTableItem = useUpsertEntityTableItem();
const { handleViewsChange, handleViewSubmit } = useTableViews({ const { handleViewsChange, handleViewSubmit } = useTableViews({
availableFilters: companiesFilters,
availableSorts,
objectId: 'company', objectId: 'company',
columnDefinitions: companiesAvailableColumnDefinitions, columnDefinitions: companiesAvailableColumnDefinitions,
}); });
@ -50,7 +48,7 @@ export function CompanyTable() {
return ( return (
<> <>
<GenericEntityTableData <EntityTableEffect
getRequestResultKey="companies" getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery} useGetRequest={useGetCompaniesQuery}
getRequestOptimisticEffectDefinition={ getRequestOptimisticEffectDefinition={
@ -59,12 +57,12 @@ export function CompanyTable() {
orderBy={sortsOrderBy} orderBy={sortsOrderBy}
whereFilters={filtersWhere} whereFilters={filtersWhere}
filterDefinitionArray={companiesFilters} filterDefinitionArray={companiesFilters}
sortDefinitionArray={companyAvailableSorts}
setContextMenuEntries={setContextMenuEntries} setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries} setActionBarEntries={setActionBarEntries}
/> />
<EntityTable <EntityTable
defaultViewName="All Companies" defaultViewName="All Companies"
availableSorts={availableSorts}
onViewsChange={handleViewsChange} onViewsChange={handleViewsChange}
onViewSubmit={handleViewSubmit} onViewSubmit={handleViewSubmit}
onImport={handleImport} onImport={handleImport}

View File

@ -17,7 +17,7 @@ export function CompanyTableMockData() {
const setEntityTableData = useSetEntityTableData(); const setEntityTableData = useSetEntityTableData();
useEffect(() => { useEffect(() => {
setEntityTableData(mockedCompaniesData, []); setEntityTableData(mockedCompaniesData, [], []);
setTableColumns(companiesAvailableColumnDefinitions); setTableColumns(companiesAvailableColumnDefinitions);
}, [setEntityTableData, setTableColumns]); }, [setEntityTableData, setTableColumns]);

View File

@ -1,6 +1,5 @@
import { EntityTable } from '@/ui/table/components/EntityTable'; import { EntityTable } from '@/ui/table/components/EntityTable';
import { useUpdateOneCompanyMutation } from '~/generated/graphql'; import { useUpdateOneCompanyMutation } from '~/generated/graphql';
import { availableSorts } from '~/pages/companies/companies-sorts';
import { CompanyTableMockData } from './CompanyTableMockData'; import { CompanyTableMockData } from './CompanyTableMockData';
@ -10,7 +9,6 @@ export function CompanyTableMockMode() {
<CompanyTableMockData /> <CompanyTableMockData />
<EntityTable <EntityTable
defaultViewName="All Companies" defaultViewName="All Companies"
availableSorts={availableSorts}
updateEntityMutation={[useUpdateOneCompanyMutation()]} updateEntityMutation={[useUpdateOneCompanyMutation()]}
/> />
</> </>

View File

@ -4,7 +4,7 @@ import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableC
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries'; import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport'; import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
import { EntityTable } from '@/ui/table/components/EntityTable'; 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 { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@ -17,7 +17,7 @@ import {
useUpdateOnePersonMutation, useUpdateOnePersonMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { peopleFilters } from '~/pages/people/people-filters'; import { peopleFilters } from '~/pages/people/people-filters';
import { availableSorts } from '~/pages/people/people-sorts'; import { peopleAvailableSorts } from '~/pages/people/people-sorts';
export function PeopleTable() { export function PeopleTable() {
const sortsOrderBy = useRecoilScopedValue( const sortsOrderBy = useRecoilScopedValue(
@ -34,8 +34,6 @@ export function PeopleTable() {
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport(); const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
const { handleViewsChange, handleViewSubmit } = useTableViews({ const { handleViewsChange, handleViewSubmit } = useTableViews({
availableFilters: peopleFilters,
availableSorts,
objectId: 'person', objectId: 'person',
columnDefinitions: peopleAvailableColumnDefinitions, columnDefinitions: peopleAvailableColumnDefinitions,
}); });
@ -49,7 +47,7 @@ export function PeopleTable() {
return ( return (
<> <>
<GenericEntityTableData <EntityTableEffect
getRequestResultKey="people" getRequestResultKey="people"
useGetRequest={useGetPeopleQuery} useGetRequest={useGetPeopleQuery}
getRequestOptimisticEffectDefinition={ getRequestOptimisticEffectDefinition={
@ -60,10 +58,10 @@ export function PeopleTable() {
filterDefinitionArray={peopleFilters} filterDefinitionArray={peopleFilters}
setContextMenuEntries={setContextMenuEntries} setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries} setActionBarEntries={setActionBarEntries}
sortDefinitionArray={peopleAvailableSorts}
/> />
<EntityTable <EntityTable
defaultViewName="All People" defaultViewName="All People"
availableSorts={availableSorts}
onViewsChange={handleViewsChange} onViewsChange={handleViewsChange}
onViewSubmit={handleViewSubmit} onViewSubmit={handleViewSubmit}
onImport={handleImport} onImport={handleImport}

View File

@ -13,7 +13,7 @@ export function PipelineAddButton() {
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({ const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({
key: 'add-pipeline-progress', dropdownId: 'add-pipeline-progress',
}); });
const createCompanyProgress = useCreateCompanyProgress(); const createCompanyProgress = useCreateCompanyProgress();
@ -53,7 +53,7 @@ export function PipelineAddButton() {
return ( return (
<DropdownButton <DropdownButton
dropdownKey="add-pipeline-progress" dropdownId="add-pipeline-progress"
buttonComponents={ buttonComponents={
<IconButton <IconButton
Icon={IconPlus} Icon={IconPlus}

View File

@ -10,29 +10,23 @@ import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
import { BoardOptionsDropdown } from './BoardOptionsDropdown'; import { BoardOptionsDropdown } from './BoardOptionsDropdown';
export type BoardHeaderProps<SortField> = ComponentProps<'div'> & { export type BoardHeaderProps = ComponentProps<'div'> & {
onStageAdd?: (boardColumn: BoardColumnDefinition) => void; onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
} & Pick< } & Pick<
ViewBarProps<SortField>, ViewBarProps,
| 'availableSorts' 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' | 'scopeContext'
| 'defaultViewName'
| 'onViewsChange'
| 'onViewSubmit'
| 'scopeContext'
>; >;
export function BoardHeader<SortField>({ export function BoardHeader({
onStageAdd, onStageAdd,
onViewsChange, onViewsChange,
onViewSubmit, onViewSubmit,
scopeContext, scopeContext,
availableSorts,
defaultViewName, defaultViewName,
}: BoardHeaderProps<SortField>) { }: BoardHeaderProps) {
return ( return (
<RecoilScope SpecificContext={DropdownRecoilScopeContext}> <RecoilScope SpecificContext={DropdownRecoilScopeContext}>
<ViewBar <ViewBar
availableSorts={availableSorts}
defaultViewName={defaultViewName} defaultViewName={defaultViewName}
onViewsChange={onViewsChange} onViewsChange={onViewsChange}
onViewSubmit={onViewSubmit} onViewSubmit={onViewSubmit}

View File

@ -27,7 +27,7 @@ export function BoardOptionsDropdown({
/> />
} }
dropdownHotkeyScope={customHotkeyScope} dropdownHotkeyScope={customHotkeyScope}
dropdownKey={BoardOptionsDropdownKey} dropdownId={BoardOptionsDropdownKey}
/> />
); );
} }

View File

@ -5,7 +5,7 @@ import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
export function BoardOptionsDropdownButton() { export function BoardOptionsDropdownButton() {
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
key: BoardOptionsDropdownKey, dropdownId: BoardOptionsDropdownKey,
}); });
function handleClick() { function handleClick() {

View File

@ -126,7 +126,7 @@ export function BoardOptionsDropdownContent({
const { handleFieldVisibilityChange } = useBoardCardFields({ scopeContext }); const { handleFieldVisibilityChange } = useBoardCardFields({ scopeContext });
const { closeDropdownButton } = useDropdownButton({ const { closeDropdownButton } = useDropdownButton({
key: BoardOptionsDropdownKey, dropdownId: BoardOptionsDropdownKey,
}); });
useScopedHotkeys( useScopedHotkeys(

View File

@ -22,7 +22,6 @@ import {
PipelineStage, PipelineStage,
useUpdateOnePipelineProgressStageMutation, useUpdateOnePipelineProgressStageMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected'; import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected';
import { useSetCardSelected } from '../hooks/useSetCardSelected'; import { useSetCardSelected } from '../hooks/useSetCardSelected';
@ -41,7 +40,7 @@ export type EntityBoardProps = {
onEditColumnTitle: (columnId: string, title: string, color: string) => void; onEditColumnTitle: (columnId: string, title: string, color: string) => void;
scopeContext: Context<string | null>; scopeContext: Context<string | null>;
} & Pick< } & Pick<
BoardHeaderProps<PipelineProgresses_Order_By>, BoardHeaderProps,
'defaultViewName' | 'onViewsChange' | 'onViewSubmit' 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'
>; >;
@ -142,7 +141,6 @@ export function EntityBoard({
<StyledWrapper> <StyledWrapper>
<StyledBoardHeader <StyledBoardHeader
defaultViewName={defaultViewName} defaultViewName={defaultViewName}
availableSorts={boardOptions.sorts}
onStageAdd={onColumnAdd} onStageAdd={onColumnAdd}
onViewsChange={onViewsChange} onViewsChange={onViewsChange}
onViewSubmit={onViewSubmit} onViewSubmit={onViewSubmit}

View File

@ -1,13 +1,12 @@
import type { ComponentType, Context } from 'react'; import type { ComponentType, Context } from 'react';
import { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity'; import { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity';
import { SortType } from '@/ui/view-bar/types/interface'; import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { PipelineProgress } from '~/generated/graphql'; import { PipelineProgress } from '~/generated/graphql';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
export type BoardOptions = { export type BoardOptions = {
newCardComponent: React.ReactNode; newCardComponent: React.ReactNode;
CardComponent: ComponentType<{ scopeContext: Context<string | null> }>; CardComponent: ComponentType<{ scopeContext: Context<string | null> }>;
filters: FilterDefinitionByEntity<PipelineProgress>[]; filters: FilterDefinitionByEntity<PipelineProgress>[];
sorts: Array<SortType<PipelineProgresses_Order_By>>; sorts: SortDefinition[];
}; };

View File

@ -15,31 +15,28 @@ import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/Drop
type OwnProps = { type OwnProps = {
buttonComponents: JSX.Element | JSX.Element[]; buttonComponents: JSX.Element | JSX.Element[];
dropdownComponents: JSX.Element | JSX.Element[]; dropdownComponents: JSX.Element | JSX.Element[];
dropdownKey: string; dropdownId: string;
hotkey?: { hotkey?: {
key: Keys; key: Keys;
scope: string; scope: string;
}; };
dropdownHotkeyScope?: HotkeyScope; dropdownHotkeyScope?: HotkeyScope;
dropdownPlacement?: Placement; dropdownPlacement?: Placement;
onDropdownToggle?: (isDropdownOpen: boolean) => void;
}; };
export function DropdownButton({ export function DropdownButton({
buttonComponents, buttonComponents,
dropdownComponents, dropdownComponents,
dropdownKey, dropdownId,
hotkey, hotkey,
dropdownHotkeyScope, dropdownHotkeyScope,
dropdownPlacement = 'bottom-end', dropdownPlacement = 'bottom-end',
onDropdownToggle,
}: OwnProps) { }: OwnProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } = const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } =
useDropdownButton({ useDropdownButton({
key: dropdownKey, dropdownId,
onDropdownToggle,
}); });
const { refs, floatingStyles } = useFloating({ const { refs, floatingStyles } = useFloating({
@ -63,7 +60,7 @@ export function DropdownButton({
const [dropdownButtonCustomHotkeyScope, setDropdownButtonCustomHotkeyScope] = const [dropdownButtonCustomHotkeyScope, setDropdownButtonCustomHotkeyScope] =
useRecoilScopedFamilyState( useRecoilScopedFamilyState(
dropdownButtonCustomHotkeyScopeScopedFamilyState, dropdownButtonCustomHotkeyScopeScopedFamilyState,
dropdownKey, dropdownId,
DropdownRecoilScopeContext, DropdownRecoilScopeContext,
); );

View File

@ -5,14 +5,7 @@ import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/drop
import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState'; import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState';
import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext';
// TODO: have a more explicit name than key export function useDropdownButton({ dropdownId }: { dropdownId: string }) {
export function useDropdownButton({
key,
onDropdownToggle,
}: {
key: string;
onDropdownToggle?: (isDropdownButtonOpen: boolean) => void;
}) {
const { const {
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope, goBackToPreviousHotkeyScope,
@ -21,20 +14,19 @@ export function useDropdownButton({
const [isDropdownButtonOpen, setIsDropdownButtonOpen] = const [isDropdownButtonOpen, setIsDropdownButtonOpen] =
useRecoilScopedFamilyState( useRecoilScopedFamilyState(
isDropdownButtonOpenScopedFamilyState, isDropdownButtonOpenScopedFamilyState,
key, dropdownId,
DropdownRecoilScopeContext, DropdownRecoilScopeContext,
); );
const [dropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState( const [dropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState(
dropdownButtonCustomHotkeyScopeScopedFamilyState, dropdownButtonCustomHotkeyScopeScopedFamilyState,
key, dropdownId,
DropdownRecoilScopeContext, DropdownRecoilScopeContext,
); );
function closeDropdownButton() { function closeDropdownButton() {
goBackToPreviousHotkeyScope(); goBackToPreviousHotkeyScope();
setIsDropdownButtonOpen(false); setIsDropdownButtonOpen(false);
onDropdownToggle?.(false);
} }
function openDropdownButton() { function openDropdownButton() {
@ -46,7 +38,6 @@ export function useDropdownButton({
dropdownButtonCustomHotkeyScope.customScopes, dropdownButtonCustomHotkeyScope.customScopes,
); );
} }
onDropdownToggle?.(true);
} }
function toggleDropdownButton() { function toggleDropdownButton() {
@ -55,7 +46,6 @@ export function useDropdownButton({
} else { } else {
openDropdownButton(); openDropdownButton();
} }
onDropdownToggle?.(isDropdownButtonOpen);
} }
return { return {

View File

@ -22,7 +22,7 @@ export function ShowPageAddButton({
entity: ActivityTargetableEntity; entity: ActivityTargetableEntity;
}) { }) {
const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({ const { closeDropdownButton, toggleDropdownButton } = useDropdownButton({
key: 'add-show-page', dropdownId: 'add-show-page',
}); });
const openCreateActivity = useOpenCreateActivityDrawer(); const openCreateActivity = useOpenCreateActivityDrawer();
@ -34,7 +34,7 @@ export function ShowPageAddButton({
return ( return (
<StyledContainer> <StyledContainer>
<DropdownButton <DropdownButton
dropdownKey="add-show-page" dropdownId="add-show-page"
buttonComponents={ buttonComponents={
<IconButton <IconButton
Icon={IconPlus} Icon={IconPlus}

View File

@ -85,25 +85,20 @@ const StyledTableContainer = styled.div`
overflow: auto; overflow: auto;
`; `;
type OwnProps<SortField> = { type OwnProps = {
updateEntityMutation: any; updateEntityMutation: any;
} & Pick< } & Pick<
TableHeaderProps<SortField>, TableHeaderProps,
| 'availableSorts' 'defaultViewName' | 'onImport' | 'onViewsChange' | 'onViewSubmit'
| 'defaultViewName'
| 'onImport'
| 'onViewsChange'
| 'onViewSubmit'
>; >;
export function EntityTable<SortField>({ export function EntityTable({
availableSorts,
defaultViewName, defaultViewName,
onImport, onImport,
onViewsChange, onViewsChange,
onViewSubmit, onViewSubmit,
updateEntityMutation, updateEntityMutation,
}: OwnProps<SortField>) { }: OwnProps) {
const tableBodyRef = useRef<HTMLDivElement>(null); const tableBodyRef = useRef<HTMLDivElement>(null);
const setRowSelectedState = useSetRowSelectedState(); const setRowSelectedState = useSetRowSelectedState();
@ -141,7 +136,6 @@ export function EntityTable<SortField>({
<StyledTableWithHeader> <StyledTableWithHeader>
<StyledTableContainer ref={tableBodyRef}> <StyledTableContainer ref={tableBodyRef}>
<TableHeader <TableHeader
availableSorts={availableSorts ?? []}
defaultViewName={defaultViewName} defaultViewName={defaultViewName}
onImport={onImport} onImport={onImport}
onViewsChange={onViewsChange} onViewsChange={onViewsChange}

View File

@ -4,9 +4,10 @@ import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimis
import { OptimisticEffectDefinition } from '@/apollo/optimistic-effect/types/OptimisticEffectDefinition'; import { OptimisticEffectDefinition } from '@/apollo/optimistic-effect/types/OptimisticEffectDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition'; import { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition';
import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { SortOrder } from '~/generated/graphql'; import { SortOrder } from '~/generated/graphql';
export function GenericEntityTableData({ export function EntityTableEffect({
useGetRequest, useGetRequest,
getRequestResultKey, getRequestResultKey,
getRequestOptimisticEffectDefinition, getRequestOptimisticEffectDefinition,
@ -19,13 +20,18 @@ export function GenericEntityTableData({
filterDefinitionArray, filterDefinitionArray,
setActionBarEntries, setActionBarEntries,
setContextMenuEntries, setContextMenuEntries,
sortDefinitionArray,
}: { }: {
// TODO: type this
useGetRequest: any; useGetRequest: any;
getRequestResultKey: string; getRequestResultKey: string;
getRequestOptimisticEffectDefinition: OptimisticEffectDefinition<any>; getRequestOptimisticEffectDefinition: OptimisticEffectDefinition<any>;
// TODO: type this and replace with defaultSorts reduce should be applied to defaultSorts in this component not before
orderBy?: any; orderBy?: any;
// TODO: type this and replace with defaultFilters reduce should be applied to defaultFilters in this component not before
whereFilters?: any; whereFilters?: any;
filterDefinitionArray: FilterDefinition[]; filterDefinitionArray: FilterDefinition[];
sortDefinitionArray: SortDefinition[];
setActionBarEntries?: () => void; setActionBarEntries?: () => void;
setContextMenuEntries?: () => void; setContextMenuEntries?: () => void;
}) { }) {
@ -36,7 +42,9 @@ export function GenericEntityTableData({
variables: { orderBy, where: whereFilters }, variables: { orderBy, where: whereFilters },
onCompleted: (data: any) => { onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? []; const entities = data[getRequestResultKey] ?? [];
setEntityTableData(entities, filterDefinitionArray);
setEntityTableData(entities, filterDefinitionArray, sortDefinitionArray);
registerOptimisticEffect({ registerOptimisticEffect({
variables: { orderBy, where: whereFilters }, variables: { orderBy, where: whereFilters },
definition: getRequestOptimisticEffectDefinition, definition: getRequestOptimisticEffectDefinition,

View File

@ -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';

View File

@ -6,7 +6,9 @@ import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyS
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState'; 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 { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition';
import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState'; import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
import { numberOfTableRowsState } from '../states/numberOfTableRowsState'; import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
@ -20,7 +22,8 @@ export function useSetEntityTableData() {
({ set, snapshot }) => ({ set, snapshot }) =>
<T extends { id: string }>( <T extends { id: string }>(
newEntityArray: T[], newEntityArray: T[],
filters: FilterDefinition[], filterDefinitionArray: FilterDefinition[],
sortDefinitionArray: SortDefinition[],
) => { ) => {
for (const entity of newEntityArray) { for (const entity of newEntityArray) {
const currentEntity = snapshot const currentEntity = snapshot
@ -46,7 +49,14 @@ export function useSetEntityTableData() {
set(numberOfTableRowsState, entityIds.length); set(numberOfTableRowsState, entityIds.length);
set(availableFiltersScopedState(tableContextScopeId), filters); set(
availableFiltersScopedState(tableContextScopeId),
filterDefinitionArray,
);
set(
availableSortsScopedState(tableContextScopeId),
sortDefinitionArray,
);
set(isFetchingEntityTableDataState, false); set(isFetchingEntityTableDataState, false);
}, },

View File

@ -2,7 +2,7 @@ import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import type { View } from '@/ui/view-bar/types/View'; import type { View } from '@/ui/view-bar/types/View';
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey'; import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
import { TableOptionsDropdownButton } from './TableOptionsDropdownButton'; import { TableOptionsDropdownButton } from './TableOptionsDropdownButton';
import { TableOptionsDropdownContent } from './TableOptionsDropdownContent'; import { TableOptionsDropdownContent } from './TableOptionsDropdownContent';
@ -22,7 +22,7 @@ export function TableOptionsDropdown({
<DropdownButton <DropdownButton
buttonComponents={<TableOptionsDropdownButton />} buttonComponents={<TableOptionsDropdownButton />}
dropdownHotkeyScope={customHotkeyScope} dropdownHotkeyScope={customHotkeyScope}
dropdownKey={TableOptionsDropdownKey} dropdownId={TableOptionsDropdownId}
dropdownComponents={ dropdownComponents={
<TableOptionsDropdownContent <TableOptionsDropdownContent
onImport={onImport} onImport={onImport}

View File

@ -1,11 +1,11 @@
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey'; import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
export function TableOptionsDropdownButton() { export function TableOptionsDropdownButton() {
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
key: TableOptionsDropdownKey, dropdownId: TableOptionsDropdownId,
}); });
return ( return (

View File

@ -18,11 +18,11 @@ import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByI
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState'; import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
import type { View } from '@/ui/view-bar/types/View'; import type { View } from '@/ui/view-bar/types/View';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
import { useTableColumns } from '../../hooks/useTableColumns'; import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector'; import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector'; import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector';
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
type TableOptionsDropdownButtonProps = { type TableOptionsDropdownButtonProps = {
@ -37,7 +37,7 @@ export function TableOptionsDropdownContent({
onImport, onImport,
}: TableOptionsDropdownButtonProps) { }: TableOptionsDropdownButtonProps) {
const { closeDropdownButton } = useDropdownButton({ const { closeDropdownButton } = useDropdownButton({
key: TableOptionsDropdownKey, dropdownId: TableOptionsDropdownId,
}); });
const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>( const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>(

View File

@ -8,27 +8,24 @@ import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoi
import { ViewBar, ViewBarProps } from '@/ui/view-bar/components/ViewBar'; import { ViewBar, ViewBarProps } from '@/ui/view-bar/components/ViewBar';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown'; import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState'; import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector'; import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector';
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState'; import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
export type TableHeaderProps<SortField> = { export type TableHeaderProps = {
onImport?: () => void; onImport?: () => void;
} & Pick< } & Pick<ViewBarProps, 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'>;
ViewBarProps<SortField>,
'availableSorts' | 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'
>;
export function TableHeader<SortField>({ export function TableHeader({
onImport, onImport,
onViewsChange, onViewsChange,
onViewSubmit, onViewSubmit,
...props ...props
}: TableHeaderProps<SortField>) { }: TableHeaderProps) {
const tableScopeId = useContextScopeId(TableRecoilScopeContext); const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const currentViewId = useRecoilScopedValue( const currentViewId = useRecoilScopedValue(
@ -84,7 +81,7 @@ export function TableHeader<SortField>({
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }} customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
/> />
} }
optionsDropdownKey={TableOptionsDropdownKey} optionsDropdownKey={TableOptionsDropdownId}
scopeContext={TableRecoilScopeContext} scopeContext={TableRecoilScopeContext}
/> />
</RecoilScope> </RecoilScope>

View File

@ -1 +0,0 @@
export const TableOptionsDropdownKey = 'table-options';

View File

@ -2,11 +2,11 @@ import { LightButton } from '@/ui/button/components/LightButton';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { IconPlus } from '@/ui/icon'; import { IconPlus } from '@/ui/icon';
import { FilterDropdownKey } from '../types/FilterDropdownKey'; import { FilterDropdownId } from '../constants/FilterDropdownId';
export function AddFilterFromDropdownButton() { export function AddFilterFromDropdownButton() {
const { toggleDropdownButton } = useDropdownButton({ const { toggleDropdownButton } = useDropdownButton({
key: FilterDropdownKey, dropdownId: FilterDropdownId,
}); });
function handleClick() { function handleClick() {

View File

@ -1,5 +1,6 @@
import { Context } from 'react'; import { Context } from 'react';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState'; import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
@ -9,12 +10,12 @@ import { SingleEntityFilterDropdownButton } from './SingleEntityFilterDropdownBu
type FilterDropdownButtonProps = { type FilterDropdownButtonProps = {
context: Context<string | null>; context: Context<string | null>;
hotkeyScope: string; hotkeyScope: HotkeyScope;
}; };
export function FilterDropdownButton({ export function FilterDropdownButton({
context,
hotkeyScope, hotkeyScope,
context,
}: FilterDropdownButtonProps) { }: FilterDropdownButtonProps) {
const [availableFilters] = useRecoilScopedState( const [availableFilters] = useRecoilScopedState(
availableFiltersScopedState, availableFiltersScopedState,

View File

@ -1,11 +1,11 @@
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { FilterDropdownKey } from '../types/FilterDropdownKey'; import { FilterDropdownId } from '../constants/FilterDropdownId';
export function MultipleFiltersButton() { export function MultipleFiltersButton() {
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
key: FilterDropdownKey, dropdownId: FilterDropdownId,
}); });
function handleClick() { function handleClick() {

View File

@ -1,24 +1,27 @@
import { Context, useCallback } from 'react'; import { Context, useCallback, useEffect } from 'react';
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; 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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { FilterDropdownId } from '../constants/FilterDropdownId';
import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState'; import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState'; import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState'; import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState'; import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
import { FilterDropdownKey } from '../types/FilterDropdownKey';
import { MultipleFiltersButton } from './MultipleFiltersButton'; import { MultipleFiltersButton } from './MultipleFiltersButton';
import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent'; import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent';
type MultipleFiltersDropdownButtonProps = { type MultipleFiltersDropdownButtonProps = {
context: Context<string | null>; context: Context<string | null>;
hotkeyScope: string; hotkeyScope: HotkeyScope;
}; };
export function MultipleFiltersDropdownButton({ export function MultipleFiltersDropdownButton({
context, context,
hotkeyScope,
}: MultipleFiltersDropdownButtonProps) { }: MultipleFiltersDropdownButtonProps) {
const [, setIsFilterDropdownOperandSelectUnfolded] = useRecoilScopedState( const [, setIsFilterDropdownOperandSelectUnfolded] = useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState, isFilterDropdownOperandSelectUnfoldedScopedState,
@ -40,6 +43,10 @@ export function MultipleFiltersDropdownButton({
context, context,
); );
const { isDropdownButtonOpen } = useDropdownButton({
dropdownId: FilterDropdownId,
});
const resetState = useCallback(() => { const resetState = useCallback(() => {
setIsFilterDropdownOperandSelectUnfolded(false); setIsFilterDropdownOperandSelectUnfolded(false);
setFilterDefinitionUsedInDropdown(null); setFilterDefinitionUsedInDropdown(null);
@ -51,14 +58,19 @@ export function MultipleFiltersDropdownButton({
setFilterDropdownSearchInput, setFilterDropdownSearchInput,
setIsFilterDropdownOperandSelectUnfolded, setIsFilterDropdownOperandSelectUnfolded,
]); ]);
useEffect(() => {
if (!isDropdownButtonOpen) {
resetState();
}
}, [isDropdownButtonOpen, resetState]);
return ( return (
<DropdownButton <DropdownButton
dropdownKey={FilterDropdownKey} dropdownId={FilterDropdownId}
buttonComponents={<MultipleFiltersButton />} buttonComponents={<MultipleFiltersButton />}
dropdownComponents={<MultipleFiltersDropdownContent context={context} />} dropdownComponents={<MultipleFiltersDropdownContent context={context} />}
onDropdownToggle={() => { dropdownHotkeyScope={hotkeyScope}
resetState();
}}
/> />
); );
} }

View File

@ -39,6 +39,8 @@ export function MultipleFiltersDropdownContent({
context, context,
); );
console.log('filterDefinitionUsedInDropdown', filterDefinitionUsedInDropdown);
return ( return (
<StyledDropdownMenu> <StyledDropdownMenu>
<> <>

View File

@ -6,6 +6,7 @@ import styled from '@emotion/styled';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
import { IconChevronDown } from '@/ui/icon'; import { IconChevronDown } from '@/ui/icon';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; 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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState'; import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '@/ui/view-bar/states/filterDropdownSearchInputScopedState'; import { filterDropdownSearchInputScopedState } from '@/ui/view-bar/states/filterDropdownSearchInputScopedState';
@ -33,7 +34,7 @@ export function SingleEntityFilterDropdownButton({
hotkeyScope, hotkeyScope,
}: { }: {
context: Context<string | null>; context: Context<string | null>;
hotkeyScope: string; hotkeyScope: HotkeyScope;
}) { }) {
const theme = useTheme(); const theme = useTheme();
@ -80,10 +81,10 @@ export function SingleEntityFilterDropdownButton({
function handleIsUnfoldedChange(newIsUnfolded: boolean) { function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) { if (newIsUnfolded) {
setHotkeyScope(hotkeyScope); setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes);
setIsFilterDropdownUnfolded(true); setIsFilterDropdownUnfolded(true);
} else { } else {
setHotkeyScope(hotkeyScope); setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes);
setIsFilterDropdownUnfolded(false); setIsFilterDropdownUnfolded(false);
setFilterDropdownSearchInput(''); setFilterDropdownSearchInput('');
} }

View File

@ -1,120 +1,136 @@
import { Context, useCallback, useState } from 'react'; 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 { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { IconChevronDown } from '@/ui/icon'; import { IconChevronDown } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem'; 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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { SortDropdownId } from '../constants/SortDropdownId';
import { availableSortsScopedState } from '../states/availableSortsScopedState';
import { sortsScopedState } from '../states/sortsScopedState'; import { sortsScopedState } from '../states/sortsScopedState';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { SortDefinition } from '../types/SortDefinition';
import { SelectedSortType, SortType } from '../types/interface'; import { SORT_DIRECTIONS, SortDirection } from '../types/SortDirection';
import DropdownButton from './DropdownButton'; export type SortDropdownButtonProps = {
export type SortDropdownButtonProps<SortField> = {
availableSorts: SortType<SortField>[];
hotkeyScope: FiltersHotkeyScope;
context: Context<string | null>; context: Context<string | null>;
hotkeyScope: HotkeyScope;
isPrimaryButton?: boolean; isPrimaryButton?: boolean;
}; };
const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc']; export function SortDropdownButton({
export function SortDropdownButton<SortField>({
context,
availableSorts,
hotkeyScope, hotkeyScope,
}: SortDropdownButtonProps<SortField>) { context,
const [isUnfolded, setIsUnfolded] = useState(false); }: SortDropdownButtonProps) {
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); const [isSortDirectionMenuUnfolded, setIsSortDirectionMenuUnfolded] =
useState(false);
const [selectedSortDirection, setSelectedSortDirection] = const [selectedSortDirection, setSelectedSortDirection] =
useState<SelectedSortType<SortField>['order']>('asc'); useState<SortDirection>('asc');
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
sortsScopedState,
context,
);
const isSortSelected = sorts.length > 0;
const onSortItemSelect = useCallback(
(sort: SortType<SortField>) => {
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],
);
const resetState = useCallback(() => { const resetState = useCallback(() => {
setIsOptionUnfolded(false); setIsSortDirectionMenuUnfolded(false);
setSelectedSortDirection('asc'); setSelectedSortDirection('asc');
}, []); }, []);
function handleIsUnfoldedChange(newIsUnfolded: boolean) { const [availableSorts] = useRecoilScopedState(
setIsUnfolded(newIsUnfolded); availableSortsScopedState,
if (!newIsUnfolded) resetState(); 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<SortField>) { function handleAddSort(selectedSortDefinition: SortDefinition) {
setIsUnfolded(false); toggleDropdownButton();
onSortItemSelect(sort);
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 ( return (
<DropdownButton <DropdownButton
label="Sort" dropdownId={SortDropdownId}
isActive={isSortSelected} dropdownHotkeyScope={hotkeyScope}
isUnfolded={isUnfolded} buttonComponents={
onIsUnfoldedChange={handleIsUnfoldedChange} <LightButton
hotkeyScope={hotkeyScope} title="Sort"
> active={isSortSelected}
{isOptionUnfolded ? ( onClick={handleButtonClick}
<StyledDropdownMenuItemsContainer> />
{options.map((option, index) => ( }
<MenuItem dropdownComponents={
key={index} <StyledDropdownMenu>
onClick={() => { {isSortDirectionMenuUnfolded ? (
setSelectedSortDirection(option); <StyledDropdownMenuItemsContainer>
setIsOptionUnfolded(false); {SORT_DIRECTIONS.map((sortOrder, index) => (
}} <MenuItem
text={option === 'asc' ? 'Ascending' : 'Descending'} key={index}
/> onClick={() => {
))} setSelectedSortDirection(sortOrder);
</StyledDropdownMenuItemsContainer> setIsSortDirectionMenuUnfolded(false);
) : ( }}
<> text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
<DropdownMenuHeader />
EndIcon={IconChevronDown} ))}
onClick={() => setIsOptionUnfolded(true)} </StyledDropdownMenuItemsContainer>
> ) : (
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} <>
</DropdownMenuHeader> <DropdownMenuHeader
<StyledDropdownMenuSeparator /> EndIcon={IconChevronDown}
onClick={() => setIsSortDirectionMenuUnfolded(true)}
<StyledDropdownMenuItemsContainer> >
{availableSorts.map((sort, index) => ( {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
<MenuItem </DropdownMenuHeader>
testId={`select-sort-${index}`} <StyledDropdownMenuSeparator />
key={index} <StyledDropdownMenuItemsContainer>
onClick={() => handleAddSort(sort)} {availableSorts.map((availableSort, index) => (
LeftIcon={sort.Icon} <MenuItem
text={sort.label} testId={`select-sort-${index}`}
/> key={index}
))} onClick={() => handleAddSort(availableSort)}
</StyledDropdownMenuItemsContainer> LeftIcon={availableSort.Icon}
</> text={availableSort.label}
)} />
</DropdownButton> ))}
</StyledDropdownMenuItemsContainer>
</>
)}
</StyledDropdownMenu>
}
></DropdownButton>
); );
} }

View File

@ -7,10 +7,7 @@ import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope'; import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
import { FilterDropdownButton } from './FilterDropdownButton'; import { FilterDropdownButton } from './FilterDropdownButton';
import { import { SortDropdownButton } from './SortDropdownButton';
SortDropdownButton,
type SortDropdownButtonProps,
} from './SortDropdownButton';
import { import {
UpdateViewButtonGroup, UpdateViewButtonGroup,
type UpdateViewButtonGroupProps, type UpdateViewButtonGroupProps,
@ -21,7 +18,7 @@ import {
type ViewsDropdownButtonProps, type ViewsDropdownButtonProps,
} from './ViewsDropdownButton'; } from './ViewsDropdownButton';
export type ViewBarProps<SortField> = ComponentProps<'div'> & { export type ViewBarProps = ComponentProps<'div'> & {
optionsDropdownButton: ReactNode; optionsDropdownButton: ReactNode;
optionsDropdownKey: string; optionsDropdownKey: string;
scopeContext: Context<string | null>; scopeContext: Context<string | null>;
@ -29,12 +26,10 @@ export type ViewBarProps<SortField> = ComponentProps<'div'> & {
ViewsDropdownButtonProps, ViewsDropdownButtonProps,
'defaultViewName' | 'onViewsChange' | 'onViewSelect' 'defaultViewName' | 'onViewsChange' | 'onViewSelect'
> & > &
Pick<SortDropdownButtonProps<SortField>, 'availableSorts'> &
Pick<ViewBarDetailsProps, 'canPersistViewFields' | 'onReset'> & Pick<ViewBarDetailsProps, 'canPersistViewFields' | 'onReset'> &
Pick<UpdateViewButtonGroupProps, 'onViewSubmit'>; Pick<UpdateViewButtonGroupProps, 'onViewSubmit'>;
export const ViewBar = <SortField,>({ export const ViewBar = ({
availableSorts,
canPersistViewFields, canPersistViewFields,
defaultViewName, defaultViewName,
onReset, onReset,
@ -45,9 +40,9 @@ export const ViewBar = <SortField,>({
optionsDropdownKey, optionsDropdownKey,
scopeContext, scopeContext,
...props ...props
}: ViewBarProps<SortField>) => { }: ViewBarProps) => {
const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({
key: optionsDropdownKey, dropdownId: optionsDropdownKey,
}); });
return ( return (
@ -67,13 +62,12 @@ export const ViewBar = <SortField,>({
rightComponent={ rightComponent={
<> <>
<FilterDropdownButton <FilterDropdownButton
hotkeyScope={{ scope: FiltersHotkeyScope.FilterDropdownButton }}
context={scopeContext} context={scopeContext}
hotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/> />
<SortDropdownButton<SortField> <SortDropdownButton
context={scopeContext} context={scopeContext}
availableSorts={availableSorts} hotkeyScope={{ scope: FiltersHotkeyScope.FilterDropdownButton }}
hotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
isPrimaryButton isPrimaryButton
/> />
{optionsDropdownButton} {optionsDropdownButton}

View File

@ -15,7 +15,6 @@ import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedS
import { canPersistFiltersScopedFamilySelector } from '../states/selectors/canPersistFiltersScopedFamilySelector'; import { canPersistFiltersScopedFamilySelector } from '../states/selectors/canPersistFiltersScopedFamilySelector';
import { canPersistSortsScopedFamilySelector } from '../states/selectors/canPersistSortsScopedFamilySelector'; import { canPersistSortsScopedFamilySelector } from '../states/selectors/canPersistSortsScopedFamilySelector';
import { sortsScopedState } from '../states/sortsScopedState'; import { sortsScopedState } from '../states/sortsScopedState';
import { SelectedSortType } from '../types/interface';
import { getOperandLabelShort } from '../utils/getOperandLabel'; import { getOperandLabelShort } from '../utils/getOperandLabel';
import { AddFilterFromDropdownButton } from './AddFilterFromDetailsButton'; import { AddFilterFromDropdownButton } from './AddFilterFromDetailsButton';
@ -97,7 +96,7 @@ const StyledAddFilterContainer = styled.div`
z-index: 5; z-index: 5;
`; `;
function ViewBarDetails<SortField>({ function ViewBarDetails({
canPersistViewFields, canPersistViewFields,
context, context,
hasFilterButton = false, hasFilterButton = false,
@ -120,10 +119,8 @@ function ViewBarDetails<SortField>({
canPersistFiltersScopedFamilySelector([recoilScopeId, currentViewId]), canPersistFiltersScopedFamilySelector([recoilScopeId, currentViewId]),
); );
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>( const [sorts, setSorts] = useRecoilScopedState(sortsScopedState, context);
sortsScopedState,
context,
);
const canPersistSorts = useRecoilValue( const canPersistSorts = useRecoilValue(
canPersistSortsScopedFamilySelector([recoilScopeId, currentViewId]), canPersistSortsScopedFamilySelector([recoilScopeId, currentViewId]),
); );
@ -177,9 +174,9 @@ function ViewBarDetails<SortField>({
<SortOrFilterChip <SortOrFilterChip
key={sort.key} key={sort.key}
testId={sort.key} testId={sort.key}
labelValue={sort.label} labelValue={sort.definition.label}
Icon={ Icon={
sort.order === 'desc' sort.direction === 'desc'
? IconArrowNarrowDown ? IconArrowNarrowDown
: IconArrowNarrowUp : IconArrowNarrowUp
} }

View File

@ -0,0 +1 @@
export const FilterDropdownId = 'filter';

View File

@ -0,0 +1 @@
export const SortDropdownId = 'sort-dropdown';

View File

@ -1,17 +0,0 @@
import { SortOrder as Order_By } from '~/generated/graphql';
import { SelectedSortType } from './types/interface';
export const reduceSortsToOrderBy = <OrderByTemplate>(
sorts: SelectedSortType<OrderByTemplate>[],
): 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();

View File

@ -0,0 +1,8 @@
import { atomFamily } from 'recoil';
import { SortDefinition } from '../types/SortDefinition';
export const availableSortsScopedState = atomFamily<SortDefinition[], string>({
key: 'availableSortsScopedState',
default: [],
});

View File

@ -1,11 +1,8 @@
import { atomFamily } from 'recoil'; import { atomFamily } from 'recoil';
import type { SelectedSortType } from '../types/interface'; import { Sort } from '../types/Sort';
export const savedSortsFamilyState = atomFamily< export const savedSortsFamilyState = atomFamily<Sort[], string | undefined>({
SelectedSortType<any>[],
string | undefined
>({
key: 'savedSortsFamilyState', key: 'savedSortsFamilyState',
default: [], default: [],
}); });

View File

@ -1,6 +1,6 @@
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
import type { SelectedSortType } from '../../types/interface'; import { Sort } from '../../types/Sort';
import { savedSortsFamilyState } from '../savedSortsFamilyState'; import { savedSortsFamilyState } from '../savedSortsFamilyState';
export const savedSortsByKeyFamilySelector = selectorFamily({ export const savedSortsByKeyFamilySelector = selectorFamily({
@ -8,7 +8,8 @@ export const savedSortsByKeyFamilySelector = selectorFamily({
get: get:
(viewId: string | undefined) => (viewId: string | undefined) =>
({ get }) => ({ get }) =>
get(savedSortsFamilyState(viewId)).reduce< get(savedSortsFamilyState(viewId)).reduce<Record<string, Sort>>(
Record<string, SelectedSortType<any>> (result, sort) => ({ ...result, [sort.key]: sort }),
>((result, sort) => ({ ...result, [sort.key]: sort }), {}), {},
),
}); });

View File

@ -2,7 +2,7 @@ import { selectorFamily } from 'recoil';
import { SortOrder } from '~/generated/graphql'; import { SortOrder } from '~/generated/graphql';
import { reduceSortsToOrderBy } from '../../helpers'; import { reduceSortsToOrderBy } from '../../utils/helpers';
import { sortsScopedState } from '../sortsScopedState'; import { sortsScopedState } from '../sortsScopedState';
export const sortsOrderByScopedSelector = selectorFamily({ export const sortsOrderByScopedSelector = selectorFamily({

View File

@ -1,8 +1,8 @@
import { atomFamily } from 'recoil'; import { atomFamily } from 'recoil';
import type { SelectedSortType } from '../types/interface'; import { Sort } from '../types/Sort';
export const sortsScopedState = atomFamily<SelectedSortType<any>[], string>({ export const sortsScopedState = atomFamily<Sort[], string>({
key: 'sortsScopedState', key: 'sortsScopedState',
default: [], default: [],
}); });

View File

@ -1 +0,0 @@
export const FilterDropdownKey = 'filter';

View File

@ -0,0 +1,8 @@
import { SortDefinition } from './SortDefinition';
import { SortDirection } from './SortDirection';
export type Sort = {
key: string;
direction: SortDirection;
definition: SortDefinition;
};

View File

@ -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[];
};

View File

@ -0,0 +1,3 @@
export const SORT_DIRECTIONS = ['asc', 'desc'] as const;
export type SortDirection = (typeof SORT_DIRECTIONS)[number];

View File

@ -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();

View File

@ -7,8 +7,6 @@ import type {
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; 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 { ViewType } from '~/generated/graphql';
import { useBoardViewFields } from './useBoardViewFields'; import { useBoardViewFields } from './useBoardViewFields';
@ -16,15 +14,11 @@ import { useViewFilters } from './useViewFilters';
import { useViews } from './useViews'; import { useViews } from './useViews';
import { useViewSorts } from './useViewSorts'; import { useViewSorts } from './useViewSorts';
export const useBoardViews = <Entity, SortField>({ export const useBoardViews = ({
availableFilters,
availableSorts,
fieldDefinitions, fieldDefinitions,
objectId, objectId,
scopeContext, scopeContext,
}: { }: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
fieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[]; fieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
objectId: 'company'; objectId: 'company';
scopeContext: Context<string | null>; scopeContext: Context<string | null>;
@ -38,19 +32,20 @@ export const useBoardViews = <Entity, SortField>({
type: ViewType.Pipeline, type: ViewType.Pipeline,
scopeContext, scopeContext,
}); });
useBoardViewFields({ useBoardViewFields({
objectId, objectId,
fieldDefinitions, fieldDefinitions,
scopeContext, scopeContext,
skipFetch: isFetchingViews, skipFetch: isFetchingViews,
}); });
const { createViewFilters, persistFilters } = useViewFilters({ const { createViewFilters, persistFilters } = useViewFilters({
availableFilters,
scopeContext, scopeContext,
skipFetch: isFetchingViews, skipFetch: isFetchingViews,
}); });
const { createViewSorts, persistSorts } = useViewSorts({ const { createViewSorts, persistSorts } = useViewSorts({
availableSorts,
scopeContext, scopeContext,
skipFetch: isFetchingViews, skipFetch: isFetchingViews,
}); });

View File

@ -5,8 +5,6 @@ import type { ColumnDefinition } from '@/ui/table/types/ColumnDefinition';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; 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 { ViewType } from '~/generated/graphql';
import { useTableViewFields } from './useTableViewFields'; import { useTableViewFields } from './useTableViewFields';
@ -14,14 +12,10 @@ import { useViewFilters } from './useViewFilters';
import { useViews } from './useViews'; import { useViews } from './useViews';
import { useViewSorts } from './useViewSorts'; import { useViewSorts } from './useViewSorts';
export const useTableViews = <Entity, SortField>({ export const useTableViews = ({
availableFilters,
availableSorts,
objectId, objectId,
columnDefinitions, columnDefinitions,
}: { }: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
objectId: 'company' | 'person'; objectId: 'company' | 'person';
columnDefinitions: ColumnDefinition<ViewFieldMetadata>[]; columnDefinitions: ColumnDefinition<ViewFieldMetadata>[];
}) => { }) => {
@ -47,12 +41,10 @@ export const useTableViews = <Entity, SortField>({
skipFetch: isFetchingViews, skipFetch: isFetchingViews,
}); });
const { createViewFilters, persistFilters } = useViewFilters({ const { createViewFilters, persistFilters } = useViewFilters({
availableFilters,
scopeContext: TableRecoilScopeContext, scopeContext: TableRecoilScopeContext,
skipFetch: isFetchingViews, skipFetch: isFetchingViews,
}); });
const { createViewSorts, persistSorts } = useViewSorts({ const { createViewSorts, persistSorts } = useViewSorts({
availableSorts,
scopeContext: TableRecoilScopeContext, scopeContext: TableRecoilScopeContext,
skipFetch: isFetchingViews, skipFetch: isFetchingViews,
}); });

View File

@ -3,12 +3,12 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState'; import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';
import { savedFiltersByKeyFamilySelector } from '@/ui/view-bar/states/selectors/savedFiltersByKeyFamilySelector'; import { savedFiltersByKeyFamilySelector } from '@/ui/view-bar/states/selectors/savedFiltersByKeyFamilySelector';
import type { Filter } from '@/ui/view-bar/types/Filter'; import type { Filter } from '@/ui/view-bar/types/Filter';
import type { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity';
import { import {
useCreateViewFiltersMutation, useCreateViewFiltersMutation,
useDeleteViewFiltersMutation, useDeleteViewFiltersMutation,
@ -17,12 +17,10 @@ import {
} from '~/generated/graphql'; } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useViewFilters = <Entity>({ export const useViewFilters = ({
availableFilters,
scopeContext, scopeContext,
skipFetch, skipFetch,
}: { }: {
availableFilters: FilterDefinitionByEntity<Entity>[];
scopeContext: Context<string | null>; scopeContext: Context<string | null>;
skipFetch?: boolean; skipFetch?: boolean;
}) => { }) => {
@ -34,6 +32,10 @@ export const useViewFilters = <Entity>({
filtersScopedState, filtersScopedState,
scopeContext, scopeContext,
); );
const [availableFilters] = useRecoilScopedState(
availableFiltersScopedState,
scopeContext,
);
const [, setSavedFilters] = useRecoilState( const [, setSavedFilters] = useRecoilState(
savedFiltersFamilyState(currentViewId), savedFiltersFamilyState(currentViewId),
); );

View File

@ -3,11 +3,12 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { availableSortsScopedState } from '@/ui/view-bar/states/availableSortsScopedState';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState'; import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState';
import { savedSortsByKeyFamilySelector } from '@/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector'; import { savedSortsByKeyFamilySelector } from '@/ui/view-bar/states/selectors/savedSortsByKeyFamilySelector';
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; 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 { import {
useCreateViewSortsMutation, useCreateViewSortsMutation,
useDeleteViewSortsMutation, useDeleteViewSortsMutation,
@ -17,12 +18,10 @@ import {
} from '~/generated/graphql'; } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useViewSorts = <SortField>({ export const useViewSorts = ({
availableSorts,
scopeContext, scopeContext,
skipFetch, skipFetch,
}: { }: {
availableSorts: SortType<SortField>[];
scopeContext: Context<string | null>; scopeContext: Context<string | null>;
skipFetch?: boolean; skipFetch?: boolean;
}) => { }) => {
@ -34,6 +33,10 @@ export const useViewSorts = <SortField>({
sortsScopedState, sortsScopedState,
scopeContext, scopeContext,
); );
const [availableSorts] = useRecoilScopedState(
availableSortsScopedState,
scopeContext,
);
const [, setSavedSorts] = useRecoilState( const [, setSavedSorts] = useRecoilState(
savedSortsFamilyState(currentViewId), savedSortsFamilyState(currentViewId),
); );
@ -51,19 +54,21 @@ export const useViewSorts = <SortField>({
onCompleted: (data) => { onCompleted: (data) => {
const nextSorts = data.viewSorts const nextSorts = data.viewSorts
.map((viewSort) => { .map((viewSort) => {
const availableSort = availableSorts.find( const foundCorrespondingSortDefinition = availableSorts.find(
(sort) => sort.key === viewSort.key, (sort) => sort.key === viewSort.key,
); );
return availableSort if (foundCorrespondingSortDefinition) {
? { return {
...availableSort, key: viewSort.key,
label: viewSort.name, definition: foundCorrespondingSortDefinition,
order: viewSort.direction.toLowerCase(), direction: viewSort.direction.toLowerCase(),
} } as Sort;
: undefined; } else {
return undefined;
}
}) })
.filter((sort): sort is SelectedSortType<SortField> => !!sort); .filter((sort): sort is Sort => !!sort);
if (!isDeeplyEqual(sorts, nextSorts)) { if (!isDeeplyEqual(sorts, nextSorts)) {
setSavedSorts(nextSorts); setSavedSorts(nextSorts);
@ -77,15 +82,15 @@ export const useViewSorts = <SortField>({
const [deleteViewSortsMutation] = useDeleteViewSortsMutation(); const [deleteViewSortsMutation] = useDeleteViewSortsMutation();
const createViewSorts = useCallback( const createViewSorts = useCallback(
(sorts: SelectedSortType<SortField>[], viewId = currentViewId) => { (sorts: Sort[], viewId = currentViewId) => {
if (!viewId || !sorts.length) return; if (!viewId || !sorts.length) return;
return createViewSortsMutation({ return createViewSortsMutation({
variables: { variables: {
data: sorts.map((sort) => ({ data: sorts.map((sort) => ({
key: sort.key, key: sort.key,
direction: sort.order as ViewSortDirection, direction: sort.direction as ViewSortDirection,
name: sort.label, name: sort.definition.label,
viewId, viewId,
})), })),
}, },
@ -95,7 +100,7 @@ export const useViewSorts = <SortField>({
); );
const updateViewSorts = useCallback( const updateViewSorts = useCallback(
(sorts: SelectedSortType<SortField>[]) => { (sorts: Sort[]) => {
if (!currentViewId || !sorts.length) return; if (!currentViewId || !sorts.length) return;
return Promise.all( return Promise.all(
@ -103,7 +108,7 @@ export const useViewSorts = <SortField>({
updateViewSortMutation({ updateViewSortMutation({
variables: { variables: {
data: { data: {
direction: sort.order as ViewSortDirection, direction: sort.direction as ViewSortDirection,
}, },
where: { where: {
viewId_key: { key: sort.key, viewId: currentViewId }, viewId_key: { key: sort.key, viewId: currentViewId },
@ -141,7 +146,7 @@ export const useViewSorts = <SortField>({
const sortsToUpdate = sorts.filter( const sortsToUpdate = sorts.filter(
(sort) => (sort) =>
savedSortsByKey[sort.key] && savedSortsByKey[sort.key] &&
savedSortsByKey[sort.key].order !== sort.order, savedSortsByKey[sort.key].direction !== sort.direction,
); );
await updateViewSorts(sortsToUpdate); await updateViewSorts(sortsToUpdate);

View File

@ -5,10 +5,9 @@ import {
IconMap, IconMap,
IconUsers, IconUsers,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { SortType } from '@/ui/view-bar/types/interface'; import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
export const availableSorts: SortType<Companies_Order_By>[] = [ export const companyAvailableSorts: SortDefinition[] = [
{ {
key: 'name', key: 'name',
label: 'Name', label: 'Name',

View File

@ -1,8 +1,7 @@
import { IconCalendarEvent, IconCurrencyDollar } from '@/ui/icon/index'; import { IconCalendarEvent, IconCurrencyDollar } from '@/ui/icon/index';
import { SortType } from '@/ui/view-bar/types/interface'; import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
export const opportunitiesSorts = [ export const opportunitiesSorts: SortDefinition[] = [
{ {
key: 'createdAt', key: 'createdAt',
label: 'Creation', label: 'Creation',
@ -18,4 +17,4 @@ export const opportunitiesSorts = [
label: 'Expected close date', label: 'Expected close date',
Icon: IconCalendarEvent, Icon: IconCalendarEvent,
}, },
] satisfies Array<SortType<PipelineProgresses_Order_By>>; ];

View File

@ -6,28 +6,27 @@ import {
IconPhone, IconPhone,
IconUser, IconUser,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { SortType } from '@/ui/view-bar/types/interface'; import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { import { SortDirection } from '@/ui/view-bar/types/SortDirection';
PersonOrderByWithRelationInput as People_Order_By,
SortOrder as Order_By,
} from '~/generated/graphql';
export const availableSorts: SortType<People_Order_By>[] = [ export const peopleAvailableSorts: SortDefinition[] = [
{ {
key: 'fullname', key: 'fullname',
label: 'People', label: 'People',
Icon: IconUser, Icon: IconUser,
orderByTemplate: (order: Order_By) => [ getOrderByTemplate: (direction: SortDirection) => [
{ firstName: order }, { firstName: direction },
{ lastName: order }, { lastName: direction },
], ],
}, },
{ {
key: 'company_name', key: 'company_name',
label: 'Company', label: 'Company',
Icon: IconBuildingSkyscraper, Icon: IconBuildingSkyscraper,
orderByTemplate: (order: Order_By) => [{ company: { name: order } }], getOrderByTemplate: (direction: SortDirection) => [
{ company: { name: direction } },
],
}, },
{ {
key: 'email', key: 'email',

View File

@ -68,7 +68,9 @@ export function Tasks() {
<FilterDropdownButton <FilterDropdownButton
key="tasks-filter-dropdown-button" key="tasks-filter-dropdown-button"
context={TasksRecoilScopeContext} context={TasksRecoilScopeContext}
hotkeyScope={RelationPickerHotkeyScope.RelationPicker} hotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
/> />
} }
/> />