Add all object level read-only behavior (#10356)
Fixes https://github.com/twentyhq/core-team-issues/issues/427 --------- Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
This commit is contained in:
@ -11,6 +11,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec
|
||||
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useCallback, useState } from 'react';
|
||||
@ -21,6 +22,8 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem =
|
||||
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { resetTableRowSelection } = useRecordTable({
|
||||
recordTableId: objectMetadataItem.namePlural,
|
||||
});
|
||||
@ -77,6 +80,7 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem =
|
||||
const isRemoteObject = objectMetadataItem.isRemote;
|
||||
|
||||
const shouldBeRegistered =
|
||||
!hasObjectReadOnlyPermission &&
|
||||
!isRemoteObject &&
|
||||
!isDeletedFilterActive &&
|
||||
isDefined(contextStoreNumberOfSelectedRecords) &&
|
||||
|
||||
@ -12,6 +12,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useCallback, useState } from 'react';
|
||||
@ -26,6 +27,8 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem =
|
||||
recordTableId: objectMetadataItem.namePlural,
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { destroyManyRecords } = useDestroyManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
@ -86,6 +89,7 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem =
|
||||
const isRemoteObject = objectMetadataItem.isRemote;
|
||||
|
||||
const shouldBeRegistered =
|
||||
!hasObjectReadOnlyPermission &&
|
||||
!isRemoteObject &&
|
||||
isDeletedFilterActive &&
|
||||
isDefined(contextStoreNumberOfSelectedRecords) &&
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
|
||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
||||
import { getRecordIndexIdFromObjectNamePlural } from '@/object-record/utils/getRecordIndexIdFromObjectNamePlural';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
|
||||
export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjectMetadataItem =
|
||||
({ objectMetadataItem }) => {
|
||||
@ -8,6 +9,8 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { createNewTableRecord } = useCreateNewTableRecord({
|
||||
objectMetadataItem,
|
||||
recordTableId,
|
||||
@ -18,7 +21,7 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec
|
||||
};
|
||||
|
||||
return {
|
||||
shouldBeRegistered: true,
|
||||
shouldBeRegistered: !hasObjectReadOnlyPermission,
|
||||
onClick,
|
||||
};
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { isNull } from '@sniptt/guards';
|
||||
@ -21,6 +22,8 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { resetTableRowSelection } = useRecordTable({
|
||||
recordTableId: objectMetadataItem.namePlural,
|
||||
});
|
||||
@ -61,7 +64,9 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||
|
||||
const shouldBeRegistered =
|
||||
!isRemoteObject && isNull(selectedRecord?.deletedAt);
|
||||
!isRemoteObject &&
|
||||
isNull(selectedRecord?.deletedAt) &&
|
||||
!hasObjectReadOnlyPermission;
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
|
||||
@ -4,6 +4,7 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
@ -20,6 +21,8 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const navigateApp = useNavigateApp();
|
||||
|
||||
const { resetTableRowSelection } = useRecordTable({
|
||||
@ -54,7 +57,9 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||
|
||||
const shouldBeRegistered =
|
||||
!isRemoteObject && isDefined(selectedRecord?.deletedAt);
|
||||
!hasObjectReadOnlyPermission &&
|
||||
!isRemoteObject &&
|
||||
isDefined(selectedRecord?.deletedAt);
|
||||
|
||||
const onClick = () => {
|
||||
if (!shouldBeRegistered) {
|
||||
|
||||
@ -17,6 +17,7 @@ import { DropZone } from '@/activities/files/components/DropZone';
|
||||
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
const StyledAttachmentsContainer = styled.div`
|
||||
@ -46,6 +47,8 @@ export const Attachments = ({
|
||||
|
||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]);
|
||||
};
|
||||
@ -91,12 +94,14 @@ export const Attachments = ({
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
/>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add file"
|
||||
variant="secondary"
|
||||
onClick={handleUploadFileClick}
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add file"
|
||||
variant="secondary"
|
||||
onClick={handleUploadFileClick}
|
||||
/>
|
||||
)}
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
)}
|
||||
</StyledDropZoneContainer>
|
||||
@ -115,13 +120,15 @@ export const Attachments = ({
|
||||
title="All"
|
||||
attachments={attachments ?? []}
|
||||
button={
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
title="Add file"
|
||||
onClick={handleUploadFileClick}
|
||||
></Button>
|
||||
!hasObjectReadOnlyPermission && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
title="Add file"
|
||||
onClick={handleUploadFileClick}
|
||||
></Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledAttachmentsContainer>
|
||||
|
||||
@ -4,6 +4,7 @@ import { NoteList } from '@/activities/notes/components/NoteList';
|
||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
@ -31,6 +32,8 @@ export const Notes = ({
|
||||
}) => {
|
||||
const { notes, loading } = useNotes(targetableObject);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
@ -56,16 +59,18 @@ export const Notes = ({
|
||||
There are no associated notes with this record.
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New note"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: [targetableObject],
|
||||
})
|
||||
}
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New note"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: [targetableObject],
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { AddTaskButton } from './AddTaskButton';
|
||||
@ -38,6 +39,8 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
@ -71,16 +74,18 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
All tasks addressed. Maintain the momentum.
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New task"
|
||||
variant={'secondary'}
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
})
|
||||
}
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New task"
|
||||
variant={'secondary'}
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
import { UserWorkspace } from '~/generated/graphql';
|
||||
|
||||
export type CurrentUserWorkspace = Pick<UserWorkspace, 'settingsPermissions'>;
|
||||
export type CurrentUserWorkspace = Pick<
|
||||
UserWorkspace,
|
||||
'settingsPermissions' | 'objectRecordsPermissions'
|
||||
>;
|
||||
|
||||
export const currentUserWorkspaceState =
|
||||
createState<CurrentUserWorkspace | null>({
|
||||
|
||||
@ -11,6 +11,7 @@ import { useColumnNewCardActions } from '@/object-record/record-board/record-boa
|
||||
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
|
||||
|
||||
@ -97,6 +98,8 @@ export const RecordBoardColumnHeader = () => {
|
||||
columnDefinition.id ?? '',
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { isOpportunitiesCompanyFieldDisabled } =
|
||||
useIsOpportunitiesCompanyFieldDisabled();
|
||||
|
||||
@ -146,12 +149,13 @@ export const RecordBoardColumnHeader = () => {
|
||||
Icon={IconDotsVertical}
|
||||
onClick={handleBoardColumnMenuOpen}
|
||||
/>
|
||||
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconPlus}
|
||||
onClick={() => handleNewButtonClick('first', isOpportunity)}
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconPlus}
|
||||
onClick={() => handleNewButtonClick('first', isOpportunity)}
|
||||
/>
|
||||
)}
|
||||
</StyledHeaderActions>
|
||||
)}
|
||||
</StyledRightContainer>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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 = ({
|
||||
@ -16,8 +17,15 @@ export const RecordBoardColumnNewRecord = ({
|
||||
scopeId: columnId,
|
||||
}),
|
||||
);
|
||||
|
||||
const { handleCreateSuccess } = useAddNewCard();
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{newRecord.isCreating && newRecord.position === position && (
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconPlus } from 'twenty-ui';
|
||||
@ -29,6 +30,12 @@ export const RecordBoardColumnNewRecordButton = ({
|
||||
|
||||
const { handleNewButtonClick } = useColumnNewCardActions(columnId);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledNewButton onClick={() => handleNewButtonClick('last', false)}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
|
||||
@ -3,6 +3,7 @@ import { useContext } from 'react';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
|
||||
@ -20,11 +21,14 @@ export const useIsFieldValueReadOnly = () => {
|
||||
objectNameSingular: metadata.objectMetadataNameSingular ?? '',
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
return isFieldValueReadOnly({
|
||||
objectNameSingular: metadata.objectMetadataNameSingular,
|
||||
fieldName: metadata.fieldName,
|
||||
fieldType: type,
|
||||
isObjectRemote: objectMetadataItem.isRemote,
|
||||
isRecordDeleted: recordFromStore?.deletedAt,
|
||||
hasObjectReadOnlyPermission,
|
||||
});
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@ type isFieldValueReadOnlyParams = {
|
||||
fieldType?: FieldMetadataType;
|
||||
isObjectRemote?: boolean;
|
||||
isRecordDeleted?: boolean;
|
||||
hasObjectReadOnlyPermission?: boolean;
|
||||
};
|
||||
|
||||
export const isFieldValueReadOnly = ({
|
||||
@ -20,6 +21,7 @@ export const isFieldValueReadOnly = ({
|
||||
fieldType,
|
||||
isObjectRemote = false,
|
||||
isRecordDeleted = false,
|
||||
hasObjectReadOnlyPermission = false,
|
||||
}: isFieldValueReadOnlyParams) => {
|
||||
if (fieldName === 'noteTargets' || fieldName === 'taskTargets') {
|
||||
return true;
|
||||
@ -33,6 +35,10 @@ export const isFieldValueReadOnly = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isWorkflowSubObjectMetadata(objectNameSingular)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -4,14 +4,15 @@ import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/use
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useHasSettingsPermission } from '@/settings/roles/hooks/useHasSettingsPermission';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { isDefined, SettingsFeatures } from 'twenty-shared';
|
||||
import { IconEyeOff, IconSettings } from 'twenty-ui';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
@ -69,37 +70,36 @@ export const useRecordGroupActions = ({
|
||||
recordGroupFieldMetadata,
|
||||
]);
|
||||
|
||||
const recordGroupActions: RecordGroupAction[] = useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
id: 'edit',
|
||||
label: 'Edit',
|
||||
icon: IconSettings,
|
||||
position: 0,
|
||||
callback: () => {
|
||||
navigateToSelectSettings();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'hide',
|
||||
label: 'Hide',
|
||||
icon: IconEyeOff,
|
||||
position: 1,
|
||||
callback: () => {
|
||||
handleRecordGroupVisibilityChange({
|
||||
...recordGroupDefinition,
|
||||
isVisible: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
].filter(isDefined),
|
||||
[
|
||||
handleRecordGroupVisibilityChange,
|
||||
navigateToSelectSettings,
|
||||
recordGroupDefinition,
|
||||
],
|
||||
const hasAccessToDataModelSettings = useHasSettingsPermission(
|
||||
SettingsFeatures.DATA_MODEL,
|
||||
);
|
||||
|
||||
const recordGroupActions: RecordGroupAction[] = [];
|
||||
|
||||
if (hasAccessToDataModelSettings) {
|
||||
recordGroupActions.push({
|
||||
id: 'edit',
|
||||
label: 'Edit',
|
||||
icon: IconSettings,
|
||||
position: 0,
|
||||
callback: () => {
|
||||
navigateToSelectSettings();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
recordGroupActions.push({
|
||||
id: 'hide',
|
||||
label: 'Hide',
|
||||
icon: IconEyeOff,
|
||||
position: 1,
|
||||
callback: () => {
|
||||
handleRecordGroupVisibilityChange({
|
||||
...recordGroupDefinition,
|
||||
isVisible: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return recordGroupActions;
|
||||
};
|
||||
|
||||
@ -4,9 +4,11 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
||||
import { RecordTableEmptyStateByGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateByGroupNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
||||
import { RecordTableEmptyStateReadOnly } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateReadOnly';
|
||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableEmptyState = () => {
|
||||
@ -17,6 +19,8 @@ export const RecordTableEmptyState = () => {
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||
const noRecordAtAll = totalCount === 0;
|
||||
|
||||
@ -27,6 +31,10 @@ export const RecordTableEmptyState = () => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return <RecordTableEmptyStateReadOnly />;
|
||||
}
|
||||
|
||||
if (isRemote) {
|
||||
return <RecordTableEmptyStateRemote />;
|
||||
} else if (isSoftDeleteActive === true) {
|
||||
|
||||
@ -19,6 +19,7 @@ type RecordTableEmptyStateDisplayButtonProps = {
|
||||
ButtonIcon: IconComponent;
|
||||
buttonTitle: string;
|
||||
onClick: () => void;
|
||||
buttonIsDisabled?: boolean;
|
||||
};
|
||||
|
||||
type RecordTableEmptyStateDisplayProps = {
|
||||
@ -54,6 +55,7 @@ export const RecordTableEmptyStateDisplay = (
|
||||
title={props.buttonTitle}
|
||||
variant={'secondary'}
|
||||
onClick={props.onClick}
|
||||
disabled={props.buttonIsDisabled}
|
||||
/>
|
||||
)}
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconPlus } from 'twenty-ui';
|
||||
|
||||
export const RecordTableEmptyStateReadOnly = () => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const objectLabel = useObjectLabel(objectMetadataItem);
|
||||
|
||||
const buttonTitle = `Add a ${objectLabel}`;
|
||||
|
||||
return (
|
||||
<RecordTableEmptyStateDisplay
|
||||
title={t`No records found`}
|
||||
subTitle={t`You are not allowed to create records in this object`}
|
||||
animatedPlaceholderType="noRecord"
|
||||
buttonTitle={buttonTitle}
|
||||
ButtonIcon={IconPlus}
|
||||
buttonIsDisabled={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -13,6 +13,7 @@ import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
@ -212,6 +213,8 @@ export const RecordTableHeaderCell = ({
|
||||
|
||||
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
key={column.fieldMetadataId}
|
||||
@ -229,7 +232,8 @@ export const RecordTableHeaderCell = ({
|
||||
<RecordTableColumnHeadWithDropdown column={column} />
|
||||
{(useIsMobile() || iconVisibility) &&
|
||||
!!column.isLabelIdentifier &&
|
||||
!isReadOnly && (
|
||||
!isReadOnly &&
|
||||
!hasObjectReadOnlyPermission && (
|
||||
<StyledHeaderIcon>
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
|
||||
@ -3,6 +3,7 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
||||
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { IconPlus } from 'twenty-ui';
|
||||
|
||||
@ -15,6 +16,8 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
||||
recordIndexAllRecordIdsComponentSelector,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { createNewTableRecordInGroup } = useCreateNewTableRecord({
|
||||
objectMetadataItem,
|
||||
recordTableId,
|
||||
@ -24,6 +27,10 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
||||
createNewTableRecordInGroup(currentRecordGroupId);
|
||||
};
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordTableActionRow
|
||||
draggableId={`add-new-record-${currentRecordGroupId}`}
|
||||
|
||||
@ -4,6 +4,7 @@ import { MultipleObjectRecordSelectItem } from '@/object-record/relation-picker/
|
||||
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
@ -75,6 +76,8 @@ export const MultiRecordSelect = ({
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
useEffect(() => {
|
||||
setHotkeyScope(instanceId);
|
||||
}, [setHotkeyScope, instanceId]);
|
||||
@ -144,7 +147,7 @@ export const MultiRecordSelect = ({
|
||||
<DropdownMenu ref={containerRef} data-select-disable width={200}>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
{isDefined(onCreate) && (
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
import { useRecordPickerRecordsOptions } from '@/object-record/relation-picker/hooks/useRecordPickerRecordsOptions';
|
||||
import { useRecordSelectSearch } from '@/object-record/relation-picker/hooks/useRecordSelectSearch';
|
||||
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@ -48,6 +49,8 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
RecordPickerComponentInstanceContext,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { records, recordPickerSearchFilter } = useRecordPickerRecordsOptions({
|
||||
objectNameSingular,
|
||||
selectedRecordIds,
|
||||
@ -69,9 +72,11 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
<>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
{records.recordsToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
{shouldDisplayDropdownMenuItems && (
|
||||
<SingleRecordSelectMenuItems
|
||||
@ -120,7 +125,7 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
{records.recordsToSelect.length > 0 && isDefined(onCreate) && (
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{isDefined(onCreate) && (
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -50,7 +50,7 @@ export type SettingsNavigationItem = {
|
||||
soon?: boolean;
|
||||
};
|
||||
|
||||
export const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
const billing = useRecoilValue(billingState);
|
||||
|
||||
const isFunctionSettingsEnabled = false;
|
||||
@ -195,3 +195,5 @@ export const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export { useSettingsNavigationItems };
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined, PermissionsOnAllObjectRecords } from 'twenty-shared';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useHasObjectReadOnlyPermission = () => {
|
||||
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
|
||||
const isPermissionEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsEnabled,
|
||||
);
|
||||
|
||||
if (!isPermissionEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDefined(currentUserWorkspace?.objectRecordsPermissions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
currentUserWorkspace?.objectRecordsPermissions.length === 1 &&
|
||||
currentUserWorkspace?.objectRecordsPermissions.includes(
|
||||
PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS,
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Button, IconButton, IconPlus, useIsMobile } from 'twenty-ui';
|
||||
@ -11,10 +12,17 @@ export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
|
||||
@ -17,6 +17,7 @@ import { SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID } from '@/ui/layout/show-page/constant
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { Dropdown } from '../../dropdown/components/Dropdown';
|
||||
@ -39,6 +40,8 @@ export const ShowPageAddButton = ({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const handleSelect = (objectNameSingular: CoreObjectNameSingular) => {
|
||||
if (objectNameSingular === CoreObjectNameSingular.Note) {
|
||||
openNote({
|
||||
@ -67,6 +70,10 @@ export const ShowPageAddButton = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Dropdown
|
||||
|
||||
@ -13,7 +13,9 @@ import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
type Checkers = Parameters<typeof checker>[0];
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
const env = loadEnv(mode, __dirname, '');
|
||||
|
||||
console.log(__dirname);
|
||||
|
||||
const {
|
||||
REACT_APP_SERVER_BASE_URL,
|
||||
|
||||
Reference in New Issue
Block a user