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