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