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