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) => {
const { handleViewsChange, handleViewSubmit } = useBoardViews({
availableFilters: opportunitiesBoardOptions.filters,
availableSorts: opportunitiesBoardOptions.sorts,
objectId: 'company',
scopeContext: CompanyBoardRecoilScopeContext,
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 { 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);

View File

@ -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 (
<>
<GenericEntityTableData
<EntityTableEffect
getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery}
getRequestOptimisticEffectDefinition={
@ -59,12 +57,12 @@ export function CompanyTable() {
orderBy={sortsOrderBy}
whereFilters={filtersWhere}
filterDefinitionArray={companiesFilters}
sortDefinitionArray={companyAvailableSorts}
setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries}
/>
<EntityTable
defaultViewName="All Companies"
availableSorts={availableSorts}
onViewsChange={handleViewsChange}
onViewSubmit={handleViewSubmit}
onImport={handleImport}

View File

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

View File

@ -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() {
<CompanyTableMockData />
<EntityTable
defaultViewName="All Companies"
availableSorts={availableSorts}
updateEntityMutation={[useUpdateOneCompanyMutation()]}
/>
</>

View File

@ -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 (
<>
<GenericEntityTableData
<EntityTableEffect
getRequestResultKey="people"
useGetRequest={useGetPeopleQuery}
getRequestOptimisticEffectDefinition={
@ -60,10 +58,10 @@ export function PeopleTable() {
filterDefinitionArray={peopleFilters}
setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries}
sortDefinitionArray={peopleAvailableSorts}
/>
<EntityTable
defaultViewName="All People"
availableSorts={availableSorts}
onViewsChange={handleViewsChange}
onViewSubmit={handleViewSubmit}
onImport={handleImport}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<string | null>;
} & Pick<
BoardHeaderProps<PipelineProgresses_Order_By>,
BoardHeaderProps,
'defaultViewName' | 'onViewsChange' | 'onViewSubmit'
>;
@ -142,7 +141,6 @@ export function EntityBoard({
<StyledWrapper>
<StyledBoardHeader
defaultViewName={defaultViewName}
availableSorts={boardOptions.sorts}
onStageAdd={onColumnAdd}
onViewsChange={onViewsChange}
onViewSubmit={onViewSubmit}

View File

@ -1,13 +1,12 @@
import type { ComponentType, Context } from 'react';
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 { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
export type BoardOptions = {
newCardComponent: React.ReactNode;
CardComponent: ComponentType<{ scopeContext: Context<string | null> }>;
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 = {
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<HTMLDivElement>(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,
);

View File

@ -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 {

View File

@ -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 (
<StyledContainer>
<DropdownButton
dropdownKey="add-show-page"
dropdownId="add-show-page"
buttonComponents={
<IconButton
Icon={IconPlus}

View File

@ -85,25 +85,20 @@ const StyledTableContainer = styled.div`
overflow: auto;
`;
type OwnProps<SortField> = {
type OwnProps = {
updateEntityMutation: any;
} & Pick<
TableHeaderProps<SortField>,
| 'availableSorts'
| 'defaultViewName'
| 'onImport'
| 'onViewsChange'
| 'onViewSubmit'
TableHeaderProps,
'defaultViewName' | 'onImport' | 'onViewsChange' | 'onViewSubmit'
>;
export function EntityTable<SortField>({
availableSorts,
export function EntityTable({
defaultViewName,
onImport,
onViewsChange,
onViewSubmit,
updateEntityMutation,
}: OwnProps<SortField>) {
}: OwnProps) {
const tableBodyRef = useRef<HTMLDivElement>(null);
const setRowSelectedState = useSetRowSelectedState();
@ -141,7 +136,6 @@ export function EntityTable<SortField>({
<StyledTableWithHeader>
<StyledTableContainer ref={tableBodyRef}>
<TableHeader
availableSorts={availableSorts ?? []}
defaultViewName={defaultViewName}
onImport={onImport}
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 { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition';
import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
import { SortOrder } from '~/generated/graphql';
export function GenericEntityTableData({
export function EntityTableEffect({
useGetRequest,
getRequestResultKey,
getRequestOptimisticEffectDefinition,
@ -19,13 +20,18 @@ export function GenericEntityTableData({
filterDefinitionArray,
setActionBarEntries,
setContextMenuEntries,
sortDefinitionArray,
}: {
// TODO: type this
useGetRequest: any;
getRequestResultKey: string;
getRequestOptimisticEffectDefinition: OptimisticEffectDefinition<any>;
// 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,

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 { 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 }) =>
<T extends { id: string }>(
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);
},

View File

@ -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({
<DropdownButton
buttonComponents={<TableOptionsDropdownButton />}
dropdownHotkeyScope={customHotkeyScope}
dropdownKey={TableOptionsDropdownKey}
dropdownId={TableOptionsDropdownId}
dropdownComponents={
<TableOptionsDropdownContent
onImport={onImport}

View File

@ -1,11 +1,11 @@
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
export function TableOptionsDropdownButton() {
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
key: TableOptionsDropdownKey,
dropdownId: TableOptionsDropdownId,
});
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 type { View } from '@/ui/view-bar/types/View';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector';
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
type TableOptionsDropdownButtonProps = {
@ -37,7 +37,7 @@ export function TableOptionsDropdownContent({
onImport,
}: TableOptionsDropdownButtonProps) {
const { closeDropdownButton } = useDropdownButton({
key: TableOptionsDropdownKey,
dropdownId: TableOptionsDropdownId,
});
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 { 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<SortField> = {
export type TableHeaderProps = {
onImport?: () => void;
} & Pick<
ViewBarProps<SortField>,
'availableSorts' | 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'
>;
} & Pick<ViewBarProps, 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'>;
export function TableHeader<SortField>({
export function TableHeader({
onImport,
onViewsChange,
onViewSubmit,
...props
}: TableHeaderProps<SortField>) {
}: TableHeaderProps) {
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const currentViewId = useRecoilScopedValue(
@ -84,7 +81,7 @@ export function TableHeader<SortField>({
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
/>
}
optionsDropdownKey={TableOptionsDropdownKey}
optionsDropdownKey={TableOptionsDropdownId}
scopeContext={TableRecoilScopeContext}
/>
</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 { 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() {

View File

@ -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<string | null>;
hotkeyScope: string;
hotkeyScope: HotkeyScope;
};
export function FilterDropdownButton({
context,
hotkeyScope,
context,
}: FilterDropdownButtonProps) {
const [availableFilters] = useRecoilScopedState(
availableFiltersScopedState,

View File

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

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 { 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<string | null>;
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 (
<DropdownButton
dropdownKey={FilterDropdownKey}
dropdownId={FilterDropdownId}
buttonComponents={<MultipleFiltersButton />}
dropdownComponents={<MultipleFiltersDropdownContent context={context} />}
onDropdownToggle={() => {
resetState();
}}
dropdownHotkeyScope={hotkeyScope}
/>
);
}

View File

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

View File

@ -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<string | null>;
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('');
}

View File

@ -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<SortField> = {
availableSorts: SortType<SortField>[];
hotkeyScope: FiltersHotkeyScope;
export type SortDropdownButtonProps = {
context: Context<string | null>;
hotkeyScope: HotkeyScope;
isPrimaryButton?: boolean;
};
const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc'];
export function SortDropdownButton<SortField>({
context,
availableSorts,
export function SortDropdownButton({
hotkeyScope,
}: SortDropdownButtonProps<SortField>) {
const [isUnfolded, setIsUnfolded] = useState(false);
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
context,
}: SortDropdownButtonProps) {
const [isSortDirectionMenuUnfolded, setIsSortDirectionMenuUnfolded] =
useState(false);
const [selectedSortDirection, setSelectedSortDirection] =
useState<SelectedSortType<SortField>['order']>('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],
);
useState<SortDirection>('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<SortField>) {
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 (
<DropdownButton
label="Sort"
isActive={isSortSelected}
isUnfolded={isUnfolded}
onIsUnfoldedChange={handleIsUnfoldedChange}
hotkeyScope={hotkeyScope}
>
{isOptionUnfolded ? (
<StyledDropdownMenuItemsContainer>
{options.map((option, index) => (
<MenuItem
key={index}
onClick={() => {
setSelectedSortDirection(option);
setIsOptionUnfolded(false);
}}
text={option === 'asc' ? 'Ascending' : 'Descending'}
/>
))}
</StyledDropdownMenuItemsContainer>
) : (
<>
<DropdownMenuHeader
EndIcon={IconChevronDown}
onClick={() => setIsOptionUnfolded(true)}
>
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
</DropdownMenuHeader>
<StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer>
{availableSorts.map((sort, index) => (
<MenuItem
testId={`select-sort-${index}`}
key={index}
onClick={() => handleAddSort(sort)}
LeftIcon={sort.Icon}
text={sort.label}
/>
))}
</StyledDropdownMenuItemsContainer>
</>
)}
</DropdownButton>
dropdownId={SortDropdownId}
dropdownHotkeyScope={hotkeyScope}
buttonComponents={
<LightButton
title="Sort"
active={isSortSelected}
onClick={handleButtonClick}
/>
}
dropdownComponents={
<StyledDropdownMenu>
{isSortDirectionMenuUnfolded ? (
<StyledDropdownMenuItemsContainer>
{SORT_DIRECTIONS.map((sortOrder, index) => (
<MenuItem
key={index}
onClick={() => {
setSelectedSortDirection(sortOrder);
setIsSortDirectionMenuUnfolded(false);
}}
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
/>
))}
</StyledDropdownMenuItemsContainer>
) : (
<>
<DropdownMenuHeader
EndIcon={IconChevronDown}
onClick={() => setIsSortDirectionMenuUnfolded(true)}
>
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
</DropdownMenuHeader>
<StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer>
{availableSorts.map((availableSort, index) => (
<MenuItem
testId={`select-sort-${index}`}
key={index}
onClick={() => handleAddSort(availableSort)}
LeftIcon={availableSort.Icon}
text={availableSort.label}
/>
))}
</StyledDropdownMenuItemsContainer>
</>
)}
</StyledDropdownMenu>
}
></DropdownButton>
);
}

View File

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

View File

@ -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<SortField>({
function ViewBarDetails({
canPersistViewFields,
context,
hasFilterButton = false,
@ -120,10 +119,8 @@ function ViewBarDetails<SortField>({
canPersistFiltersScopedFamilySelector([recoilScopeId, currentViewId]),
);
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
sortsScopedState,
context,
);
const [sorts, setSorts] = useRecoilScopedState(sortsScopedState, context);
const canPersistSorts = useRecoilValue(
canPersistSortsScopedFamilySelector([recoilScopeId, currentViewId]),
);
@ -177,9 +174,9 @@ function ViewBarDetails<SortField>({
<SortOrFilterChip
key={sort.key}
testId={sort.key}
labelValue={sort.label}
labelValue={sort.definition.label}
Icon={
sort.order === 'desc'
sort.direction === 'desc'
? IconArrowNarrowDown
: 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 type { SelectedSortType } from '../types/interface';
import { Sort } from '../types/Sort';
export const savedSortsFamilyState = atomFamily<
SelectedSortType<any>[],
string | undefined
>({
export const savedSortsFamilyState = atomFamily<Sort[], string | undefined>({
key: 'savedSortsFamilyState',
default: [],
});

View File

@ -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<string, SelectedSortType<any>>
>((result, sort) => ({ ...result, [sort.key]: sort }), {}),
get(savedSortsFamilyState(viewId)).reduce<Record<string, Sort>>(
(result, sort) => ({ ...result, [sort.key]: sort }),
{},
),
});

View File

@ -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({

View File

@ -1,8 +1,8 @@
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',
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 { 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 = <Entity, SortField>({
availableFilters,
availableSorts,
export const useBoardViews = ({
fieldDefinitions,
objectId,
scopeContext,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
fieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
objectId: 'company';
scopeContext: Context<string | null>;
@ -38,19 +32,20 @@ export const useBoardViews = <Entity, SortField>({
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,
});

View File

@ -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 = <Entity, SortField>({
availableFilters,
availableSorts,
export const useTableViews = ({
objectId,
columnDefinitions,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
objectId: 'company' | 'person';
columnDefinitions: ColumnDefinition<ViewFieldMetadata>[];
}) => {
@ -47,12 +41,10 @@ export const useTableViews = <Entity, SortField>({
skipFetch: isFetchingViews,
});
const { createViewFilters, persistFilters } = useViewFilters({
availableFilters,
scopeContext: TableRecoilScopeContext,
skipFetch: isFetchingViews,
});
const { createViewSorts, persistSorts } = useViewSorts({
availableSorts,
scopeContext: TableRecoilScopeContext,
skipFetch: isFetchingViews,
});

View File

@ -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 = <Entity>({
availableFilters,
export const useViewFilters = ({
scopeContext,
skipFetch,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
scopeContext: Context<string | null>;
skipFetch?: boolean;
}) => {
@ -34,6 +32,10 @@ export const useViewFilters = <Entity>({
filtersScopedState,
scopeContext,
);
const [availableFilters] = useRecoilScopedState(
availableFiltersScopedState,
scopeContext,
);
const [, setSavedFilters] = useRecoilState(
savedFiltersFamilyState(currentViewId),
);

View File

@ -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 = <SortField>({
availableSorts,
export const useViewSorts = ({
scopeContext,
skipFetch,
}: {
availableSorts: SortType<SortField>[];
scopeContext: Context<string | null>;
skipFetch?: boolean;
}) => {
@ -34,6 +33,10 @@ export const useViewSorts = <SortField>({
sortsScopedState,
scopeContext,
);
const [availableSorts] = useRecoilScopedState(
availableSortsScopedState,
scopeContext,
);
const [, setSavedSorts] = useRecoilState(
savedSortsFamilyState(currentViewId),
);
@ -51,19 +54,21 @@ export const useViewSorts = <SortField>({
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<SortField> => !!sort);
.filter((sort): sort is Sort => !!sort);
if (!isDeeplyEqual(sorts, nextSorts)) {
setSavedSorts(nextSorts);
@ -77,15 +82,15 @@ export const useViewSorts = <SortField>({
const [deleteViewSortsMutation] = useDeleteViewSortsMutation();
const createViewSorts = useCallback(
(sorts: SelectedSortType<SortField>[], 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 = <SortField>({
);
const updateViewSorts = useCallback(
(sorts: SelectedSortType<SortField>[]) => {
(sorts: Sort[]) => {
if (!currentViewId || !sorts.length) return;
return Promise.all(
@ -103,7 +108,7 @@ export const useViewSorts = <SortField>({
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 = <SortField>({
const sortsToUpdate = sorts.filter(
(sort) =>
savedSortsByKey[sort.key] &&
savedSortsByKey[sort.key].order !== sort.order,
savedSortsByKey[sort.key].direction !== sort.direction,
);
await updateViewSorts(sortsToUpdate);

View File

@ -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<Companies_Order_By>[] = [
export const companyAvailableSorts: SortDefinition[] = [
{
key: 'name',
label: 'Name',

View File

@ -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<SortType<PipelineProgresses_Order_By>>;
];

View File

@ -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<People_Order_By>[] = [
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',

View File

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