Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12bf71215f |
@@ -1,5 +1,17 @@
|
||||
import {
|
||||
type ContextStorePageType,
|
||||
type ContextStoreViewType,
|
||||
type RecordGqlOperationFilter,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
export type FrontComponentExecutionContext = {
|
||||
frontComponentId: string;
|
||||
userId: string | null;
|
||||
recordId: string | null;
|
||||
pageType: ContextStorePageType | null;
|
||||
viewType: ContextStoreViewType | null;
|
||||
viewId: string | null;
|
||||
objectMetadataItemId: string | null;
|
||||
numberOfSelectedRecords: number;
|
||||
graphqlFilter: RecordGqlOperationFilter | null;
|
||||
};
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ export const HeadlessFrontComponentRendererEngineCommand = () => {
|
||||
frontComponentId={context.frontComponentId}
|
||||
commandMenuItemId={commandMenuItemId}
|
||||
recordId={recordId}
|
||||
contextStoreInstanceId={context.contextStoreInstanceId}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
+3
-13
@@ -1,21 +1,11 @@
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { type RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
import { type RecordShowParentView } from 'twenty-shared/types';
|
||||
|
||||
type RecordShowParentViewComponentState = {
|
||||
parentViewComponentId: string;
|
||||
parentViewObjectNameSingular: string;
|
||||
parentViewFilterGroups: RecordFilterGroup[];
|
||||
parentViewFilters: RecordFilter[];
|
||||
parentViewSorts: RecordSort[];
|
||||
};
|
||||
export type { RecordShowParentView } from 'twenty-shared/types';
|
||||
|
||||
export const contextStoreRecordShowParentViewComponentState =
|
||||
createAtomComponentState<
|
||||
RecordShowParentViewComponentState | undefined | null
|
||||
>({
|
||||
createAtomComponentState<RecordShowParentView | undefined | null>({
|
||||
key: 'contextStoreRecordShowParentViewComponentState',
|
||||
defaultValue: undefined,
|
||||
componentInstanceContext: ContextStoreComponentInstanceContext,
|
||||
|
||||
+2
-13
@@ -1,19 +1,8 @@
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
import { type ContextStoreTargetedRecordsRule } from 'twenty-shared/types';
|
||||
|
||||
type ContextStoreTargetedRecordsRuleSelectionMode = {
|
||||
mode: 'selection';
|
||||
selectedRecordIds: string[];
|
||||
};
|
||||
|
||||
type ContextStoreTargetedRecordsRuleExclusionMode = {
|
||||
mode: 'exclusion';
|
||||
excludedRecordIds: string[];
|
||||
};
|
||||
|
||||
export type ContextStoreTargetedRecordsRule =
|
||||
| ContextStoreTargetedRecordsRuleSelectionMode
|
||||
| ContextStoreTargetedRecordsRuleExclusionMode;
|
||||
export type { ContextStoreTargetedRecordsRule } from 'twenty-shared/types';
|
||||
|
||||
export const contextStoreTargetedRecordsRuleComponentState =
|
||||
createAtomComponentState<ContextStoreTargetedRecordsRule>({
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export enum ContextStoreViewType {
|
||||
Table = 'table',
|
||||
Kanban = 'kanban',
|
||||
}
|
||||
export { ContextStoreViewType } from 'twenty-shared/types';
|
||||
|
||||
+3
@@ -19,12 +19,14 @@ type FrontComponentRendererProps = {
|
||||
frontComponentId: string;
|
||||
commandMenuItemId?: string;
|
||||
recordId?: string;
|
||||
contextStoreInstanceId?: string;
|
||||
};
|
||||
|
||||
export const FrontComponentRenderer = ({
|
||||
frontComponentId,
|
||||
commandMenuItemId,
|
||||
recordId,
|
||||
contextStoreInstanceId,
|
||||
}: FrontComponentRendererProps) => {
|
||||
const { colorScheme } = useContext(ThemeContext);
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
@@ -39,6 +41,7 @@ export const FrontComponentRenderer = ({
|
||||
frontComponentId,
|
||||
commandMenuItemId,
|
||||
recordId,
|
||||
contextStoreInstanceId,
|
||||
});
|
||||
|
||||
const handleError = useCallback(
|
||||
|
||||
+198
-1
@@ -1,6 +1,24 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import {
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
type ContextStoreTargetedRecordsRule,
|
||||
} from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useFrontComponentExecutionContext } from '@/front-components/hooks/useFrontComponentExecutionContext';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { getTestEnrichedObjectMetadataItemsMock } from '~/testing/utils/getTestEnrichedObjectMetadataItemsMock';
|
||||
|
||||
const mockNavigateApp = jest.fn();
|
||||
const mockRequestAccessTokenRefresh = jest.fn();
|
||||
@@ -16,7 +34,40 @@ const mockEnqueueWarningSnackBar = jest.fn();
|
||||
const mockCloseSidePanelMenu = jest.fn();
|
||||
const mockSetCommandMenuItemProgress = jest.fn();
|
||||
|
||||
const enrichedObjectMetadataItems = getTestEnrichedObjectMetadataItemsMock();
|
||||
const personObjectMetadataItem = enrichedObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'person',
|
||||
)!;
|
||||
const personNameFieldId = personObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'name',
|
||||
)!.id;
|
||||
|
||||
let mockCurrentUser: { id: string } | null = { id: 'user-123' };
|
||||
let mockCurrentWorkspaceMember:
|
||||
| { id: string; timeZone: string }
|
||||
| null = { id: 'workspace-member-123', timeZone: 'Europe/Paris' };
|
||||
let mockObjectMetadataItems = enrichedObjectMetadataItems;
|
||||
|
||||
const CONTEXT_STORE_DEFAULTS = new Map<unknown, unknown>([
|
||||
[contextStoreCurrentPageTypeComponentState, null],
|
||||
[contextStoreCurrentViewTypeComponentState, null],
|
||||
[contextStoreCurrentViewIdComponentState, undefined],
|
||||
[contextStoreCurrentObjectMetadataItemIdComponentState, undefined],
|
||||
[
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
{ mode: 'selection', selectedRecordIds: [] },
|
||||
],
|
||||
[contextStoreNumberOfSelectedRecordsComponentState, 0],
|
||||
[contextStoreFiltersComponentState, []],
|
||||
[contextStoreFilterGroupsComponentState, []],
|
||||
[contextStoreAnyFieldFilterValueComponentState, ''],
|
||||
]);
|
||||
|
||||
const contextStoreOverrides = new Map<unknown, unknown>();
|
||||
|
||||
const setContextStoreValue = (state: unknown, value: unknown) => {
|
||||
contextStoreOverrides.set(state, value);
|
||||
};
|
||||
|
||||
jest.mock('~/hooks/useNavigateApp', () => ({
|
||||
useNavigateApp: () => mockNavigateApp,
|
||||
@@ -72,9 +123,30 @@ jest.mock('twenty-ui/display', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/utilities/state/jotai/hooks/useAtomStateValue', () => ({
|
||||
useAtomStateValue: () => mockCurrentUser,
|
||||
useAtomStateValue: (atom: unknown) => {
|
||||
if (atom === currentUserState) {
|
||||
return mockCurrentUser;
|
||||
}
|
||||
if (atom === currentWorkspaceMemberState) {
|
||||
return mockCurrentWorkspaceMember;
|
||||
}
|
||||
if (atom === objectMetadataItemsSelector) {
|
||||
return mockObjectMetadataItems;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue',
|
||||
() => ({
|
||||
useAtomComponentStateValue: (componentState: unknown) =>
|
||||
contextStoreOverrides.has(componentState)
|
||||
? contextStoreOverrides.get(componentState)
|
||||
: CONTEXT_STORE_DEFAULTS.get(componentState),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('@/ui/utilities/state/jotai/hooks/useSetAtomState', () => ({
|
||||
useSetAtomState: () => mockSetSidePanelSearch,
|
||||
}));
|
||||
@@ -90,6 +162,12 @@ describe('useFrontComponentExecutionContext', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockCurrentUser = { id: 'user-123' };
|
||||
mockCurrentWorkspaceMember = {
|
||||
id: 'workspace-member-123',
|
||||
timeZone: 'Europe/Paris',
|
||||
};
|
||||
mockObjectMetadataItems = enrichedObjectMetadataItems;
|
||||
contextStoreOverrides.clear();
|
||||
});
|
||||
|
||||
describe('executionContext', () => {
|
||||
@@ -105,6 +183,12 @@ describe('useFrontComponentExecutionContext', () => {
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
userId: 'user-123',
|
||||
recordId: 'record-456',
|
||||
pageType: null,
|
||||
viewType: null,
|
||||
viewId: null,
|
||||
objectMetadataItemId: null,
|
||||
numberOfSelectedRecords: 0,
|
||||
graphqlFilter: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,6 +215,119 @@ describe('useFrontComponentExecutionContext', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('graphqlFilter', () => {
|
||||
it('should be null when objectMetadataItem cannot be resolved', () => {
|
||||
setContextStoreValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
'unknown-object-id',
|
||||
);
|
||||
const { result } = renderHook(() =>
|
||||
useFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.executionContext.graphqlFilter).toBeNull();
|
||||
});
|
||||
|
||||
it('should return { id: { in: [] } } for empty selection mode', () => {
|
||||
setContextStoreValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
personObjectMetadataItem.id,
|
||||
);
|
||||
const targetedRecordsRule: ContextStoreTargetedRecordsRule = {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
};
|
||||
setContextStoreValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
targetedRecordsRule,
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.executionContext.graphqlFilter).toEqual({
|
||||
id: { in: [] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should compose selection mode with selected record ids', () => {
|
||||
setContextStoreValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
personObjectMetadataItem.id,
|
||||
);
|
||||
const targetedRecordsRule: ContextStoreTargetedRecordsRule = {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: ['1', '2', '3'],
|
||||
};
|
||||
setContextStoreValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
targetedRecordsRule,
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.executionContext.graphqlFilter).toEqual({
|
||||
and: [{}, { id: { in: ['1', '2', '3'] } }, {}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compose exclusion mode with raw filters', () => {
|
||||
setContextStoreValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
personObjectMetadataItem.id,
|
||||
);
|
||||
const targetedRecordsRule: ContextStoreTargetedRecordsRule = {
|
||||
mode: 'exclusion',
|
||||
excludedRecordIds: ['1', '2', '3'],
|
||||
};
|
||||
setContextStoreValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
targetedRecordsRule,
|
||||
);
|
||||
const filters: RecordFilter[] = [
|
||||
{
|
||||
id: 'name-filter',
|
||||
fieldMetadataId: personNameFieldId,
|
||||
value: 'John',
|
||||
displayValue: 'John',
|
||||
displayAvatarUrl: undefined,
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
type: 'TEXT',
|
||||
label: 'Name',
|
||||
},
|
||||
];
|
||||
setContextStoreValue(contextStoreFiltersComponentState, filters);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.executionContext.graphqlFilter).toEqual({
|
||||
and: [
|
||||
{},
|
||||
{
|
||||
or: [
|
||||
{ name: { firstName: { ilike: '%John%' } } },
|
||||
{ name: { lastName: { ilike: '%John%' } } },
|
||||
],
|
||||
},
|
||||
{ not: { id: { in: ['1', '2', '3'] } } },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigate', () => {
|
||||
it('should call navigateApp with the provided arguments', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
|
||||
+86
@@ -6,14 +6,27 @@ import {
|
||||
import { type AppPath, type EnqueueSnackbarParams } from 'twenty-shared/types';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useCommandMenuConfirmationModal } from '@/command-menu-item/confirmation-modal/hooks/useCommandMenuConfirmationModal';
|
||||
import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand';
|
||||
import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentPageTypeComponentState } from '@/context-store/states/contextStoreCurrentPageTypeComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { useRequestApplicationTokenRefresh } from '@/front-components/hooks/useRequestApplicationTokenRefresh';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
@@ -24,15 +37,19 @@ export const useFrontComponentExecutionContext = ({
|
||||
frontComponentId,
|
||||
commandMenuItemId,
|
||||
recordId,
|
||||
contextStoreInstanceId,
|
||||
}: {
|
||||
frontComponentId: string;
|
||||
commandMenuItemId?: string;
|
||||
recordId?: string;
|
||||
contextStoreInstanceId?: string;
|
||||
}): {
|
||||
executionContext: FrontComponentExecutionContext;
|
||||
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
|
||||
} => {
|
||||
const currentUser = useAtomStateValue(currentUserState);
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
const navigateApp = useNavigateApp();
|
||||
const { requestAccessTokenRefresh } = useRequestApplicationTokenRefresh({
|
||||
frontComponentId,
|
||||
@@ -124,10 +141,79 @@ export const useFrontComponentExecutionContext = ({
|
||||
}
|
||||
};
|
||||
|
||||
const contextStoreCurrentPageType = useAtomComponentStateValue(
|
||||
contextStoreCurrentPageTypeComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreCurrentViewType = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreCurrentViewId = useAtomComponentStateValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreCurrentObjectMetadataItemId =
|
||||
useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreNumberOfSelectedRecords = useAtomComponentStateValue(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreFilters = useAtomComponentStateValue(
|
||||
contextStoreFiltersComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreFilterGroups = useAtomComponentStateValue(
|
||||
contextStoreFilterGroupsComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
contextStoreInstanceId,
|
||||
);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === contextStoreCurrentObjectMetadataItemId,
|
||||
);
|
||||
|
||||
const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
const userTimezone =
|
||||
currentWorkspaceMember?.timeZone !== 'system'
|
||||
? (currentWorkspaceMember?.timeZone ?? systemTimeZone)
|
||||
: systemTimeZone;
|
||||
|
||||
const graphqlFilter = isDefined(objectMetadataItem)
|
||||
? (computeContextStoreFilters({
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies: {
|
||||
currentWorkspaceMemberId: currentWorkspaceMember?.id,
|
||||
timeZone: userTimezone,
|
||||
},
|
||||
contextStoreAnyFieldFilterValue,
|
||||
}) ?? null)
|
||||
: null;
|
||||
|
||||
const executionContext: FrontComponentExecutionContext = {
|
||||
frontComponentId,
|
||||
userId: currentUser?.id ?? null,
|
||||
recordId: recordId ?? null,
|
||||
pageType: contextStoreCurrentPageType,
|
||||
viewType: contextStoreCurrentViewType,
|
||||
viewId: contextStoreCurrentViewId ?? null,
|
||||
objectMetadataItemId: contextStoreCurrentObjectMetadataItemId ?? null,
|
||||
numberOfSelectedRecords: contextStoreNumberOfSelectedRecords,
|
||||
graphqlFilter,
|
||||
};
|
||||
|
||||
const unmountFrontComponent: FrontComponentHostCommunicationApi['unmountFrontComponent'] =
|
||||
|
||||
+1
-8
@@ -1,8 +1 @@
|
||||
import { type RecordFilterGroupLogicalOperator } from 'twenty-shared/types';
|
||||
|
||||
export type RecordFilterGroup = {
|
||||
id: string;
|
||||
parentRecordFilterGroupId?: string | null;
|
||||
logicalOperator: RecordFilterGroupLogicalOperator;
|
||||
positionInRecordFilterGroup?: number | null;
|
||||
};
|
||||
export type { RecordFilterGroup } from 'twenty-shared/types';
|
||||
|
||||
+2
-27
@@ -1,32 +1,7 @@
|
||||
import { type FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||
import {
|
||||
type FilterableAndTSVectorFieldType,
|
||||
type ViewFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
import { type FilterableAndTSVectorFieldType } from 'twenty-shared/types';
|
||||
|
||||
// RLS-specific: references a workspace member field for dynamic "Me" comparisons
|
||||
export type RLSDynamicValue = {
|
||||
workspaceMemberFieldMetadataId: string;
|
||||
workspaceMemberSubFieldName?: string | null;
|
||||
};
|
||||
|
||||
export type RecordFilter = {
|
||||
id: string;
|
||||
fieldMetadataId: string;
|
||||
value: string;
|
||||
/** @deprecated We shouldn't implement new features with this field and instead try to create utils to obtain the displayValue at runtime */
|
||||
displayValue: string;
|
||||
type: FilterableAndTSVectorFieldType;
|
||||
recordFilterGroupId?: string;
|
||||
displayAvatarUrl?: string;
|
||||
operand: ViewFilterOperand;
|
||||
positionInRecordFilterGroup?: number | null;
|
||||
label: string;
|
||||
subFieldName?: CompositeFieldSubFieldName | null | undefined;
|
||||
// RLS-specific: when set, filter compares against current user's field value
|
||||
rlsDynamicValue?: RLSDynamicValue | null;
|
||||
};
|
||||
export type { RecordFilter, RLSDynamicValue } from 'twenty-shared/types';
|
||||
|
||||
export type RecordFilterToRecordInputOperand<
|
||||
T extends FilterableAndTSVectorFieldType,
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
import { type ViewSortDirection } from '~/generated-metadata/graphql';
|
||||
|
||||
export type RecordSort = {
|
||||
id: string;
|
||||
fieldMetadataId: string;
|
||||
direction: ViewSortDirection;
|
||||
};
|
||||
export type { RecordSort } from 'twenty-shared/types';
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { Suspense, lazy } from 'react';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
|
||||
@@ -49,6 +50,7 @@ export const FrontComponentWidgetRenderer = ({
|
||||
<FrontComponentRenderer
|
||||
frontComponentId={frontComponentId}
|
||||
recordId={targetRecordIdentifier?.id}
|
||||
contextStoreInstanceId={MAIN_CONTEXT_STORE_INSTANCE_ID}
|
||||
/>
|
||||
</Suspense>
|
||||
</StyledContainer>
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
import { Suspense, lazy } from 'react';
|
||||
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { viewableFrontComponentIdComponentState } from '@/side-panel/pages/front-component/states/viewableFrontComponentIdComponentState';
|
||||
import { viewableFrontComponentRecordContextComponentState } from '@/side-panel/pages/front-component/states/viewableFrontComponentRecordContextComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
@@ -29,6 +30,7 @@ export const SidePanelFrontComponentPage = () => {
|
||||
<FrontComponentRenderer
|
||||
frontComponentId={viewableFrontComponentId}
|
||||
recordId={viewableFrontComponentRecordContext?.recordId}
|
||||
contextStoreInstanceId={MAIN_CONTEXT_STORE_INSTANCE_ID}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
+3
@@ -1,6 +1,8 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { Suspense, lazy } from 'react';
|
||||
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
|
||||
const FrontComponentRenderer = lazy(() =>
|
||||
import('@/front-components/components/FrontComponentRenderer').then(
|
||||
(module) => ({ default: module.FrontComponentRenderer }),
|
||||
@@ -25,6 +27,7 @@ export const SettingsApplicationCustomTab = ({
|
||||
<Suspense fallback={null}>
|
||||
<FrontComponentRenderer
|
||||
frontComponentId={settingsCustomTabFrontComponentId}
|
||||
contextStoreInstanceId={MAIN_CONTEXT_STORE_INSTANCE_ID}
|
||||
/>
|
||||
</Suspense>
|
||||
</StyledContainer>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectGraphqlFilter = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): RecordGqlOperationFilter | null => context.graphqlFilter;
|
||||
|
||||
export const useGraphqlFilter = (): RecordGqlOperationFilter | null => {
|
||||
return useFrontComponentExecutionContext(selectGraphqlFilter);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectNumberOfSelectedRecords = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): number => context.numberOfSelectedRecords;
|
||||
|
||||
export const useNumberOfSelectedRecords = (): number => {
|
||||
return useFrontComponentExecutionContext(selectNumberOfSelectedRecords);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectObjectMetadataItemId = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): string | null => context.objectMetadataItemId;
|
||||
|
||||
export const useObjectMetadataItemId = (): string | null => {
|
||||
return useFrontComponentExecutionContext(selectObjectMetadataItemId);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type ContextStorePageType } from 'twenty-shared/types';
|
||||
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectPageType = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): ContextStorePageType | null => context.pageType;
|
||||
|
||||
export const usePageType = (): ContextStorePageType | null => {
|
||||
return useFrontComponentExecutionContext(selectPageType);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectViewId = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): string | null => context.viewId;
|
||||
|
||||
export const useViewId = (): string | null => {
|
||||
return useFrontComponentExecutionContext(selectViewId);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type ContextStoreViewType } from 'twenty-shared/types';
|
||||
|
||||
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
|
||||
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
|
||||
|
||||
const selectViewType = (
|
||||
context: FrontComponentExecutionContext,
|
||||
): ContextStoreViewType | null => context.viewType;
|
||||
|
||||
export const useViewType = (): ContextStoreViewType | null => {
|
||||
return useFrontComponentExecutionContext(selectViewType);
|
||||
};
|
||||
@@ -35,8 +35,14 @@ export { unmountFrontComponent } from './functions/unmountFrontComponent';
|
||||
export { updateProgress } from './functions/updateProgress';
|
||||
export { useFrontComponentExecutionContext } from './hooks/useFrontComponentExecutionContext';
|
||||
export { useFrontComponentId } from './hooks/useFrontComponentId';
|
||||
export { useGraphqlFilter } from './hooks/useGraphqlFilter';
|
||||
export { useNumberOfSelectedRecords } from './hooks/useNumberOfSelectedRecords';
|
||||
export { useObjectMetadataItemId } from './hooks/useObjectMetadataItemId';
|
||||
export { usePageType } from './hooks/usePageType';
|
||||
export { useRecordId } from './hooks/useRecordId';
|
||||
export { useUserId } from './hooks/useUserId';
|
||||
export { useViewId } from './hooks/useViewId';
|
||||
export { useViewType } from './hooks/useViewType';
|
||||
export type { FrontComponentExecutionContext } from './types/FrontComponentExecutionContext';
|
||||
export { getFrontComponentCommandErrorDedupeKey } from './utils/getFrontComponentCommandErrorDedupeKey';
|
||||
export type {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import {
|
||||
type ContextStorePageType,
|
||||
type ContextStoreViewType,
|
||||
type RecordGqlOperationFilter,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
export type FrontComponentExecutionContext = {
|
||||
frontComponentId: string;
|
||||
userId: string | null;
|
||||
recordId: string | null;
|
||||
pageType: ContextStorePageType | null;
|
||||
viewType: ContextStoreViewType | null;
|
||||
viewId: string | null;
|
||||
objectMetadataItemId: string | null;
|
||||
numberOfSelectedRecords: number;
|
||||
graphqlFilter: RecordGqlOperationFilter | null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
type ContextStoreTargetedRecordsRuleSelectionMode = {
|
||||
mode: 'selection';
|
||||
selectedRecordIds: string[];
|
||||
};
|
||||
|
||||
type ContextStoreTargetedRecordsRuleExclusionMode = {
|
||||
mode: 'exclusion';
|
||||
excludedRecordIds: string[];
|
||||
};
|
||||
|
||||
export type ContextStoreTargetedRecordsRule =
|
||||
| ContextStoreTargetedRecordsRuleSelectionMode
|
||||
| ContextStoreTargetedRecordsRuleExclusionMode;
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum ContextStoreViewType {
|
||||
Table = 'table',
|
||||
Kanban = 'kanban',
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type FilterableAndTSVectorFieldType } from './FilterableFieldType';
|
||||
import { type ViewFilterOperand } from './ViewFilterOperand';
|
||||
|
||||
export type RLSDynamicValue = {
|
||||
workspaceMemberFieldMetadataId: string;
|
||||
workspaceMemberSubFieldName?: string | null;
|
||||
};
|
||||
|
||||
export type RecordFilter = {
|
||||
id: string;
|
||||
fieldMetadataId: string;
|
||||
value: string;
|
||||
displayValue: string;
|
||||
type: FilterableAndTSVectorFieldType;
|
||||
recordFilterGroupId?: string;
|
||||
displayAvatarUrl?: string;
|
||||
operand: ViewFilterOperand;
|
||||
positionInRecordFilterGroup?: number | null;
|
||||
label: string;
|
||||
subFieldName?: string | null | undefined;
|
||||
rlsDynamicValue?: RLSDynamicValue | null;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { type RecordFilterGroupLogicalOperator } from './RecordFilterGroupLogicalOperator';
|
||||
|
||||
export type RecordFilterGroup = {
|
||||
id: string;
|
||||
parentRecordFilterGroupId?: string | null;
|
||||
logicalOperator: RecordFilterGroupLogicalOperator;
|
||||
positionInRecordFilterGroup?: number | null;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type RecordFilter } from './RecordFilter';
|
||||
import { type RecordFilterGroup } from './RecordFilterGroup';
|
||||
import { type RecordSort } from './RecordSort';
|
||||
|
||||
export type RecordShowParentView = {
|
||||
parentViewComponentId: string;
|
||||
parentViewObjectNameSingular: string;
|
||||
parentViewFilterGroups: RecordFilterGroup[];
|
||||
parentViewFilters: RecordFilter[];
|
||||
parentViewSorts: RecordSort[];
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export type RecordSort = {
|
||||
id: string;
|
||||
fieldMetadataId: string;
|
||||
direction: string;
|
||||
};
|
||||
@@ -59,6 +59,8 @@ export type { ConfigVariableValue } from './ConfigVariableValue';
|
||||
export type { ConfirmationModalCaller } from './ConfirmationModalCaller';
|
||||
export { ConnectedAccountProvider } from './ConnectedAccountProvider';
|
||||
export { ContextStorePageType } from './ContextStorePageType';
|
||||
export type { ContextStoreTargetedRecordsRule } from './ContextStoreTargetedRecordsRule';
|
||||
export { ContextStoreViewType } from './ContextStoreViewType';
|
||||
export { CoreObjectNameSingular } from './CoreObjectNameSingular';
|
||||
export { CrudOperationType } from './CrudOperationType';
|
||||
export type { EmailAttachment } from './EmailAttachment';
|
||||
@@ -209,6 +211,8 @@ export type { RatioAggregateConfig } from './page-layout/ratio-aggregate-config.
|
||||
export type { PartialFieldMetadataItem } from './PartialFieldMetadataItem';
|
||||
export type { PartialFieldMetadataItemOption } from './PartialFieldMetadataOption';
|
||||
export type { QueryCursorDirection } from './QueryCursorDirection';
|
||||
export type { RLSDynamicValue, RecordFilter } from './RecordFilter';
|
||||
export type { RecordFilterGroup } from './RecordFilterGroup';
|
||||
export { RecordFilterGroupLogicalOperator } from './RecordFilterGroupLogicalOperator';
|
||||
export type { RecordFilterValueDependencies } from './RecordFilterValueDependencies';
|
||||
export type { RecordGqlFieldsDeprecated } from './RecordGqlFieldsDeprecated';
|
||||
@@ -250,6 +254,8 @@ export type { RecordGqlOperationGqlRecordFields } from './RecordGqlOperationGqlR
|
||||
export type { RecordGqlOperationOrderBy } from './RecordGqlOperationOrderBy';
|
||||
export type { RecordGqlOperationSignature } from './RecordGqlOperationSignature';
|
||||
export type { RecordGqlOperationVariables } from './RecordGqlOperationVariables';
|
||||
export type { RecordShowParentView } from './RecordShowParentView';
|
||||
export type { RecordSort } from './RecordSort';
|
||||
export type { RelationAndMorphRelationFieldMetadataType } from './RelationAndMorphRelationFieldMetadataType';
|
||||
export type { RelationCreationPayload } from './RelationCreationPayload';
|
||||
export { RelationOnDeleteAction } from './RelationOnDeleteAction.type';
|
||||
|
||||
Reference in New Issue
Block a user