diff --git a/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx b/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx
index cfb5eefda..769690cf0 100644
--- a/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx
+++ b/front/src/modules/companies/table/components/TableActionBarButtonCreateActivityCompany.tsx
@@ -1,6 +1,7 @@
+import { IconCheckbox, IconNotes } from '@tabler/icons-react';
+
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
-import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
-import { TableActionBarButtonToggleTasks } from '@/ui/table/action-bar/components/TableActionBarButtonOpenTasks';
+import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton';
import { ActivityType, CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateActivityCompany() {
@@ -13,10 +14,14 @@ export function TableActionBarButtonCreateActivityCompany() {
return (
<>
- }
onClick={() => handleButtonClick(ActivityType.Note)}
/>
- }
onClick={() => handleButtonClick(ActivityType.Task)}
/>
>
diff --git a/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies copy.tsx b/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies copy.tsx
new file mode 100644
index 000000000..04469aa2e
--- /dev/null
+++ b/front/src/modules/companies/table/components/TableActionBarButtonDeleteCompanies copy.tsx
@@ -0,0 +1,54 @@
+import { getOperationName } from '@apollo/client/utilities';
+import { useRecoilState, useRecoilValue } from 'recoil';
+
+import { GET_PIPELINES } from '@/pipeline/queries';
+import { IconTrash } from '@/ui/icon/index';
+import { EntityTableContextMenuEntry } from '@/ui/table/context-menu/components/EntityTableContextMenuEntry';
+import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
+import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
+import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
+import { useDeleteManyCompaniesMutation } from '~/generated/graphql';
+
+export function TableContextMenuEntryDeleteCompanies() {
+ const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
+
+ const resetRowSelection = useResetTableRowSelection();
+
+ const [deleteCompanies] = useDeleteManyCompaniesMutation({
+ refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
+ });
+
+ const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
+
+ async function handleDeleteClick() {
+ const rowIdsToDelete = selectedRowIds;
+
+ resetRowSelection();
+
+ await deleteCompanies({
+ variables: {
+ ids: rowIdsToDelete,
+ },
+ optimisticResponse: {
+ __typename: 'Mutation',
+ deleteManyCompany: {
+ count: rowIdsToDelete.length,
+ },
+ },
+ update: () => {
+ setTableRowIds(
+ tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
+ );
+ },
+ });
+ }
+
+ return (
+ }
+ type="warning"
+ onClick={handleDeleteClick}
+ />
+ );
+}
diff --git a/front/src/modules/companies/table/components/TableContextMenuEntryCreateActivityCompany copy.tsx b/front/src/modules/companies/table/components/TableContextMenuEntryCreateActivityCompany copy.tsx
new file mode 100644
index 000000000..76d1fd4f9
--- /dev/null
+++ b/front/src/modules/companies/table/components/TableContextMenuEntryCreateActivityCompany copy.tsx
@@ -0,0 +1,29 @@
+import { IconCheckbox, IconNotes } from '@tabler/icons-react';
+
+import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
+import { EntityTableContextMenuEntry } from '@/ui/table/context-menu/components/EntityTableContextMenuEntry';
+import { ActivityType, CommentableType } from '~/generated/graphql';
+
+export function TableContextMenuEntryCreateActivityCompany() {
+ const openCreateActivityRightDrawer =
+ useOpenCreateActivityDrawerForSelectedRowIds();
+
+ async function handleButtonClick(type: ActivityType) {
+ openCreateActivityRightDrawer(type, CommentableType.Company);
+ }
+
+ return (
+ <>
+ }
+ onClick={() => handleButtonClick(ActivityType.Note)}
+ />
+ }
+ onClick={() => handleButtonClick(ActivityType.Task)}
+ />
+ >
+ );
+}
diff --git a/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx b/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx
index 9d8e271ca..2a9f065ce 100644
--- a/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx
+++ b/front/src/modules/people/table/components/TableActionBarButtonCreateActivityPeople.tsx
@@ -1,6 +1,7 @@
+import { IconCheckbox, IconNotes } from '@tabler/icons-react';
+
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
-import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
-import { TableActionBarButtonToggleTasks } from '@/ui/table/action-bar/components/TableActionBarButtonOpenTasks';
+import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton';
import { ActivityType, CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateActivityPeople() {
@@ -13,10 +14,14 @@ export function TableActionBarButtonCreateActivityPeople() {
return (
<>
- }
onClick={() => handleButtonClick(ActivityType.Note)}
/>
- }
onClick={() => handleButtonClick(ActivityType.Task)}
/>
>
diff --git a/front/src/modules/people/table/components/TableActionContextMenuEntryDeletePeople.tsx b/front/src/modules/people/table/components/TableActionContextMenuEntryDeletePeople.tsx
new file mode 100644
index 000000000..b03edf0b6
--- /dev/null
+++ b/front/src/modules/people/table/components/TableActionContextMenuEntryDeletePeople.tsx
@@ -0,0 +1,53 @@
+import { getOperationName } from '@apollo/client/utilities';
+import { useRecoilState, useRecoilValue } from 'recoil';
+
+import { GET_PEOPLE } from '@/people/queries';
+import { IconTrash } from '@/ui/icon/index';
+import { EntityTableContextMenuEntry } from '@/ui/table/context-menu/components/EntityTableContextMenuEntry';
+import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
+import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
+import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
+import { useDeleteManyPersonMutation } from '~/generated/graphql';
+
+export function TableContextMenuEntryDeletePeople() {
+ const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
+ const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
+
+ const resetRowSelection = useResetTableRowSelection();
+
+ const [deleteManyPerson] = useDeleteManyPersonMutation({
+ refetchQueries: [getOperationName(GET_PEOPLE) ?? ''],
+ });
+
+ async function handleDeleteClick() {
+ const rowIdsToDelete = selectedRowIds;
+
+ resetRowSelection();
+
+ await deleteManyPerson({
+ variables: {
+ ids: rowIdsToDelete,
+ },
+ optimisticResponse: {
+ __typename: 'Mutation',
+ deleteManyPerson: {
+ count: rowIdsToDelete.length,
+ },
+ },
+ update: () => {
+ setTableRowIds(
+ tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
+ );
+ },
+ });
+ }
+
+ return (
+ }
+ type="warning"
+ onClick={handleDeleteClick}
+ />
+ );
+}
diff --git a/front/src/modules/people/table/components/TableContextMenuEntryCreateActivityPeople.tsx b/front/src/modules/people/table/components/TableContextMenuEntryCreateActivityPeople.tsx
new file mode 100644
index 000000000..8aa70de10
--- /dev/null
+++ b/front/src/modules/people/table/components/TableContextMenuEntryCreateActivityPeople.tsx
@@ -0,0 +1,29 @@
+import { IconCheckbox, IconNotes } from '@tabler/icons-react';
+
+import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
+import { EntityTableContextMenuEntry } from '@/ui/table/context-menu/components/EntityTableContextMenuEntry';
+import { ActivityType, CommentableType } from '~/generated/graphql';
+
+export function TableContextMenuEntryCreateActivityPeople() {
+ const openCreateActivityRightDrawer =
+ useOpenCreateActivityDrawerForSelectedRowIds();
+
+ async function handleButtonClick(type: ActivityType) {
+ openCreateActivityRightDrawer(type, CommentableType.Person);
+ }
+
+ return (
+ <>
+ }
+ onClick={() => handleButtonClick(ActivityType.Note)}
+ />
+ }
+ onClick={() => handleButtonClick(ActivityType.Task)}
+ />
+ >
+ );
+}
diff --git a/front/src/modules/ui/action-bar/components/ActionBar.tsx b/front/src/modules/ui/action-bar/components/ActionBar.tsx
index 72f7d8361..8de0c70de 100644
--- a/front/src/modules/ui/action-bar/components/ActionBar.tsx
+++ b/front/src/modules/ui/action-bar/components/ActionBar.tsx
@@ -4,42 +4,11 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
import { contextMenuPositionState } from '@/ui/table/states/contextMenuPositionState';
-import { PositionType } from '../types/PositionType';
-
type OwnProps = {
children: React.ReactNode | React.ReactNode[];
selectedIds: string[];
};
-type StyledContainerProps = {
- position: PositionType;
-};
-
-const StyledContainerContextMenu = styled.div`
- align-items: center;
- background: ${({ theme }) => theme.background.secondary};
- border: 1px solid ${({ theme }) => theme.border.color.medium};
- border-radius: ${({ theme }) => theme.border.radius.md};
- bottom: ${(props) => (props.position.x ? 'auto' : '38px')};
- box-shadow: ${({ theme }) => theme.boxShadow.strong};
-
- left: ${(props) => (props.position.x ? `${props.position.x}px` : '50%')};
- padding-bottom: ${({ theme }) => theme.spacing(1)};
- padding-left: ${({ theme }) => theme.spacing(1)};
- padding-right: ${({ theme }) => theme.spacing(1)};
- padding-top: ${({ theme }) => theme.spacing(1)};
- position: ${(props) => (props.position.x ? 'fixed' : 'absolute')};
- top: ${(props) => (props.position.y ? `${props.position.y}px` : 'auto')};
-
- transform: translateX(-50%);
- width: 160px;
- z-index: 1;
-
- div {
- justify-content: left;
- }
-`;
-
const StyledContainerActionBar = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.secondary};
@@ -79,16 +48,9 @@ export function ActionBar({ children, selectedIds }: OwnProps) {
};
}, [setContextMenuPosition]);
- if (selectedIds.length === 0) {
+ if (selectedIds.length === 0 || position.x || position.y) {
return null;
}
- if (position.x && position.y) {
- return (
-
- {children}
-
- );
- }
return (
{children}
diff --git a/front/src/modules/ui/context-menu/components/ContextMenu.tsx b/front/src/modules/ui/context-menu/components/ContextMenu.tsx
new file mode 100644
index 000000000..b9867e5b1
--- /dev/null
+++ b/front/src/modules/ui/context-menu/components/ContextMenu.tsx
@@ -0,0 +1,64 @@
+import React, { useEffect } from 'react';
+import styled from '@emotion/styled';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
+
+import { contextMenuPositionState } from '@/ui/table/states/contextMenuPositionState';
+
+import { PositionType } from '../types/PositionType';
+
+type OwnProps = {
+ children: React.ReactNode | React.ReactNode[];
+ selectedIds: string[];
+};
+
+type StyledContainerProps = {
+ position: PositionType;
+};
+
+const StyledContainerContextMenu = styled.div`
+ align-items: flex-start;
+ background: ${({ theme }) => theme.background.secondary};
+ border: 1px solid ${({ theme }) => theme.border.color.light};
+ border-radius: ${({ theme }) => theme.border.radius.md};
+ box-shadow: ${({ theme }) => theme.boxShadow.strong};
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+
+ left: ${(props) => `${props.position.x}px`};
+ position: fixed;
+ top: ${(props) => `${props.position.y}px`};
+
+ transform: translateX(-50%);
+ width: 160px;
+ z-index: 1;
+`;
+
+export function ContextMenu({ children, selectedIds }: OwnProps) {
+ const position = useRecoilValue(contextMenuPositionState);
+ const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
+
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (!(event.target as HTMLElement).closest('.action-bar')) {
+ setContextMenuPosition({ x: null, y: null });
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside);
+
+ // Cleanup the event listener when the component unmounts
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [setContextMenuPosition]);
+
+ if (selectedIds.length === 0 || (!position.x && !position.y)) {
+ return null;
+ }
+ return (
+
+ {children}
+
+ );
+}
diff --git a/front/src/modules/ui/action-bar/types/PositionType.ts b/front/src/modules/ui/context-menu/types/PositionType.ts
similarity index 100%
rename from front/src/modules/ui/action-bar/types/PositionType.ts
rename to front/src/modules/ui/context-menu/types/PositionType.ts
diff --git a/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenComments.tsx b/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenComments.tsx
deleted file mode 100644
index 8da8db328..000000000
--- a/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenComments.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { IconNotes } from '@/ui/icon/index';
-
-import { EntityTableActionBarButton } from './EntityTableActionBarButton';
-
-type OwnProps = {
- onClick: () => void;
-};
-
-export function TableActionBarButtonToggleComments({ onClick }: OwnProps) {
- return (
- }
- onClick={onClick}
- />
- );
-}
diff --git a/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenTasks.tsx b/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenTasks.tsx
deleted file mode 100644
index b407ec239..000000000
--- a/front/src/modules/ui/table/action-bar/components/TableActionBarButtonOpenTasks.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { IconCheckbox } from '@/ui/icon/index';
-
-import { EntityTableActionBarButton } from './EntityTableActionBarButton';
-
-type OwnProps = {
- onClick: () => void;
-};
-
-export function TableActionBarButtonToggleTasks({ onClick }: OwnProps) {
- return (
- }
- onClick={onClick}
- />
- );
-}
diff --git a/front/src/modules/ui/table/context-menu/components/EntityTableContextMenu.tsx b/front/src/modules/ui/table/context-menu/components/EntityTableContextMenu.tsx
new file mode 100644
index 000000000..846e88560
--- /dev/null
+++ b/front/src/modules/ui/table/context-menu/components/EntityTableContextMenu.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { ContextMenu } from '@/ui/context-menu/components/ContextMenu';
+
+import { selectedRowIdsSelector } from '../../states/selectedRowIdsSelector';
+
+type OwnProps = {
+ children: React.ReactNode | React.ReactNode[];
+};
+
+export function EntityTableContextMenu({ children }: OwnProps) {
+ const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
+
+ return {children};
+}
diff --git a/front/src/modules/ui/table/context-menu/components/EntityTableContextMenuEntry.tsx b/front/src/modules/ui/table/context-menu/components/EntityTableContextMenuEntry.tsx
new file mode 100644
index 000000000..3adfb049e
--- /dev/null
+++ b/front/src/modules/ui/table/context-menu/components/EntityTableContextMenuEntry.tsx
@@ -0,0 +1,58 @@
+import { ReactNode } from 'react';
+import styled from '@emotion/styled';
+
+type OwnProps = {
+ icon: ReactNode;
+ label: string;
+ type?: 'standard' | 'warning';
+ onClick: () => void;
+};
+
+type StyledButtonProps = {
+ type: 'standard' | 'warning';
+};
+
+const StyledButton = styled.div`
+ align-items: center;
+ align-self: stretch;
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+ color: ${(props) =>
+ props.type === 'warning'
+ ? props.theme.color.red
+ : props.theme.font.color.secondary};
+ cursor: pointer;
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(2)};
+
+ height: 32px;
+ padding-left: ${({ theme }) => theme.spacing(1)};
+ padding-right: ${({ theme }) => theme.spacing(1)};
+ transition: background 0.1s ease;
+ user-select: none;
+
+ &:hover {
+ background: ${({ theme, type }) =>
+ type === 'warning'
+ ? theme.tag.background.red
+ : theme.background.tertiary};
+ }
+`;
+
+const StyledButtonLabel = styled.div`
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+ margin-left: ${({ theme }) => theme.spacing(2)};
+`;
+
+export function EntityTableContextMenuEntry({
+ label,
+ icon,
+ type = 'standard',
+ onClick,
+}: OwnProps) {
+ return (
+
+ {icon}
+ {label}
+
+ );
+}
diff --git a/front/src/modules/ui/table/states/contextMenuPositionState.ts b/front/src/modules/ui/table/states/contextMenuPositionState.ts
index 1832437b5..7cd20a1c6 100644
--- a/front/src/modules/ui/table/states/contextMenuPositionState.ts
+++ b/front/src/modules/ui/table/states/contextMenuPositionState.ts
@@ -1,6 +1,6 @@
import { atom } from 'recoil';
-import { PositionType } from '@/ui/action-bar/types/PositionType';
+import { PositionType } from '@/ui/context-menu/types/PositionType';
export const contextMenuPositionState = atom({
key: 'contextMenuPositionState',
diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx
index 79d04fd69..95741730c 100644
--- a/front/src/pages/companies/Companies.tsx
+++ b/front/src/pages/companies/Companies.tsx
@@ -7,10 +7,13 @@ import { v4 } from 'uuid';
import { CompanyTable } from '@/companies/table/components/CompanyTable';
import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany';
import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
+import { TableContextMenuEntryDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies copy';
+import { TableContextMenuEntryCreateActivityCompany } from '@/companies/table/components/TableContextMenuEntryCreateActivityCompany copy';
import { SEARCH_COMPANY_QUERY } from '@/search/queries/search';
import { IconBuildingSkyscraper } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
+import { EntityTableContextMenu } from '@/ui/table/context-menu/components/EntityTableContextMenu';
import { TableContext } from '@/ui/table/states/TableContext';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
@@ -72,6 +75,10 @@ export function Companies() {
+
+
+
+
>
diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx
index 4ed08ad2c..127b6f736 100644
--- a/front/src/pages/people/People.tsx
+++ b/front/src/pages/people/People.tsx
@@ -6,9 +6,12 @@ import { v4 } from 'uuid';
import { PeopleTable } from '@/people/table/components/PeopleTable';
import { TableActionBarButtonCreateActivityPeople } from '@/people/table/components/TableActionBarButtonCreateActivityPeople';
import { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople';
+import { TableContextMenuEntryDeletePeople } from '@/people/table/components/TableActionContextMenuEntryDeletePeople';
+import { TableContextMenuEntryCreateActivityPeople } from '@/people/table/components/TableContextMenuEntryCreateActivityPeople';
import { IconUser } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
+import { EntityTableContextMenu } from '@/ui/table/context-menu/components/EntityTableContextMenu';
import { TableContext } from '@/ui/table/states/TableContext';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
@@ -67,6 +70,10 @@ export function People() {
+
+
+
+
);