mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-25 20:00:34 +03:00
Merge branch 'main' into fix/operand-dropdown
This commit is contained in:
commit
d61abf9564
@ -33,3 +33,7 @@ Your turn 👇
|
|||||||
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-crm)
|
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-crm)
|
||||||
|
|
||||||
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/is-twenty-crm-the-right-tool-for-your-business-heres-my-honest-review-0d41e9d8a7eb)
|
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/is-twenty-crm-the-right-tool-for-your-business-heres-my-honest-review-0d41e9d8a7eb)
|
||||||
|
|
||||||
|
» 30-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) blog Link: [blog](https://dev.to/adityadeshlahre/transform-your-customer-relationships-with-the-leading-open-source-crm-twenty-161d)
|
||||||
|
|
||||||
|
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) blog Link: [blog](https://zedblock.com/blog/twenty-crm)
|
||||||
|
@ -31,3 +31,7 @@ Your turn 👇
|
|||||||
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-self-host)
|
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-self-host)
|
||||||
|
|
||||||
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/comprehensive-guide-to-self-hosting-twenty-crm-26e7fa36c846)
|
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/comprehensive-guide-to-self-hosting-twenty-crm-26e7fa36c846)
|
||||||
|
|
||||||
|
» 30-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) blog Link: [blog](https://dev.to/adityadeshlahre/complete-guide-to-self-hosting-twenty-crm-2h08)
|
||||||
|
|
||||||
|
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) blog Link: [blog](https://dev.to/raju_gangitla_91920e1427f/self-hosting-twenty-crm-a-complete-guide-559n)
|
||||||
|
@ -18,11 +18,13 @@ Your turn 👇
|
|||||||
////////////////////////////
|
////////////////////////////
|
||||||
|
|
||||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
|
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
|
||||||
|
|
||||||
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) Figma Link: [Figma](https://www.figma.com/design/XE21QdkFuy0IJHtmW7TURa/Twenty-(rajeevDewangan)?node-id=0-1&node-type=canvas&t=BYBulCT6hpJu6E8G-0)
|
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) Figma Link: [Figma](https://www.figma.com/design/XE21QdkFuy0IJHtmW7TURa/Twenty-(rajeevDewangan)?node-id=0-1&node-type=canvas&t=BYBulCT6hpJu6E8G-0)
|
||||||
|
|
||||||
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1)
|
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1)
|
||||||
|
|
||||||
» 24-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Figma Link: [Figma](https://www.figma.com/design/2qlAPS3llwf8jrWKGHEf6O/Twenty-(sateshcharan)?node-id=1633-94880&t=GIceWxqyY0ajWXnZ-1)
|
» 24-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Figma Link: [Figma](https://www.figma.com/design/2qlAPS3llwf8jrWKGHEf6O/Twenty-(sateshcharan)?node-id=1633-94880&t=GIceWxqyY0ajWXnZ-1)
|
||||||
|
|
||||||
» 28-October-2024 by [Vanshdeep Singh](https://oss.gg/Vanshdeepsingh-2232) Figma Link:[Figma](https://www.figma.com/design/akgDOb37YLUW9iWLB155EV/Twenty-(Copy)?node-id=478-19796&t=8Gz1yqls2Q3dsN9h-1)
|
» 28-October-2024 by [Vanshdeep Singh](https://oss.gg/Vanshdeepsingh-2232) Figma Link:[Figma](https://www.figma.com/design/akgDOb37YLUW9iWLB155EV/Twenty-(Copy)?node-id=478-19796&t=8Gz1yqls2Q3dsN9h-1)
|
||||||
|
|
||||||
|
» 30-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) Figma Link:[Figma](https://www.figma.com/design/4hlpS6LOIaJqDbd6T6SlXc/CustomTwentyByAdityDeshlahre?node-id=478-19796&t=Dp8EBpl0FxVjiGHT-1) Figma Preview Link:[Prototype](https://www.figma.com/proto/4hlpS6LOIaJqDbd6T6SlXc/CustomTwentyByAdityDeshlahre?node-id=478-19796&t=Dp8EBpl0FxVjiGHT-1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,4 +20,9 @@ Your turn 👇
|
|||||||
|
|
||||||
» 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view)
|
» 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view)
|
||||||
|
|
||||||
|
» 24-October-2024 by [chrisdadev13](https://oss.gg/chrisdadev13) video Link: [video](https://www.loom.com/share/f98f34f8d9b34728998847d3b97a16b7)
|
||||||
|
|
||||||
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://drive.google.com/file/d/1-wgzofJaWmnMcFgZZV5uYNNgtbJKJ_1G/view?usp=sharing/)
|
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://drive.google.com/file/d/1-wgzofJaWmnMcFgZZV5uYNNgtbJKJ_1G/view?usp=sharing/)
|
||||||
|
|
||||||
|
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) video Link: [video](https://www.loom.com/share/f072bf31fb46449d98d6826a3a824fe9?sid=21f2c3f4-f286-43a2-98aa-d1fb92c3a86e)
|
||||||
|
|
||||||
|
@ -20,4 +20,5 @@ Your turn 👇
|
|||||||
|
|
||||||
» 25-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) template Link: [template]()
|
» 25-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) template Link: [template]()
|
||||||
|
|
||||||
|
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) video Link: [video](https://www.loom.com/share/89f86ef895e946fbbbbae3cc90559bb7?sid=dbebe60b-3ece-4ac8-acb2-184043c6f87b)
|
||||||
---
|
---
|
||||||
|
@ -20,4 +20,6 @@ Your turn 👇
|
|||||||
|
|
||||||
» 26-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) guide Link: [guide](https://dev.to/sateshcharan/supercharge-your-marketing-with-twentycrm-n8n-1hfd)
|
» 26-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) guide Link: [guide](https://dev.to/sateshcharan/supercharge-your-marketing-with-twentycrm-n8n-1hfd)
|
||||||
|
|
||||||
|
» 31-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) guide Link: [guide](https://dev.to/raju_gangitla_91920e1427f/automating-people-data-sync-n8n-workflow-for-twenty-crm-and-google-sheets-5789
|
||||||
|
)
|
||||||
---
|
---
|
||||||
|
@ -102,6 +102,7 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
position,
|
position,
|
||||||
Icon: IconTrash,
|
Icon: IconTrash,
|
||||||
accent: 'danger',
|
accent: 'danger',
|
||||||
|
isPinned: true,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setIsDeleteRecordsModalOpen(true);
|
setIsDeleteRecordsModalOpen(true);
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton';
|
||||||
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
|
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
|
||||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
@ -30,7 +31,9 @@ export const RecordIndexActionMenuBar = () => {
|
|||||||
actionMenuEntriesComponentSelector,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (actionMenuEntries.length === 0) {
|
const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned);
|
||||||
|
|
||||||
|
if (pinnedEntries.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,9 +45,10 @@ export const RecordIndexActionMenuBar = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
|
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
|
||||||
{actionMenuEntries.map((entry, index) => (
|
{pinnedEntries.map((entry, index) => (
|
||||||
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
|
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
|
||||||
))}
|
))}
|
||||||
|
<RecordIndexActionMenuBarAllActionsButton />
|
||||||
</BottomBar>
|
</BottomBar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { IconLayoutSidebarRightExpand } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledButton = styled.div`
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
transition: background ${({ theme }) => theme.animation.duration.fast} ease;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.background.tertiary};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledButtonLabel = styled.div`
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledShortcutLabel = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledSeparator = styled.div<{ size: 'sm' | 'md' }>`
|
||||||
|
background: ${({ theme }) => theme.border.color.light};
|
||||||
|
height: ${({ theme, size }) => theme.spacing(size === 'sm' ? 4 : 8)};
|
||||||
|
margin: 0 ${({ theme }) => theme.spacing(1)};
|
||||||
|
width: 1px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RecordIndexActionMenuBarAllActionsButton = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { openCommandMenu } = useCommandMenu();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledSeparator size="md" />
|
||||||
|
<StyledButton onClick={() => openCommandMenu()}>
|
||||||
|
<IconLayoutSidebarRightExpand size={theme.icon.size.md} />
|
||||||
|
<StyledButtonLabel>All Actions</StyledButtonLabel>
|
||||||
|
<StyledSeparator size="sm" />
|
||||||
|
<StyledShortcutLabel>⌘K</StyledShortcutLabel>
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -2,31 +2,24 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
||||||
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
|
||||||
|
|
||||||
type RecordIndexActionMenuBarEntryProps = {
|
type RecordIndexActionMenuBarEntryProps = {
|
||||||
entry: ActionMenuEntry;
|
entry: ActionMenuEntry;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledButton = styled.div<{ accent: MenuItemAccent }>`
|
const StyledButton = styled.div`
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
color: ${(props) =>
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
props.accent === 'danger'
|
|
||||||
? props.theme.color.red
|
|
||||||
: props.theme.font.color.secondary};
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
transition: background 0.1s ease;
|
transition: background ${({ theme }) => theme.animation.duration.fast} ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${({ theme, accent }) =>
|
background: ${({ theme }) => theme.background.tertiary};
|
||||||
accent === 'danger'
|
|
||||||
? theme.background.danger
|
|
||||||
: theme.background.tertiary};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -40,10 +33,7 @@ export const RecordIndexActionMenuBarEntry = ({
|
|||||||
}: RecordIndexActionMenuBarEntryProps) => {
|
}: RecordIndexActionMenuBarEntryProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<StyledButton
|
<StyledButton onClick={() => entry.onClick?.()}>
|
||||||
accent={entry.accent ?? 'default'}
|
|
||||||
onClick={() => entry.onClick?.()}
|
|
||||||
>
|
|
||||||
{entry.Icon && <entry.Icon size={theme.icon.size.md} />}
|
{entry.Icon && <entry.Icon size={theme.icon.size.md} />}
|
||||||
<StyledButtonLabel>{entry.label}</StyledButtonLabel>
|
<StyledButtonLabel>{entry.label}</StyledButtonLabel>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
@ -10,15 +10,15 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
|
|||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||||
import { userEvent, waitFor, within } from '@storybook/test';
|
import { userEvent, waitFor, within } from '@storybook/test';
|
||||||
import { IconCheckbox, IconTrash } from 'twenty-ui';
|
import { IconTrash, RouterDecorator } from 'twenty-ui';
|
||||||
|
|
||||||
const deleteMock = jest.fn();
|
const deleteMock = jest.fn();
|
||||||
const markAsDoneMock = jest.fn();
|
|
||||||
|
|
||||||
const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
||||||
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
|
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
|
||||||
component: RecordIndexActionMenuBar,
|
component: RecordIndexActionMenuBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
RouterDecorator,
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<ContextStoreComponentInstanceContext.Provider
|
<ContextStoreComponentInstanceContext.Provider
|
||||||
value={{ instanceId: 'story-action-menu' }}
|
value={{ instanceId: 'story-action-menu' }}
|
||||||
@ -48,6 +48,7 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
|||||||
[
|
[
|
||||||
'delete',
|
'delete',
|
||||||
{
|
{
|
||||||
|
isPinned: true,
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
position: 0,
|
position: 0,
|
||||||
@ -55,16 +56,6 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
|||||||
onClick: deleteMock,
|
onClick: deleteMock,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
'markAsDone',
|
|
||||||
{
|
|
||||||
key: 'markAsDone',
|
|
||||||
label: 'Mark as done',
|
|
||||||
position: 1,
|
|
||||||
Icon: IconCheckbox,
|
|
||||||
onClick: markAsDoneMock,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
set(
|
set(
|
||||||
@ -120,12 +111,8 @@ export const WithButtonClicks: Story = {
|
|||||||
const deleteButton = await canvas.findByText('Delete');
|
const deleteButton = await canvas.findByText('Delete');
|
||||||
await userEvent.click(deleteButton);
|
await userEvent.click(deleteButton);
|
||||||
|
|
||||||
const markAsDoneButton = await canvas.findByText('Mark as done');
|
|
||||||
await userEvent.click(markAsDoneButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(deleteMock).toHaveBeenCalled();
|
expect(deleteMock).toHaveBeenCalled();
|
||||||
expect(markAsDoneMock).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ export type ActionMenuEntry = {
|
|||||||
label: string;
|
label: string;
|
||||||
position: number;
|
position: number;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
|
isPinned?: boolean;
|
||||||
accent?: MenuItemAccent;
|
accent?: MenuItemAccent;
|
||||||
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
||||||
ConfirmationModal?: ReactNode;
|
ConfirmationModal?: ReactNode;
|
||||||
|
@ -19,8 +19,6 @@ export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSig
|
|||||||
__typename: true,
|
__typename: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
activity: true,
|
|
||||||
activityId: true,
|
|
||||||
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum';
|
|||||||
|
|
||||||
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
||||||
import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect';
|
import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect';
|
||||||
|
import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId';
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||||
@ -16,7 +17,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
|||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||||
@ -69,9 +70,15 @@ export const RecordBoard = () => {
|
|||||||
const { resetRecordSelection, setRecordAsSelected } =
|
const { resetRecordSelection, setRecordAsSelected } =
|
||||||
useRecordBoardSelection(recordBoardId);
|
useRecordBoardSelection(recordBoardId);
|
||||||
|
|
||||||
useListenClickOutsideByClassName({
|
useListenClickOutsideV2({
|
||||||
classNames: ['record-board-card'],
|
excludeClassNames: [
|
||||||
excludeClassNames: ['bottom-bar', 'action-menu-dropdown', 'command-menu'],
|
'bottom-bar',
|
||||||
|
'action-menu-dropdown',
|
||||||
|
'command-menu',
|
||||||
|
'modal-backdrop',
|
||||||
|
],
|
||||||
|
listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
refs: [boardRef],
|
||||||
callback: resetRecordSelection,
|
callback: resetRecordSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export const RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID = 'record-board';
|
@ -84,9 +84,9 @@ export const MultiItemFieldInput = <T,>({
|
|||||||
setInputValue(value);
|
setInputValue(value);
|
||||||
if (!validateInput) return;
|
if (!validateInput) return;
|
||||||
|
|
||||||
if (errorData.isValid) {
|
setErrorData(
|
||||||
setErrorData(errorData);
|
errorData.isValid ? errorData : { isValid: true, errorMessage: '' },
|
||||||
}
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddButtonClick = () => {
|
const handleAddButtonClick = () => {
|
||||||
|
@ -7,6 +7,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
|
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
|
||||||
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
|
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
|
||||||
|
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
||||||
@ -81,12 +82,14 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
||||||
const unselectedRowIds = useRecoilValue(unselectedRowIdsSelector());
|
const unselectedRowIds = useRecoilValue(unselectedRowIdsSelector());
|
||||||
|
|
||||||
|
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasUserSelectedAllRows) {
|
if (hasUserSelectedAllRows) {
|
||||||
setContextStoreTargetedRecords({
|
setContextStoreTargetedRecords({
|
||||||
mode: 'exclusion',
|
mode: 'exclusion',
|
||||||
excludedRecordIds: unselectedRowIds,
|
excludedRecordIds: unselectedRowIds,
|
||||||
filters: [],
|
filters: recordIndexFilters,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setContextStoreTargetedRecords({
|
setContextStoreTargetedRecords({
|
||||||
@ -103,6 +106,7 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
hasUserSelectedAllRows,
|
hasUserSelectedAllRows,
|
||||||
|
recordIndexFilters,
|
||||||
selectedRowIds,
|
selectedRowIds,
|
||||||
setContextStoreTargetedRecords,
|
setContextStoreTargetedRecords,
|
||||||
unselectedRowIds,
|
unselectedRowIds,
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||||
|
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
||||||
|
|
||||||
type RecordTableInternalEffectProps = {
|
type RecordTableInternalEffectProps = {
|
||||||
recordTableId: string;
|
recordTableId: string;
|
||||||
|
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableInternalEffect = ({
|
export const RecordTableInternalEffect = ({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
|
tableBodyRef,
|
||||||
}: RecordTableInternalEffectProps) => {
|
}: RecordTableInternalEffectProps) => {
|
||||||
const { leaveTableFocus, resetTableRowSelection, useMapKeyboardToSoftFocus } =
|
const leaveTableFocus = useLeaveTableFocus(recordTableId);
|
||||||
useRecordTable({ recordTableId });
|
|
||||||
|
const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({
|
||||||
|
recordTableId,
|
||||||
|
});
|
||||||
|
|
||||||
useMapKeyboardToSoftFocus();
|
useMapKeyboardToSoftFocus();
|
||||||
|
|
||||||
@ -25,9 +32,15 @@ export const RecordTableInternalEffect = ({
|
|||||||
TableHotkeyScope.Table,
|
TableHotkeyScope.Table,
|
||||||
);
|
);
|
||||||
|
|
||||||
useListenClickOutsideByClassName({
|
useListenClickOutsideV2({
|
||||||
classNames: ['entity-table-cell'],
|
excludeClassNames: [
|
||||||
excludeClassNames: ['bottom-bar', 'action-menu-dropdown', 'command-menu'],
|
'bottom-bar',
|
||||||
|
'action-menu-dropdown',
|
||||||
|
'command-menu',
|
||||||
|
'modal-backdrop',
|
||||||
|
],
|
||||||
|
listenerId: RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
refs: [tableBodyRef],
|
||||||
callback: () => {
|
callback: () => {
|
||||||
leaveTableFocus();
|
leaveTableFocus();
|
||||||
},
|
},
|
||||||
|
@ -87,7 +87,10 @@ export const RecordTableWithWrappers = ({
|
|||||||
onDragSelectionChange={setRowSelected}
|
onDragSelectionChange={setRowSelected}
|
||||||
/>
|
/>
|
||||||
</StyledTableInternalContainer>
|
</StyledTableInternalContainer>
|
||||||
<RecordTableInternalEffect recordTableId={recordTableId} />
|
<RecordTableInternalEffect
|
||||||
|
tableBodyRef={tableBodyRef}
|
||||||
|
recordTableId={recordTableId}
|
||||||
|
/>
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
</StyledTableWithHeader>
|
</StyledTableWithHeader>
|
||||||
</RecordUpdateContext.Provider>
|
</RecordUpdateContext.Provider>
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export const RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID = 'record-table';
|
@ -1,19 +1,13 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
|
||||||
|
|
||||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||||
import { useCloseCurrentTableCellInEditMode } from './useCloseCurrentTableCellInEditMode';
|
|
||||||
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
||||||
|
|
||||||
export const useLeaveTableFocus = (recordTableId?: string) => {
|
export const useLeaveTableFocus = (recordTableId?: string) => {
|
||||||
const disableSoftFocus = useDisableSoftFocus(recordTableId);
|
const disableSoftFocus = useDisableSoftFocus(recordTableId);
|
||||||
const closeCurrentCellInEditMode =
|
|
||||||
useCloseCurrentTableCellInEditMode(recordTableId);
|
|
||||||
|
|
||||||
const { isSoftFocusActiveState } = useRecordTableStates(recordTableId);
|
const { isSoftFocusActiveState } = useRecordTableStates(recordTableId);
|
||||||
|
|
||||||
@ -27,28 +21,14 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
|
|||||||
isSoftFocusActiveState,
|
isSoftFocusActiveState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentHotkeyScope = snapshot
|
|
||||||
.getLoadable(currentHotkeyScopeState)
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
|
|
||||||
if (!isSoftFocusActive) {
|
if (!isSoftFocusActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentHotkeyScope?.scope === TableHotkeyScope.Table) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeCurrentCellInEditMode();
|
|
||||||
disableSoftFocus();
|
disableSoftFocus();
|
||||||
},
|
},
|
||||||
[
|
[disableSoftFocus, isSoftFocusActiveState, resetTableRowSelection],
|
||||||
closeCurrentCellInEditMode,
|
|
||||||
disableSoftFocus,
|
|
||||||
isSoftFocusActiveState,
|
|
||||||
resetTableRowSelection,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,10 +4,19 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
|
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||||
|
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
export const RecordTableCellFieldInput = () => {
|
export const RecordTableCellFieldInput = () => {
|
||||||
|
const { getClickOutsideListenerIsActivatedState } =
|
||||||
|
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
|
||||||
|
const setClickOutsideListenerIsActivated = useSetRecoilState(
|
||||||
|
getClickOutsideListenerIsActivatedState,
|
||||||
|
);
|
||||||
|
|
||||||
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
|
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
|
||||||
useContext(RecordTableContext);
|
useContext(RecordTableContext);
|
||||||
|
|
||||||
@ -40,6 +49,8 @@ export const RecordTableCellFieldInput = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClickOutside: FieldInputEvent = (persistField) => {
|
const handleClickOutside: FieldInputEvent = (persistField) => {
|
||||||
|
setClickOutsideListenerIsActivated(false);
|
||||||
|
|
||||||
onUpsertRecord({
|
onUpsertRecord({
|
||||||
persistField,
|
persistField,
|
||||||
recordId,
|
recordId,
|
||||||
|
@ -21,6 +21,8 @@ import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
|
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||||
|
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
@ -42,6 +44,9 @@ export type OpenTableCellArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||||
|
const { getClickOutsideListenerIsActivatedState } =
|
||||||
|
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
|
||||||
|
|
||||||
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
|
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
|
||||||
const moveEditModeToTableCellPosition =
|
const moveEditModeToTableCellPosition =
|
||||||
useMoveEditModeToTableCellPosition(tableScopeId);
|
useMoveEditModeToTableCellPosition(tableScopeId);
|
||||||
@ -65,7 +70,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const openTableCell = useRecoilCallback(
|
const openTableCell = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot, set }) =>
|
||||||
({
|
({
|
||||||
initialValue,
|
initialValue,
|
||||||
cellPosition,
|
cellPosition,
|
||||||
@ -80,6 +85,8 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set(getClickOutsideListenerIsActivatedState, false);
|
||||||
|
|
||||||
const isFirstColumnCell = cellPosition.column === 0;
|
const isFirstColumnCell = cellPosition.column === 0;
|
||||||
|
|
||||||
const fieldValue = getSnapshotValue(
|
const fieldValue = getSnapshotValue(
|
||||||
@ -137,17 +144,18 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
getClickOutsideListenerIsActivatedState,
|
||||||
setDragSelectionStartEnabled,
|
setDragSelectionStartEnabled,
|
||||||
|
moveEditModeToTableCellPosition,
|
||||||
|
initDraftValue,
|
||||||
toggleClickOutsideListener,
|
toggleClickOutsideListener,
|
||||||
leaveTableFocus,
|
leaveTableFocus,
|
||||||
setHotkeyScope,
|
navigate,
|
||||||
initDraftValue,
|
indexIdentifierUrl,
|
||||||
moveEditModeToTableCellPosition,
|
|
||||||
openRightDrawer,
|
|
||||||
setViewableRecordId,
|
setViewableRecordId,
|
||||||
setViewableRecordNameSingular,
|
setViewableRecordNameSingular,
|
||||||
indexIdentifierUrl,
|
openRightDrawer,
|
||||||
navigate,
|
setHotkeyScope,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ export const SingleEntitySelect = ({
|
|||||||
onEntitySelected,
|
onEntitySelected,
|
||||||
relationObjectNameSingular,
|
relationObjectNameSingular,
|
||||||
relationPickerScopeId,
|
relationPickerScopeId,
|
||||||
selectedEntity,
|
|
||||||
selectedRelationRecordIds,
|
selectedRelationRecordIds,
|
||||||
width = 200,
|
width = 200,
|
||||||
}: SingleEntitySelectProps) => {
|
}: SingleEntitySelectProps) => {
|
||||||
@ -61,7 +60,6 @@ export const SingleEntitySelect = ({
|
|||||||
onEntitySelected,
|
onEntitySelected,
|
||||||
relationObjectNameSingular,
|
relationObjectNameSingular,
|
||||||
relationPickerScopeId,
|
relationPickerScopeId,
|
||||||
selectedEntity,
|
|
||||||
selectedRelationRecordIds,
|
selectedRelationRecordIds,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -37,6 +37,7 @@ export type SingleEntitySelectMenuItemsProps = {
|
|||||||
onAllEntitySelected?: () => void;
|
onAllEntitySelected?: () => void;
|
||||||
hotkeyScope?: string;
|
hotkeyScope?: string;
|
||||||
isFiltered: boolean;
|
isFiltered: boolean;
|
||||||
|
shouldSelectEmptyOption?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SingleEntitySelectMenuItems = ({
|
export const SingleEntitySelectMenuItems = ({
|
||||||
@ -56,6 +57,7 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
onAllEntitySelected,
|
onAllEntitySelected,
|
||||||
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
|
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
|
||||||
isFiltered,
|
isFiltered,
|
||||||
|
shouldSelectEmptyOption,
|
||||||
}: SingleEntitySelectMenuItemsProps) => {
|
}: SingleEntitySelectMenuItemsProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -181,7 +183,7 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
onClick={() => onEntitySelected()}
|
onClick={() => onEntitySelected()}
|
||||||
LeftIcon={EmptyIcon}
|
LeftIcon={EmptyIcon}
|
||||||
text={emptyLabel}
|
text={emptyLabel}
|
||||||
selected={!selectedEntity}
|
selected={shouldSelectEmptyOption === true}
|
||||||
hovered={isSelectedSelectNoneButton}
|
hovered={isSelectedSelectNoneButton}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -35,7 +35,6 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
onEntitySelected,
|
onEntitySelected,
|
||||||
relationObjectNameSingular,
|
relationObjectNameSingular,
|
||||||
relationPickerScopeId = 'relation-picker',
|
relationPickerScopeId = 'relation-picker',
|
||||||
selectedEntity,
|
|
||||||
selectedRelationRecordIds,
|
selectedRelationRecordIds,
|
||||||
dropdownPlacement,
|
dropdownPlacement,
|
||||||
}: SingleEntitySelectMenuItemsWithSearchProps) => {
|
}: SingleEntitySelectMenuItemsWithSearchProps) => {
|
||||||
@ -71,11 +70,11 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
entitiesToSelect={entities.entitiesToSelect}
|
entitiesToSelect={entities.entitiesToSelect}
|
||||||
loading={entities.loading}
|
loading={entities.loading}
|
||||||
selectedEntity={
|
selectedEntity={
|
||||||
selectedEntity ??
|
entities.selectedEntities.length === 1
|
||||||
(entities.selectedEntities.length === 1
|
|
||||||
? entities.selectedEntities[0]
|
? entities.selectedEntities[0]
|
||||||
: undefined)
|
: undefined
|
||||||
}
|
}
|
||||||
|
shouldSelectEmptyOption={selectedRelationRecordIds?.length === 0}
|
||||||
hotkeyScope={relationPickerScopeId}
|
hotkeyScope={relationPickerScopeId}
|
||||||
onCreate={onCreateWithInput}
|
onCreate={onCreateWithInput}
|
||||||
isFiltered={!!relationPickerSearchFilter}
|
isFiltered={!!relationPickerSearchFilter}
|
||||||
|
@ -37,7 +37,7 @@ export const sanitizeRecordInput = ({
|
|||||||
(field) => field.name === relationIdFieldName,
|
(field) => field.name === relationIdFieldName,
|
||||||
);
|
);
|
||||||
|
|
||||||
return relationIdFieldMetadataItem && fieldValue?.id
|
return relationIdFieldMetadataItem
|
||||||
? [relationIdFieldName, fieldValue?.id ?? null]
|
? [relationIdFieldName, fieldValue?.id ?? null]
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,6 @@ type SettingsDataModelFieldSettingsFormCardProps = {
|
|||||||
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
|
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
|
||||||
|
|
||||||
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
|
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
|
||||||
display: grid;
|
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||||
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
|
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
|
||||||
import { ViewBar } from '@/views/components/ViewBar';
|
import { ViewBar } from '@/views/components/ViewBar';
|
||||||
@ -20,36 +22,54 @@ export const SignInBackgroundMockContainer = () => {
|
|||||||
const recordIndexId = 'sign-up-mock-record-table-id';
|
const recordIndexId = 'sign-up-mock-record-table-id';
|
||||||
const viewBarId = 'companies-mock';
|
const viewBarId = 'companies-mock';
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<ViewComponentInstanceContext.Provider value={{ instanceId: viewBarId }}>
|
<RecordIndexRootPropsContext.Provider
|
||||||
<ContextStoreComponentInstanceContext.Provider
|
value={{
|
||||||
value={{
|
recordIndexId,
|
||||||
instanceId: recordIndexId,
|
objectNamePlural,
|
||||||
}}
|
objectNameSingular,
|
||||||
|
objectMetadataItem,
|
||||||
|
onIndexRecordsLoaded: () => {},
|
||||||
|
indexIdentifierUrl: () => '',
|
||||||
|
onCreateRecord: () => {},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ViewComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordIndexId }}
|
||||||
>
|
>
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
<ContextStoreComponentInstanceContext.Provider
|
||||||
value={{ instanceId: recordIndexId }}
|
value={{
|
||||||
|
instanceId: recordIndexId,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ViewBar
|
<ActionMenuComponentInstanceContext.Provider
|
||||||
viewBarId={viewBarId}
|
value={{ instanceId: recordIndexId }}
|
||||||
onCurrentViewChange={async () => {}}
|
>
|
||||||
optionsDropdownButton={<></>}
|
<ViewBar
|
||||||
/>
|
viewBarId={viewBarId}
|
||||||
<SignInBackgroundMockContainerEffect
|
onCurrentViewChange={() => {}}
|
||||||
objectNamePlural={objectNamePlural}
|
optionsDropdownButton={<></>}
|
||||||
recordTableId={recordIndexId}
|
/>
|
||||||
viewId={viewBarId}
|
<SignInBackgroundMockContainerEffect
|
||||||
/>
|
objectNamePlural={objectNamePlural}
|
||||||
<RecordTableWithWrappers
|
recordTableId={recordIndexId}
|
||||||
objectNameSingular={objectNameSingular}
|
viewId={viewBarId}
|
||||||
recordTableId={recordIndexId}
|
/>
|
||||||
viewBarId={viewBarId}
|
<RecordTableWithWrappers
|
||||||
updateRecordMutation={() => {}}
|
objectNameSingular={objectNameSingular}
|
||||||
/>
|
recordTableId={recordIndexId}
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
viewBarId={viewBarId}
|
||||||
</ContextStoreComponentInstanceContext.Provider>
|
updateRecordMutation={() => {}}
|
||||||
</ViewComponentInstanceContext.Provider>
|
/>
|
||||||
|
</ActionMenuComponentInstanceContext.Provider>
|
||||||
|
</ContextStoreComponentInstanceContext.Provider>
|
||||||
|
</ViewComponentInstanceContext.Provider>
|
||||||
|
</RecordIndexRootPropsContext.Provider>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,7 @@ const StyledControlContainer = styled.div<{
|
|||||||
color: ${({ disabled, theme }) =>
|
color: ${({ disabled, theme }) =>
|
||||||
disabled ? theme.font.color.tertiary : theme.font.color.primary};
|
disabled ? theme.font.color.tertiary : theme.font.color.primary};
|
||||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
|
text-align: left;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIconChevronDown = styled(IconChevronDown)<{
|
const StyledIconChevronDown = styled(IconChevronDown)<{
|
||||||
|
@ -126,7 +126,6 @@ const StyledContainer = styled.div<{ calendarDisabled?: boolean }>`
|
|||||||
}
|
}
|
||||||
& .react-datepicker__month-dropdown {
|
& .react-datepicker__month-dropdown {
|
||||||
left: ${({ theme }) => theme.spacing(2)};
|
left: ${({ theme }) => theme.spacing(2)};
|
||||||
width: 160px;
|
|
||||||
height: 260px;
|
height: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ export type NavigationDrawerProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledAnimatedContainer = styled(motion.div)`
|
const StyledAnimatedContainer = styled(motion.div)<{ isSettings?: boolean }>`
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: ${({ isSettings }) => (isSettings ? 'visible' : 'hidden')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledContainer = styled.div<{
|
const StyledContainer = styled.div<{
|
||||||
@ -50,11 +50,12 @@ const StyledContainer = styled.div<{
|
|||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const StyledItemsContainer = styled.div`
|
|
||||||
|
const StyledItemsContainer = styled.div<{ isSettings?: boolean }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
overflow: hidden;
|
overflow: ${({ isSettings }) => (isSettings ? 'visible' : 'hidden')};
|
||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -102,6 +103,7 @@ export const NavigationDrawer = ({
|
|||||||
transition={{
|
transition={{
|
||||||
duration: theme.animation.duration.normal,
|
duration: theme.animation.duration.normal,
|
||||||
}}
|
}}
|
||||||
|
isSettings={isSettingsDrawer}
|
||||||
>
|
>
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
isSettings={isSettingsDrawer}
|
isSettings={isSettingsDrawer}
|
||||||
@ -118,7 +120,9 @@ export const NavigationDrawer = ({
|
|||||||
showCollapseButton={isHovered}
|
showCollapseButton={isHovered}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<StyledItemsContainer>{children}</StyledItemsContainer>
|
<StyledItemsContainer isSettings={isSettingsDrawer}>
|
||||||
|
{children}
|
||||||
|
</StyledItemsContainer>
|
||||||
{footer}
|
{footer}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</StyledAnimatedContainer>
|
</StyledAnimatedContainer>
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
|
import { fireEvent, renderHook } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { fireEvent, render, renderHook } from '@testing-library/react';
|
|
||||||
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ClickOutsideMode,
|
ClickOutsideMode,
|
||||||
useListenClickOutside,
|
useListenClickOutside,
|
||||||
useListenClickOutsideByClassName,
|
|
||||||
} from '../useListenClickOutside';
|
} from '../useListenClickOutside';
|
||||||
|
|
||||||
const containerRef = React.createRef<HTMLDivElement>();
|
const containerRef = React.createRef<HTMLDivElement>();
|
||||||
@ -77,59 +76,3 @@ describe('useListenClickOutside', () => {
|
|||||||
expect(callback).toHaveBeenCalled();
|
expect(callback).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('useListenClickOutsideByClassName', () => {
|
|
||||||
it('should trigger the callback when clicking outside the specified class names', () => {
|
|
||||||
const callback = jest.fn();
|
|
||||||
const { container } = render(
|
|
||||||
<div>
|
|
||||||
<div className="wont-trigger other-class">Inside</div>
|
|
||||||
<div className="will-trigger">Outside</div>
|
|
||||||
</div>,
|
|
||||||
);
|
|
||||||
|
|
||||||
renderHook(() =>
|
|
||||||
useListenClickOutsideByClassName({
|
|
||||||
classNames: ['wont-trigger'],
|
|
||||||
callback,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
const notClickableElement = container.querySelector('.will-trigger');
|
|
||||||
if (isDefined(notClickableElement)) {
|
|
||||||
fireEvent.mouseDown(notClickableElement);
|
|
||||||
fireEvent.click(notClickableElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(callback).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not trigger the callback when clicking inside the specified class names', () => {
|
|
||||||
const callback = jest.fn();
|
|
||||||
const { container } = render(
|
|
||||||
<div>
|
|
||||||
<div className="wont-trigger other-class">Inside</div>
|
|
||||||
<div className="will-trigger">Outside</div>
|
|
||||||
</div>,
|
|
||||||
);
|
|
||||||
|
|
||||||
renderHook(() =>
|
|
||||||
useListenClickOutsideByClassName({
|
|
||||||
classNames: ['wont-trigger'],
|
|
||||||
callback,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
const notClickableElement = container.querySelector('.wont-trigger');
|
|
||||||
if (isDefined(notClickableElement)) {
|
|
||||||
fireEvent.mouseDown(notClickableElement);
|
|
||||||
fireEvent.click(notClickableElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(callback).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -138,58 +138,3 @@ export const useListenClickOutside = <T extends Element>({
|
|||||||
}
|
}
|
||||||
}, [refs, callback, mode, enabled, isMouseDownInside]);
|
}, [refs, callback, mode, enabled, isMouseDownInside]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useListenClickOutsideByClassName = ({
|
|
||||||
classNames,
|
|
||||||
excludeClassNames,
|
|
||||||
callback,
|
|
||||||
}: {
|
|
||||||
classNames: string[];
|
|
||||||
excludeClassNames?: string[];
|
|
||||||
callback: () => void;
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
|
||||||
if (!(event.target instanceof Node)) return;
|
|
||||||
|
|
||||||
const clickedElement = event.target as HTMLElement;
|
|
||||||
let isClickedInside = false;
|
|
||||||
let isClickedOnExcluded = false;
|
|
||||||
let currentElement: HTMLElement | null = clickedElement;
|
|
||||||
|
|
||||||
while (currentElement) {
|
|
||||||
const currentClassList = currentElement.classList;
|
|
||||||
|
|
||||||
isClickedInside = classNames.some((className) =>
|
|
||||||
currentClassList.contains(className),
|
|
||||||
);
|
|
||||||
isClickedOnExcluded =
|
|
||||||
excludeClassNames?.some((className) =>
|
|
||||||
currentClassList.contains(className),
|
|
||||||
) ?? false;
|
|
||||||
|
|
||||||
if (isClickedInside || isClickedOnExcluded) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentElement = currentElement.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isClickedInside && !isClickedOnExcluded) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
document.addEventListener('touchend', handleClickOutside, {
|
|
||||||
capture: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
document.removeEventListener('touchend', handleClickOutside, {
|
|
||||||
capture: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [callback, classNames, excludeClassNames]);
|
|
||||||
};
|
|
||||||
|
@ -10,6 +10,7 @@ export enum ClickOutsideMode {
|
|||||||
|
|
||||||
export type ClickOutsideListenerProps<T extends Element> = {
|
export type ClickOutsideListenerProps<T extends Element> = {
|
||||||
refs: Array<React.RefObject<T>>;
|
refs: Array<React.RefObject<T>>;
|
||||||
|
excludeClassNames?: string[];
|
||||||
callback: (event: MouseEvent | TouchEvent) => void;
|
callback: (event: MouseEvent | TouchEvent) => void;
|
||||||
mode?: ClickOutsideMode;
|
mode?: ClickOutsideMode;
|
||||||
listenerId: string;
|
listenerId: string;
|
||||||
@ -18,6 +19,7 @@ export type ClickOutsideListenerProps<T extends Element> = {
|
|||||||
|
|
||||||
export const useListenClickOutsideV2 = <T extends Element>({
|
export const useListenClickOutsideV2 = <T extends Element>({
|
||||||
refs,
|
refs,
|
||||||
|
excludeClassNames,
|
||||||
callback,
|
callback,
|
||||||
mode = ClickOutsideMode.compareHTMLRef,
|
mode = ClickOutsideMode.compareHTMLRef,
|
||||||
listenerId,
|
listenerId,
|
||||||
@ -106,11 +108,34 @@ export const useListenClickOutsideV2 = <T extends Element>({
|
|||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
if (mode === ClickOutsideMode.compareHTMLRef) {
|
if (mode === ClickOutsideMode.compareHTMLRef) {
|
||||||
|
const clickedElement = event.target as HTMLElement;
|
||||||
|
let isClickedOnExcluded = false;
|
||||||
|
let currentElement: HTMLElement | null = clickedElement;
|
||||||
|
|
||||||
|
while (currentElement) {
|
||||||
|
const currentClassList = currentElement.classList;
|
||||||
|
|
||||||
|
isClickedOnExcluded =
|
||||||
|
excludeClassNames?.some((className) =>
|
||||||
|
currentClassList.contains(className),
|
||||||
|
) ?? false;
|
||||||
|
|
||||||
|
if (isClickedOnExcluded) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentElement = currentElement.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
const clickedOnAtLeastOneRef = refs
|
const clickedOnAtLeastOneRef = refs
|
||||||
.filter((ref) => !!ref.current)
|
.filter((ref) => !!ref.current)
|
||||||
.some((ref) => ref.current?.contains(event.target as Node));
|
.some((ref) => ref.current?.contains(event.target as Node));
|
||||||
|
|
||||||
if (!clickedOnAtLeastOneRef && !isMouseDownInside) {
|
if (
|
||||||
|
!clickedOnAtLeastOneRef &&
|
||||||
|
!isMouseDownInside &&
|
||||||
|
!isClickedOnExcluded
|
||||||
|
) {
|
||||||
callback(event);
|
callback(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +176,13 @@ export const useListenClickOutsideV2 = <T extends Element>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mode, refs, callback, getClickOutsideListenerIsMouseDownInsideState],
|
[
|
||||||
|
getClickOutsideListenerIsMouseDownInsideState,
|
||||||
|
mode,
|
||||||
|
refs,
|
||||||
|
excludeClassNames,
|
||||||
|
callback,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -87,9 +87,9 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Here we might instead want to get view from unsaved filters ?
|
// Here we might instead want to get view from unsaved filters ?
|
||||||
const view = await getViewFromCache(currentViewId);
|
const sourceView = await getViewFromCache(currentViewId);
|
||||||
|
|
||||||
if (!isDefined(view)) {
|
if (!isDefined(sourceView)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,23 +97,23 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
|||||||
|
|
||||||
const newView = await createOneRecord({
|
const newView = await createOneRecord({
|
||||||
id: id ?? v4(),
|
id: id ?? v4(),
|
||||||
name: name ?? view.name,
|
name: name ?? sourceView.name,
|
||||||
icon: icon ?? view.icon,
|
icon: icon ?? sourceView.icon,
|
||||||
key: null,
|
key: null,
|
||||||
kanbanFieldMetadataId:
|
kanbanFieldMetadataId:
|
||||||
kanbanFieldMetadataId ?? view.kanbanFieldMetadataId,
|
kanbanFieldMetadataId ?? sourceView.kanbanFieldMetadataId,
|
||||||
type: type ?? view.type,
|
type: type ?? sourceView.type,
|
||||||
objectMetadataId: view.objectMetadataId,
|
objectMetadataId: sourceView.objectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isUndefinedOrNull(newView)) {
|
if (isUndefinedOrNull(newView)) {
|
||||||
throw new Error('Failed to create view');
|
throw new Error('Failed to create view');
|
||||||
}
|
}
|
||||||
|
|
||||||
await createViewFieldRecords(view.viewFields, newView);
|
await createViewFieldRecords(sourceView.viewFields, newView);
|
||||||
|
|
||||||
if (type === ViewType.Kanban) {
|
if (type === ViewType.Kanban) {
|
||||||
if (!isNonEmptyArray(view.viewGroups)) {
|
if (!isNonEmptyArray(sourceView.viewGroups)) {
|
||||||
if (!isDefined(kanbanFieldMetadataId)) {
|
if (!isDefined(kanbanFieldMetadataId)) {
|
||||||
throw new Error('Kanban view must have a kanban field');
|
throw new Error('Kanban view must have a kanban field');
|
||||||
}
|
}
|
||||||
@ -144,22 +144,24 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
|||||||
|
|
||||||
await createViewGroupRecords(viewGroupsToCreate, newView);
|
await createViewGroupRecords(viewGroupsToCreate, newView);
|
||||||
} else {
|
} else {
|
||||||
await createViewGroupRecords(view.viewGroups, newView);
|
await createViewGroupRecords(sourceView.viewGroups, newView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCopyFiltersAndSorts === true) {
|
if (shouldCopyFiltersAndSorts === true) {
|
||||||
const sourceViewCombinedFilterGroups = getViewFilterGroupsCombined(
|
const sourceViewCombinedFilterGroups = getViewFilterGroupsCombined(
|
||||||
view.id,
|
sourceView.id,
|
||||||
);
|
);
|
||||||
const sourceViewCombinedFilters = getViewFiltersCombined(view.id);
|
const sourceViewCombinedFilters = getViewFiltersCombined(
|
||||||
const sourceViewCombinedSorts = getViewSortsCombined(view.id);
|
sourceView.id,
|
||||||
|
);
|
||||||
|
const sourceViewCombinedSorts = getViewSortsCombined(sourceView.id);
|
||||||
|
|
||||||
await createViewSortRecords(sourceViewCombinedSorts, view);
|
await createViewSortRecords(sourceViewCombinedSorts, newView);
|
||||||
await createViewFilterRecords(sourceViewCombinedFilters, view);
|
await createViewFilterRecords(sourceViewCombinedFilters, newView);
|
||||||
await createViewFilterGroupRecords(
|
await createViewFilterGroupRecords(
|
||||||
sourceViewCombinedFilterGroups,
|
sourceViewCombinedFilterGroups,
|
||||||
view,
|
newView,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +113,33 @@ export const graphqlMocks = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
graphql.query('FindManyViews', ({ variables }) => {
|
||||||
|
const objectMetadataId = variables.filter?.objectMetadataId?.eq;
|
||||||
|
const viewType = variables.filter?.type?.eq;
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
data: {
|
||||||
|
views: {
|
||||||
|
edges: mockedViewsData
|
||||||
|
.filter(
|
||||||
|
(view) =>
|
||||||
|
view?.objectMetadataId === objectMetadataId &&
|
||||||
|
view?.type === viewType,
|
||||||
|
)
|
||||||
|
.map((view) => ({
|
||||||
|
node: view,
|
||||||
|
cursor: null,
|
||||||
|
})),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: false,
|
||||||
|
hasPreviousPage: false,
|
||||||
|
startCursor: null,
|
||||||
|
endCursor: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
graphql.query('SearchWorkspaceMembers', () => {
|
graphql.query('SearchWorkspaceMembers', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
|
@ -25,7 +25,8 @@ export const mockedViewsData = [
|
|||||||
createdAt: '2021-09-01T00:00:00.000Z',
|
createdAt: '2021-09-01T00:00:00.000Z',
|
||||||
updatedAt: '2021-09-01T00:00:00.000Z',
|
updatedAt: '2021-09-01T00:00:00.000Z',
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
|
viewFilterGroups: [],
|
||||||
|
viewGroups: [],
|
||||||
__typename: 'View',
|
__typename: 'View',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,6 +41,8 @@ export const mockedViewsData = [
|
|||||||
createdAt: '2021-09-01T00:00:00.000Z',
|
createdAt: '2021-09-01T00:00:00.000Z',
|
||||||
updatedAt: '2021-09-01T00:00:00.000Z',
|
updatedAt: '2021-09-01T00:00:00.000Z',
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
|
viewFilterGroups: [],
|
||||||
|
viewGroups: [],
|
||||||
__typename: 'View',
|
__typename: 'View',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,6 +57,8 @@ export const mockedViewsData = [
|
|||||||
createdAt: '2021-09-01T00:00:00.000Z',
|
createdAt: '2021-09-01T00:00:00.000Z',
|
||||||
updatedAt: '2021-09-01T00:00:00.000Z',
|
updatedAt: '2021-09-01T00:00:00.000Z',
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
|
viewFilterGroups: [],
|
||||||
|
viewGroups: [],
|
||||||
__typename: 'View',
|
__typename: 'View',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -68,6 +73,8 @@ export const mockedViewsData = [
|
|||||||
createdAt: '2021-09-01T00:00:00.000Z',
|
createdAt: '2021-09-01T00:00:00.000Z',
|
||||||
updatedAt: '2021-09-01T00:00:00.000Z',
|
updatedAt: '2021-09-01T00:00:00.000Z',
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
|
viewFilterGroups: [],
|
||||||
|
viewGroups: [],
|
||||||
__typename: 'View',
|
__typename: 'View',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Command } from 'nest-commander';
|
|
||||||
import { ObjectLiteral, Repository } from 'typeorm';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import { Command } from 'nest-commander';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||||
|
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
|
||||||
|
|
||||||
@Command({
|
@Command({
|
||||||
name: 'upgrade-0.32:copy-webhook-operation-into-operations',
|
name: 'upgrade-0.32:copy-webhook-operation-into-operations',
|
||||||
|
@ -79,11 +79,10 @@ export class SimplifySearchVectorExpressionCommand extends ActiveWorkspacesComma
|
|||||||
fieldsUsedForSearch = SEARCH_FIELDS_FOR_OPPORTUNITY;
|
fieldsUsedForSearch = SEARCH_FIELDS_FOR_OPPORTUNITY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
}
|
||||||
throw new Error(
|
|
||||||
`search vector has unexpected standardId: ${searchVectorField.standardId}`,
|
if (fieldsUsedForSearch.length === 0) {
|
||||||
);
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.searchService.updateSearchVector(
|
await this.searchService.updateSearchVector(
|
||||||
|
@ -4,6 +4,7 @@ import { Command } from 'nest-commander';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||||
|
import { CopyWebhookOperationIntoOperationsCommand } from 'src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command';
|
||||||
import { SimplifySearchVectorExpressionCommand } from 'src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression';
|
import { SimplifySearchVectorExpressionCommand } from 'src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||||
@ -25,6 +26,7 @@ export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner {
|
|||||||
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||||
private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand,
|
private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand,
|
||||||
private readonly simplifySearchVectorExpressionCommand: SimplifySearchVectorExpressionCommand,
|
private readonly simplifySearchVectorExpressionCommand: SimplifySearchVectorExpressionCommand,
|
||||||
|
private readonly copyWebhookOperationIntoOperationsCommand: CopyWebhookOperationIntoOperationsCommand,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository);
|
||||||
}
|
}
|
||||||
@ -54,5 +56,11 @@ export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner {
|
|||||||
options,
|
options,
|
||||||
workspaceIds,
|
workspaceIds,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.copyWebhookOperationIntoOperationsCommand.executeActiveWorkspacesCommand(
|
||||||
|
passedParam,
|
||||||
|
options,
|
||||||
|
workspaceIds,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,12 +88,12 @@ export class GraphqlQuerySearchResolverService
|
|||||||
qb.where(
|
qb.where(
|
||||||
searchTerms === ''
|
searchTerms === ''
|
||||||
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
|
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
|
||||||
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`,
|
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery('simple', :searchTerms)`,
|
||||||
searchTerms === '' ? {} : { searchTerms },
|
searchTerms === '' ? {} : { searchTerms },
|
||||||
).orWhere(
|
).orWhere(
|
||||||
searchTermsOr === ''
|
searchTermsOr === ''
|
||||||
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
|
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
|
||||||
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTermsOr)`,
|
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery('simple', :searchTermsOr)`,
|
||||||
searchTermsOr === '' ? {} : { searchTermsOr },
|
searchTermsOr === '' ? {} : { searchTermsOr },
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -25,7 +25,6 @@ const ATTACHMENT_GQL_FIELDS = `
|
|||||||
updatedAt
|
updatedAt
|
||||||
deletedAt
|
deletedAt
|
||||||
authorId
|
authorId
|
||||||
activityId
|
|
||||||
taskId
|
taskId
|
||||||
noteId
|
noteId
|
||||||
personId
|
personId
|
||||||
@ -70,7 +69,6 @@ describe('attachments resolvers (integration)', () => {
|
|||||||
expect(attachment).toHaveProperty('updatedAt');
|
expect(attachment).toHaveProperty('updatedAt');
|
||||||
expect(attachment).toHaveProperty('deletedAt');
|
expect(attachment).toHaveProperty('deletedAt');
|
||||||
expect(attachment).toHaveProperty('authorId');
|
expect(attachment).toHaveProperty('authorId');
|
||||||
expect(attachment).toHaveProperty('activityId');
|
|
||||||
expect(attachment).toHaveProperty('taskId');
|
expect(attachment).toHaveProperty('taskId');
|
||||||
expect(attachment).toHaveProperty('noteId');
|
expect(attachment).toHaveProperty('noteId');
|
||||||
expect(attachment).toHaveProperty('personId');
|
expect(attachment).toHaveProperty('personId');
|
||||||
@ -104,7 +102,6 @@ describe('attachments resolvers (integration)', () => {
|
|||||||
expect(createdAttachment).toHaveProperty('updatedAt');
|
expect(createdAttachment).toHaveProperty('updatedAt');
|
||||||
expect(createdAttachment).toHaveProperty('deletedAt');
|
expect(createdAttachment).toHaveProperty('deletedAt');
|
||||||
expect(createdAttachment).toHaveProperty('authorId');
|
expect(createdAttachment).toHaveProperty('authorId');
|
||||||
expect(createdAttachment).toHaveProperty('activityId');
|
|
||||||
expect(createdAttachment).toHaveProperty('taskId');
|
expect(createdAttachment).toHaveProperty('taskId');
|
||||||
expect(createdAttachment).toHaveProperty('noteId');
|
expect(createdAttachment).toHaveProperty('noteId');
|
||||||
expect(createdAttachment).toHaveProperty('personId');
|
expect(createdAttachment).toHaveProperty('personId');
|
||||||
@ -137,7 +134,6 @@ describe('attachments resolvers (integration)', () => {
|
|||||||
expect(attachments).toHaveProperty('updatedAt');
|
expect(attachments).toHaveProperty('updatedAt');
|
||||||
expect(attachments).toHaveProperty('deletedAt');
|
expect(attachments).toHaveProperty('deletedAt');
|
||||||
expect(attachments).toHaveProperty('authorId');
|
expect(attachments).toHaveProperty('authorId');
|
||||||
expect(attachments).toHaveProperty('activityId');
|
|
||||||
expect(attachments).toHaveProperty('taskId');
|
expect(attachments).toHaveProperty('taskId');
|
||||||
expect(attachments).toHaveProperty('noteId');
|
expect(attachments).toHaveProperty('noteId');
|
||||||
expect(attachments).toHaveProperty('personId');
|
expect(attachments).toHaveProperty('personId');
|
||||||
@ -169,7 +165,6 @@ describe('attachments resolvers (integration)', () => {
|
|||||||
expect(attachment).toHaveProperty('updatedAt');
|
expect(attachment).toHaveProperty('updatedAt');
|
||||||
expect(attachment).toHaveProperty('deletedAt');
|
expect(attachment).toHaveProperty('deletedAt');
|
||||||
expect(attachment).toHaveProperty('authorId');
|
expect(attachment).toHaveProperty('authorId');
|
||||||
expect(attachment).toHaveProperty('activityId');
|
|
||||||
expect(attachment).toHaveProperty('taskId');
|
expect(attachment).toHaveProperty('taskId');
|
||||||
expect(attachment).toHaveProperty('noteId');
|
expect(attachment).toHaveProperty('noteId');
|
||||||
expect(attachment).toHaveProperty('personId');
|
expect(attachment).toHaveProperty('personId');
|
||||||
|
@ -14,8 +14,8 @@ import { generateRecordName } from 'test/integration/utils/generate-record-name'
|
|||||||
const NOTE_TARGET_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
|
const NOTE_TARGET_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
|
||||||
const NOTE_TARGET_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
|
const NOTE_TARGET_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
|
||||||
const NOTE_TARGET_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
|
const NOTE_TARGET_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
|
||||||
const PERSON_1_ID = '777a8457-eb2d-40ac-a707-441b615b6989';
|
const PERSON_1_ID = 'f875ff46-b881-41ba-973a-b9cd5345e8f0';
|
||||||
const PERSON_2_ID = '777a8457-eb2d-40ac-a707-331b615b6989';
|
const PERSON_2_ID = '1fe0f78c-8c59-4ce6-ae02-56571331b252';
|
||||||
const NOTE_TARGET_GQL_FIELDS = `
|
const NOTE_TARGET_GQL_FIELDS = `
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
|
@ -14,8 +14,8 @@ import { generateRecordName } from 'test/integration/utils/generate-record-name'
|
|||||||
const TASK_TARGET_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
|
const TASK_TARGET_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
|
||||||
const TASK_TARGET_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
|
const TASK_TARGET_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
|
||||||
const TASK_TARGET_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
|
const TASK_TARGET_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
|
||||||
const PERSON_1_ID = '777a8457-eb2d-40ac-a707-441b615b6989';
|
const PERSON_1_ID = 'f875ff46-b881-41ba-973a-b9cd5345e8f0';
|
||||||
const PERSON_2_ID = '777a8457-eb2d-40ac-a707-331b615b6989';
|
const PERSON_2_ID = '1fe0f78c-8c59-4ce6-ae02-56571331b252';
|
||||||
const TASK_TARGET_GQL_FIELDS = `
|
const TASK_TARGET_GQL_FIELDS = `
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
|
@ -42,7 +42,7 @@ const StyledTag = styled.h3<{
|
|||||||
padding: 0 ${spacing2};
|
padding: 0 ${spacing2};
|
||||||
border: ${({ variant, theme }) =>
|
border: ${({ variant, theme }) =>
|
||||||
variant === 'outline' || variant === 'border'
|
variant === 'outline' || variant === 'border'
|
||||||
? `1px ${variant === 'border' ? 'solid' : 'dash'} ${theme.border.color.strong}`
|
? `1px ${variant === 'border' ? 'solid' : 'dashed'} ${theme.border.color.strong}`
|
||||||
: ''};
|
: ''};
|
||||||
|
|
||||||
gap: ${spacing1};
|
gap: ${spacing1};
|
||||||
|
Loading…
Reference in New Issue
Block a user