[FE] handle restricted objects 2 (#12437)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -67,7 +67,10 @@ export const ActivityRichTextEditor = ({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
});
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({ recordId: activityId });
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: activityId,
|
||||
objectMetadataId: objectMetadataItemActivity.id,
|
||||
});
|
||||
|
||||
const isReadOnly = isFieldValueReadOnly({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
|
||||
@ -7,8 +7,11 @@ 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 { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
@ -17,8 +20,6 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
const StyledAttachmentsContainer = styled.div`
|
||||
display: flex;
|
||||
@ -47,8 +48,6 @@ 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]);
|
||||
};
|
||||
@ -63,6 +62,16 @@ export const Attachments = ({
|
||||
|
||||
const isAttachmentsEmpty = !attachments || attachments.length === 0;
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (loading && isAttachmentsEmpty) {
|
||||
return <SkeletonLoader />;
|
||||
}
|
||||
@ -94,7 +103,7 @@ export const Attachments = ({
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add file"
|
||||
@ -120,7 +129,7 @@ export const Attachments = ({
|
||||
title="All"
|
||||
attachments={attachments ?? []}
|
||||
button={
|
||||
!hasObjectReadOnlyPermission && (
|
||||
!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
|
||||
@ -12,9 +12,10 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
export const usePrepareFindManyActivitiesQuery = ({
|
||||
activityObjectNameSingular,
|
||||
@ -32,6 +33,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
||||
|
||||
const cache = useApolloClient().cache;
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { upsertFindManyRecordsQueryInCache: upsertFindManyActivitiesInCache } =
|
||||
useUpsertFindManyRecordsQueryInCache({
|
||||
@ -64,6 +66,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
||||
objectMetadataItem: targetableObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const activityTargets: (TaskTarget | NoteTarget)[] =
|
||||
|
||||
@ -3,9 +3,12 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
||||
import { NoteList } from '@/activities/notes/components/NoteList';
|
||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
@ -14,8 +17,6 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
const StyledNotesContainer = styled.div`
|
||||
display: flex;
|
||||
@ -32,14 +33,22 @@ export const Notes = ({
|
||||
}) => {
|
||||
const { notes, loading } = useNotes(targetableObject);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
|
||||
const isNotesEmpty = !notes || notes.length === 0;
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (loading && isNotesEmpty) {
|
||||
return <SkeletonLoader />;
|
||||
}
|
||||
@ -59,7 +68,7 @@ export const Notes = ({
|
||||
There are no associated notes with this record.
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New note"
|
||||
@ -81,7 +90,7 @@ export const Notes = ({
|
||||
title="All"
|
||||
notes={notes}
|
||||
button={
|
||||
!hasObjectReadOnlyPermission && (
|
||||
!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
|
||||
@ -1,27 +1,31 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
|
||||
export const AddTaskButton = ({
|
||||
activityTargetableObjects,
|
||||
activityTargetableObject,
|
||||
}: {
|
||||
activityTargetableObjects?: ActivityTargetableObject[];
|
||||
activityTargetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: activityTargetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
if (
|
||||
!isNonEmptyArray(activityTargetableObjects) ||
|
||||
hasObjectReadOnlyPermission
|
||||
) {
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (!hasObjectUpdatePermissions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -33,7 +37,7 @@ export const AddTaskButton = ({
|
||||
title="Add task"
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: activityTargetableObjects,
|
||||
targetableObjects: [activityTargetableObject],
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
@ -22,7 +22,7 @@ export const ObjectTasks = ({ targetableObject }: ObjectTasksProps) => {
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'entity-tasks-filter-scope' }}
|
||||
>
|
||||
<TaskGroups targetableObjects={[targetableObject]} />
|
||||
<TaskGroups targetableObject={targetableObject} />
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
|
||||
export const PageAddTaskButton = () => {
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
// TODO: fetch workspace member from filter here
|
||||
|
||||
const handleClick = () => {
|
||||
openCreateActivity({
|
||||
targetableObjects: [],
|
||||
});
|
||||
};
|
||||
|
||||
return <PageAddButton onClick={handleClick} />;
|
||||
};
|
||||
@ -5,13 +5,14 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
||||
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { AddTaskButton } from './AddTaskButton';
|
||||
import { TaskList } from './TaskList';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
@ -20,8 +21,8 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { AddTaskButton } from './AddTaskButton';
|
||||
import { TaskList } from './TaskList';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -31,15 +32,23 @@ const StyledContainer = styled.div`
|
||||
|
||||
type TaskGroupsProps = {
|
||||
filterDropdownId?: string;
|
||||
targetableObjects?: ActivityTargetableObject[];
|
||||
targetableObject: ActivityTargetableObject;
|
||||
};
|
||||
|
||||
export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
export const TaskGroups = ({ targetableObject }: TaskGroupsProps) => {
|
||||
const { tasks, tasksLoading } = useTasks({
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
targetableObjects: [targetableObject],
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
@ -74,14 +83,14 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
All tasks addressed. Maintain the momentum.
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New task"
|
||||
variant={'secondary'}
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
targetableObjects: [targetableObject],
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -107,7 +116,7 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
tasks={tasksByStatus}
|
||||
button={
|
||||
(status === 'TODO' || !hasTodoStatus) && (
|
||||
<AddTaskButton activityTargetableObjects={targetableObjects} />
|
||||
<AddTaskButton activityTargetableObject={targetableObject} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -42,12 +42,10 @@ export const Empty: Story = {};
|
||||
|
||||
export const WithTasks: Story = {
|
||||
args: {
|
||||
targetableObjects: [
|
||||
{
|
||||
id: mockedTasks[0].taskTargets?.[0].personId,
|
||||
targetObjectNameSingular: 'person',
|
||||
},
|
||||
] as ActivityTargetableObject[],
|
||||
targetableObject: {
|
||||
id: mockedTasks[0].taskTargets?.[0].personId,
|
||||
targetObjectNameSingular: 'person',
|
||||
} as ActivityTargetableObject,
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
|
||||
Reference in New Issue
Block a user