Remove opportunity specific code on Kanban (#11000)
In this PR: - clean board from opportunity specific logic - remove in place creation in board - trigger creation in right drawer instead
This commit is contained in:
@ -1,7 +1,6 @@
|
|||||||
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
|
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
@ -15,24 +14,14 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec
|
|||||||
throw new Error('Current view ID is not defined');
|
throw new Error('Current view ID is not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordTableId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
|
||||||
objectMetadataItem.namePlural,
|
|
||||||
currentViewId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||||
|
|
||||||
const { createNewTableRecord } = useCreateNewTableRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordTableId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClick = () => {
|
|
||||||
createNewTableRecord();
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldBeRegistered: !hasObjectReadOnlyPermission,
|
shouldBeRegistered: !hasObjectReadOnlyPermission,
|
||||||
onClick,
|
onClick: createNewIndexRecord,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
|
|||||||
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
@ -130,13 +131,19 @@ describe('useActivityTargetObjectRecords', () => {
|
|||||||
objectMetadataItemsState,
|
objectMetadataItemsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { activityTargetObjectRecords } =
|
const setRecordFromStore = useSetRecoilState(
|
||||||
useActivityTargetObjectRecords(task);
|
recordStoreFamilyState(task.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { activityTargetObjectRecords } = useActivityTargetObjectRecords(
|
||||||
|
task.id,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activityTargetObjectRecords,
|
activityTargetObjectRecords,
|
||||||
setCurrentWorkspaceMember,
|
setCurrentWorkspaceMember,
|
||||||
setObjectMetadataItems,
|
setObjectMetadataItems,
|
||||||
|
setRecordFromStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ wrapper: Wrapper },
|
{ wrapper: Wrapper },
|
||||||
@ -145,6 +152,7 @@ describe('useActivityTargetObjectRecords', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
|
result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
|
||||||
result.current.setObjectMetadataItems(generatedMockObjectMetadataItems);
|
result.current.setObjectMetadataItems(generatedMockObjectMetadataItems);
|
||||||
|
result.current.setRecordFromStore(task);
|
||||||
});
|
});
|
||||||
|
|
||||||
const activityTargetObjectRecords =
|
const activityTargetObjectRecords =
|
||||||
|
|||||||
@ -8,14 +8,19 @@ import { Task } from '@/activities/types/Task';
|
|||||||
import { TaskTarget } from '@/activities/types/TaskTarget';
|
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
export const useActivityTargetObjectRecords = (
|
export const useActivityTargetObjectRecords = (
|
||||||
activity?: Task | Note,
|
activityRecordId?: string,
|
||||||
activityTargets?: NoteTarget[] | TaskTarget[],
|
activityTargets?: NoteTarget[] | TaskTarget[],
|
||||||
) => {
|
) => {
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const activity = useRecoilValue(
|
||||||
|
recordStoreFamilyState(activityRecordId ?? ''),
|
||||||
|
) as Note | Task | null;
|
||||||
|
|
||||||
if (!isDefined(activity) && !isDefined(activityTargets)) {
|
if (!isDefined(activity) && !isDefined(activityTargets)) {
|
||||||
return { activityTargetObjectRecords: [] };
|
return { activityTargetObjectRecords: [] };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,6 @@ import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTa
|
|||||||
import { useOpenActivityTargetInlineCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetInlineCellEditMode';
|
import { useOpenActivityTargetInlineCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetInlineCellEditMode';
|
||||||
import { useUpdateActivityTargetFromInlineCell } from '@/activities/inline-cell/hooks/useUpdateActivityTargetFromInlineCell';
|
import { useUpdateActivityTargetFromInlineCell } from '@/activities/inline-cell/hooks/useUpdateActivityTargetFromInlineCell';
|
||||||
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
|
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
|
||||||
import { Note } from '@/activities/types/Note';
|
|
||||||
import { Task } from '@/activities/types/Task';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
@ -22,7 +20,7 @@ import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-rec
|
|||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
|
||||||
type ActivityTargetsInlineCellProps = {
|
type ActivityTargetsInlineCellProps = {
|
||||||
activity: Task | Note;
|
activityRecordId: string;
|
||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
activityObjectNameSingular:
|
activityObjectNameSingular:
|
||||||
@ -31,15 +29,15 @@ type ActivityTargetsInlineCellProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityTargetsInlineCell = ({
|
export const ActivityTargetsInlineCell = ({
|
||||||
activity,
|
activityRecordId,
|
||||||
showLabel = true,
|
showLabel = true,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
activityObjectNameSingular,
|
activityObjectNameSingular,
|
||||||
}: ActivityTargetsInlineCellProps) => {
|
}: ActivityTargetsInlineCellProps) => {
|
||||||
const { activityTargetObjectRecords } =
|
const { activityTargetObjectRecords } =
|
||||||
useActivityTargetObjectRecords(activity);
|
useActivityTargetObjectRecords(activityRecordId);
|
||||||
|
|
||||||
const multipleRecordPickerInstanceId = `multiple-record-picker-target-${activity.id}`;
|
const multipleRecordPickerInstanceId = `multiple-record-picker-target-${activityRecordId}`;
|
||||||
|
|
||||||
const { closeInlineCell } = useInlineCell();
|
const { closeInlineCell } = useInlineCell();
|
||||||
|
|
||||||
@ -58,7 +56,7 @@ export const ActivityTargetsInlineCell = ({
|
|||||||
const { FieldContextProvider: ActivityTargetsContextProvider } =
|
const { FieldContextProvider: ActivityTargetsContextProvider } =
|
||||||
useFieldContext({
|
useFieldContext({
|
||||||
objectNameSingular: activityObjectNameSingular,
|
objectNameSingular: activityObjectNameSingular,
|
||||||
objectRecordId: activity.id,
|
objectRecordId: activityRecordId,
|
||||||
fieldMetadataName: fieldDefinition.metadata.fieldName,
|
fieldMetadataName: fieldDefinition.metadata.fieldName,
|
||||||
fieldPosition: 3,
|
fieldPosition: 3,
|
||||||
overridenIsFieldEmpty: activityTargetObjectRecords.length === 0,
|
overridenIsFieldEmpty: activityTargetObjectRecords.length === 0,
|
||||||
@ -70,11 +68,11 @@ export const ActivityTargetsInlineCell = ({
|
|||||||
const { updateActivityTargetFromInlineCell } =
|
const { updateActivityTargetFromInlineCell } =
|
||||||
useUpdateActivityTargetFromInlineCell({
|
useUpdateActivityTargetFromInlineCell({
|
||||||
activityObjectNameSingular,
|
activityObjectNameSingular,
|
||||||
activityId: activity.id,
|
activityId: activityRecordId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordFieldInputScope recordFieldInputScopeId={activity?.id ?? ''}>
|
<RecordFieldInputScope recordFieldInputScopeId={activityRecordId}>
|
||||||
<FieldFocusContextProvider>
|
<FieldFocusContextProvider>
|
||||||
{ActivityTargetsContextProvider && (
|
{ActivityTargetsContextProvider && (
|
||||||
<ActivityTargetsContextProvider>
|
<ActivityTargetsContextProvider>
|
||||||
|
|||||||
@ -96,7 +96,7 @@ export const NoteCard = ({
|
|||||||
{NoteTargetsContextProvider && (
|
{NoteTargetsContextProvider && (
|
||||||
<NoteTargetsContextProvider>
|
<NoteTargetsContextProvider>
|
||||||
<ActivityTargetsInlineCell
|
<ActivityTargetsInlineCell
|
||||||
activity={note}
|
activityRecordId={note.id}
|
||||||
activityObjectNameSingular={CoreObjectNameSingular.Note}
|
activityObjectNameSingular={CoreObjectNameSingular.Note}
|
||||||
/>
|
/>
|
||||||
</NoteTargetsContextProvider>
|
</NoteTargetsContextProvider>
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export const TaskRow = ({ task }: { task: Task }) => {
|
|||||||
<TaskTargetsContextProvider>
|
<TaskTargetsContextProvider>
|
||||||
<ActivityTargetsInlineCell
|
<ActivityTargetsInlineCell
|
||||||
activityObjectNameSingular={CoreObjectNameSingular.Task}
|
activityObjectNameSingular={CoreObjectNameSingular.Task}
|
||||||
activity={task}
|
activityRecordId={task.id}
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
maxWidth={200}
|
maxWidth={200}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import { TimelineActivity } from '@/activities/timeline-activities/types/Timelin
|
|||||||
import { getTimelineActivityAuthorFullName } from '@/activities/timeline-activities/utils/getTimelineActivityAuthorFullName';
|
import { getTimelineActivityAuthorFullName } from '@/activities/timeline-activities/utils/getTimelineActivityAuthorFullName';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
@ -104,7 +106,10 @@ export const EventRow = ({
|
|||||||
}: EventRowProps) => {
|
}: EventRowProps) => {
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
const { labelIdentifierValue } = useContext(TimelineActivityContext);
|
const { recordId } = useContext(TimelineActivityContext);
|
||||||
|
|
||||||
|
const recordFromStore = useRecoilValue(recordStoreFamilyState(recordId));
|
||||||
|
|
||||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt);
|
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt);
|
||||||
const linkedObjectMetadataItem = useLinkedObjectObjectMetadataItem(
|
const linkedObjectMetadataItem = useLinkedObjectObjectMetadataItem(
|
||||||
event.linkedObjectMetadataId,
|
event.linkedObjectMetadataId,
|
||||||
@ -114,6 +119,18 @@ export const EventRow = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUndefinedOrNull(recordFromStore)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isUndefinedOrNull(mainObjectMetadataItem)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelIdentifier = getObjectRecordIdentifier({
|
||||||
|
objectMetadataItem: mainObjectMetadataItem,
|
||||||
|
record: recordFromStore,
|
||||||
|
});
|
||||||
|
|
||||||
const authorFullName = getTimelineActivityAuthorFullName(
|
const authorFullName = getTimelineActivityAuthorFullName(
|
||||||
event,
|
event,
|
||||||
currentWorkspaceMember,
|
currentWorkspaceMember,
|
||||||
@ -143,7 +160,7 @@ export const EventRow = ({
|
|||||||
<StyledSummary>
|
<StyledSummary>
|
||||||
<EventRowDynamicComponent
|
<EventRowDynamicComponent
|
||||||
authorFullName={authorFullName}
|
authorFullName={authorFullName}
|
||||||
labelIdentifierValue={labelIdentifierValue}
|
labelIdentifierValue={labelIdentifier.name}
|
||||||
event={event}
|
event={event}
|
||||||
mainObjectMetadataItem={mainObjectMetadataItem}
|
mainObjectMetadataItem={mainObjectMetadataItem}
|
||||||
linkedObjectMetadataItem={linkedObjectMetadataItem}
|
linkedObjectMetadataItem={linkedObjectMetadataItem}
|
||||||
|
|||||||
@ -17,9 +17,7 @@ const meta: Meta<typeof TimelineActivities> = {
|
|||||||
SnackBarDecorator,
|
SnackBarDecorator,
|
||||||
(Story) => {
|
(Story) => {
|
||||||
return (
|
return (
|
||||||
<TimelineActivityContext.Provider
|
<TimelineActivityContext.Provider value={{ recordId: 'mock-id' }}>
|
||||||
value={{ labelIdentifierValue: 'Mock' }}
|
|
||||||
>
|
|
||||||
<Story />
|
<Story />
|
||||||
</TimelineActivityContext.Provider>
|
</TimelineActivityContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
type TimelineActivityContextValue = {
|
type TimelineActivityContextValue = {
|
||||||
labelIdentifierValue: string;
|
recordId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TimelineActivityContext =
|
export const TimelineActivityContext =
|
||||||
createContext<TimelineActivityContextValue>({
|
createContext<TimelineActivityContextValue>({
|
||||||
labelIdentifierValue: '',
|
recordId: '',
|
||||||
});
|
});
|
||||||
|
|||||||
@ -16,9 +16,7 @@ const meta: Meta<typeof EventCardMessage> = {
|
|||||||
SnackBarDecorator,
|
SnackBarDecorator,
|
||||||
(Story) => {
|
(Story) => {
|
||||||
return (
|
return (
|
||||||
<TimelineActivityContext.Provider
|
<TimelineActivityContext.Provider value={{ recordId: 'mock-id' }}>
|
||||||
value={{ labelIdentifierValue: 'Mock' }}
|
|
||||||
>
|
|
||||||
<Story />
|
<Story />
|
||||||
</TimelineActivityContext.Provider>
|
</TimelineActivityContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
|
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
|
||||||
import { isNewViewableRecordLoadingComponentState } from '@/command-menu/pages/record-page/states/isNewViewableRecordLoadingComponentState';
|
import { isNewViewableRecordLoadingComponentState } from '@/command-menu/pages/record-page/states/isNewViewableRecordLoadingComponentState';
|
||||||
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
|
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
|
||||||
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
|
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
|
||||||
@ -7,6 +8,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
|
|||||||
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
||||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||||
|
import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect';
|
||||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||||
@ -74,13 +76,23 @@ export const CommandMenuRecordPage = () => {
|
|||||||
{!isNewViewableRecordLoading && (
|
{!isNewViewableRecordLoading && (
|
||||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||||
)}
|
)}
|
||||||
<RecordShowContainer
|
<TimelineActivityContext.Provider
|
||||||
objectNameSingular={objectNameSingular}
|
value={{
|
||||||
objectRecordId={objectRecordId}
|
recordId: objectRecordId,
|
||||||
loading={false}
|
}}
|
||||||
isInRightDrawer={true}
|
>
|
||||||
isNewRightDrawerItemLoading={isNewViewableRecordLoading}
|
<RecordShowEffect
|
||||||
/>
|
objectNameSingular={objectNameSingular}
|
||||||
|
recordId={objectRecordId}
|
||||||
|
/>
|
||||||
|
<RecordShowContainer
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
objectRecordId={objectRecordId}
|
||||||
|
loading={false}
|
||||||
|
isInRightDrawer={true}
|
||||||
|
isNewRightDrawerItemLoading={isNewViewableRecordLoading}
|
||||||
|
/>
|
||||||
|
</TimelineActivityContext.Provider>
|
||||||
</RecordFieldValueSelectorContextProvider>
|
</RecordFieldValueSelectorContextProvider>
|
||||||
</StyledRightDrawerRecord>
|
</StyledRightDrawerRecord>
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
</ActionMenuComponentInstanceContext.Provider>
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import styled from '@emotion/styled';
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { InView, useInView } from 'react-intersection-observer';
|
import { InView, useInView } from 'react-intersection-observer';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
import { AnimatedEaseInOut } from 'twenty-ui';
|
import { AnimatedEaseInOut } from 'twenty-ui';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
@ -71,15 +70,7 @@ const StyledBoardCardWrapper = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordBoardCard = ({
|
export const RecordBoardCard = () => {
|
||||||
isCreating = false,
|
|
||||||
onCreateSuccess,
|
|
||||||
position,
|
|
||||||
}: {
|
|
||||||
isCreating?: boolean;
|
|
||||||
onCreateSuccess?: () => void;
|
|
||||||
position?: 'first' | 'last';
|
|
||||||
}) => {
|
|
||||||
const navigate = useNavigateApp();
|
const navigate = useNavigateApp();
|
||||||
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
||||||
|
|
||||||
@ -134,18 +125,16 @@ export const RecordBoardCard = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCardClick = () => {
|
const handleCardClick = () => {
|
||||||
if (!isCreating) {
|
if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) {
|
||||||
if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) {
|
openRecordInCommandMenu({
|
||||||
openRecordInCommandMenu({
|
recordId,
|
||||||
recordId,
|
objectNameSingular,
|
||||||
objectNameSingular,
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
navigate(AppPath.RecordShowPage, {
|
||||||
navigate(AppPath.RecordShowPage, {
|
objectNameSingular,
|
||||||
objectNameSingular,
|
objectRecordId: recordId,
|
||||||
objectRecordId: recordId,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,16 +155,12 @@ export const RecordBoardCard = ({
|
|||||||
(boardField) => !boardField.isLabelIdentifier,
|
(boardField) => !boardField.isLabelIdentifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
const labelIdentifierField = visibleFieldDefinitions.find(
|
|
||||||
(field) => field.isLabelIdentifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBoardCardWrapper
|
<StyledBoardCardWrapper
|
||||||
className="record-board-card"
|
className="record-board-card"
|
||||||
onContextMenu={handleActionMenuDropdown}
|
onContextMenu={handleActionMenuDropdown}
|
||||||
>
|
>
|
||||||
{!isCreating && <RecordValueSetterEffect recordId={recordId} />}
|
<RecordValueSetterEffect recordId={recordId} />
|
||||||
<InView>
|
<InView>
|
||||||
<StyledBoardCard
|
<StyledBoardCard
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
@ -183,16 +168,10 @@ export const RecordBoardCard = ({
|
|||||||
onMouseLeave={onMouseLeaveBoard}
|
onMouseLeave={onMouseLeaveBoard}
|
||||||
onClick={handleCardClick}
|
onClick={handleCardClick}
|
||||||
>
|
>
|
||||||
{isDefined(labelIdentifierField) && (
|
<RecordBoardCardHeader
|
||||||
<RecordBoardCardHeader
|
isCardExpanded={isCardExpanded}
|
||||||
identifierFieldDefinition={labelIdentifierField}
|
setIsCardExpanded={setIsCardExpanded}
|
||||||
isCreating={isCreating}
|
/>
|
||||||
onCreateSuccess={onCreateSuccess}
|
|
||||||
position={position}
|
|
||||||
isCardExpanded={isCardExpanded}
|
|
||||||
setIsCardExpanded={setIsCardExpanded}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<AnimatedEaseInOut
|
<AnimatedEaseInOut
|
||||||
isOpen={isCardExpanded || !isCompactModeActive}
|
isOpen={isCardExpanded || !isCompactModeActive}
|
||||||
initial={false}
|
initial={false}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
|
import { RecordBoardCardBodyContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBodyContainer';
|
||||||
|
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
|
||||||
|
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||||
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
|
||||||
import {
|
import {
|
||||||
FieldContext,
|
FieldContext,
|
||||||
RecordUpdateHook,
|
RecordUpdateHook,
|
||||||
RecordUpdateHookParams,
|
RecordUpdateHookParams,
|
||||||
} from '@/object-record/record-field/contexts/FieldContext';
|
} from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
|
||||||
import { RecordBoardCardBodyContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBodyContainer';
|
|
||||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
|
||||||
|
|
||||||
export const RecordBoardCardBody = ({
|
export const RecordBoardCardBody = ({
|
||||||
fieldDefinitions,
|
fieldDefinitions,
|
||||||
@ -42,8 +42,7 @@ export const RecordBoardCardBody = ({
|
|||||||
value={{
|
value={{
|
||||||
recordId,
|
recordId,
|
||||||
maxWidth: 156,
|
maxWidth: 156,
|
||||||
recoilScopeId:
|
recoilScopeId: `board-card-${recordId}-${fieldDefinition.fieldMetadataId}`,
|
||||||
(recordId || 'new') + fieldDefinition.fieldMetadataId,
|
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
fieldDefinition: {
|
fieldDefinition: {
|
||||||
disableTooltip: false,
|
disableTooltip: false,
|
||||||
|
|||||||
@ -4,33 +4,19 @@ import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useR
|
|||||||
import { RecordBoardCardHeaderContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeaderContainer';
|
import { RecordBoardCardHeaderContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeaderContainer';
|
||||||
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
|
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
|
||||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
|
||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
|
||||||
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
|
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
|
||||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||||
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
|
||||||
import {
|
|
||||||
FieldContext,
|
|
||||||
RecordUpdateHook,
|
|
||||||
RecordUpdateHookParams,
|
|
||||||
} from '@/object-record/record-field/contexts/FieldContext';
|
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
|
||||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
|
||||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
|
||||||
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
|
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Dispatch, SetStateAction, useContext, useState } from 'react';
|
import { Dispatch, SetStateAction, useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import {
|
import {
|
||||||
@ -42,11 +28,6 @@ import {
|
|||||||
LightIconButton,
|
LightIconButton,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
const StyledTextInput = styled(TextInput)`
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
|
||||||
width: ${({ theme }) => theme.spacing(53)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCompactIconContainer = styled.div`
|
const StyledCompactIconContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -59,38 +40,21 @@ const StyledCheckboxContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type RecordBoardCardHeaderProps = {
|
type RecordBoardCardHeaderProps = {
|
||||||
isCreating?: boolean;
|
|
||||||
onCreateSuccess?: () => void;
|
|
||||||
position?: 'first' | 'last';
|
|
||||||
identifierFieldDefinition: RecordBoardFieldDefinition<FieldMetadata>;
|
|
||||||
isCardExpanded?: boolean;
|
isCardExpanded?: boolean;
|
||||||
setIsCardExpanded?: Dispatch<SetStateAction<boolean>>;
|
setIsCardExpanded?: Dispatch<SetStateAction<boolean>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordBoardCardHeader = ({
|
export const RecordBoardCardHeader = ({
|
||||||
isCreating = false,
|
|
||||||
onCreateSuccess,
|
|
||||||
position,
|
|
||||||
identifierFieldDefinition,
|
|
||||||
isCardExpanded,
|
isCardExpanded,
|
||||||
setIsCardExpanded,
|
setIsCardExpanded,
|
||||||
}: RecordBoardCardHeaderProps) => {
|
}: RecordBoardCardHeaderProps) => {
|
||||||
const [newLabelValue, setNewLabelValue] = useState('');
|
|
||||||
|
|
||||||
const columnId = useContext(RecordBoardColumnContext)?.columnId;
|
|
||||||
|
|
||||||
const { handleBlur, handleInputEnter } = useAddNewCard({
|
|
||||||
recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { recordId } = useContext(RecordBoardCardContext);
|
const { recordId } = useContext(RecordBoardCardContext);
|
||||||
|
|
||||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
||||||
|
|
||||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||||
|
|
||||||
const { updateOneRecord, objectMetadataItem } =
|
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||||
useContext(RecordBoardContext);
|
|
||||||
|
|
||||||
const recordBoardId = useAvailableScopeIdOrThrow(
|
const recordBoardId = useAvailableScopeIdOrThrow(
|
||||||
RecordBoardScopeInternalContext,
|
RecordBoardScopeInternalContext,
|
||||||
@ -100,11 +64,6 @@ export const RecordBoardCardHeader = ({
|
|||||||
isRecordBoardCompactModeActiveComponentState,
|
isRecordBoardCompactModeActiveComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isIdentifierEmpty = isFieldValueEmpty({
|
|
||||||
fieldDefinition: identifierFieldDefinition,
|
|
||||||
fieldValue: record?.[identifierFieldDefinition.metadata.fieldName],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { checkIfLastUnselectAndCloseDropdown } =
|
const { checkIfLastUnselectAndCloseDropdown } =
|
||||||
useRecordBoardSelection(recordBoardId);
|
useRecordBoardSelection(recordBoardId);
|
||||||
|
|
||||||
@ -114,112 +73,52 @@ export const RecordBoardCardHeader = ({
|
|||||||
recordId,
|
recordId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const useUpdateOneRecordHook: RecordUpdateHook = () => {
|
|
||||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
|
||||||
updateOneRecord?.({
|
|
||||||
idToUpdate: variables.where.id as string,
|
|
||||||
updateOneRecordInput: variables.updateOneRecordInput,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return [updateEntity, { loading: false }];
|
|
||||||
};
|
|
||||||
|
|
||||||
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
|
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordBoardCardHeaderContainer showCompactView={showCompactView}>
|
<RecordBoardCardHeaderContainer showCompactView={showCompactView}>
|
||||||
<StopPropagationContainer>
|
<StopPropagationContainer>
|
||||||
{isCreating && position !== undefined ? (
|
{isDefined(record) && (
|
||||||
<RecordInlineCellEditMode>
|
<RecordChip
|
||||||
<StyledTextInput
|
objectNameSingular={objectMetadataItem.nameSingular}
|
||||||
autoFocus
|
record={record}
|
||||||
value={newLabelValue}
|
variant={AvatarChipVariant.Transparent}
|
||||||
onInputEnter={() =>
|
maxWidth={150}
|
||||||
handleInputEnter(newLabelValue, position, onCreateSuccess)
|
to={
|
||||||
}
|
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
|
||||||
onBlur={() =>
|
? indexIdentifierUrl(recordId)
|
||||||
handleBlur(newLabelValue, position, onCreateSuccess)
|
: undefined
|
||||||
}
|
}
|
||||||
onChange={(text: string) => setNewLabelValue(text)}
|
/>
|
||||||
placeholder={identifierFieldDefinition.label}
|
|
||||||
/>
|
|
||||||
</RecordInlineCellEditMode>
|
|
||||||
) : isIdentifierEmpty ? (
|
|
||||||
<FieldContext.Provider
|
|
||||||
value={{
|
|
||||||
recordId,
|
|
||||||
maxWidth: 156,
|
|
||||||
recoilScopeId:
|
|
||||||
(isCreating ? 'new' : recordId) +
|
|
||||||
identifierFieldDefinition.fieldMetadataId,
|
|
||||||
isLabelIdentifier: true,
|
|
||||||
fieldDefinition: {
|
|
||||||
disableTooltip: false,
|
|
||||||
fieldMetadataId: identifierFieldDefinition.fieldMetadataId,
|
|
||||||
label: `Set ${identifierFieldDefinition.label}`,
|
|
||||||
iconName: identifierFieldDefinition.iconName,
|
|
||||||
type: identifierFieldDefinition.type,
|
|
||||||
metadata: identifierFieldDefinition.metadata,
|
|
||||||
defaultValue: identifierFieldDefinition.defaultValue,
|
|
||||||
editButtonIcon: getFieldButtonIcon({
|
|
||||||
metadata: identifierFieldDefinition.metadata,
|
|
||||||
type: identifierFieldDefinition.type,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
useUpdateRecord: useUpdateOneRecordHook,
|
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordInlineCell />
|
|
||||||
</FieldContext.Provider>
|
|
||||||
) : (
|
|
||||||
isDefined(record) && (
|
|
||||||
<RecordChip
|
|
||||||
objectNameSingular={objectMetadataItem.nameSingular}
|
|
||||||
record={record}
|
|
||||||
variant={AvatarChipVariant.Transparent}
|
|
||||||
maxWidth={150}
|
|
||||||
to={
|
|
||||||
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
|
|
||||||
? indexIdentifierUrl(recordId)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</StopPropagationContainer>
|
</StopPropagationContainer>
|
||||||
|
|
||||||
{!isCreating && (
|
{showCompactView && (
|
||||||
<>
|
<StyledCompactIconContainer className="compact-icon-container">
|
||||||
{showCompactView && (
|
<StopPropagationContainer>
|
||||||
<StyledCompactIconContainer className="compact-icon-container">
|
<LightIconButton
|
||||||
<StopPropagationContainer>
|
Icon={isCardExpanded ? IconEyeOff : IconEye}
|
||||||
<LightIconButton
|
accent="tertiary"
|
||||||
Icon={isCardExpanded ? IconEyeOff : IconEye}
|
onClick={() => {
|
||||||
accent="tertiary"
|
setIsCardExpanded?.((prev) => !prev);
|
||||||
onClick={() => {
|
}}
|
||||||
setIsCardExpanded?.((prev) => !prev);
|
/>
|
||||||
}}
|
</StopPropagationContainer>
|
||||||
/>
|
</StyledCompactIconContainer>
|
||||||
</StopPropagationContainer>
|
|
||||||
</StyledCompactIconContainer>
|
|
||||||
)}
|
|
||||||
<StyledCheckboxContainer className="checkbox-container">
|
|
||||||
<StopPropagationContainer>
|
|
||||||
<Checkbox
|
|
||||||
hoverable
|
|
||||||
checked={isCurrentCardSelected}
|
|
||||||
onChange={() => {
|
|
||||||
setIsCurrentCardSelected(!isCurrentCardSelected);
|
|
||||||
checkIfLastUnselectAndCloseDropdown();
|
|
||||||
}}
|
|
||||||
variant={CheckboxVariant.Secondary}
|
|
||||||
/>
|
|
||||||
</StopPropagationContainer>
|
|
||||||
</StyledCheckboxContainer>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
<StyledCheckboxContainer className="checkbox-container">
|
||||||
|
<StopPropagationContainer>
|
||||||
|
<Checkbox
|
||||||
|
hoverable
|
||||||
|
checked={isCurrentCardSelected}
|
||||||
|
onChange={() => {
|
||||||
|
setIsCurrentCardSelected(!isCurrentCardSelected);
|
||||||
|
checkIfLastUnselectAndCloseDropdown();
|
||||||
|
}}
|
||||||
|
variant={CheckboxVariant.Secondary}
|
||||||
|
/>
|
||||||
|
</StopPropagationContainer>
|
||||||
|
</StyledCheckboxContainer>
|
||||||
</RecordBoardCardHeaderContainer>
|
</RecordBoardCardHeaderContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,16 +3,11 @@ import { Draggable, DroppableProvided } from '@hello-pangea/dnd';
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
|
||||||
import { RecordBoardColumnCardContainerSkeletonLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader';
|
import { RecordBoardColumnCardContainerSkeletonLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader';
|
||||||
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
|
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
|
||||||
import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader';
|
import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader';
|
||||||
import { RecordBoardColumnNewOpportunity } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity';
|
|
||||||
import { RecordBoardColumnNewRecord } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecord';
|
|
||||||
import { RecordBoardColumnNewRecordButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton';
|
import { RecordBoardColumnNewRecordButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
|
||||||
import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading';
|
import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading';
|
||||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||||
@ -50,7 +45,6 @@ export const RecordBoardColumnCardsContainer = ({
|
|||||||
droppableProvided,
|
droppableProvided,
|
||||||
}: RecordBoardColumnCardsContainerProps) => {
|
}: RecordBoardColumnCardsContainerProps) => {
|
||||||
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
|
||||||
|
|
||||||
const columnId = columnDefinition.id;
|
const columnId = columnDefinition.id;
|
||||||
|
|
||||||
@ -68,42 +62,12 @@ export const RecordBoardColumnCardsContainer = ({
|
|||||||
isRecordBoardCompactModeActiveComponentState,
|
isRecordBoardCompactModeActiveComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isOpportunitiesCompanyFieldDisabled } =
|
|
||||||
useIsOpportunitiesCompanyFieldDisabled();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnCardsContainer
|
<StyledColumnCardsContainer
|
||||||
ref={droppableProvided?.innerRef}
|
ref={droppableProvided?.innerRef}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...droppableProvided?.droppableProps}
|
{...droppableProvided?.droppableProps}
|
||||||
>
|
>
|
||||||
<Draggable
|
|
||||||
draggableId={`new-${columnDefinition.id}-top`}
|
|
||||||
index={-1}
|
|
||||||
isDragDisabled={true}
|
|
||||||
>
|
|
||||||
{(draggableProvided) => (
|
|
||||||
<div
|
|
||||||
ref={draggableProvided?.innerRef}
|
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
||||||
{...draggableProvided?.draggableProps}
|
|
||||||
>
|
|
||||||
{objectMetadataItem.nameSingular ===
|
|
||||||
CoreObjectNameSingular.Opportunity &&
|
|
||||||
!isOpportunitiesCompanyFieldDisabled ? (
|
|
||||||
<RecordBoardColumnNewOpportunity
|
|
||||||
columnId={columnDefinition.id}
|
|
||||||
position="first"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<RecordBoardColumnNewRecord
|
|
||||||
columnId={columnDefinition.id}
|
|
||||||
position="first"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
{isRecordIndexBoardColumnLoading ? (
|
{isRecordIndexBoardColumnLoading ? (
|
||||||
Array.from(
|
Array.from(
|
||||||
{
|
{
|
||||||
@ -138,23 +102,8 @@ export const RecordBoardColumnCardsContainer = ({
|
|||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...draggableProvided?.draggableProps}
|
{...draggableProvided?.draggableProps}
|
||||||
>
|
>
|
||||||
{objectMetadataItem.nameSingular ===
|
|
||||||
CoreObjectNameSingular.Opportunity &&
|
|
||||||
!isOpportunitiesCompanyFieldDisabled ? (
|
|
||||||
<RecordBoardColumnNewOpportunity
|
|
||||||
columnId={columnDefinition.id}
|
|
||||||
position="last"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<RecordBoardColumnNewRecord
|
|
||||||
columnId={columnDefinition.id}
|
|
||||||
position="last"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<StyledNewButtonContainer>
|
<StyledNewButtonContainer>
|
||||||
<RecordBoardColumnNewRecordButton
|
<RecordBoardColumnNewRecordButton />
|
||||||
columnId={columnDefinition.id}
|
|
||||||
/>
|
|
||||||
</StyledNewButtonContainer>
|
</StyledNewButtonContainer>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
||||||
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn';
|
import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn';
|
||||||
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
|
|
||||||
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
|
||||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||||
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
|
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
|
||||||
@ -69,7 +67,8 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
||||||
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
||||||
|
|
||||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
const { objectMetadataItem, selectFieldMetadataItem } =
|
||||||
|
useContext(RecordBoardContext);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
@ -94,18 +93,11 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
const { aggregateValue, aggregateLabel } =
|
const { aggregateValue, aggregateLabel } =
|
||||||
useAggregateRecordsForRecordBoardColumn();
|
useAggregateRecordsForRecordBoardColumn();
|
||||||
|
|
||||||
const { handleNewButtonClick } = useColumnNewCardActions(
|
|
||||||
columnDefinition.id ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||||
|
|
||||||
const { isOpportunitiesCompanyFieldDisabled } =
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
useIsOpportunitiesCompanyFieldDisabled();
|
objectMetadataItem: objectMetadataItem,
|
||||||
|
});
|
||||||
const isOpportunity =
|
|
||||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity &&
|
|
||||||
!isOpportunitiesCompanyFieldDisabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumn>
|
<StyledColumn>
|
||||||
@ -153,7 +145,12 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
<LightIconButton
|
<LightIconButton
|
||||||
accent="tertiary"
|
accent="tertiary"
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
onClick={() => handleNewButtonClick('first', isOpportunity)}
|
onClick={() => {
|
||||||
|
createNewIndexRecord({
|
||||||
|
position: 'first',
|
||||||
|
[selectFieldMetadataItem.name]: columnDefinition.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledHeaderActions>
|
</StyledHeaderActions>
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
|
||||||
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
|
|
||||||
import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker';
|
|
||||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
|
||||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
|
||||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
export const RecordBoardColumnNewOpportunity = ({
|
|
||||||
columnId,
|
|
||||||
position,
|
|
||||||
}: {
|
|
||||||
columnId: string;
|
|
||||||
position: 'last' | 'first';
|
|
||||||
}) => {
|
|
||||||
const newRecord = useRecoilValue(
|
|
||||||
recordBoardNewRecordByColumnIdSelector({
|
|
||||||
familyKey: columnId,
|
|
||||||
scopeId: columnId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { handleCreateSuccess, handleEntitySelect } = useAddNewCard({
|
|
||||||
recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { createOneRecord: createCompany } = useCreateOneRecord({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.Company,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
|
||||||
const setViewableRecordNameSingular = useSetRecoilState(
|
|
||||||
viewableRecordNameSingularState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
|
||||||
|
|
||||||
const createCompanyOpportunityAndOpenCommandMenu = async (
|
|
||||||
searchInput?: string,
|
|
||||||
) => {
|
|
||||||
const newRecordId = v4();
|
|
||||||
|
|
||||||
const createdCompany = await createCompany({
|
|
||||||
id: newRecordId,
|
|
||||||
name: searchInput,
|
|
||||||
});
|
|
||||||
|
|
||||||
setViewableRecordId(newRecordId);
|
|
||||||
setViewableRecordNameSingular(CoreObjectNameSingular.Company);
|
|
||||||
openRecordInCommandMenu({
|
|
||||||
recordId: newRecordId,
|
|
||||||
objectNameSingular: CoreObjectNameSingular.Company,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(createdCompany)) {
|
|
||||||
handleEntitySelect(position, createdCompany);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{newRecord.isCreating && newRecord.position === position && (
|
|
||||||
<OverlayContainer>
|
|
||||||
<SingleRecordPicker
|
|
||||||
componentInstanceId={`add-new-card-record-picker-column-${columnId}`}
|
|
||||||
onCancel={() => handleCreateSuccess(position, columnId, false)}
|
|
||||||
onRecordSelected={(company) =>
|
|
||||||
company ? handleEntitySelect(position, company) : null
|
|
||||||
}
|
|
||||||
objectNameSingular={CoreObjectNameSingular.Company}
|
|
||||||
onCreate={createCompanyOpportunityAndOpenCommandMenu}
|
|
||||||
/>
|
|
||||||
</OverlayContainer>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
|
||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
|
||||||
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
|
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
export const RecordBoardColumnNewRecord = ({
|
|
||||||
columnId,
|
|
||||||
position,
|
|
||||||
}: {
|
|
||||||
columnId: string;
|
|
||||||
position: 'first' | 'last';
|
|
||||||
}) => {
|
|
||||||
const newRecord = useRecoilValue(
|
|
||||||
recordBoardNewRecordByColumnIdSelector({
|
|
||||||
familyKey: columnId,
|
|
||||||
scopeId: columnId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { handleCreateSuccess } = useAddNewCard({
|
|
||||||
recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{newRecord.isCreating && newRecord.position === position && (
|
|
||||||
<RecordBoardCard
|
|
||||||
isCreating={true}
|
|
||||||
onCreateSuccess={() => handleCreateSuccess(position)}
|
|
||||||
position={position}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useContext } from 'react';
|
||||||
import { IconPlus } from 'twenty-ui';
|
import { IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledNewButton = styled.button`
|
const StyledNewButton = styled.button`
|
||||||
@ -21,23 +24,33 @@ const StyledNewButton = styled.button`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordBoardColumnNewRecordButton = ({
|
export const RecordBoardColumnNewRecordButton = () => {
|
||||||
columnId,
|
|
||||||
}: {
|
|
||||||
columnId: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { handleNewButtonClick } = useColumnNewCardActions(columnId);
|
const { objectMetadataItem, selectFieldMetadataItem } =
|
||||||
|
useContext(RecordBoardContext);
|
||||||
|
|
||||||
|
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||||
|
|
||||||
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
|
objectMetadataItem: objectMetadataItem,
|
||||||
|
});
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
if (hasObjectReadOnlyPermission) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledNewButton onClick={() => handleNewButtonClick('last', false)}>
|
<StyledNewButton
|
||||||
|
onClick={() => {
|
||||||
|
createNewIndexRecord({
|
||||||
|
position: 'last',
|
||||||
|
[selectFieldMetadataItem.name]: columnDefinition.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IconPlus size={theme.icon.size.md} />
|
<IconPlus size={theme.icon.size.md} />
|
||||||
New
|
New
|
||||||
</StyledNewButton>
|
</StyledNewButton>
|
||||||
|
|||||||
@ -1,235 +0,0 @@
|
|||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
|
||||||
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
|
|
||||||
import { useSingleRecordPickerSearch } from '@/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch';
|
|
||||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
|
||||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
|
||||||
import { useCallback, useContext } from 'react';
|
|
||||||
import { RecoilState, useRecoilCallback } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
type SetFunction = <T>(
|
|
||||||
recoilVal: RecoilState<T>,
|
|
||||||
valOrUpdater: T | ((currVal: T) => T),
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
type UseAddNewCardProps = {
|
|
||||||
recordPickerComponentInstanceId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAddNewCard = ({
|
|
||||||
recordPickerComponentInstanceId,
|
|
||||||
}: UseAddNewCardProps) => {
|
|
||||||
const columnContext = useContext(RecordBoardColumnContext);
|
|
||||||
const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } =
|
|
||||||
useContext(RecordBoardContext);
|
|
||||||
const { resetSearchFilter } = useSingleRecordPickerSearch(
|
|
||||||
recordPickerComponentInstanceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const getColumnDefinitionId = useCallback(
|
|
||||||
(columnId?: string) => {
|
|
||||||
const columnDefinitionId = columnId || columnContext?.columnDefinition.id;
|
|
||||||
if (!columnDefinitionId) {
|
|
||||||
throw new Error('Column ID is required');
|
|
||||||
}
|
|
||||||
return columnDefinitionId;
|
|
||||||
},
|
|
||||||
[columnContext],
|
|
||||||
);
|
|
||||||
|
|
||||||
const addNewItem = useCallback(
|
|
||||||
(
|
|
||||||
set: SetFunction,
|
|
||||||
columnDefinitionId: string,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
isOpportunity: boolean,
|
|
||||||
) => {
|
|
||||||
set(
|
|
||||||
recordBoardNewRecordByColumnIdSelector({
|
|
||||||
familyKey: columnDefinitionId,
|
|
||||||
scopeId: columnDefinitionId,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
columnId: columnDefinitionId,
|
|
||||||
isCreating: true,
|
|
||||||
position,
|
|
||||||
isOpportunity,
|
|
||||||
company: null,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const createRecord = useCallback(
|
|
||||||
(
|
|
||||||
labelValue: string,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
isOpportunity: boolean,
|
|
||||||
company?: SingleRecordPickerRecord,
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
(isOpportunity && company !== null) ||
|
|
||||||
(!isOpportunity && labelValue !== '')
|
|
||||||
) {
|
|
||||||
// TODO: Refactor this whole section (Add new card): this should be:
|
|
||||||
// - simpler
|
|
||||||
// - piloted by metadata,
|
|
||||||
// - avoid drill down props, especially internal stuff
|
|
||||||
// - and follow record table pending record creation logic
|
|
||||||
let computedLabelIdentifierValue: any = labelValue;
|
|
||||||
|
|
||||||
const labelIdentifierField = objectMetadataItem?.fields.find(
|
|
||||||
(field) =>
|
|
||||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isDefined(labelIdentifierField)) {
|
|
||||||
throw new Error('Label identifier field not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelIdentifierField.type === FieldMetadataType.FULL_NAME) {
|
|
||||||
computedLabelIdentifierValue = {
|
|
||||||
firstName: labelValue,
|
|
||||||
lastName: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
createOneRecord({
|
|
||||||
[selectFieldMetadataItem.name]: columnContext?.columnDefinition.value,
|
|
||||||
position,
|
|
||||||
...(isOpportunity
|
|
||||||
? { companyId: company?.id, name: company?.name }
|
|
||||||
: {
|
|
||||||
[labelIdentifierField.name]: computedLabelIdentifierValue,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
objectMetadataItem?.fields,
|
|
||||||
objectMetadataItem?.labelIdentifierFieldMetadataId,
|
|
||||||
createOneRecord,
|
|
||||||
selectFieldMetadataItem?.name,
|
|
||||||
columnContext?.columnDefinition?.value,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAddNewCardClick = useRecoilCallback(
|
|
||||||
({ set }) =>
|
|
||||||
(
|
|
||||||
labelValue: string,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
isOpportunity: boolean,
|
|
||||||
columnId?: string,
|
|
||||||
): void => {
|
|
||||||
const columnDefinitionId = getColumnDefinitionId(columnId);
|
|
||||||
addNewItem(set, columnDefinitionId, position, isOpportunity);
|
|
||||||
if (isOpportunity) {
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope(
|
|
||||||
SingleRecordPickerHotkeyScope.SingleRecordPicker,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createRecord(labelValue, position, isOpportunity);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
addNewItem,
|
|
||||||
createRecord,
|
|
||||||
getColumnDefinitionId,
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreateSuccess = useRecoilCallback(
|
|
||||||
({ set }) =>
|
|
||||||
(
|
|
||||||
position: 'first' | 'last',
|
|
||||||
columnId?: string,
|
|
||||||
isOpportunity = false,
|
|
||||||
): void => {
|
|
||||||
const columnDefinitionId = getColumnDefinitionId(columnId);
|
|
||||||
set(
|
|
||||||
recordBoardNewRecordByColumnIdSelector({
|
|
||||||
familyKey: columnDefinitionId,
|
|
||||||
scopeId: columnDefinitionId,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
id: '',
|
|
||||||
columnId: columnDefinitionId,
|
|
||||||
isCreating: false,
|
|
||||||
position,
|
|
||||||
isOpportunity: Boolean(isOpportunity),
|
|
||||||
company: null,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
resetSearchFilter();
|
|
||||||
if (isOpportunity === true) {
|
|
||||||
goBackToPreviousHotkeyScope();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[getColumnDefinitionId, goBackToPreviousHotkeyScope, resetSearchFilter],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreate = (
|
|
||||||
labelValue: string,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
onCreateSuccess?: () => void,
|
|
||||||
) => {
|
|
||||||
if (labelValue.trim() !== '' && position !== undefined) {
|
|
||||||
handleAddNewCardClick(labelValue.trim(), position, false, '');
|
|
||||||
onCreateSuccess?.();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = (
|
|
||||||
labelValue: string,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
onCreateSuccess?: () => void,
|
|
||||||
) => {
|
|
||||||
if (labelValue.trim() === '') {
|
|
||||||
onCreateSuccess?.();
|
|
||||||
} else {
|
|
||||||
handleCreate(labelValue, position, onCreateSuccess);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputEnter = (
|
|
||||||
labelValue: string,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
onCreateSuccess?: () => void,
|
|
||||||
) => {
|
|
||||||
handleCreate(labelValue, position, onCreateSuccess);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEntitySelect = useCallback(
|
|
||||||
(
|
|
||||||
position: 'first' | 'last',
|
|
||||||
company: SingleRecordPickerRecord,
|
|
||||||
columnId?: string,
|
|
||||||
) => {
|
|
||||||
const columnDefinitionId = getColumnDefinitionId(columnId);
|
|
||||||
createRecord('', position, true, company);
|
|
||||||
handleCreateSuccess(position, columnDefinitionId, true);
|
|
||||||
},
|
|
||||||
[createRecord, handleCreateSuccess, getColumnDefinitionId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleAddNewCardClick,
|
|
||||||
handleCreateSuccess,
|
|
||||||
handleBlur,
|
|
||||||
handleInputEnter,
|
|
||||||
handleEntitySelect,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
|
||||||
|
|
||||||
export const useColumnNewCardActions = (columnId: string) => {
|
|
||||||
const { handleAddNewCardClick } = useAddNewCard({
|
|
||||||
recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleNewButtonClick = (
|
|
||||||
position: 'first' | 'last',
|
|
||||||
isOpportunity: boolean,
|
|
||||||
) => {
|
|
||||||
handleAddNewCardClick('', position, isOpportunity, columnId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleNewButtonClick,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
|
|
||||||
export const useIsOpportunitiesCompanyFieldDisabled = () => {
|
|
||||||
const { objectMetadataItem: opportunityMetadataItem } = useObjectMetadataItem(
|
|
||||||
{
|
|
||||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const isOpportunitiesCompanyFieldDisabled =
|
|
||||||
!opportunityMetadataItem.fields.find(
|
|
||||||
(field) => field.name === CoreObjectNameSingular.Company,
|
|
||||||
)?.isActive || false;
|
|
||||||
return {
|
|
||||||
isOpportunitiesCompanyFieldDisabled,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
|
||||||
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
|
|
||||||
|
|
||||||
export type NewCard = {
|
|
||||||
id: string;
|
|
||||||
columnId: string;
|
|
||||||
isCreating: boolean;
|
|
||||||
position: 'first' | 'last';
|
|
||||||
isOpportunity: boolean;
|
|
||||||
company: SingleRecordPickerRecord | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const recordBoardNewRecordByColumnIdComponentFamilyState =
|
|
||||||
createComponentFamilyState<NewCard, string>({
|
|
||||||
key: 'recordBoardNewRecordByColumnIdComponentFamilyState',
|
|
||||||
defaultValue: {
|
|
||||||
id: '',
|
|
||||||
columnId: '',
|
|
||||||
isCreating: false,
|
|
||||||
position: 'last',
|
|
||||||
isOpportunity: false,
|
|
||||||
company: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
|
|
||||||
import {
|
|
||||||
NewCard,
|
|
||||||
recordBoardNewRecordByColumnIdComponentFamilyState,
|
|
||||||
} from '../recordBoardNewRecordByColumnIdComponentFamilyState';
|
|
||||||
|
|
||||||
export const recordBoardNewRecordByColumnIdSelector =
|
|
||||||
createComponentFamilySelector<NewCard, string>({
|
|
||||||
key: 'recordBoardNewRecordByColumnIdSelector',
|
|
||||||
get:
|
|
||||||
({ familyKey, scopeId }: { familyKey: string; scopeId: string }) =>
|
|
||||||
({ get }) => {
|
|
||||||
return get(
|
|
||||||
recordBoardNewRecordByColumnIdComponentFamilyState({
|
|
||||||
familyKey,
|
|
||||||
scopeId,
|
|
||||||
}),
|
|
||||||
) as NewCard;
|
|
||||||
},
|
|
||||||
set:
|
|
||||||
({ familyKey, scopeId }: { familyKey: string; scopeId: string }) =>
|
|
||||||
({ set }, newValue) => {
|
|
||||||
set(
|
|
||||||
recordBoardNewRecordByColumnIdComponentFamilyState({
|
|
||||||
familyKey,
|
|
||||||
scopeId,
|
|
||||||
}),
|
|
||||||
newValue as NewCard,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -2,11 +2,12 @@ import { useContext } from 'react';
|
|||||||
|
|
||||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
|
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
|
||||||
|
|
||||||
@ -15,8 +16,11 @@ export const useIsFieldValueReadOnly = () => {
|
|||||||
|
|
||||||
const { metadata, type } = fieldDefinition;
|
const { metadata, type } = fieldDefinition;
|
||||||
|
|
||||||
const recordFromStore = useRecoilValue<ObjectRecord | null>(
|
const recordDeletedAt = useRecoilValue<ObjectRecord | null>(
|
||||||
recordStoreFamilyState(recordId),
|
recordStoreFamilySelector({
|
||||||
|
recordId,
|
||||||
|
fieldName: 'deletedAt',
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const contextStoreCurrentViewType = useRecoilComponentValueV2(
|
const contextStoreCurrentViewType = useRecoilComponentValueV2(
|
||||||
@ -34,7 +38,7 @@ export const useIsFieldValueReadOnly = () => {
|
|||||||
fieldName: metadata.fieldName,
|
fieldName: metadata.fieldName,
|
||||||
fieldType: type,
|
fieldType: type,
|
||||||
isObjectRemote: objectMetadataItem.isRemote,
|
isObjectRemote: objectMetadataItem.isRemote,
|
||||||
isRecordDeleted: recordFromStore?.deletedAt,
|
isRecordDeleted: isDefined(recordDeletedAt),
|
||||||
hasObjectReadOnlyPermission,
|
hasObjectReadOnlyPermission,
|
||||||
contextStoreCurrentViewType,
|
contextStoreCurrentViewType,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export const RelationFromManyFieldDisplay = () => {
|
|||||||
fieldDefinition?.metadata.relationObjectMetadataNameSingular;
|
fieldDefinition?.metadata.relationObjectMetadataNameSingular;
|
||||||
|
|
||||||
const { activityTargetObjectRecords } = useActivityTargetObjectRecords(
|
const { activityTargetObjectRecords } = useActivityTargetObjectRecords(
|
||||||
undefined,
|
'',
|
||||||
fieldValue as NoteTarget[] | TaskTarget[],
|
fieldValue as NoteTarget[] | TaskTarget[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { availableRecordGroupIdsComponentSelector } from '@/object-record/record
|
|||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { useCreateNewTableRecordInGroup } from '@/object-record/record-table/hooks/useCreateNewTableRecordInGroup';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
@ -44,23 +44,31 @@ export const RecordIndexAddRecordInGroupDropdown = ({
|
|||||||
(field) => field.id === recordGroupFieldMetadata?.id,
|
(field) => field.id === recordGroupFieldMetadata?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { createNewTableRecordInGroup } = useCreateNewTableRecordInGroup();
|
|
||||||
|
|
||||||
const { closeDropdown } = useDropdown(dropdownId);
|
const { closeDropdown } = useDropdown(dropdownId);
|
||||||
|
|
||||||
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
|
objectMetadataItem,
|
||||||
|
});
|
||||||
|
|
||||||
const handleCreateNewTableRecordInGroup = useRecoilCallback(
|
const handleCreateNewTableRecordInGroup = useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
(recordGroup: RecordGroupDefinition) => {
|
(recordGroup: RecordGroupDefinition) => {
|
||||||
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
|
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
|
||||||
createNewTableRecordInGroup(recordGroup.id);
|
|
||||||
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
||||||
|
if (!selectFieldMetadataItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createNewIndexRecord({
|
||||||
|
[selectFieldMetadataItem.name]: recordGroup.value,
|
||||||
|
});
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
closeDropdown,
|
|
||||||
createNewTableRecordInGroup,
|
|
||||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
|
||||||
isRecordGroupTableSectionToggledState,
|
isRecordGroupTableSectionToggledState,
|
||||||
|
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||||
|
selectFieldMetadataItem,
|
||||||
|
createNewIndexRecord,
|
||||||
|
closeDropdown,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
|
||||||
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
|
||||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
|
||||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
|
||||||
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
|
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
|
||||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
export const RecordIndexPageKanbanAddButton = () => {
|
|
||||||
const dropdownId = `record-index-page-add-button-dropdown`;
|
|
||||||
|
|
||||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
|
||||||
|
|
||||||
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
|
||||||
visibleRecordGroupIdsComponentFamilySelector,
|
|
||||||
ViewType.Kanban,
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordIndexKanbanFieldMetadataId = useRecoilValue(
|
|
||||||
recordIndexKanbanFieldMetadataIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectFieldMetadataItem = objectMetadataItem.fields.find(
|
|
||||||
(field) => field.id === recordIndexKanbanFieldMetadataId,
|
|
||||||
);
|
|
||||||
const isOpportunity =
|
|
||||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity;
|
|
||||||
|
|
||||||
const { closeDropdown } = useDropdown(dropdownId);
|
|
||||||
const { isOpportunitiesCompanyFieldDisabled } =
|
|
||||||
useIsOpportunitiesCompanyFieldDisabled();
|
|
||||||
const { handleAddNewCardClick } = useAddNewCard({
|
|
||||||
recordPickerComponentInstanceId: `add-new-card-record-picker`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleItemClick = useCallback(
|
|
||||||
(columnDefinition: RecordGroupDefinition) => {
|
|
||||||
const isOpportunityEnabled =
|
|
||||||
isOpportunity && !isOpportunitiesCompanyFieldDisabled;
|
|
||||||
handleAddNewCardClick(
|
|
||||||
'',
|
|
||||||
'first',
|
|
||||||
isOpportunityEnabled,
|
|
||||||
columnDefinition.id,
|
|
||||||
);
|
|
||||||
closeDropdown();
|
|
||||||
},
|
|
||||||
[
|
|
||||||
isOpportunity,
|
|
||||||
handleAddNewCardClick,
|
|
||||||
closeDropdown,
|
|
||||||
isOpportunitiesCompanyFieldDisabled,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!selectFieldMetadataItem) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
dropdownMenuWidth="200px"
|
|
||||||
dropdownPlacement="bottom-start"
|
|
||||||
clickableComponent={<PageAddButton />}
|
|
||||||
dropdownId={dropdownId}
|
|
||||||
dropdownComponents={
|
|
||||||
<DropdownMenuItemsContainer>
|
|
||||||
{visibleRecordGroupIds.map((recordGroupId) => (
|
|
||||||
<RecordIndexPageKanbanAddMenuItem
|
|
||||||
key={recordGroupId}
|
|
||||||
columnId={recordGroupId}
|
|
||||||
onItemClick={handleItemClick}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
}
|
|
||||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -6,11 +6,9 @@ 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 { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||||
import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon';
|
import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon';
|
||||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
|
||||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
|
|
||||||
import { useInlineCell } from '../hooks/useInlineCell';
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
|
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
||||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||||
@ -18,6 +16,7 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit
|
|||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
|
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||||
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
||||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||||
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
||||||
@ -28,7 +27,6 @@ import {
|
|||||||
RecordInlineCellContext,
|
RecordInlineCellContext,
|
||||||
RecordInlineCellContextProps,
|
RecordInlineCellContextProps,
|
||||||
} from './RecordInlineCellContext';
|
} from './RecordInlineCellContext';
|
||||||
|
|
||||||
type RecordInlineCellProps = {
|
type RecordInlineCellProps = {
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
|
||||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
|
||||||
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
|
||||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
|
||||||
import { RIGHT_DRAWER_RECORD_INSTANCE_ID } from '@/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId';
|
|
||||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
|
||||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
|
||||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
|
||||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
|
||||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
|
||||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
|
||||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
|
||||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
const StyledRightDrawerRecord = styled.div<{ isMobile: boolean }>`
|
|
||||||
height: ${({ theme, isMobile }) =>
|
|
||||||
isMobile ? `calc(100% - ${theme.spacing(16)})` : '100%'};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RightDrawerRecord = () => {
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
|
|
||||||
const viewableRecordNameSingular = useRecoilValue(
|
|
||||||
viewableRecordNameSingularState,
|
|
||||||
);
|
|
||||||
const isNewViewableRecordLoading = useRecoilValue(
|
|
||||||
isNewViewableRecordLoadingState,
|
|
||||||
);
|
|
||||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
|
||||||
|
|
||||||
if (!viewableRecordNameSingular && !isNewViewableRecordLoading) {
|
|
||||||
throw new Error(`Object name is not defined`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { objectNameSingular, objectRecordId } = useRecordShowPage(
|
|
||||||
viewableRecordNameSingular ?? '',
|
|
||||||
viewableRecordId ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RecordFilterGroupsComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
|
||||||
>
|
|
||||||
<RecordFiltersComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
|
||||||
>
|
|
||||||
<RecordSortsComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
|
||||||
>
|
|
||||||
<ContextStoreComponentInstanceContext.Provider
|
|
||||||
value={{
|
|
||||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID }}
|
|
||||||
>
|
|
||||||
<StyledRightDrawerRecord isMobile={isMobile}>
|
|
||||||
<RecordFieldValueSelectorContextProvider>
|
|
||||||
{!isNewViewableRecordLoading && (
|
|
||||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
|
||||||
)}
|
|
||||||
<RecordShowContainer
|
|
||||||
objectNameSingular={objectNameSingular}
|
|
||||||
objectRecordId={objectRecordId}
|
|
||||||
loading={false}
|
|
||||||
isInRightDrawer={true}
|
|
||||||
isNewRightDrawerItemLoading={isNewViewableRecordLoading}
|
|
||||||
/>
|
|
||||||
</RecordFieldValueSelectorContextProvider>
|
|
||||||
</StyledRightDrawerRecord>
|
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
|
||||||
</ContextStoreComponentInstanceContext.Provider>
|
|
||||||
</RecordSortsComponentInstanceContext.Provider>
|
|
||||||
</RecordFiltersComponentInstanceContext.Provider>
|
|
||||||
</RecordFilterGroupsComponentInstanceContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
|
||||||
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
|
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
|
||||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
|
||||||
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
// TODO: this should be better implemented if we refactor field input so that it's easier to implement logic like that
|
|
||||||
// Idea : maybe we could use draftValue instead of the newValue in the events
|
|
||||||
// Idea : we can remove all our listeners in the many input types and replace them with a onClose event that gives the event type
|
|
||||||
// (tab, shift-tab, click-outside, escape, enter) and the newValue, that will reduce the boilerplate
|
|
||||||
// and also the need to have our difficult to understand persist logic
|
|
||||||
// the goal would be that we could easily call usePersistField anywhere under a FieldContext and it would work
|
|
||||||
export const RightDrawerTitleRecordInlineCell = () => {
|
|
||||||
const { closeInlineCell } = useInlineCell();
|
|
||||||
const persistField = usePersistField();
|
|
||||||
|
|
||||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
|
||||||
|
|
||||||
const { getDraftValueSelector } = useRecordFieldInput<unknown>(
|
|
||||||
`${recordId}-${fieldDefinition.metadata.fieldName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const draftValue = useRecoilValue(getDraftValueSelector());
|
|
||||||
|
|
||||||
useListenRightDrawerClose(() => {
|
|
||||||
if (draftValue !== undefined) {
|
|
||||||
persistField(draftValue);
|
|
||||||
}
|
|
||||||
closeInlineCell();
|
|
||||||
});
|
|
||||||
|
|
||||||
return <RecordInlineCell />;
|
|
||||||
};
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export const RIGHT_DRAWER_RECORD_INSTANCE_ID = 'right-drawer-record';
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import groupBy from 'lodash.groupby';
|
import groupBy from 'lodash.groupby';
|
||||||
|
|
||||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||||
import { Note } from '@/activities/types/Note';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { Task } from '@/activities/types/Task';
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
@ -15,7 +15,6 @@ import { useRecordShowContainerData } from '@/object-record/record-show/hooks/us
|
|||||||
import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
|
import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
|
||||||
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
|
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
|
||||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
type FieldsCardProps = {
|
type FieldsCardProps = {
|
||||||
@ -27,22 +26,20 @@ export const FieldsCard = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
}: FieldsCardProps) => {
|
}: FieldsCardProps) => {
|
||||||
const {
|
const { recordLoading, labelIdentifierFieldMetadataItem, isPrefetchLoading } =
|
||||||
recordFromStore,
|
useRecordShowContainerData({
|
||||||
recordLoading,
|
objectNameSingular,
|
||||||
objectMetadataItem,
|
objectRecordId,
|
||||||
labelIdentifierFieldMetadataItem,
|
});
|
||||||
isPrefetchLoading,
|
|
||||||
objectMetadataItems,
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
} = useRecordShowContainerData({
|
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
|
||||||
});
|
});
|
||||||
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
|
||||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
recordFromStore,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const availableFieldMetadataItems = objectMetadataItem.fields
|
const availableFieldMetadataItems = objectMetadataItem.fields
|
||||||
@ -87,100 +84,94 @@ export const FieldsCard = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isDefined(recordFromStore) && (
|
<PropertyBox>
|
||||||
<>
|
{isPrefetchLoading ? (
|
||||||
<PropertyBox>
|
<PropertyBoxSkeletonLoader />
|
||||||
{isPrefetchLoading ? (
|
) : (
|
||||||
<PropertyBoxSkeletonLoader />
|
<>
|
||||||
) : (
|
{inlineRelationFieldMetadataItems?.map(
|
||||||
<>
|
(fieldMetadataItem, index) => (
|
||||||
{inlineRelationFieldMetadataItems?.map(
|
<FieldContext.Provider
|
||||||
(fieldMetadataItem, index) => (
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
<FieldContext.Provider
|
value={{
|
||||||
key={objectRecordId + fieldMetadataItem.id}
|
recordId: objectRecordId,
|
||||||
value={{
|
maxWidth: 200,
|
||||||
recordId: objectRecordId,
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
maxWidth: 200,
|
isLabelIdentifier: false,
|
||||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
isLabelIdentifier: false,
|
field: fieldMetadataItem,
|
||||||
fieldDefinition:
|
position: index,
|
||||||
formatFieldMetadataItemAsColumnDefinition({
|
objectMetadataItem,
|
||||||
field: fieldMetadataItem,
|
showLabel: true,
|
||||||
position: index,
|
labelWidth: 90,
|
||||||
objectMetadataItem,
|
}),
|
||||||
showLabel: true,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
labelWidth: 90,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
}),
|
}}
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
>
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
<ActivityTargetsInlineCell
|
||||||
}}
|
activityObjectNameSingular={
|
||||||
>
|
objectNameSingular as
|
||||||
<ActivityTargetsInlineCell
|
| CoreObjectNameSingular.Note
|
||||||
activityObjectNameSingular={
|
| CoreObjectNameSingular.Task
|
||||||
objectNameSingular as
|
}
|
||||||
| CoreObjectNameSingular.Note
|
activityRecordId={objectRecordId}
|
||||||
| CoreObjectNameSingular.Task
|
showLabel={true}
|
||||||
}
|
maxWidth={200}
|
||||||
activity={recordFromStore as Task | Note}
|
/>
|
||||||
showLabel={true}
|
</FieldContext.Provider>
|
||||||
maxWidth={200}
|
),
|
||||||
/>
|
|
||||||
</FieldContext.Provider>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
{inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
|
||||||
<FieldContext.Provider
|
|
||||||
key={objectRecordId + fieldMetadataItem.id}
|
|
||||||
value={{
|
|
||||||
recordId: objectRecordId,
|
|
||||||
maxWidth: 200,
|
|
||||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
fieldDefinition:
|
|
||||||
formatFieldMetadataItemAsColumnDefinition({
|
|
||||||
field: fieldMetadataItem,
|
|
||||||
position: index,
|
|
||||||
objectMetadataItem,
|
|
||||||
showLabel: true,
|
|
||||||
labelWidth: 90,
|
|
||||||
}),
|
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordInlineCell loading={recordLoading} />
|
|
||||||
</FieldContext.Provider>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</PropertyBox>
|
{inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||||
<RecordDetailDuplicatesSection
|
<FieldContext.Provider
|
||||||
objectRecordId={objectRecordId}
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
objectNameSingular={objectNameSingular}
|
value={{
|
||||||
|
recordId: objectRecordId,
|
||||||
|
maxWidth: 200,
|
||||||
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
|
isLabelIdentifier: false,
|
||||||
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
|
field: fieldMetadataItem,
|
||||||
|
position: index,
|
||||||
|
objectMetadataItem,
|
||||||
|
showLabel: true,
|
||||||
|
labelWidth: 90,
|
||||||
|
}),
|
||||||
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordInlineCell loading={recordLoading} />
|
||||||
|
</FieldContext.Provider>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PropertyBox>
|
||||||
|
<RecordDetailDuplicatesSection
|
||||||
|
objectRecordId={objectRecordId}
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
/>
|
||||||
|
{boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||||
|
<FieldContext.Provider
|
||||||
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
|
value={{
|
||||||
|
recordId: objectRecordId,
|
||||||
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
|
isLabelIdentifier: false,
|
||||||
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
|
field: fieldMetadataItem,
|
||||||
|
position: index,
|
||||||
|
objectMetadataItem,
|
||||||
|
}),
|
||||||
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordDetailRelationSection
|
||||||
|
loading={isPrefetchLoading || recordLoading}
|
||||||
/>
|
/>
|
||||||
{boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
</FieldContext.Provider>
|
||||||
<FieldContext.Provider
|
))}
|
||||||
key={objectRecordId + fieldMetadataItem.id}
|
|
||||||
value={{
|
|
||||||
recordId: objectRecordId,
|
|
||||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
|
||||||
field: fieldMetadataItem,
|
|
||||||
position: index,
|
|
||||||
objectMetadataItem,
|
|
||||||
}),
|
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordDetailRelationSection
|
|
||||||
loading={isPrefetchLoading || recordLoading}
|
|
||||||
/>
|
|
||||||
</FieldContext.Provider>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
|||||||
objectLabelPlural: string;
|
objectLabelPlural: string;
|
||||||
labelIdentifierFieldMetadataItem?: FieldMetadataItem;
|
labelIdentifierFieldMetadataItem?: FieldMetadataItem;
|
||||||
}) => {
|
}) => {
|
||||||
const { record, loading } = useFindOneRecord({
|
const { loading } = useFindOneRecord({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
recordGqlFields: {
|
recordGqlFields: {
|
||||||
@ -52,7 +52,6 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
|||||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
recordFromStore: record ?? null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
@ -3,10 +3,13 @@ import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer
|
|||||||
|
|
||||||
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
|
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
|
||||||
|
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { RecordShowContainerContextStoreTargetedRecordsEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreTargetedRecordsEffect';
|
import { RecordShowContainerContextStoreTargetedRecordsEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreTargetedRecordsEffect';
|
||||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||||
import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs';
|
import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
import { ShowPageSubContainer } from '@/ui/layout/show-page/components/ShowPageSubContainer';
|
import { ShowPageSubContainer } from '@/ui/layout/show-page/components/ShowPageSubContainer';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
type RecordShowContainerProps = {
|
type RecordShowContainerProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -23,16 +26,22 @@ export const RecordShowContainer = ({
|
|||||||
isInRightDrawer = false,
|
isInRightDrawer = false,
|
||||||
isNewRightDrawerItemLoading = false,
|
isNewRightDrawerItemLoading = false,
|
||||||
}: RecordShowContainerProps) => {
|
}: RecordShowContainerProps) => {
|
||||||
const {
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
recordFromStore,
|
objectNameSingular,
|
||||||
objectMetadataItem,
|
});
|
||||||
isPrefetchLoading,
|
|
||||||
recordLoading,
|
const { isPrefetchLoading, recordLoading } = useRecordShowContainerData({
|
||||||
} = useRecordShowContainerData({
|
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const recordDeletedAt = useRecoilValue<string | null>(
|
||||||
|
recordStoreFamilySelector({
|
||||||
|
recordId: objectRecordId,
|
||||||
|
fieldName: 'deletedAt',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const { layout, tabs } = useRecordShowContainerTabs(
|
const { layout, tabs } = useRecordShowContainerTabs(
|
||||||
loading,
|
loading,
|
||||||
objectNameSingular as CoreObjectNameSingular,
|
objectNameSingular as CoreObjectNameSingular,
|
||||||
@ -45,7 +54,7 @@ export const RecordShowContainer = ({
|
|||||||
<RecordShowContainerContextStoreTargetedRecordsEffect
|
<RecordShowContainerContextStoreTargetedRecordsEffect
|
||||||
recordId={objectRecordId}
|
recordId={objectRecordId}
|
||||||
/>
|
/>
|
||||||
{recordFromStore && recordFromStore.deletedAt && (
|
{recordDeletedAt && (
|
||||||
<InformationBannerDeletedRecord
|
<InformationBannerDeletedRecord
|
||||||
recordId={objectRecordId}
|
recordId={objectRecordId}
|
||||||
objectNameSingular={objectNameSingular}
|
objectNameSingular={objectNameSingular}
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
|
import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
|
type RecordShowEffectProps = {
|
||||||
|
objectNameSingular: string;
|
||||||
|
recordId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordShowEffect = ({
|
||||||
|
objectNameSingular,
|
||||||
|
recordId,
|
||||||
|
}: RecordShowEffectProps) => {
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||||
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
|
||||||
|
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
|
||||||
|
buildFindOneRecordForShowPageOperationSignature({
|
||||||
|
objectMetadataItem,
|
||||||
|
objectMetadataItems,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { record } = useFindOneRecord({
|
||||||
|
objectRecordId: recordId,
|
||||||
|
objectNameSingular,
|
||||||
|
recordGqlFields: FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE.fields,
|
||||||
|
withSoftDeleted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [recordFromStore, setRecordFromStore] = useRecoilState(
|
||||||
|
recordStoreFamilyState(recordId),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDefined(record) && !isDeeplyEqual(record, recordFromStore)) {
|
||||||
|
setRecordFromStore(record);
|
||||||
|
}
|
||||||
|
}, [record, recordFromStore, setRecordFromStore]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -4,10 +4,13 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
|
|||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
||||||
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
||||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||||
import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader';
|
import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
@ -25,28 +28,36 @@ export const SummaryCard = ({
|
|||||||
isNewRightDrawerItemLoading,
|
isNewRightDrawerItemLoading,
|
||||||
isInRightDrawer,
|
isInRightDrawer,
|
||||||
}: SummaryCardProps) => {
|
}: SummaryCardProps) => {
|
||||||
const {
|
const { recordLoading, labelIdentifierFieldMetadataItem, isPrefetchLoading } =
|
||||||
recordFromStore,
|
useRecordShowContainerData({
|
||||||
recordLoading,
|
objectNameSingular,
|
||||||
labelIdentifierFieldMetadataItem,
|
objectRecordId,
|
||||||
isPrefetchLoading,
|
});
|
||||||
recordIdentifier,
|
|
||||||
} = useRecordShowContainerData({
|
const recordCreatedAt = useRecoilValue<string | null>(
|
||||||
objectNameSingular,
|
recordStoreFamilySelector({
|
||||||
objectRecordId,
|
recordId: objectRecordId,
|
||||||
});
|
fieldName: 'createdAt',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const { onUploadPicture, useUpdateOneObjectRecordMutation } =
|
const { onUploadPicture, useUpdateOneObjectRecordMutation } =
|
||||||
useRecordShowContainerActions({
|
useRecordShowContainerActions({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
recordFromStore,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
|
const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
|
||||||
const isMobile = useIsMobile() || isInRightDrawer;
|
const isMobile = useIsMobile() || isInRightDrawer;
|
||||||
|
|
||||||
if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) {
|
const recordIdentifier = useRecoilValue(
|
||||||
|
recordStoreIdentifierFamilySelector({
|
||||||
|
objectNameSingular,
|
||||||
|
recordId: objectRecordId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isNewRightDrawerItemLoading || !isDefined(recordCreatedAt)) {
|
||||||
return <ShowPageSummaryCardSkeletonLoader />;
|
return <ShowPageSummaryCardSkeletonLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +69,7 @@ export const SummaryCard = ({
|
|||||||
icon={Icon}
|
icon={Icon}
|
||||||
iconColor={IconColor}
|
iconColor={IconColor}
|
||||||
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
||||||
date={recordFromStore.createdAt ?? ''}
|
date={recordCreatedAt ?? ''}
|
||||||
loading={isPrefetchLoading || recordLoading}
|
loading={isPrefetchLoading || recordLoading}
|
||||||
title={
|
title={
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import {
|
|||||||
RecordUpdateHook,
|
RecordUpdateHook,
|
||||||
RecordUpdateHookParams,
|
RecordUpdateHookParams,
|
||||||
} from '@/object-record/record-field/contexts/FieldContext';
|
} from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|
||||||
import { FileFolder } from '~/generated-metadata/graphql';
|
import { FileFolder } from '~/generated-metadata/graphql';
|
||||||
import { useUploadImageMutation } from '~/generated/graphql';
|
import { useUploadImageMutation } from '~/generated/graphql';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
@ -11,13 +10,11 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|||||||
interface UseRecordShowContainerActionsProps {
|
interface UseRecordShowContainerActionsProps {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
objectRecordId: string;
|
objectRecordId: string;
|
||||||
recordFromStore: ObjectRecord | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRecordShowContainerActions = ({
|
export const useRecordShowContainerActions = ({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
recordFromStore,
|
|
||||||
}: UseRecordShowContainerActionsProps) => {
|
}: UseRecordShowContainerActionsProps) => {
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
|
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
|
||||||
@ -47,7 +44,7 @@ export const useRecordShowContainerActions = ({
|
|||||||
|
|
||||||
const avatarUrl = result?.data?.uploadImage;
|
const avatarUrl = result?.data?.uploadImage;
|
||||||
|
|
||||||
if (!avatarUrl || isUndefinedOrNull(updateOneRecord) || !recordFromStore) {
|
if (!avatarUrl || isUndefinedOrNull(updateOneRecord)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
|
||||||
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|
||||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
type UseRecordShowContainerDataProps = {
|
type UseRecordShowContainerDataProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -17,10 +12,6 @@ export const useRecordShowContainerData = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
}: UseRecordShowContainerDataProps) => {
|
}: UseRecordShowContainerDataProps) => {
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { labelIdentifierFieldMetadataItem } =
|
const { labelIdentifierFieldMetadataItem } =
|
||||||
useLabelIdentifierFieldMetadataItem({
|
useLabelIdentifierFieldMetadataItem({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -30,28 +21,11 @@ export const useRecordShowContainerData = ({
|
|||||||
recordLoadingFamilyState(objectRecordId),
|
recordLoadingFamilyState(objectRecordId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [recordFromStore] = useRecoilState<ObjectRecord | null>(
|
|
||||||
recordStoreFamilyState(objectRecordId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordIdentifier = useRecoilValue(
|
|
||||||
recordStoreIdentifierFamilySelector({
|
|
||||||
objectNameSingular,
|
|
||||||
recordId: objectRecordId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isPrefetchLoading = useIsPrefetchLoading();
|
const isPrefetchLoading = useIsPrefetchLoading();
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
recordFromStore,
|
|
||||||
recordLoading,
|
recordLoading,
|
||||||
objectMetadataItem,
|
|
||||||
labelIdentifierFieldMetadataItem,
|
labelIdentifierFieldMetadataItem,
|
||||||
isPrefetchLoading,
|
isPrefetchLoading,
|
||||||
recordIdentifier,
|
|
||||||
objectMetadataItems,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,14 +8,15 @@ import { RecordLayout } from '@/object-record/record-show/types/RecordLayout';
|
|||||||
import { SingleTabProps } from '@/ui/layout/tab/components/TabList';
|
import { SingleTabProps } from '@/ui/layout/tab/components/TabList';
|
||||||
import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab';
|
import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import {
|
import {
|
||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
|
IconHome,
|
||||||
IconMail,
|
IconMail,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
IconPrinter,
|
IconPrinter,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconHome,
|
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { FeatureFlagKey } from '~/generated/graphql';
|
import { FeatureFlagKey } from '~/generated/graphql';
|
||||||
@ -34,195 +35,200 @@ export const useRecordShowContainerTabs = (
|
|||||||
// Object-specific layouts that override or extend the base layout
|
// Object-specific layouts that override or extend the base layout
|
||||||
const OBJECT_SPECIFIC_LAYOUTS: Partial<
|
const OBJECT_SPECIFIC_LAYOUTS: Partial<
|
||||||
Record<CoreObjectNameSingular, RecordLayout>
|
Record<CoreObjectNameSingular, RecordLayout>
|
||||||
> = {
|
> = useMemo(
|
||||||
[CoreObjectNameSingular.Note]: {
|
() => ({
|
||||||
tabs: {
|
[CoreObjectNameSingular.Note]: {
|
||||||
richText: {
|
tabs: {
|
||||||
title: 'Note',
|
richText: {
|
||||||
position: 101,
|
title: 'Note',
|
||||||
Icon: IconNotes,
|
position: 101,
|
||||||
cards: [{ type: CardType.RichTextCard }],
|
Icon: IconNotes,
|
||||||
hide: {
|
cards: [{ type: CardType.RichTextCard }],
|
||||||
ifMobile: false,
|
hide: {
|
||||||
ifDesktop: false,
|
ifMobile: false,
|
||||||
ifInRightDrawer: false,
|
ifDesktop: false,
|
||||||
ifFeaturesDisabled: [],
|
ifInRightDrawer: false,
|
||||||
ifRequiredObjectsInactive: [],
|
ifFeaturesDisabled: [],
|
||||||
ifRelationsMissing: [],
|
ifRequiredObjectsInactive: [],
|
||||||
|
ifRelationsMissing: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
tasks: null,
|
||||||
|
notes: null,
|
||||||
},
|
},
|
||||||
tasks: null,
|
|
||||||
notes: null,
|
|
||||||
},
|
},
|
||||||
},
|
[CoreObjectNameSingular.Task]: {
|
||||||
[CoreObjectNameSingular.Task]: {
|
tabs: {
|
||||||
tabs: {
|
richText: {
|
||||||
richText: {
|
title: 'Note',
|
||||||
title: 'Note',
|
position: 101,
|
||||||
position: 101,
|
Icon: IconNotes,
|
||||||
Icon: IconNotes,
|
cards: [{ type: CardType.RichTextCard }],
|
||||||
cards: [{ type: CardType.RichTextCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [],
|
||||||
ifFeaturesDisabled: [],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
|
tasks: null,
|
||||||
|
notes: null,
|
||||||
},
|
},
|
||||||
tasks: null,
|
|
||||||
notes: null,
|
|
||||||
},
|
},
|
||||||
},
|
[CoreObjectNameSingular.Company]: {
|
||||||
[CoreObjectNameSingular.Company]: {
|
tabs: {
|
||||||
tabs: {
|
emails: {
|
||||||
emails: {
|
title: 'Emails',
|
||||||
title: 'Emails',
|
position: 600,
|
||||||
position: 600,
|
Icon: IconMail,
|
||||||
Icon: IconMail,
|
cards: [{ type: CardType.EmailCard }],
|
||||||
cards: [{ type: CardType.EmailCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [],
|
||||||
ifFeaturesDisabled: [],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
},
|
calendar: {
|
||||||
calendar: {
|
title: 'Calendar',
|
||||||
title: 'Calendar',
|
position: 700,
|
||||||
position: 700,
|
Icon: IconCalendarEvent,
|
||||||
Icon: IconCalendarEvent,
|
cards: [{ type: CardType.CalendarCard }],
|
||||||
cards: [{ type: CardType.CalendarCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [],
|
||||||
ifFeaturesDisabled: [],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
[CoreObjectNameSingular.Person]: {
|
||||||
[CoreObjectNameSingular.Person]: {
|
tabs: {
|
||||||
tabs: {
|
emails: {
|
||||||
emails: {
|
title: 'Emails',
|
||||||
title: 'Emails',
|
position: 600,
|
||||||
position: 600,
|
Icon: IconMail,
|
||||||
Icon: IconMail,
|
cards: [{ type: CardType.EmailCard }],
|
||||||
cards: [{ type: CardType.EmailCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [],
|
||||||
ifFeaturesDisabled: [],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
},
|
calendar: {
|
||||||
calendar: {
|
title: 'Calendar',
|
||||||
title: 'Calendar',
|
position: 700,
|
||||||
position: 700,
|
Icon: IconCalendarEvent,
|
||||||
Icon: IconCalendarEvent,
|
cards: [{ type: CardType.CalendarCard }],
|
||||||
cards: [{ type: CardType.CalendarCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [],
|
||||||
ifFeaturesDisabled: [],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
[CoreObjectNameSingular.Workflow]: {
|
||||||
[CoreObjectNameSingular.Workflow]: {
|
hideSummaryAndFields: true,
|
||||||
hideSummaryAndFields: true,
|
tabs: {
|
||||||
tabs: {
|
workflow: {
|
||||||
workflow: {
|
title: 'Flow',
|
||||||
title: 'Flow',
|
position: 0,
|
||||||
position: 0,
|
Icon: IconSettings,
|
||||||
Icon: IconSettings,
|
cards: [{ type: CardType.WorkflowCard }],
|
||||||
cards: [{ type: CardType.WorkflowCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
||||||
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
|
timeline: null,
|
||||||
|
fields: null,
|
||||||
},
|
},
|
||||||
timeline: null,
|
|
||||||
fields: null,
|
|
||||||
},
|
},
|
||||||
},
|
[CoreObjectNameSingular.WorkflowVersion]: {
|
||||||
[CoreObjectNameSingular.WorkflowVersion]: {
|
tabs: {
|
||||||
tabs: {
|
workflowVersion: {
|
||||||
workflowVersion: {
|
title: 'Flow',
|
||||||
title: 'Flow',
|
position: 0,
|
||||||
position: 0,
|
Icon: IconSettings,
|
||||||
Icon: IconSettings,
|
cards: [{ type: CardType.WorkflowVersionCard }],
|
||||||
cards: [{ type: CardType.WorkflowVersionCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
||||||
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
|
timeline: null,
|
||||||
},
|
},
|
||||||
timeline: null,
|
|
||||||
},
|
},
|
||||||
},
|
[CoreObjectNameSingular.WorkflowRun]: {
|
||||||
[CoreObjectNameSingular.WorkflowRun]: {
|
tabs: {
|
||||||
tabs: {
|
workflowRunOutput: {
|
||||||
workflowRunOutput: {
|
title: 'Output',
|
||||||
title: 'Output',
|
position: 0,
|
||||||
position: 0,
|
Icon: IconPrinter,
|
||||||
Icon: IconPrinter,
|
cards: [{ type: CardType.WorkflowRunOutputCard }],
|
||||||
cards: [{ type: CardType.WorkflowRunOutputCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
||||||
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
},
|
workflowRunFlow: {
|
||||||
workflowRunFlow: {
|
title: 'Flow',
|
||||||
title: 'Flow',
|
position: 0,
|
||||||
position: 0,
|
Icon: IconSettings,
|
||||||
Icon: IconSettings,
|
cards: [{ type: CardType.WorkflowRunCard }],
|
||||||
cards: [{ type: CardType.WorkflowRunCard }],
|
hide: {
|
||||||
hide: {
|
ifMobile: false,
|
||||||
ifMobile: false,
|
ifDesktop: false,
|
||||||
ifDesktop: false,
|
ifInRightDrawer: false,
|
||||||
ifInRightDrawer: false,
|
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
||||||
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
ifRequiredObjectsInactive: [],
|
||||||
ifRequiredObjectsInactive: [],
|
ifRelationsMissing: [],
|
||||||
ifRelationsMissing: [],
|
},
|
||||||
},
|
},
|
||||||
|
timeline: null,
|
||||||
},
|
},
|
||||||
timeline: null,
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
};
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
// Merge base layout with object-specific layout
|
// Merge base layout with object-specific layout
|
||||||
const recordLayout: RecordLayout = {
|
const recordLayout: RecordLayout = useMemo(() => {
|
||||||
...BASE_RECORD_LAYOUT,
|
return {
|
||||||
...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular] || {}),
|
...BASE_RECORD_LAYOUT,
|
||||||
tabs: {
|
...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular] || {}),
|
||||||
...BASE_RECORD_LAYOUT.tabs,
|
tabs: {
|
||||||
...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular]?.tabs || {}),
|
...BASE_RECORD_LAYOUT.tabs,
|
||||||
},
|
...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular]?.tabs || {}),
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
}, [OBJECT_SPECIFIC_LAYOUTS, targetObjectNameSingular]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layout: recordLayout,
|
layout: recordLayout,
|
||||||
|
|||||||
@ -1,19 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
import { useIcons } from 'twenty-ui';
|
import { useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
|
|
||||||
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
|
|
||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|
||||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
|
||||||
import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { capitalize, isDefined } from 'twenty-shared';
|
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
export const useRecordShowPage = (
|
export const useRecordShowPage = (
|
||||||
propsObjectNameSingular: string,
|
propsObjectNameSingular: string,
|
||||||
@ -32,77 +21,13 @@ export const useRecordShowPage = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
|
||||||
const { labelIdentifierFieldMetadataItem } =
|
|
||||||
useLabelIdentifierFieldMetadataItem({ objectNameSingular });
|
|
||||||
const { sortedFavorites: favorites } = useFavorites();
|
|
||||||
const { createFavorite } = useCreateFavorite();
|
|
||||||
const { deleteFavorite } = useDeleteFavorite();
|
|
||||||
const setEntityFields = useSetRecoilState(
|
|
||||||
recordStoreFamilyState(objectRecordId),
|
|
||||||
);
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const headerIcon = getIcon(objectMetadataItem?.icon);
|
const headerIcon = getIcon(objectMetadataItem?.icon);
|
||||||
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
|
|
||||||
buildFindOneRecordForShowPageOperationSignature({
|
|
||||||
objectMetadataItem,
|
|
||||||
objectMetadataItems,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { record, loading } = useFindOneRecord({
|
|
||||||
objectRecordId,
|
|
||||||
objectNameSingular,
|
|
||||||
recordGqlFields: FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE.fields,
|
|
||||||
withSoftDeleted: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isDefined(record)) {
|
|
||||||
setEntityFields(record);
|
|
||||||
}
|
|
||||||
}, [record, setEntityFields]);
|
|
||||||
|
|
||||||
const correspondingFavorite = favorites.find(
|
|
||||||
(favorite) => favorite.recordId === objectRecordId,
|
|
||||||
);
|
|
||||||
const isFavorite = isDefined(correspondingFavorite);
|
|
||||||
|
|
||||||
const handleFavoriteButtonClick = async () => {
|
|
||||||
if (!objectNameSingular || !record) return;
|
|
||||||
|
|
||||||
if (isFavorite) {
|
|
||||||
deleteFavorite(correspondingFavorite.id);
|
|
||||||
} else {
|
|
||||||
createFavorite(record, objectNameSingular);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const labelIdentifierFieldValue =
|
|
||||||
record?.[labelIdentifierFieldMetadataItem?.name ?? ''];
|
|
||||||
const pageName =
|
|
||||||
labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FULL_NAME
|
|
||||||
? [
|
|
||||||
labelIdentifierFieldValue?.firstName,
|
|
||||||
labelIdentifierFieldValue?.lastName,
|
|
||||||
].join(' ')
|
|
||||||
: isDefined(labelIdentifierFieldValue)
|
|
||||||
? `${labelIdentifierFieldValue}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const pageTitle = pageName.trim()
|
|
||||||
? `${pageName} - ${capitalize(objectNameSingular)}`
|
|
||||||
: capitalize(objectNameSingular);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
headerIcon,
|
headerIcon,
|
||||||
loading,
|
|
||||||
pageTitle,
|
|
||||||
pageName,
|
|
||||||
isFavorite,
|
|
||||||
record,
|
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
handleFavoriteButtonClick,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/
|
|||||||
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
|
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
|
||||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
import { hasPendingRecordComponentSelector } from '@/object-record/record-table/states/selectors/hasPendingRecordComponentSelector';
|
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
@ -52,20 +51,13 @@ export const RecordTable = () => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasPendingRecord = useRecoilComponentValueV2(
|
|
||||||
hasPendingRecordComponentSelector,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasRecordGroups = useRecoilComponentValueV2(
|
const hasRecordGroups = useRecoilComponentValueV2(
|
||||||
hasRecordGroupsComponentSelector,
|
hasRecordGroupsComponentSelector,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordTableIsEmpty =
|
const recordTableIsEmpty =
|
||||||
!isRecordTableInitialLoading &&
|
!isRecordTableInitialLoading && allRecordIds.length === 0;
|
||||||
allRecordIds.length === 0 &&
|
|
||||||
!hasPendingRecord;
|
|
||||||
|
|
||||||
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
||||||
import { useUpsertTableRecordNoGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup';
|
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
||||||
import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
|
import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
|
||||||
import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover';
|
import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover';
|
||||||
@ -23,20 +22,6 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({
|
|||||||
}: RecordTableNoRecordGroupBodyContextProviderProps) => {
|
}: RecordTableNoRecordGroupBodyContextProviderProps) => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const { upsertTableRecordNoGroup } = useUpsertTableRecordNoGroup();
|
|
||||||
|
|
||||||
const handleUpsertTableRecordNoRecordGroup = ({
|
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName,
|
|
||||||
}: {
|
|
||||||
persistField: () => void;
|
|
||||||
recordId: string;
|
|
||||||
fieldName: string;
|
|
||||||
}) => {
|
|
||||||
upsertTableRecordNoGroup(persistField, recordId, fieldName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
|
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
|
||||||
|
|
||||||
const handleOpenTableCell = (args: OpenTableCellArgs) => {
|
const handleOpenTableCell = (args: OpenTableCellArgs) => {
|
||||||
@ -82,7 +67,6 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({
|
|||||||
return (
|
return (
|
||||||
<RecordTableBodyContextProvider
|
<RecordTableBodyContextProvider
|
||||||
value={{
|
value={{
|
||||||
onUpsertRecord: handleUpsertTableRecordNoRecordGroup,
|
|
||||||
onOpenTableCell: handleOpenTableCell,
|
onOpenTableCell: handleOpenTableCell,
|
||||||
onMoveFocus: handleMoveFocus,
|
onMoveFocus: handleMoveFocus,
|
||||||
onCloseTableCell: handleCloseTableCell,
|
onCloseTableCell: handleCloseTableCell,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
||||||
import { useUpsertTableRecordInGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup';
|
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
||||||
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
|
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
|
||||||
import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover';
|
import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover';
|
||||||
@ -20,26 +19,10 @@ type RecordTableRecordGroupBodyContextProviderProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableRecordGroupBodyContextProvider = ({
|
export const RecordTableRecordGroupBodyContextProvider = ({
|
||||||
recordGroupId,
|
|
||||||
children,
|
children,
|
||||||
}: RecordTableRecordGroupBodyContextProviderProps) => {
|
}: RecordTableRecordGroupBodyContextProviderProps) => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const { upsertTableRecordInGroup } =
|
|
||||||
useUpsertTableRecordInGroup(recordGroupId);
|
|
||||||
|
|
||||||
const handleupsertTableRecordInGroup = ({
|
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName,
|
|
||||||
}: {
|
|
||||||
persistField: () => void;
|
|
||||||
recordId: string;
|
|
||||||
fieldName: string;
|
|
||||||
}) => {
|
|
||||||
upsertTableRecordInGroup(persistField, recordId, fieldName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
|
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
|
||||||
|
|
||||||
const handleOpenTableCell = (args: OpenTableCellArgs) => {
|
const handleOpenTableCell = (args: OpenTableCellArgs) => {
|
||||||
@ -52,8 +35,7 @@ export const RecordTableRecordGroupBodyContextProvider = ({
|
|||||||
moveFocus(direction);
|
moveFocus(direction);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { closeTableCellInGroup } =
|
const { closeTableCellInGroup } = useCloseRecordTableCellInGroup();
|
||||||
useCloseRecordTableCellInGroup(recordGroupId);
|
|
||||||
|
|
||||||
const handlecloseTableCellInGroup = () => {
|
const handlecloseTableCellInGroup = () => {
|
||||||
closeTableCellInGroup();
|
closeTableCellInGroup();
|
||||||
@ -86,7 +68,6 @@ export const RecordTableRecordGroupBodyContextProvider = ({
|
|||||||
return (
|
return (
|
||||||
<RecordTableBodyContextProvider
|
<RecordTableBodyContextProvider
|
||||||
value={{
|
value={{
|
||||||
onUpsertRecord: handleupsertTableRecordInGroup,
|
|
||||||
onOpenTableCell: handleOpenTableCell,
|
onOpenTableCell: handleOpenTableCell,
|
||||||
onMoveFocus: handleMoveFocus,
|
onMoveFocus: handleMoveFocus,
|
||||||
onCloseTableCell: handlecloseTableCellInGroup,
|
onCloseTableCell: handlecloseTableCellInGroup,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useC
|
|||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
||||||
import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow';
|
|
||||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
||||||
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
|
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
|
||||||
import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore';
|
import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore';
|
||||||
@ -57,7 +56,6 @@ export const RecordTableRecordGroupRows = () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<RecordTablePendingRecordGroupRow />
|
|
||||||
<RecordTableRecordGroupSectionAddNew />
|
<RecordTableRecordGroupSectionAddNew />
|
||||||
<RecordTableRecordGroupSectionLoadMore />
|
<RecordTableRecordGroupSectionLoadMore />
|
||||||
<RecordTableAggregateFooter
|
<RecordTableAggregateFooter
|
||||||
|
|||||||
@ -90,7 +90,6 @@ const meta: Meta = {
|
|||||||
>
|
>
|
||||||
<RecordTableBodyContextProvider
|
<RecordTableBodyContextProvider
|
||||||
value={{
|
value={{
|
||||||
onUpsertRecord: () => {},
|
|
||||||
onOpenTableCell: () => {},
|
onOpenTableCell: () => {},
|
||||||
onMoveFocus: () => {},
|
onMoveFocus: () => {},
|
||||||
onCloseTableCell: () => {},
|
onCloseTableCell: () => {},
|
||||||
|
|||||||
@ -8,15 +8,6 @@ import { createRequiredContext } from '~/utils/createRequiredContext';
|
|||||||
|
|
||||||
export type RecordTableBodyContextProps = {
|
export type RecordTableBodyContextProps = {
|
||||||
recordGroupId?: string;
|
recordGroupId?: string;
|
||||||
onUpsertRecord: ({
|
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName,
|
|
||||||
}: {
|
|
||||||
persistField: () => void;
|
|
||||||
recordId: string;
|
|
||||||
fieldName: string;
|
|
||||||
}) => void;
|
|
||||||
onOpenTableCell: (args: OpenTableCellArgs) => void;
|
onOpenTableCell: (args: OpenTableCellArgs) => void;
|
||||||
onMoveFocus: (direction: MoveFocusDirection) => void;
|
onMoveFocus: (direction: MoveFocusDirection) => void;
|
||||||
onCloseTableCell: () => void;
|
onCloseTableCell: () => void;
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import { isNull } from '@sniptt/guards';
|
|
||||||
|
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
type RecordTableEmptyHandlerProps = {
|
type RecordTableEmptyHandlerProps = {
|
||||||
@ -25,15 +22,8 @@ export const RecordTableEmptyHandler = ({
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const pendingRecordId = useRecoilComponentValueV2(
|
|
||||||
recordTablePendingRecordIdComponentState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordTableIsEmpty =
|
const recordTableIsEmpty =
|
||||||
!isRecordTableInitialLoading &&
|
!isRecordTableInitialLoading && allRecordIds.length === 0;
|
||||||
allRecordIds.length === 0 &&
|
|
||||||
isNull(pendingRecordId);
|
|
||||||
|
|
||||||
if (recordTableIsEmpty) {
|
if (recordTableIsEmpty) {
|
||||||
return <RecordTableEmptyState />;
|
return <RecordTableEmptyState />;
|
||||||
|
|||||||
@ -5,18 +5,17 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
|||||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||||
import { getEmptyStateSubTitle } from '@/object-record/record-table/empty-state/utils/getEmptyStateSubTitle';
|
import { getEmptyStateSubTitle } from '@/object-record/record-table/empty-state/utils/getEmptyStateSubTitle';
|
||||||
import { getEmptyStateTitle } from '@/object-record/record-table/empty-state/utils/getEmptyStateTitle';
|
import { getEmptyStateTitle } from '@/object-record/record-table/empty-state/utils/getEmptyStateTitle';
|
||||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
|
|
||||||
export const RecordTableEmptyStateNoGroupNoRecordAtAll = () => {
|
export const RecordTableEmptyStateNoGroupNoRecordAtAll = () => {
|
||||||
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const { createNewTableRecord } = useCreateNewTableRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordTableId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
createNewTableRecord();
|
createNewIndexRecord();
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectLabel = useObjectLabel(objectMetadataItem);
|
const objectLabel = useObjectLabel(objectMetadataItem);
|
||||||
|
|||||||
@ -3,18 +3,17 @@ import { IconPlus } from 'twenty-ui';
|
|||||||
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
|
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
|
|
||||||
export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
|
export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
|
||||||
const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const { createNewTableRecord } = useCreateNewTableRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordTableId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
createNewTableRecord();
|
createNewIndexRecord();
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectLabel = useObjectLabel(objectMetadataItem);
|
const objectLabel = useObjectLabel(objectMetadataItem);
|
||||||
|
|||||||
@ -1,174 +0,0 @@
|
|||||||
import { renderHook } from '@testing-library/react';
|
|
||||||
import { createState } from '@ui/utilities/state/utils/createState';
|
|
||||||
import { ReactNode, act } from 'react';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
|
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
|
||||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
|
||||||
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
|
||||||
import { useUpsertTableRecordInGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup';
|
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
|
||||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
|
||||||
|
|
||||||
const draftValue = 'updated Name';
|
|
||||||
const recordGroupId = 'recordGroupId';
|
|
||||||
|
|
||||||
// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks
|
|
||||||
// (this is not easy to maintain while refactoring)
|
|
||||||
jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
useCreateOneRecord: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const draftValueState = createState<string | null>({
|
|
||||||
key: 'draftValueState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
jest.mock(
|
|
||||||
'@/object-record/record-field/hooks/internal/useRecordFieldInputStates',
|
|
||||||
() => ({
|
|
||||||
__esModule: true,
|
|
||||||
useRecordFieldInputStates: jest.fn(() => ({
|
|
||||||
getDraftValueSelector: () => draftValueState,
|
|
||||||
})),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordTablePendingRecordIdByGroupComponentFamilyState = createFamilyState<
|
|
||||||
string | null,
|
|
||||||
string
|
|
||||||
>({
|
|
||||||
key: 'recordTablePendingRecordIdByGroupComponentFamilyState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createOneRecordMock = jest.fn();
|
|
||||||
const updateOneRecordMock = jest.fn();
|
|
||||||
(useCreateOneRecord as jest.Mock).mockReturnValue({
|
|
||||||
createOneRecord: createOneRecordMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Wrapper = ({
|
|
||||||
children,
|
|
||||||
pendingRecordIdMockedValue,
|
|
||||||
draftValueMockedValue,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
pendingRecordIdMockedValue: string | null;
|
|
||||||
draftValueMockedValue: string | null;
|
|
||||||
}) => (
|
|
||||||
<RecoilRoot
|
|
||||||
initializeState={(snapshot) => {
|
|
||||||
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
|
|
||||||
snapshot.set(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState(recordGroupId),
|
|
||||||
pendingRecordIdMockedValue,
|
|
||||||
);
|
|
||||||
snapshot.set(draftValueState, draftValueMockedValue);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordTableContextProvider
|
|
||||||
recordTableId="recordTableId"
|
|
||||||
objectNameSingular={CoreObjectNameSingular.Person}
|
|
||||||
viewBarId="viewBarId"
|
|
||||||
>
|
|
||||||
<RecordTableComponentInstanceContext.Provider
|
|
||||||
value={{
|
|
||||||
instanceId: CoreObjectNamePlural.Person,
|
|
||||||
onColumnsChange: jest.fn(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ViewComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: CoreObjectNamePlural.Person }}
|
|
||||||
>
|
|
||||||
<FieldContext.Provider
|
|
||||||
value={{
|
|
||||||
recordId: 'recordId',
|
|
||||||
fieldDefinition: {
|
|
||||||
...textfieldDefinition,
|
|
||||||
metadata: {
|
|
||||||
...textfieldDefinition.metadata,
|
|
||||||
objectMetadataNameSingular: CoreObjectNameSingular.Person,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hotkeyScope: TableHotkeyScope.Table,
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</FieldContext.Provider>
|
|
||||||
</ViewComponentInstanceContext.Provider>
|
|
||||||
</RecordTableComponentInstanceContext.Provider>
|
|
||||||
</RecordTableContextProvider>
|
|
||||||
</RecoilRoot>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('useUpsertTableRecordInGroup', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
createOneRecordMock.mockClear();
|
|
||||||
updateOneRecordMock.mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls update record if there is no pending record', async () => {
|
|
||||||
/**
|
|
||||||
* {
|
|
||||||
objectNameSingular: 'person',
|
|
||||||
recordTableId: 'recordTableId',
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useUpsertTableRecordInGroup(recordGroupId),
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) =>
|
|
||||||
Wrapper({
|
|
||||||
pendingRecordIdMockedValue: null,
|
|
||||||
draftValueMockedValue: null,
|
|
||||||
children,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.upsertTableRecordInGroup(
|
|
||||||
updateOneRecordMock,
|
|
||||||
'recordId',
|
|
||||||
'name',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(createOneRecordMock).not.toHaveBeenCalled();
|
|
||||||
expect(updateOneRecordMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls update record if pending record is empty', async () => {
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useUpsertTableRecordInGroup(recordGroupId),
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) =>
|
|
||||||
Wrapper({
|
|
||||||
pendingRecordIdMockedValue: null,
|
|
||||||
draftValueMockedValue: draftValue,
|
|
||||||
children,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.upsertTableRecordInGroup(
|
|
||||||
updateOneRecordMock,
|
|
||||||
'recordId',
|
|
||||||
'name',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(createOneRecordMock).not.toHaveBeenCalled();
|
|
||||||
expect(updateOneRecordMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,160 +0,0 @@
|
|||||||
import { renderHook } from '@testing-library/react';
|
|
||||||
import { createState } from '@ui/utilities/state/utils/createState';
|
|
||||||
import { ReactNode, act } from 'react';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
|
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
|
||||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
|
||||||
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
|
||||||
import { useUpsertTableRecordNoGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup';
|
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
|
||||||
|
|
||||||
const draftValue = 'updated Name';
|
|
||||||
|
|
||||||
// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks
|
|
||||||
// (this is not easy to maintain while refactoring)
|
|
||||||
jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
useCreateOneRecord: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const draftValueState = createState<string | null>({
|
|
||||||
key: 'draftValueState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
jest.mock(
|
|
||||||
'@/object-record/record-field/hooks/internal/useRecordFieldInputStates',
|
|
||||||
() => ({
|
|
||||||
__esModule: true,
|
|
||||||
useRecordFieldInputStates: jest.fn(() => ({
|
|
||||||
getDraftValueSelector: () => draftValueState,
|
|
||||||
})),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const pendingRecordIdState = createState<string | null>({
|
|
||||||
key: 'pendingRecordIdState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createOneRecordMock = jest.fn();
|
|
||||||
const updateOneRecordMock = jest.fn();
|
|
||||||
(useCreateOneRecord as jest.Mock).mockReturnValue({
|
|
||||||
createOneRecord: createOneRecordMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Wrapper = ({
|
|
||||||
children,
|
|
||||||
pendingRecordIdMockedValue,
|
|
||||||
draftValueMockedValue,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
pendingRecordIdMockedValue: string | null;
|
|
||||||
draftValueMockedValue: string | null;
|
|
||||||
}) => (
|
|
||||||
<RecoilRoot
|
|
||||||
initializeState={(snapshot) => {
|
|
||||||
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
|
|
||||||
snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue);
|
|
||||||
snapshot.set(draftValueState, draftValueMockedValue);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordTableContextProvider
|
|
||||||
recordTableId="recordTableId"
|
|
||||||
objectNameSingular={CoreObjectNameSingular.Person}
|
|
||||||
viewBarId="viewBarId"
|
|
||||||
>
|
|
||||||
<RecordTableComponentInstanceContext.Provider
|
|
||||||
value={{
|
|
||||||
instanceId: CoreObjectNamePlural.Person,
|
|
||||||
onColumnsChange: jest.fn(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ViewComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: CoreObjectNamePlural.Person }}
|
|
||||||
>
|
|
||||||
<FieldContext.Provider
|
|
||||||
value={{
|
|
||||||
recordId: 'recordId',
|
|
||||||
fieldDefinition: {
|
|
||||||
...textfieldDefinition,
|
|
||||||
metadata: {
|
|
||||||
...textfieldDefinition.metadata,
|
|
||||||
objectMetadataNameSingular: CoreObjectNameSingular.Person,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hotkeyScope: TableHotkeyScope.Table,
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</FieldContext.Provider>
|
|
||||||
</ViewComponentInstanceContext.Provider>
|
|
||||||
</RecordTableComponentInstanceContext.Provider>
|
|
||||||
</RecordTableContextProvider>
|
|
||||||
</RecoilRoot>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('useUpsertTableRecordNoGroup', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
createOneRecordMock.mockClear();
|
|
||||||
updateOneRecordMock.mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls update record if there is no pending record', async () => {
|
|
||||||
/**
|
|
||||||
* {
|
|
||||||
objectNameSingular: 'person',
|
|
||||||
recordTableId: 'recordTableId',
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const { result } = renderHook(() => useUpsertTableRecordNoGroup(), {
|
|
||||||
wrapper: ({ children }) =>
|
|
||||||
Wrapper({
|
|
||||||
pendingRecordIdMockedValue: null,
|
|
||||||
draftValueMockedValue: null,
|
|
||||||
children,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.upsertTableRecordNoGroup(
|
|
||||||
updateOneRecordMock,
|
|
||||||
'recordId',
|
|
||||||
'name',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(createOneRecordMock).not.toHaveBeenCalled();
|
|
||||||
expect(updateOneRecordMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls update record if pending record is empty', async () => {
|
|
||||||
const { result } = renderHook(() => useUpsertTableRecordNoGroup(), {
|
|
||||||
wrapper: ({ children }) =>
|
|
||||||
Wrapper({
|
|
||||||
pendingRecordIdMockedValue: null,
|
|
||||||
draftValueMockedValue: draftValue,
|
|
||||||
children,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.upsertTableRecordNoGroup(
|
|
||||||
updateOneRecordMock,
|
|
||||||
'recordId',
|
|
||||||
'name',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(createOneRecordMock).not.toHaveBeenCalled();
|
|
||||||
expect(updateOneRecordMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
|
|
||||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
|
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
|
||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
|
||||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
|
||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
|
|
||||||
const { objectMetadataItem, objectNameSingular } =
|
|
||||||
useRecordTableContextOrThrow();
|
|
||||||
|
|
||||||
const { createOneRecord } = useCreateOneRecord({
|
|
||||||
objectNameSingular,
|
|
||||||
shouldMatchRootQueryFilter: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recordTablePendingRecordIdByGroupFamilyState =
|
|
||||||
useRecoilComponentCallbackStateV2(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordIndexRecordIdsByGroupFamilyState =
|
|
||||||
useRecoilComponentCallbackStateV2(
|
|
||||||
recordIndexRecordIdsByGroupComponentFamilyState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const upsertTableRecordInGroup = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
(persistField: () => void, recordId: string, fieldName: string) => {
|
|
||||||
const labelIdentifierFieldMetadataItem =
|
|
||||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
|
||||||
|
|
||||||
const fieldScopeId = getScopeIdFromComponentId(
|
|
||||||
`${recordId}-${fieldName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const draftValueSelector = extractComponentSelector(
|
|
||||||
recordFieldInputDraftValueComponentSelector,
|
|
||||||
fieldScopeId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const draftValue = getSnapshotValue(snapshot, draftValueSelector());
|
|
||||||
|
|
||||||
// We're in a record group
|
|
||||||
const recordTablePendingRecordId = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState(recordGroupId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordGroupDefinition = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
recordGroupDefinitionFamilyState(recordGroupId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordGroupIds = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
recordIndexRecordIdsByGroupFamilyState(recordGroupId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordGroupFieldMetadataItem = objectMetadataItem.fields.find(
|
|
||||||
(fieldMetadata) =>
|
|
||||||
fieldMetadata.id === recordGroupDefinition?.fieldMetadataId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastId = recordGroupIds?.[recordGroupIds.length - 1];
|
|
||||||
|
|
||||||
const objectRecord = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
recordStoreFamilyState(lastId),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
isDefined(recordTablePendingRecordId) &&
|
|
||||||
isDefined(recordGroupDefinition) &&
|
|
||||||
isDefined(recordGroupFieldMetadataItem) &&
|
|
||||||
isDefined(draftValue)
|
|
||||||
) {
|
|
||||||
createOneRecord({
|
|
||||||
id: recordTablePendingRecordId,
|
|
||||||
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
|
|
||||||
[recordGroupFieldMetadataItem.name]: recordGroupDefinition.value,
|
|
||||||
position: (objectRecord?.position ?? 0) + 0.0001,
|
|
||||||
});
|
|
||||||
} else if (!recordTablePendingRecordId) {
|
|
||||||
persistField();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
createOneRecord,
|
|
||||||
objectMetadataItem,
|
|
||||||
recordGroupId,
|
|
||||||
recordIndexRecordIdsByGroupFamilyState,
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { upsertTableRecordInGroup };
|
|
||||||
};
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
|
|
||||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
|
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
|
||||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
|
||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const useUpsertTableRecordNoGroup = () => {
|
|
||||||
const { objectMetadataItem, objectNameSingular, recordTableId } =
|
|
||||||
useRecordTableContextOrThrow();
|
|
||||||
|
|
||||||
const { createOneRecord } = useCreateOneRecord({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recordTablePendingRecordIdState = useRecoilComponentCallbackStateV2(
|
|
||||||
recordTablePendingRecordIdComponentState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const upsertTableRecordNoGroup = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
(persistField: () => void, recordId: string, fieldName: string) => {
|
|
||||||
const labelIdentifierFieldMetadataItem =
|
|
||||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
|
||||||
|
|
||||||
const fieldScopeId = getScopeIdFromComponentId(
|
|
||||||
`${recordId}-${fieldName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const draftValueSelector = extractComponentSelector(
|
|
||||||
recordFieldInputDraftValueComponentSelector,
|
|
||||||
fieldScopeId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const draftValue = getSnapshotValue(snapshot, draftValueSelector());
|
|
||||||
|
|
||||||
const recordTablePendingRecordId = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
recordTablePendingRecordIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) {
|
|
||||||
createOneRecord({
|
|
||||||
id: recordTablePendingRecordId,
|
|
||||||
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
|
|
||||||
position: 'first',
|
|
||||||
});
|
|
||||||
} else if (!recordTablePendingRecordId) {
|
|
||||||
persistField();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[createOneRecord, objectMetadataItem, recordTablePendingRecordIdState],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { upsertTableRecordNoGroup };
|
|
||||||
};
|
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
|
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||||
|
import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
|
|
||||||
|
export const useCreateNewIndexRecord = ({
|
||||||
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
|
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
||||||
|
|
||||||
|
const { createOneRecord } = useCreateOneRecord({
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
shouldMatchRootQueryFilter: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigate = useNavigateApp();
|
||||||
|
|
||||||
|
const { openRecordTitleCell } = useRecordTitleCell();
|
||||||
|
|
||||||
|
const createNewIndexRecord = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
async (recordInput?: Partial<ObjectRecord>) => {
|
||||||
|
const recordId = v4();
|
||||||
|
|
||||||
|
const recordIndexOpenRecordIn = snapshot
|
||||||
|
.getLoadable(recordIndexOpenRecordInState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
await createOneRecord({ id: recordId, ...recordInput });
|
||||||
|
|
||||||
|
if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) {
|
||||||
|
openRecordInCommandMenu({
|
||||||
|
recordId,
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
isNewRecord: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
openRecordTitleCell({
|
||||||
|
recordId,
|
||||||
|
fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate(AppPath.RecordShowPage, {
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
objectRecordId: recordId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
createOneRecord,
|
||||||
|
navigate,
|
||||||
|
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
openRecordInCommandMenu,
|
||||||
|
openRecordTitleCell,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
createNewIndexRecord,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
|
||||||
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
|
||||||
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
|
|
||||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
|
||||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
|
||||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
export const useCreateNewTableRecordInGroup = () => {
|
|
||||||
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
|
|
||||||
|
|
||||||
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
|
|
||||||
scopeId: recordIndexId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
|
|
||||||
const recordTablePendingRecordIdByGroupFamilyState =
|
|
||||||
useRecoilComponentCallbackStateV2(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
|
||||||
recordIndexId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
|
||||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
|
||||||
|
|
||||||
const createNewTableRecordInGroup = useRecoilCallback(
|
|
||||||
({ set }) =>
|
|
||||||
(recordGroupId: string) => {
|
|
||||||
const recordId = v4();
|
|
||||||
|
|
||||||
set(
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState(recordGroupId),
|
|
||||||
recordId,
|
|
||||||
);
|
|
||||||
setSelectedTableCellEditMode(-1, 0);
|
|
||||||
setHotkeyScope(
|
|
||||||
DEFAULT_CELL_SCOPE.scope,
|
|
||||||
DEFAULT_CELL_SCOPE.customScopes,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
|
|
||||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
|
||||||
getDropdownFocusIdForRecordField(
|
|
||||||
recordId,
|
|
||||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
|
||||||
'table-cell',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
objectMetadataItem,
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState,
|
|
||||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
|
||||||
setHotkeyScope,
|
|
||||||
setSelectedTableCellEditMode,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createNewTableRecordInGroup,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
|
||||||
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
|
||||||
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
|
|
||||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
|
||||||
import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell';
|
|
||||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
|
||||||
|
|
||||||
export const useCreateNewTableRecord = ({
|
|
||||||
objectMetadataItem,
|
|
||||||
recordTableId,
|
|
||||||
}: {
|
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
|
||||||
recordTableId: string;
|
|
||||||
}) => {
|
|
||||||
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
|
|
||||||
scopeId: recordTableId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
|
|
||||||
const recordTablePendingRecordIdByGroupFamilyState =
|
|
||||||
useRecoilComponentCallbackStateV2(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
|
||||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
|
||||||
|
|
||||||
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
|
||||||
|
|
||||||
const { createOneRecord } = useCreateOneRecord({
|
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
shouldMatchRootQueryFilter: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const navigate = useNavigateApp();
|
|
||||||
|
|
||||||
const { openRecordTitleCell } = useRecordTitleCell();
|
|
||||||
|
|
||||||
const createNewTableRecord = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
async () => {
|
|
||||||
const recordId = v4();
|
|
||||||
|
|
||||||
const recordIndexOpenRecordIn = snapshot
|
|
||||||
.getLoadable(recordIndexOpenRecordInState)
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
await createOneRecord({ id: recordId });
|
|
||||||
|
|
||||||
if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) {
|
|
||||||
openRecordInCommandMenu({
|
|
||||||
recordId,
|
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
isNewRecord: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
openRecordTitleCell({
|
|
||||||
recordId,
|
|
||||||
fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
navigate(AppPath.RecordShowPage, {
|
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
objectRecordId: recordId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
createOneRecord,
|
|
||||||
navigate,
|
|
||||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
openRecordInCommandMenu,
|
|
||||||
openRecordTitleCell,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
const createNewTableRecordInGroup = useRecoilCallback(
|
|
||||||
({ set }) =>
|
|
||||||
(recordGroupId: string) => {
|
|
||||||
const recordId = v4();
|
|
||||||
|
|
||||||
set(
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState(recordGroupId),
|
|
||||||
recordId,
|
|
||||||
);
|
|
||||||
setSelectedTableCellEditMode(-1, 0);
|
|
||||||
setHotkeyScope(
|
|
||||||
DEFAULT_CELL_SCOPE.scope,
|
|
||||||
DEFAULT_CELL_SCOPE.customScopes,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
|
|
||||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
|
||||||
getDropdownFocusIdForRecordField(
|
|
||||||
recordId,
|
|
||||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
|
||||||
'table-cell',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
objectMetadataItem,
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState,
|
|
||||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
|
||||||
setHotkeyScope,
|
|
||||||
setSelectedTableCellEditMode,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createNewTableRecord,
|
|
||||||
createNewTableRecordInGroup,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -4,7 +4,6 @@ import { RecordTableNoRecordGroupRows } from '@/object-record/record-table/compo
|
|||||||
import { RecordTableBodyDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContextProvider';
|
import { RecordTableBodyDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContextProvider';
|
||||||
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
|
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
|
||||||
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
|
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
|
||||||
import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow';
|
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
@ -25,7 +24,6 @@ export const RecordTableNoRecordGroupBody = () => {
|
|||||||
<RecordTableNoRecordGroupBodyContextProvider>
|
<RecordTableNoRecordGroupBodyContextProvider>
|
||||||
<RecordTableBodyDragDropContextProvider>
|
<RecordTableBodyDragDropContextProvider>
|
||||||
<RecordTableBodyDroppable>
|
<RecordTableBodyDroppable>
|
||||||
<RecordTablePendingRow />
|
|
||||||
<RecordTableNoRecordGroupRows />
|
<RecordTableNoRecordGroupRows />
|
||||||
</RecordTableBodyDroppable>
|
</RecordTableBodyDroppable>
|
||||||
</RecordTableBodyDragDropContextProvider>
|
</RecordTableBodyDragDropContextProvider>
|
||||||
|
|||||||
@ -10,28 +10,19 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput
|
|||||||
export const RecordTableCellFieldInput = () => {
|
export const RecordTableCellFieldInput = () => {
|
||||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
|
const { onMoveFocus, onCloseTableCell } = useRecordTableBodyContextOrThrow();
|
||||||
useRecordTableBodyContextOrThrow();
|
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
const isFieldReadOnly = useIsFieldValueReadOnly();
|
||||||
|
|
||||||
const handleEnter: FieldInputEvent = (persistField) => {
|
const handleEnter: FieldInputEvent = (persistField) => {
|
||||||
onUpsertRecord({
|
persistField();
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
onCloseTableCell();
|
onCloseTableCell();
|
||||||
onMoveFocus('down');
|
onMoveFocus('down');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit: FieldInputEvent = (persistField) => {
|
const handleSubmit: FieldInputEvent = (persistField) => {
|
||||||
onUpsertRecord({
|
persistField();
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
onCloseTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
@ -46,42 +37,26 @@ export const RecordTableCellFieldInput = () => {
|
|||||||
) => {
|
) => {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
onUpsertRecord({
|
persistField();
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
onCloseTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEscape: FieldInputEvent = (persistField) => {
|
const handleEscape: FieldInputEvent = (persistField) => {
|
||||||
onUpsertRecord({
|
persistField();
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
onCloseTableCell();
|
onCloseTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTab: FieldInputEvent = (persistField) => {
|
const handleTab: FieldInputEvent = (persistField) => {
|
||||||
onUpsertRecord({
|
persistField();
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
onCloseTableCell();
|
onCloseTableCell();
|
||||||
onMoveFocus('right');
|
onMoveFocus('right');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShiftTab: FieldInputEvent = (persistField) => {
|
const handleShiftTab: FieldInputEvent = (persistField) => {
|
||||||
onUpsertRecord({
|
persistField();
|
||||||
persistField,
|
|
||||||
recordId,
|
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
onCloseTableCell();
|
onCloseTableCell();
|
||||||
onMoveFocus('left');
|
onMoveFocus('left');
|
||||||
|
|||||||
@ -32,7 +32,6 @@ jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({
|
|||||||
|
|
||||||
const onColumnsChange = jest.fn();
|
const onColumnsChange = jest.fn();
|
||||||
const recordTableId = 'scopeId';
|
const recordTableId = 'scopeId';
|
||||||
const recordGroupId = 'recordGroupId';
|
|
||||||
|
|
||||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
<RecoilRoot
|
<RecoilRoot
|
||||||
@ -86,7 +85,7 @@ describe('useCloseRecordTableCellInGroup', () => {
|
|||||||
currentTableCellInEditModePosition,
|
currentTableCellInEditModePosition,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...useCloseRecordTableCellInGroup(recordGroupId),
|
...useCloseRecordTableCellInGroup(),
|
||||||
...useDragSelect(),
|
...useDragSelect(),
|
||||||
isTableCellInEditMode,
|
isTableCellInEditMode,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,11 +7,9 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
|
|||||||
|
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
|
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
|
||||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
|
|
||||||
export const useCloseRecordTableCellInGroup = (recordGroupId: string) => {
|
export const useCloseRecordTableCellInGroup = () => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
@ -24,26 +22,15 @@ export const useCloseRecordTableCellInGroup = (recordGroupId: string) => {
|
|||||||
const closeCurrentTableCellInEditMode =
|
const closeCurrentTableCellInEditMode =
|
||||||
useCloseCurrentTableCellInEditMode(recordTableId);
|
useCloseCurrentTableCellInEditMode(recordTableId);
|
||||||
|
|
||||||
const recordTablePendingRecordIdByGroupFamilyState =
|
|
||||||
useRecoilComponentCallbackStateV2(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const closeTableCellInGroup = useRecoilCallback(
|
const closeTableCellInGroup = useRecoilCallback(
|
||||||
({ reset }) =>
|
() => () => {
|
||||||
() => {
|
toggleClickOutsideListener(true);
|
||||||
toggleClickOutsideListener(true);
|
setDragSelectionStartEnabled(true);
|
||||||
setDragSelectionStartEnabled(true);
|
closeCurrentTableCellInEditMode();
|
||||||
closeCurrentTableCellInEditMode();
|
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||||
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
},
|
||||||
|
|
||||||
reset(recordTablePendingRecordIdByGroupFamilyState(recordGroupId));
|
|
||||||
},
|
|
||||||
[
|
[
|
||||||
closeCurrentTableCellInEditMode,
|
closeCurrentTableCellInEditMode,
|
||||||
recordGroupId,
|
|
||||||
recordTablePendingRecordIdByGroupFamilyState,
|
|
||||||
setDragSelectionStartEnabled,
|
setDragSelectionStartEnabled,
|
||||||
setHotkeyScope,
|
setHotkeyScope,
|
||||||
toggleClickOutsideListener,
|
toggleClickOutsideListener,
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useResetRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
||||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
@ -7,9 +5,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
|
|||||||
|
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
|
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
|
||||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export const useCloseRecordTableCellNoGroup = () => {
|
export const useCloseRecordTableCellNoGroup = () => {
|
||||||
@ -26,23 +22,13 @@ export const useCloseRecordTableCellNoGroup = () => {
|
|||||||
const closeCurrentTableCellInEditMode =
|
const closeCurrentTableCellInEditMode =
|
||||||
useCloseCurrentTableCellInEditMode(recordTableId);
|
useCloseCurrentTableCellInEditMode(recordTableId);
|
||||||
|
|
||||||
const pendingRecordIdState = useRecoilComponentCallbackStateV2(
|
|
||||||
recordTablePendingRecordIdComponentState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetRecordTablePendingRecordId =
|
|
||||||
useResetRecoilState(pendingRecordIdState);
|
|
||||||
|
|
||||||
const closeTableCellNoGroup = useCallback(() => {
|
const closeTableCellNoGroup = useCallback(() => {
|
||||||
toggleClickOutsideListener(true);
|
toggleClickOutsideListener(true);
|
||||||
setDragSelectionStartEnabled(true);
|
setDragSelectionStartEnabled(true);
|
||||||
closeCurrentTableCellInEditMode();
|
closeCurrentTableCellInEditMode();
|
||||||
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||||
resetRecordTablePendingRecordId();
|
|
||||||
}, [
|
}, [
|
||||||
closeCurrentTableCellInEditMode,
|
closeCurrentTableCellInEditMode,
|
||||||
resetRecordTablePendingRecordId,
|
|
||||||
setDragSelectionStartEnabled,
|
setDragSelectionStartEnabled,
|
||||||
setHotkeyScope,
|
setHotkeyScope,
|
||||||
toggleClickOutsideListener,
|
toggleClickOutsideListener,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { IconPlus, LightIconButton } from 'twenty-ui';
|
|||||||
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
|
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
|
||||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||||
@ -107,7 +107,7 @@ type RecordTableHeaderCellProps = {
|
|||||||
export const RecordTableHeaderCell = ({
|
export const RecordTableHeaderCell = ({
|
||||||
column,
|
column,
|
||||||
}: RecordTableHeaderCellProps) => {
|
}: RecordTableHeaderCellProps) => {
|
||||||
const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const resizeFieldOffsetState = useRecoilComponentCallbackStateV2(
|
const resizeFieldOffsetState = useRecoilComponentCallbackStateV2(
|
||||||
resizeFieldOffsetComponentState,
|
resizeFieldOffsetComponentState,
|
||||||
@ -202,13 +202,12 @@ export const RecordTableHeaderCell = ({
|
|||||||
const disableColumnResize =
|
const disableColumnResize =
|
||||||
column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
|
column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
|
||||||
|
|
||||||
const { createNewTableRecord } = useCreateNewTableRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordTableId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlePlusButtonClick = () => {
|
const handlePlusButtonClick = () => {
|
||||||
createNewTableRecord();
|
createNewIndexRecord();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
|
||||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
|
||||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
|
||||||
|
|
||||||
export const RecordTablePendingRecordGroupRow = () => {
|
|
||||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
|
||||||
|
|
||||||
const pendingRecordId = useRecoilComponentFamilyValueV2(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
|
||||||
currentRecordGroupId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!pendingRecordId) return <></>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RecordTableRow
|
|
||||||
key={pendingRecordId}
|
|
||||||
recordId={pendingRecordId}
|
|
||||||
rowIndexForDrag={-1}
|
|
||||||
rowIndexForFocus={-1}
|
|
||||||
isPendingRow
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
|
||||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
|
|
||||||
export const RecordTablePendingRow = () => {
|
|
||||||
const pendingRecordId = useRecoilComponentValueV2(
|
|
||||||
recordTablePendingRecordIdComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!pendingRecordId) return <></>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RecordTableRow
|
|
||||||
key={pendingRecordId}
|
|
||||||
recordId={pendingRecordId}
|
|
||||||
rowIndexForDrag={-1}
|
|
||||||
rowIndexForFocus={-1}
|
|
||||||
isPendingRow
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,15 +1,17 @@
|
|||||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||||
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { IconPlus } from 'twenty-ui';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
export const RecordTableRecordGroupSectionAddNew = () => {
|
export const RecordTableRecordGroupSectionAddNew = () => {
|
||||||
const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
const currentRecordGroupId = useCurrentRecordGroupId();
|
||||||
|
|
||||||
@ -17,16 +19,19 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
|||||||
recordIndexAllRecordIdsComponentSelector,
|
recordIndexAllRecordIdsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const recordGroup = useRecoilValue(
|
||||||
|
recordGroupDefinitionFamilyState(currentRecordGroupId),
|
||||||
|
);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||||
|
|
||||||
const { createNewTableRecordInGroup } = useCreateNewTableRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordTableId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAddNewRecord = () => {
|
const fieldMetadataItem = objectMetadataItem.fields.find(
|
||||||
createNewTableRecordInGroup(currentRecordGroupId);
|
(field) => field.id === recordGroup?.fieldMetadataId,
|
||||||
};
|
);
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
if (hasObjectReadOnlyPermission) {
|
||||||
return null;
|
return null;
|
||||||
@ -38,7 +43,16 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
|||||||
draggableIndex={recordIds.length + 2}
|
draggableIndex={recordIds.length + 2}
|
||||||
LeftIcon={IconPlus}
|
LeftIcon={IconPlus}
|
||||||
text={t`Add new`}
|
text={t`Add new`}
|
||||||
onClick={handleAddNewRecord}
|
onClick={() => {
|
||||||
|
if (!fieldMetadataItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewIndexRecord({
|
||||||
|
position: 'last',
|
||||||
|
[fieldMetadataItem.name]: recordGroup?.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
|
||||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
|
||||||
|
|
||||||
export const recordTablePendingRecordIdByGroupComponentFamilyState =
|
|
||||||
createComponentFamilyStateV2<string | null, RecordGroupDefinition['id']>({
|
|
||||||
key: 'recordTablePendingRecordIdByGroupComponentFamilyState',
|
|
||||||
defaultValue: null,
|
|
||||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
|
||||||
});
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
|
||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
|
||||||
|
|
||||||
export const recordTablePendingRecordIdComponentState = createComponentStateV2<
|
|
||||||
string | null
|
|
||||||
>({
|
|
||||||
key: 'recordTablePendingRecordIdComponentState',
|
|
||||||
defaultValue: null,
|
|
||||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
|
||||||
});
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
|
||||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
|
||||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
|
||||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
|
||||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const hasPendingRecordComponentSelector = createComponentSelectorV2({
|
|
||||||
key: 'hasPendingRecordComponentSelector',
|
|
||||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
|
||||||
get:
|
|
||||||
({ instanceId }) =>
|
|
||||||
({ get }) => {
|
|
||||||
const hasRecordGroups = get(
|
|
||||||
hasRecordGroupsComponentSelector.selectorFamily({ instanceId }),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasRecordGroups) {
|
|
||||||
const pendingRecordId = get(
|
|
||||||
recordTablePendingRecordIdComponentState.atomFamily({ instanceId }),
|
|
||||||
);
|
|
||||||
|
|
||||||
return isDefined(pendingRecordId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const recordGroupIds = get(
|
|
||||||
recordGroupIdsComponentState.atomFamily({ instanceId }),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const recordGroupId of recordGroupIds) {
|
|
||||||
const pendingRecordId = get(
|
|
||||||
recordTablePendingRecordIdByGroupComponentFamilyState.atomFamily({
|
|
||||||
instanceId,
|
|
||||||
familyKey: recordGroupId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDefined(pendingRecordId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -8,14 +8,15 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
|
|||||||
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
||||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||||
|
import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect';
|
||||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||||
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
|
|
||||||
import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader';
|
import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader';
|
||||||
|
import { RecordShowPageTitle } from '~/pages/object-record/RecordShowPageTitle';
|
||||||
|
|
||||||
export const RecordShowPage = () => {
|
export const RecordShowPage = () => {
|
||||||
const parameters = useParams<{
|
const parameters = useParams<{
|
||||||
@ -23,14 +24,7 @@ export const RecordShowPage = () => {
|
|||||||
objectRecordId: string;
|
objectRecordId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const {
|
const { objectNameSingular, objectRecordId, headerIcon } = useRecordShowPage(
|
||||||
pageTitle,
|
|
||||||
objectNameSingular,
|
|
||||||
objectRecordId,
|
|
||||||
headerIcon,
|
|
||||||
loading,
|
|
||||||
pageName,
|
|
||||||
} = useRecordShowPage(
|
|
||||||
parameters.objectNameSingular ?? '',
|
parameters.objectNameSingular ?? '',
|
||||||
parameters.objectRecordId ?? '',
|
parameters.objectRecordId ?? '',
|
||||||
);
|
);
|
||||||
@ -54,7 +48,10 @@ export const RecordShowPage = () => {
|
|||||||
>
|
>
|
||||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageTitle title={pageTitle} />
|
<RecordShowPageTitle
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
objectRecordId={objectRecordId}
|
||||||
|
/>
|
||||||
<RecordShowPageHeader
|
<RecordShowPageHeader
|
||||||
objectNameSingular={objectNameSingular}
|
objectNameSingular={objectNameSingular}
|
||||||
objectRecordId={objectRecordId}
|
objectRecordId={objectRecordId}
|
||||||
@ -64,12 +61,18 @@ export const RecordShowPage = () => {
|
|||||||
</RecordShowPageHeader>
|
</RecordShowPageHeader>
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<TimelineActivityContext.Provider
|
<TimelineActivityContext.Provider
|
||||||
value={{ labelIdentifierValue: pageName }}
|
value={{
|
||||||
|
recordId: objectRecordId,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<RecordShowEffect
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
recordId={objectRecordId}
|
||||||
|
/>
|
||||||
<RecordShowContainer
|
<RecordShowContainer
|
||||||
objectNameSingular={objectNameSingular}
|
objectNameSingular={objectNameSingular}
|
||||||
objectRecordId={objectRecordId}
|
objectRecordId={objectRecordId}
|
||||||
loading={loading}
|
loading={false}
|
||||||
/>
|
/>
|
||||||
</TimelineActivityContext.Provider>
|
</TimelineActivityContext.Provider>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { FieldMetadataType, capitalize, isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
|
export const RecordShowPageTitle = ({
|
||||||
|
objectNameSingular,
|
||||||
|
objectRecordId,
|
||||||
|
}: {
|
||||||
|
objectNameSingular: string;
|
||||||
|
objectRecordId: string;
|
||||||
|
}) => {
|
||||||
|
const { labelIdentifierFieldMetadataItem } =
|
||||||
|
useLabelIdentifierFieldMetadataItem({ objectNameSingular });
|
||||||
|
|
||||||
|
const record = useRecoilValue(recordStoreFamilyState(objectRecordId));
|
||||||
|
const labelIdentifierFieldValue = record?.labelIdentifierFieldValue;
|
||||||
|
|
||||||
|
const pageName =
|
||||||
|
labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FULL_NAME
|
||||||
|
? [
|
||||||
|
labelIdentifierFieldValue?.firstName,
|
||||||
|
labelIdentifierFieldValue?.lastName,
|
||||||
|
].join(' ')
|
||||||
|
: isDefined(labelIdentifierFieldValue)
|
||||||
|
? `${labelIdentifierFieldValue}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const pageTitle = pageName.trim()
|
||||||
|
? `${pageName} - ${capitalize(objectNameSingular)}`
|
||||||
|
: capitalize(objectNameSingular);
|
||||||
|
|
||||||
|
return <PageTitle title={pageTitle} />;
|
||||||
|
};
|
||||||
@ -46,7 +46,6 @@ export const RecordTableDecorator: Decorator = (Story) => {
|
|||||||
onActionMenuDropdownOpened: () => {},
|
onActionMenuDropdownOpened: () => {},
|
||||||
onMoveFocus: () => {},
|
onMoveFocus: () => {},
|
||||||
onMoveSoftFocusToCurrentCell: () => {},
|
onMoveSoftFocusToCurrentCell: () => {},
|
||||||
onUpsertRecord: () => {},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Story />
|
<Story />
|
||||||
|
|||||||
@ -104,7 +104,7 @@ const StyledInput = styled.input<InputProps>`
|
|||||||
disabled && isChecked
|
disabled && isChecked
|
||||||
? theme.adaptiveColors.blue3
|
? theme.adaptiveColors.blue3
|
||||||
: indeterminate || isChecked
|
: indeterminate || isChecked
|
||||||
? theme.adaptiveColors.blue3
|
? theme.color.blue
|
||||||
: 'transparent'};
|
: 'transparent'};
|
||||||
border-color: ${({
|
border-color: ${({
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
Reference in New Issue
Block a user