diff --git a/front/src/modules/companies/board/components/CompanyBoard.tsx b/front/src/modules/companies/board/components/CompanyBoard.tsx
index c851f0e26..145dca41f 100644
--- a/front/src/modules/companies/board/components/CompanyBoard.tsx
+++ b/front/src/modules/companies/board/components/CompanyBoard.tsx
@@ -5,6 +5,7 @@ import {
} from '@/ui/board/components/EntityBoard';
import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar';
import { EntityBoardContextMenu } from '@/ui/board/components/EntityBoardContextMenu';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
import { useBoardViews } from '@/views/hooks/useBoardViews';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
@@ -16,25 +17,38 @@ type CompanyBoardProps = Pick<
'onColumnAdd' | 'onColumnDelete' | 'onEditColumnTitle'
>;
-export const CompanyBoard = ({ ...props }: CompanyBoardProps) => {
- const { handleViewsChange, handleViewSubmit } = useBoardViews({
- objectId: 'company',
- scopeContext: CompanyBoardRecoilScopeContext,
- fieldDefinitions: pipelineAvailableFieldDefinitions,
- });
+export const CompanyBoard = ({
+ onColumnAdd,
+ onColumnDelete,
+ onEditColumnTitle,
+}: CompanyBoardProps) => {
+ const { createView, deleteView, submitCurrentView, updateView } =
+ useBoardViews({
+ objectId: 'company',
+ scopeContext: CompanyBoardRecoilScopeContext,
+ fieldDefinitions: pipelineAvailableFieldDefinitions,
+ });
return (
<>
-
+
+
+
>
diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx
index 974891a72..1e34f2ae0 100644
--- a/front/src/modules/companies/table/components/CompanyTable.tsx
+++ b/front/src/modules/companies/table/components/CompanyTable.tsx
@@ -8,6 +8,7 @@ import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect';
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
import { filtersWhereScopedSelector } from '@/ui/view-bar/states/selectors/filtersWhereScopedSelector';
import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector';
import { useTableViews } from '@/views/hooks/useTableViews';
@@ -32,10 +33,11 @@ export function CompanyTable() {
const [updateEntityMutation] = useUpdateOneCompanyMutation();
const upsertEntityTableItem = useUpsertEntityTableItem();
- const { handleViewsChange, handleViewSubmit } = useTableViews({
- objectId: 'company',
- columnDefinitions: companiesAvailableColumnDefinitions,
- });
+ const { createView, deleteView, submitCurrentView, updateView } =
+ useTableViews({
+ objectId: 'company',
+ columnDefinitions: companiesAvailableColumnDefinitions,
+ });
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
@@ -61,27 +63,34 @@ export function CompanyTable() {
setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries}
/>
-
- updateEntityMutation({
+
+ {
- if (!data.updateOneCompany) {
- return;
- }
- upsertEntityTableItem(data.updateOneCompany);
- },
- })
- }
- />
+ }: {
+ variables: UpdateOneCompanyMutationVariables;
+ }) =>
+ updateEntityMutation({
+ variables,
+ onCompleted: (data) => {
+ if (!data.updateOneCompany) {
+ return;
+ }
+ upsertEntityTableItem(data.updateOneCompany);
+ },
+ })
+ }
+ />
+
>
);
}
diff --git a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx
index 0d699bb43..e52f77546 100644
--- a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx
+++ b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx
@@ -1,4 +1,5 @@
import { EntityTable } from '@/ui/table/components/EntityTable';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
import { useUpdateOneCompanyMutation } from '~/generated/graphql';
import { CompanyTableMockData } from './CompanyTableMockData';
@@ -7,10 +8,9 @@ export function CompanyTableMockMode() {
return (
<>
-
+
+
+
>
);
}
diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx
index 8ef195a5e..980b65913 100644
--- a/front/src/modules/people/table/components/PeopleTable.tsx
+++ b/front/src/modules/people/table/components/PeopleTable.tsx
@@ -8,6 +8,7 @@ import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect';
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
import { filtersWhereScopedSelector } from '@/ui/view-bar/states/selectors/filtersWhereScopedSelector';
import { sortsOrderByScopedSelector } from '@/ui/view-bar/states/selectors/sortsOrderByScopedSelector';
import { useTableViews } from '@/views/hooks/useTableViews';
@@ -33,10 +34,11 @@ export function PeopleTable() {
const upsertEntityTableItem = useUpsertEntityTableItem();
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
- const { handleViewsChange, handleViewSubmit } = useTableViews({
- objectId: 'person',
- columnDefinitions: peopleAvailableColumnDefinitions,
- });
+ const { createView, deleteView, submitCurrentView, updateView } =
+ useTableViews({
+ objectId: 'person',
+ columnDefinitions: peopleAvailableColumnDefinitions,
+ });
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
const { setActionBarEntries } = usePersonTableActionBarEntries();
@@ -60,27 +62,34 @@ export function PeopleTable() {
setActionBarEntries={setActionBarEntries}
sortDefinitionArray={peopleAvailableSorts}
/>
-
- updateEntityMutation({
+
+ {
- if (!data.updateOnePerson) {
- return;
- }
- upsertEntityTableItem(data.updateOnePerson);
- },
- })
- }
- />
+ }: {
+ variables: UpdateOnePersonMutationVariables;
+ }) =>
+ updateEntityMutation({
+ variables,
+ onCompleted: (data) => {
+ if (!data.updateOnePerson) {
+ return;
+ }
+ upsertEntityTableItem(data.updateOnePerson);
+ },
+ })
+ }
+ />
+
>
);
}
diff --git a/front/src/modules/ui/board/components/BoardHeader.tsx b/front/src/modules/ui/board/components/BoardHeader.tsx
index 7b2169975..3141a4edc 100644
--- a/front/src/modules/ui/board/components/BoardHeader.tsx
+++ b/front/src/modules/ui/board/components/BoardHeader.tsx
@@ -1,46 +1,101 @@
-import type { ComponentProps } from 'react';
+import { useContext } from 'react';
+import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
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 { ViewBar, type ViewBarProps } from '@/ui/view-bar/components/ViewBar';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
+import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
+import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
+import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
+import { canPersistBoardCardFieldsScopedFamilySelector } from '../states/selectors/canPersistBoardCardFieldsScopedFamilySelector';
import type { BoardColumnDefinition } from '../types/BoardColumnDefinition';
import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
import { BoardOptionsDropdown } from './BoardOptionsDropdown';
-export type BoardHeaderProps = ComponentProps<'div'> & {
+export type BoardHeaderProps = {
+ className?: string;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
-} & Pick<
- ViewBarProps,
- 'defaultViewName' | 'onViewsChange' | 'onViewSubmit' | 'scopeContext'
- >;
+} & Pick;
export function BoardHeader({
+ className,
onStageAdd,
- onViewsChange,
- onViewSubmit,
scopeContext,
- defaultViewName,
}: BoardHeaderProps) {
+ const { onCurrentViewSubmit, ...viewBarContextProps } =
+ useContext(ViewBarContext);
+ const tableScopeId = useContextScopeId(scopeContext);
+
+ const currentViewId = useRecoilScopedValue(
+ currentViewIdScopedState,
+ scopeContext,
+ );
+ const canPersistBoardCardFields = useRecoilValue(
+ canPersistBoardCardFieldsScopedFamilySelector([
+ tableScopeId,
+ currentViewId,
+ ]),
+ );
+ const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
+ boardCardFieldsScopedState,
+ scopeContext,
+ );
+ const [savedBoardCardFields, setSavedBoardCardFields] = useRecoilState(
+ savedBoardCardFieldsFamilyState(currentViewId),
+ );
+
+ const handleViewBarReset = () => setBoardCardFields(savedBoardCardFields);
+
+ const handleViewSelect = useRecoilCallback(
+ ({ set, snapshot }) =>
+ async (viewId: string) => {
+ const savedBoardCardFields = await snapshot.getPromise(
+ savedBoardCardFieldsFamilyState(viewId),
+ );
+ set(boardCardFieldsScopedState(tableScopeId), savedBoardCardFields);
+ },
+ [tableScopeId],
+ );
+
+ const handleCurrentViewSubmit = async () => {
+ if (canPersistBoardCardFields) {
+ setSavedBoardCardFields(boardCardFields);
+ }
+
+ await onCurrentViewSubmit?.();
+ };
+
return (
-
- }
- optionsDropdownKey={BoardOptionsDropdownKey}
- scopeContext={scopeContext}
- />
+
+
+ }
+ optionsDropdownKey={BoardOptionsDropdownKey}
+ scopeContext={scopeContext}
+ />
+
);
}
diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx
index 6a44b2f2b..016b2523a 100644
--- a/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx
+++ b/front/src/modules/ui/board/components/BoardOptionsDropdown.tsx
@@ -10,20 +10,22 @@ import {
type BoardOptionsDropdownProps = Pick<
BoardOptionsDropdownContentProps,
- 'customHotkeyScope' | 'onStageAdd' | 'onViewsChange' | 'scopeContext'
+ 'customHotkeyScope' | 'onStageAdd' | 'scopeContext'
>;
export function BoardOptionsDropdown({
customHotkeyScope,
- ...props
+ onStageAdd,
+ scopeContext,
}: BoardOptionsDropdownProps) {
return (
}
dropdownComponents={
}
dropdownHotkeyScope={customHotkeyScope}
diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx
index 662db6129..08c403437 100644
--- a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx
+++ b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx
@@ -1,7 +1,7 @@
import { type Context, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
-import { useRecoilState, useRecoilValue } from 'recoil';
+import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
@@ -23,15 +23,17 @@ import { MenuItemNavigate } from '@/ui/menu-item/components/MenuItemNavigate';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
+import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewFieldsVisibilityDropdownSection } from '@/ui/view-bar/components/ViewFieldsVisibilityDropdownSection';
import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView';
import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
-import type { View } from '@/ui/view-bar/types/View';
import { useBoardCardFields } from '../hooks/useBoardCardFields';
+import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardColumnsState } from '../states/boardColumnsState';
+import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector';
import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector';
import type { BoardColumnDefinition } from '../types/BoardColumnDefinition';
@@ -40,7 +42,6 @@ import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
export type BoardOptionsDropdownContentProps = {
customHotkeyScope: HotkeyScope;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
- onViewsChange?: (views: View[]) => void | Promise;
scopeContext: Context;
};
@@ -60,10 +61,10 @@ type ColumnForCreate = {
export function BoardOptionsDropdownContent({
customHotkeyScope,
onStageAdd,
- onViewsChange,
scopeContext,
}: BoardOptionsDropdownContentProps) {
const theme = useTheme();
+ const scopeId = useContextScopeId(scopeContext);
const stageInputRef = useRef(null);
const viewEditInputRef = useRef(null);
@@ -106,15 +107,24 @@ export function BoardOptionsDropdownContent({
onStageAdd?.(columnToCreate);
};
- const { upsertView } = useUpsertView({
- onViewsChange,
- scopeContext,
- });
+ const { upsertView } = useUpsertView({ scopeContext });
- const handleViewNameSubmit = async () => {
- const name = viewEditInputRef.current?.value;
- await upsertView(name);
- };
+ const handleViewNameSubmit = useRecoilCallback(
+ ({ set, snapshot }) =>
+ async () => {
+ const boardCardFields = await snapshot.getPromise(
+ boardCardFieldsScopedState(scopeId),
+ );
+ const isCreateMode = viewEditMode.mode === 'create';
+ const name = viewEditInputRef.current?.value;
+ const view = await upsertView(name);
+
+ if (view && isCreateMode) {
+ set(savedBoardCardFieldsFamilyState(view.id), boardCardFields);
+ }
+ },
+ [scopeId, upsertView, viewEditMode.mode],
+ );
const resetMenu = () => setCurrentMenu(undefined);
diff --git a/front/src/modules/ui/board/components/EntityBoard.tsx b/front/src/modules/ui/board/components/EntityBoard.tsx
index 8f883996c..fb7d08d49 100644
--- a/front/src/modules/ui/board/components/EntityBoard.tsx
+++ b/front/src/modules/ui/board/components/EntityBoard.tsx
@@ -6,10 +6,7 @@ import { useRecoilState } from 'recoil';
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
-import {
- BoardHeader,
- BoardHeaderProps,
-} from '@/ui/board/components/BoardHeader';
+import { BoardHeader } from '@/ui/board/components/BoardHeader';
import { StyledBoard } from '@/ui/board/components/StyledBoard';
import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
@@ -39,10 +36,7 @@ export type EntityBoardProps = {
onColumnDelete?: (boardColumnId: string) => void;
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
scopeContext: Context;
-} & Pick<
- BoardHeaderProps,
- 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'
->;
+};
const StyledWrapper = styled.div`
display: flex;
@@ -57,12 +51,9 @@ const StyledBoardHeader = styled(BoardHeader)`
export function EntityBoard({
boardOptions,
- defaultViewName,
onColumnAdd,
onColumnDelete,
onEditColumnTitle,
- onViewsChange,
- onViewSubmit,
scopeContext,
}: EntityBoardProps) {
const [boardColumns] = useRecoilState(boardColumnsState);
@@ -139,13 +130,7 @@ export function EntityBoard({
return (boardColumns?.length ?? 0) > 0 ? (
-
+
diff --git a/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts b/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts
new file mode 100644
index 000000000..17506cb3b
--- /dev/null
+++ b/front/src/modules/ui/board/states/savedBoardCardFieldsFamilyState.ts
@@ -0,0 +1,14 @@
+import { atomFamily } from 'recoil';
+
+import type {
+ ViewFieldDefinition,
+ ViewFieldMetadata,
+} from '@/ui/editable-field/types/ViewField';
+
+export const savedBoardCardFieldsFamilyState = atomFamily<
+ ViewFieldDefinition[],
+ string | undefined
+>({
+ key: 'savedBoardCardFieldsFamilyState',
+ default: [],
+});
diff --git a/front/src/modules/ui/board/states/selectors/canPersistBoardCardFieldsScopedFamilySelector.ts b/front/src/modules/ui/board/states/selectors/canPersistBoardCardFieldsScopedFamilySelector.ts
new file mode 100644
index 000000000..620e0802c
--- /dev/null
+++ b/front/src/modules/ui/board/states/selectors/canPersistBoardCardFieldsScopedFamilySelector.ts
@@ -0,0 +1,17 @@
+import { selectorFamily } from 'recoil';
+
+import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
+
+import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
+import { savedBoardCardFieldsFamilyState } from '../savedBoardCardFieldsFamilyState';
+
+export const canPersistBoardCardFieldsScopedFamilySelector = selectorFamily({
+ key: 'canPersistBoardCardFieldsScopedFamilySelector',
+ get:
+ ([scopeId, viewId]: [string, string | undefined]) =>
+ ({ get }) =>
+ !isDeeplyEqual(
+ get(savedBoardCardFieldsFamilyState(viewId)),
+ get(boardCardFieldsScopedState(scopeId)),
+ ),
+});
diff --git a/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts b/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts
new file mode 100644
index 000000000..34db430d1
--- /dev/null
+++ b/front/src/modules/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector.ts
@@ -0,0 +1,18 @@
+import { selectorFamily } from 'recoil';
+
+import type {
+ ViewFieldDefinition,
+ ViewFieldMetadata,
+} from '@/ui/editable-field/types/ViewField';
+
+import { savedBoardCardFieldsFamilyState } from '../savedBoardCardFieldsFamilyState';
+
+export const savedBoardCardFieldsByKeyFamilySelector = selectorFamily({
+ key: 'savedBoardCardFieldsByKeyFamilySelector',
+ get:
+ (viewId: string | undefined) =>
+ ({ get }) =>
+ get(savedBoardCardFieldsFamilyState(viewId)).reduce<
+ Record>
+ >((result, field) => ({ ...result, [field.key]: field }), {}),
+});
diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx
index 8da25cbc6..a36b56fc4 100644
--- a/front/src/modules/ui/table/components/EntityTable.tsx
+++ b/front/src/modules/ui/table/components/EntityTable.tsx
@@ -87,18 +87,9 @@ const StyledTableContainer = styled.div`
type OwnProps = {
updateEntityMutation: any;
-} & Pick<
- TableHeaderProps,
- 'defaultViewName' | 'onImport' | 'onViewsChange' | 'onViewSubmit'
->;
+} & Pick;
-export function EntityTable({
- defaultViewName,
- onImport,
- onViewsChange,
- onViewSubmit,
- updateEntityMutation,
-}: OwnProps) {
+export function EntityTable({ onImport, updateEntityMutation }: OwnProps) {
const tableBodyRef = useRef(null);
const setRowSelectedState = useSetRowSelectedState();
@@ -135,12 +126,7 @@ export function EntityTable({
-
+
diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx
index 4a74a845f..2f1bead26 100644
--- a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx
+++ b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx
@@ -1,6 +1,5 @@
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 { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
@@ -8,13 +7,11 @@ import { TableOptionsDropdownButton } from './TableOptionsDropdownButton';
import { TableOptionsDropdownContent } from './TableOptionsDropdownContent';
type TableOptionsDropdownProps = {
- onViewsChange?: (views: View[]) => void;
onImport?: () => void;
customHotkeyScope: HotkeyScope;
};
export function TableOptionsDropdown({
- onViewsChange,
onImport,
customHotkeyScope,
}: TableOptionsDropdownProps) {
@@ -23,12 +20,7 @@ export function TableOptionsDropdown({
buttonComponents={}
dropdownHotkeyScope={customHotkeyScope}
dropdownId={TableOptionsDropdownId}
- dropdownComponents={
-
- }
+ dropdownComponents={}
/>
);
}
diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx
index 39fcc462b..031676a00 100644
--- a/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx
+++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx
@@ -1,5 +1,5 @@
import { useRef, useState } from 'react';
-import { useRecoilValue } from 'recoil';
+import { useRecoilCallback, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
@@ -11,31 +11,33 @@ import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
+import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewFieldsVisibilityDropdownSection } from '@/ui/view-bar/components/ViewFieldsVisibilityDropdownSection';
import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView';
import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
-import type { View } from '@/ui/view-bar/types/View';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
+import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector';
+import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
type TableOptionsDropdownButtonProps = {
- onViewsChange?: (views: View[]) => void | Promise;
onImport?: () => void;
};
type TableOptionsMenu = 'fields';
export function TableOptionsDropdownContent({
- onViewsChange,
onImport,
}: TableOptionsDropdownButtonProps) {
+ const scopeId = useContextScopeId(TableRecoilScopeContext);
+
const { closeDropdownButton } = useDropdownButton({
dropdownId: TableOptionsDropdownId,
});
@@ -60,17 +62,28 @@ export function TableOptionsDropdownContent({
TableRecoilScopeContext,
);
+ const { handleColumnVisibilityChange } = useTableColumns();
+
const { upsertView } = useUpsertView({
- onViewsChange,
scopeContext: TableRecoilScopeContext,
});
- const { handleColumnVisibilityChange } = useTableColumns();
+ const handleViewNameSubmit = useRecoilCallback(
+ ({ set, snapshot }) =>
+ async () => {
+ const tableColumns = await snapshot.getPromise(
+ tableColumnsScopedState(scopeId),
+ );
+ const isCreateMode = viewEditMode.mode === 'create';
+ const name = viewEditInputRef.current?.value;
+ const view = await upsertView(name);
- const handleViewNameSubmit = async () => {
- const name = viewEditInputRef.current?.value;
- await upsertView(name);
- };
+ if (view && isCreateMode) {
+ set(savedTableColumnsFamilyState(view.id), tableColumns);
+ }
+ },
+ [scopeId, upsertView, viewEditMode.mode],
+ );
const handleSelectMenu = (option: TableOptionsMenu) => {
handleViewNameSubmit();
diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx
index ef374ace4..1611bdfe7 100644
--- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx
+++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx
@@ -1,3 +1,4 @@
+import { useContext } from 'react';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
@@ -5,7 +6,8 @@ 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 { ViewBar, ViewBarProps } from '@/ui/view-bar/components/ViewBar';
+import { ViewBar } from '@/ui/view-bar/components/ViewBar';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
@@ -18,14 +20,11 @@ import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
export type TableHeaderProps = {
onImport?: () => void;
-} & Pick;
+};
-export function TableHeader({
- onImport,
- onViewsChange,
- onViewSubmit,
- ...props
-}: TableHeaderProps) {
+export function TableHeader({ onImport }: TableHeaderProps) {
+ const { onCurrentViewSubmit, ...viewBarContextProps } =
+ useContext(ViewBarContext);
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const currentViewId = useRecoilScopedValue(
@@ -43,9 +42,7 @@ export function TableHeader({
savedTableColumnsFamilyState(currentViewId),
);
- function handleViewBarReset() {
- setTableColumns(savedTableColumns);
- }
+ const handleViewBarReset = () => setTableColumns(savedTableColumns);
const handleViewSelect = useRecoilCallback(
({ set, snapshot }) =>
@@ -58,32 +55,36 @@ export function TableHeader({
[tableScopeId],
);
- async function handleViewSubmit() {
+ async function handleCurrentViewSubmit() {
if (canPersistTableColumns) {
setSavedTableColumns(tableColumns);
}
- await onViewSubmit?.();
+ await onCurrentViewSubmit?.();
}
return (
-
- }
- optionsDropdownKey={TableOptionsDropdownId}
- scopeContext={TableRecoilScopeContext}
- />
+
+
+ }
+ optionsDropdownKey={TableOptionsDropdownId}
+ scopeContext={TableRecoilScopeContext}
+ />
+
);
}
diff --git a/front/src/modules/ui/top-bar/TopBar.tsx b/front/src/modules/ui/top-bar/TopBar.tsx
index 71dfe2ba3..2d96eef7b 100644
--- a/front/src/modules/ui/top-bar/TopBar.tsx
+++ b/front/src/modules/ui/top-bar/TopBar.tsx
@@ -1,7 +1,8 @@
-import type { ComponentProps, ReactNode } from 'react';
+import type { ReactNode } from 'react';
import styled from '@emotion/styled';
-type OwnProps = ComponentProps<'div'> & {
+type OwnProps = {
+ className?: string;
leftComponent?: ReactNode;
rightComponent?: ReactNode;
bottomComponent?: ReactNode;
@@ -40,14 +41,14 @@ const StyledRightSection = styled.div`
`;
export function TopBar({
+ className,
leftComponent,
rightComponent,
bottomComponent,
displayBottomBorder = true,
- ...props
}: OwnProps) {
return (
-
+
{leftComponent}
{rightComponent}
diff --git a/front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx b/front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx
index 878a461fe..ec3d242b3 100644
--- a/front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx
+++ b/front/src/modules/ui/view-bar/components/UpdateViewButtonGroup.tsx
@@ -1,4 +1,4 @@
-import { type Context, useCallback, useState } from 'react';
+import { type Context, useCallback, useContext, useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
@@ -21,6 +21,8 @@ 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 { ViewBarContext } from '../contexts/ViewBarContext';
+
const StyledContainer = styled.div`
display: inline-flex;
margin-right: ${({ theme }) => theme.spacing(2)};
@@ -28,22 +30,20 @@ const StyledContainer = styled.div`
`;
export type UpdateViewButtonGroupProps = {
- canPersistViewFields?: boolean;
hotkeyScope: string;
onViewEditModeChange?: () => void;
- onViewSubmit?: () => void | Promise;
scopeContext: Context;
};
export const UpdateViewButtonGroup = ({
- canPersistViewFields,
hotkeyScope,
onViewEditModeChange,
- onViewSubmit,
scopeContext,
}: UpdateViewButtonGroupProps) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const { canPersistViewFields, onCurrentViewSubmit } =
+ useContext(ViewBarContext);
const recoilScopeId = useContextScopeId(scopeContext);
const currentViewId = useRecoilScopedValue(
@@ -89,7 +89,7 @@ export const UpdateViewButtonGroup = ({
if (canPersistFilters) setSavedFilters(filters);
if (canPersistSorts) setSavedSorts(sorts);
- await onViewSubmit?.();
+ await onCurrentViewSubmit?.();
};
useScopedHotkeys(
diff --git a/front/src/modules/ui/view-bar/components/ViewBar.tsx b/front/src/modules/ui/view-bar/components/ViewBar.tsx
index 0d1e7becf..3dc27cd33 100644
--- a/front/src/modules/ui/view-bar/components/ViewBar.tsx
+++ b/front/src/modules/ui/view-bar/components/ViewBar.tsx
@@ -1,4 +1,4 @@
-import type { ComponentProps, Context, ReactNode } from 'react';
+import type { Context, ReactNode } from 'react';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { TopBar } from '@/ui/top-bar/TopBar';
@@ -8,38 +8,22 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
import { FilterDropdownButton } from './FilterDropdownButton';
import { SortDropdownButton } from './SortDropdownButton';
-import {
- UpdateViewButtonGroup,
- type UpdateViewButtonGroupProps,
-} from './UpdateViewButtonGroup';
-import ViewBarDetails, { type ViewBarDetailsProps } from './ViewBarDetails';
-import {
- ViewsDropdownButton,
- type ViewsDropdownButtonProps,
-} from './ViewsDropdownButton';
+import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
+import ViewBarDetails from './ViewBarDetails';
+import { ViewsDropdownButton } from './ViewsDropdownButton';
-export type ViewBarProps = ComponentProps<'div'> & {
+export type ViewBarProps = {
+ className?: string;
optionsDropdownButton: ReactNode;
optionsDropdownKey: string;
scopeContext: Context;
-} & Pick<
- ViewsDropdownButtonProps,
- 'defaultViewName' | 'onViewsChange' | 'onViewSelect'
- > &
- Pick &
- Pick;
+};
export const ViewBar = ({
- canPersistViewFields,
- defaultViewName,
- onReset,
- onViewsChange,
- onViewSelect,
- onViewSubmit,
+ className,
optionsDropdownButton,
optionsDropdownKey,
scopeContext,
- ...props
}: ViewBarProps) => {
const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({
dropdownId: optionsDropdownKey,
@@ -47,13 +31,10 @@ export const ViewBar = ({
return (
@@ -75,15 +56,11 @@ export const ViewBar = ({
}
bottomComponent={
diff --git a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx
index 59d7cf8af..f721a1a53 100644
--- a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx
+++ b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx
@@ -1,4 +1,4 @@
-import type { Context, ReactNode } from 'react';
+import { type Context, type ReactNode, useContext } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
@@ -7,6 +7,7 @@ import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextS
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { ViewBarContext } from '../contexts/ViewBarContext';
import { useRemoveFilter } from '../hooks/useRemoveFilter';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
@@ -23,10 +24,8 @@ import { AddFilterFromDropdownButton } from './AddFilterFromDetailsButton';
import SortOrFilterChip from './SortOrFilterChip';
export type ViewBarDetailsProps = {
- canPersistViewFields?: boolean;
context: Context;
hasFilterButton?: boolean;
- onReset?: () => void;
rightComponent?: ReactNode;
};
@@ -99,12 +98,11 @@ const StyledAddFilterContainer = styled.div`
`;
function ViewBarDetails({
- canPersistViewFields,
context,
hasFilterButton = false,
- onReset,
rightComponent,
}: ViewBarDetailsProps) {
+ const { canPersistViewFields, onViewBarReset } = useContext(ViewBarContext);
const recoilScopeId = useContextScopeId(context);
const currentViewId = useRecoilScopedValue(currentViewIdScopedState, context);
@@ -155,7 +153,7 @@ function ViewBarDetails({
const removeFilter = useRemoveFilter(context);
function handleCancelClick() {
- onReset?.();
+ onViewBarReset?.();
setFilters(savedFilters);
setSorts(savedSorts);
}
diff --git a/front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx b/front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx
index f6fa901fa..280363b10 100644
--- a/front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx
+++ b/front/src/modules/ui/view-bar/components/ViewsDropdownButton.tsx
@@ -2,6 +2,7 @@ import {
type Context,
type MouseEvent,
useCallback,
+ useContext,
useEffect,
useState,
} from 'react';
@@ -22,7 +23,6 @@ import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
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 DropdownButton from '@/ui/view-bar/components/DropdownButton';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
@@ -33,10 +33,12 @@ import { currentViewScopedSelector } from '@/ui/view-bar/states/selectors/curren
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState';
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
import { viewsScopedState } from '@/ui/view-bar/states/viewsScopedState';
-import type { View } from '@/ui/view-bar/types/View';
import { ViewsHotkeyScope } from '@/ui/view-bar/types/ViewsHotkeyScope';
import { assertNotNull } from '~/utils/assert';
+import { ViewBarContext } from '../contexts/ViewBarContext';
+import { useRemoveView } from '../hooks/useRemoveView';
+
const StyledBoldDropdownMenuItemsContainer = styled(
StyledDropdownMenuItemsContainer,
)`
@@ -71,39 +73,28 @@ const StyledViewName = styled.span`
`;
export type ViewsDropdownButtonProps = {
- defaultViewName: string;
hotkeyScope: ViewsHotkeyScope;
onViewEditModeChange?: () => void;
- onViewsChange?: (views: View[]) => void | Promise;
- onViewSelect?: (viewId: string) => void | Promise;
scopeContext: Context;
};
export const ViewsDropdownButton = ({
- defaultViewName,
hotkeyScope,
onViewEditModeChange,
- onViewsChange,
- onViewSelect,
scopeContext,
}: ViewsDropdownButtonProps) => {
const theme = useTheme();
- const [isUnfolded, setIsUnfolded] = useState(false);
+ const { defaultViewName, onViewSelect } = useContext(ViewBarContext);
const recoilScopeId = useContextScopeId(scopeContext);
- const [, setCurrentViewId] = useRecoilScopedState(
- currentViewIdScopedState,
- scopeContext,
- );
+ const [isUnfolded, setIsUnfolded] = useState(false);
+
const currentView = useRecoilScopedValue(
currentViewScopedSelector,
scopeContext,
);
- const [views, setViews] = useRecoilScopedState(
- viewsScopedState,
- scopeContext,
- );
+ const views = useRecoilScopedValue(viewsScopedState, scopeContext);
const setViewEditMode = useSetRecoilState(viewEditModeState);
const {
@@ -146,20 +137,17 @@ export const ViewsDropdownButton = ({
[setViewEditMode, onViewEditModeChange],
);
- const handleDeleteViewButtonClick = useCallback(
- async (event: MouseEvent, viewId: string) => {
- event.stopPropagation();
+ const { removeView } = useRemoveView({ scopeContext });
- if (currentView?.id === viewId) setCurrentViewId(undefined);
+ const handleDeleteViewButtonClick = async (
+ event: MouseEvent,
+ viewId: string,
+ ) => {
+ event.stopPropagation();
- const nextViews = views.filter((view) => view.id !== viewId);
-
- setViews(nextViews);
- await onViewsChange?.(nextViews);
- setIsUnfolded(false);
- },
- [currentView?.id, onViewsChange, setCurrentViewId, setViews, views],
- );
+ await removeView(viewId);
+ setIsUnfolded(false);
+ };
useEffect(() => {
isUnfolded
diff --git a/front/src/modules/ui/view-bar/contexts/ViewBarContext.ts b/front/src/modules/ui/view-bar/contexts/ViewBarContext.ts
new file mode 100644
index 000000000..66ae8630d
--- /dev/null
+++ b/front/src/modules/ui/view-bar/contexts/ViewBarContext.ts
@@ -0,0 +1,14 @@
+import { createContext } from 'react';
+
+import type { View } from '../types/View';
+
+export const ViewBarContext = createContext<{
+ canPersistViewFields?: boolean;
+ defaultViewName?: string;
+ onCurrentViewSubmit?: () => void | Promise;
+ onViewBarReset?: () => void;
+ onViewCreate?: (view: View) => void | Promise;
+ onViewEdit?: (view: View) => void | Promise;
+ onViewRemove?: (viewId: string) => void | Promise;
+ onViewSelect?: (viewId: string) => void | Promise;
+}>({});
diff --git a/front/src/modules/ui/view-bar/hooks/useRemoveView.ts b/front/src/modules/ui/view-bar/hooks/useRemoveView.ts
new file mode 100644
index 000000000..8f9f66da9
--- /dev/null
+++ b/front/src/modules/ui/view-bar/hooks/useRemoveView.ts
@@ -0,0 +1,37 @@
+import { type Context, useContext } from 'react';
+import { useRecoilCallback } from 'recoil';
+
+import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
+
+import { ViewBarContext } from '../contexts/ViewBarContext';
+import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
+import { viewsScopedState } from '../states/viewsScopedState';
+
+export const useRemoveView = ({
+ scopeContext,
+}: {
+ scopeContext: Context;
+}) => {
+ const { onViewRemove } = useContext(ViewBarContext);
+ const recoilScopeId = useContextScopeId(scopeContext);
+
+ const removeView = useRecoilCallback(
+ ({ set, snapshot }) =>
+ async (viewId: string) => {
+ const currentViewId = await snapshot.getPromise(
+ currentViewIdScopedState(recoilScopeId),
+ );
+
+ if (currentViewId === viewId)
+ set(currentViewIdScopedState(recoilScopeId), undefined);
+
+ set(viewsScopedState(recoilScopeId), (previousViews) =>
+ previousViews.filter((view) => view.id !== viewId),
+ );
+ await onViewRemove?.(viewId);
+ },
+ [onViewRemove, recoilScopeId],
+ );
+
+ return { removeView };
+};
diff --git a/front/src/modules/ui/view-bar/hooks/useUpsertView.ts b/front/src/modules/ui/view-bar/hooks/useUpsertView.ts
index a52b6ba39..2dd0b8fd4 100644
--- a/front/src/modules/ui/view-bar/hooks/useUpsertView.ts
+++ b/front/src/modules/ui/view-bar/hooks/useUpsertView.ts
@@ -1,37 +1,30 @@
-import { Context, useCallback } from 'react';
+import { type Context, useCallback, useContext } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { v4 } from 'uuid';
-import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
+import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { ViewBarContext } from '../contexts/ViewBarContext';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
import { filtersScopedState } from '../states/filtersScopedState';
import { savedFiltersFamilyState } from '../states/savedFiltersFamilyState';
import { savedSortsFamilyState } from '../states/savedSortsFamilyState';
+import { viewsByIdScopedSelector } from '../states/selectors/viewsByIdScopedSelector';
import { sortsScopedState } from '../states/sortsScopedState';
import { viewEditModeState } from '../states/viewEditModeState';
import { viewsScopedState } from '../states/viewsScopedState';
-import type { View } from '../types/View';
export const useUpsertView = ({
- onViewsChange,
scopeContext,
}: {
- onViewsChange?: (views: View[]) => void | Promise;
scopeContext: Context;
}) => {
+ const { onViewCreate, onViewEdit } = useContext(ViewBarContext);
+ const recoilScopeId = useContextScopeId(scopeContext);
+
const filters = useRecoilScopedValue(filtersScopedState, scopeContext);
const sorts = useRecoilScopedValue(sortsScopedState, scopeContext);
-
- const [, setCurrentViewId] = useRecoilScopedState(
- currentViewIdScopedState,
- scopeContext,
- );
- const [views, setViews] = useRecoilScopedState(
- viewsScopedState,
- scopeContext,
- );
const [viewEditMode, setViewEditMode] = useRecoilState(viewEditModeState);
const resetViewEditMode = useCallback(
@@ -40,44 +33,63 @@ export const useUpsertView = ({
);
const upsertView = useRecoilCallback(
- ({ set }) =>
+ ({ set, snapshot }) =>
async (name?: string) => {
- if (!viewEditMode.mode || !name) return resetViewEditMode();
+ if (!viewEditMode.mode || !name) {
+ resetViewEditMode();
+ return;
+ }
if (viewEditMode.mode === 'create') {
- const viewToCreate = { id: v4(), name };
- const nextViews = [...views, viewToCreate];
+ const createdView = { id: v4(), name };
- set(savedFiltersFamilyState(viewToCreate.id), filters);
- set(savedSortsFamilyState(viewToCreate.id), sorts);
+ set(savedFiltersFamilyState(createdView.id), filters);
+ set(savedSortsFamilyState(createdView.id), sorts);
- setViews(nextViews);
- await onViewsChange?.(nextViews);
+ set(viewsScopedState(recoilScopeId), (previousViews) => [
+ ...previousViews,
+ createdView,
+ ]);
- setCurrentViewId(viewToCreate.id);
+ await onViewCreate?.(createdView);
+
+ resetViewEditMode();
+
+ set(currentViewIdScopedState(recoilScopeId), createdView.id);
+
+ return createdView;
}
- if (viewEditMode.mode === 'edit') {
- const nextViews = views.map((view) =>
- view.id === viewEditMode.viewId ? { ...view, name } : view,
+ if (viewEditMode.mode === 'edit' && viewEditMode.viewId) {
+ const viewsById = await snapshot.getPromise(
+ viewsByIdScopedSelector(recoilScopeId),
+ );
+ const editedView = { ...viewsById[viewEditMode.viewId], name };
+
+ set(viewsScopedState(recoilScopeId), (previousViews) =>
+ previousViews.map((previousView) =>
+ previousView.id === viewEditMode.viewId
+ ? editedView
+ : previousView,
+ ),
);
- setViews(nextViews);
- await onViewsChange?.(nextViews);
- }
+ await onViewEdit?.(editedView);
- return resetViewEditMode();
+ resetViewEditMode();
+
+ return editedView;
+ }
},
[
filters,
- onViewsChange,
+ onViewCreate,
+ onViewEdit,
+ recoilScopeId,
resetViewEditMode,
- setCurrentViewId,
- setViews,
sorts,
viewEditMode.mode,
viewEditMode.viewId,
- views,
],
);
diff --git a/front/src/modules/views/hooks/useBoardViewFields.ts b/front/src/modules/views/hooks/useBoardViewFields.ts
index 1742279f5..019c40ae2 100644
--- a/front/src/modules/views/hooks/useBoardViewFields.ts
+++ b/front/src/modules/views/hooks/useBoardViewFields.ts
@@ -1,7 +1,10 @@
import { type Context } from 'react';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
import { availableBoardCardFieldsScopedState } from '@/ui/board/states/availableBoardCardFieldsScopedState';
import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState';
+import { savedBoardCardFieldsFamilyState } from '@/ui/board/states/savedBoardCardFieldsFamilyState';
+import { savedBoardCardFieldsByKeyFamilySelector } from '@/ui/board/states/selectors/savedBoardCardFieldsByKeyFamilySelector';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
@@ -13,6 +16,7 @@ import {
SortOrder,
useCreateViewFieldsMutation,
useGetViewFieldsQuery,
+ useUpdateViewFieldMutation,
} from '~/generated/graphql';
import { assertNotNull } from '~/utils/assert';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -49,8 +53,15 @@ export const useBoardViewFields = ({
boardCardFieldsScopedState,
scopeContext,
);
+ const setSavedBoardCardFields = useSetRecoilState(
+ savedBoardCardFieldsFamilyState(currentViewId),
+ );
+ const savedBoardCardFieldsByKey = useRecoilValue(
+ savedBoardCardFieldsByKeyFamilySelector(currentViewId),
+ );
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
+ const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const createViewFields = (
fields: ViewFieldDefinition[],
@@ -68,6 +79,27 @@ export const useBoardViewFields = ({
});
};
+ const updateViewFields = (
+ fields: ViewFieldDefinition[],
+ ) => {
+ if (!currentViewId || !fields.length) return;
+
+ return Promise.all(
+ fields.map((field) =>
+ updateViewFieldMutation({
+ variables: {
+ data: {
+ isVisible: field.isVisible,
+ },
+ where: {
+ viewId_key: { key: field.key, viewId: currentViewId },
+ },
+ },
+ }),
+ ),
+ );
+ };
+
const { refetch } = useGetViewFieldsQuery({
skip: !currentViewId || skipFetch,
variables: {
@@ -102,6 +134,7 @@ export const useBoardViewFields = ({
.filter>(assertNotNull);
if (!isDeeplyEqual(boardCardFields, nextFields)) {
+ setSavedBoardCardFields(nextFields);
setBoardCardFields(nextFields);
}
@@ -110,4 +143,24 @@ export const useBoardViewFields = ({
}
},
});
+
+ const persistCardFields = async () => {
+ if (!currentViewId) return;
+
+ const viewFieldsToCreate = boardCardFields.filter(
+ (field) => !savedBoardCardFieldsByKey[field.key],
+ );
+ await createViewFields(viewFieldsToCreate);
+
+ const viewFieldsToUpdate = boardCardFields.filter(
+ (field) =>
+ savedBoardCardFieldsByKey[field.key] &&
+ savedBoardCardFieldsByKey[field.key].isVisible !== field.isVisible,
+ );
+ await updateViewFields(viewFieldsToUpdate);
+
+ return refetch();
+ };
+
+ return { createViewFields, persistCardFields };
};
diff --git a/front/src/modules/views/hooks/useBoardViews.ts b/front/src/modules/views/hooks/useBoardViews.ts
index 9f01509dc..28b1ec2dc 100644
--- a/front/src/modules/views/hooks/useBoardViews.ts
+++ b/front/src/modules/views/hooks/useBoardViews.ts
@@ -1,5 +1,6 @@
import type { Context } from 'react';
+import { boardCardFieldsScopedState } from '@/ui/board/states/boardCardFieldsScopedState';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
@@ -23,17 +24,21 @@ export const useBoardViews = ({
objectId: 'company';
scopeContext: Context;
}) => {
+ const boardCardFields = useRecoilScopedValue(
+ boardCardFieldsScopedState,
+ scopeContext,
+ );
const filters = useRecoilScopedValue(filtersScopedState, scopeContext);
const sorts = useRecoilScopedValue(sortsScopedState, scopeContext);
- const { handleViewsChange, isFetchingViews } = useViews({
+ const { createView, deleteView, isFetchingViews, updateView } = useViews({
objectId,
onViewCreate: handleViewCreate,
type: ViewType.Pipeline,
scopeContext,
});
- useBoardViewFields({
+ const { createViewFields, persistCardFields } = useBoardViewFields({
objectId,
fieldDefinitions,
scopeContext,
@@ -51,14 +56,16 @@ export const useBoardViews = ({
});
async function handleViewCreate(viewId: string) {
+ await createViewFields(boardCardFields, viewId);
await createViewFilters(filters, viewId);
await createViewSorts(sorts, viewId);
}
- const handleViewSubmit = async () => {
+ const submitCurrentView = async () => {
+ await persistCardFields();
await persistFilters();
await persistSorts();
};
- return { handleViewsChange, handleViewSubmit };
+ return { createView, deleteView, submitCurrentView, updateView };
};
diff --git a/front/src/modules/views/hooks/useTableViews.ts b/front/src/modules/views/hooks/useTableViews.ts
index fbe87ec81..ff878a547 100644
--- a/front/src/modules/views/hooks/useTableViews.ts
+++ b/front/src/modules/views/hooks/useTableViews.ts
@@ -29,7 +29,7 @@ export const useTableViews = ({
);
const sorts = useRecoilScopedValue(sortsScopedState, TableRecoilScopeContext);
- const { handleViewsChange, isFetchingViews } = useViews({
+ const { createView, deleteView, isFetchingViews, updateView } = useViews({
objectId,
onViewCreate: handleViewCreate,
type: ViewType.Table,
@@ -55,11 +55,11 @@ export const useTableViews = ({
await createViewSorts(sorts, viewId);
}
- const handleViewSubmit = async () => {
+ const submitCurrentView = async () => {
await persistColumns();
await persistFilters();
await persistSorts();
};
- return { handleViewsChange, handleViewSubmit };
+ return { createView, deleteView, submitCurrentView, updateView };
};
diff --git a/front/src/modules/views/hooks/useViews.ts b/front/src/modules/views/hooks/useViews.ts
index 23c373e08..2d263c156 100644
--- a/front/src/modules/views/hooks/useViews.ts
+++ b/front/src/modules/views/hooks/useViews.ts
@@ -1,13 +1,13 @@
import type { Context } from 'react';
+import { getOperationName } from '@apollo/client/utilities';
import { useRecoilCallback } from 'recoil';
+import { savedBoardCardFieldsFamilyState } from '@/ui/board/states/savedBoardCardFieldsFamilyState';
import { savedTableColumnsFamilyState } from '@/ui/table/states/savedTableColumnsFamilyState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
-import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';
import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState';
-import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewsScopedState } from '@/ui/view-bar/states/viewsScopedState';
import type { View } from '@/ui/view-bar/types/View';
import {
@@ -19,6 +19,8 @@ import {
} from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
+import { GET_VIEWS } from '../graphql/queries/getViews';
+
export const useViews = ({
objectId,
onViewCreate,
@@ -38,7 +40,6 @@ export const useViews = ({
viewsScopedState,
scopeContext,
);
- const viewsById = useRecoilScopedValue(viewsByIdScopedSelector, scopeContext);
const [createViewMutation] = useCreateViewMutation();
const [updateViewMutation] = useUpdateViewMutation();
@@ -53,26 +54,34 @@ export const useViews = ({
type,
},
},
+ refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
});
if (data?.view) await onViewCreate?.(data.view.id);
};
- const updateView = (view: View) =>
- updateViewMutation({
+ const updateView = async (view: View) => {
+ await updateViewMutation({
variables: {
data: { name: view.name },
where: { id: view.id },
},
+ refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
});
+ };
- const deleteView = (viewId: string) =>
- deleteViewMutation({ variables: { where: { id: viewId } } });
+ const deleteView = async (viewId: string) => {
+ await deleteViewMutation({
+ variables: { where: { id: viewId } },
+ refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
+ });
+ };
const handleResetSavedViews = useRecoilCallback(
({ reset }) =>
() => {
views.forEach((view) => {
+ reset(savedBoardCardFieldsFamilyState(view.id));
reset(savedTableColumnsFamilyState(view.id));
reset(savedFiltersFamilyState(view.id));
reset(savedSortsFamilyState(view.id));
@@ -81,7 +90,7 @@ export const useViews = ({
[views],
);
- const { loading, refetch } = useGetViewsQuery({
+ const { loading } = useGetViewsQuery({
variables: {
where: {
objectId: { equals: objectId },
@@ -115,32 +124,10 @@ export const useViews = ({
},
});
- const handleViewsChange = async (nextViews: View[]) => {
- const viewToCreate = nextViews.find((nextView) => !viewsById[nextView.id]);
- if (viewToCreate) {
- await createView(viewToCreate);
- await refetch();
- return;
- }
-
- const viewToUpdate = nextViews.find(
- (nextView) =>
- viewsById[nextView.id] && viewsById[nextView.id].name !== nextView.name,
- );
- if (viewToUpdate) {
- await updateView(viewToUpdate);
- await refetch();
- return;
- }
-
- const nextViewIds = nextViews.map((nextView) => nextView.id);
- const viewIdToDelete = Object.keys(viewsById).find(
- (previousViewId) => !nextViewIds.includes(previousViewId),
- );
- if (viewIdToDelete) await deleteView(viewIdToDelete);
-
- await refetch();
+ return {
+ createView,
+ deleteView,
+ isFetchingViews: loading,
+ updateView,
};
-
- return { handleViewsChange, isFetchingViews: loading };
};