Attachments (#2716)
* create attachment site * add deletion * - fix person create attachment * - add presentation type - add some more file endings - various fixes
This commit is contained in:
@ -0,0 +1,64 @@
|
||||
import { IconDotsVertical, IconDownload, IconTrash } from '@/ui/display/icon';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
|
||||
type AttachmentDropdownProps = {
|
||||
onDownload: () => void;
|
||||
onDelete: () => void;
|
||||
scopeKey: string;
|
||||
};
|
||||
|
||||
export const AttachmentDropdown = ({
|
||||
onDownload,
|
||||
onDelete,
|
||||
scopeKey,
|
||||
}: AttachmentDropdownProps) => {
|
||||
const dropdownScopeId = `${scopeKey}-settings-field-active-action-dropdown`;
|
||||
|
||||
const { closeDropdown } = useDropdown({ dropdownScopeId });
|
||||
|
||||
const handleDownload = () => {
|
||||
onDownload();
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete();
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu width="160px">
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Download"
|
||||
LeftIcon={IconDownload}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownScopeId,
|
||||
}}
|
||||
/>
|
||||
</DropdownScope>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,64 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentType,
|
||||
} from '@/activities/files/types/Attachment';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
import {
|
||||
IconFile,
|
||||
IconFileText,
|
||||
IconFileZip,
|
||||
IconHeadphones,
|
||||
IconPhoto,
|
||||
IconPresentation,
|
||||
IconTable,
|
||||
IconVideo,
|
||||
} from '@/ui/input/constants/icons';
|
||||
|
||||
const StyledIconContainer = styled.div<{ background: string }>`
|
||||
align-items: center;
|
||||
background: ${({ background }) => background};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.grayScale.gray0};
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: 20px;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
`;
|
||||
|
||||
const IconMapping: { [key in AttachmentType]: IconComponent } = {
|
||||
Archive: IconFileZip,
|
||||
Audio: IconHeadphones,
|
||||
Image: IconPhoto,
|
||||
Presentation: IconPresentation,
|
||||
Spreadsheet: IconTable,
|
||||
TextDocument: IconFileText,
|
||||
Video: IconVideo,
|
||||
Other: IconFile,
|
||||
};
|
||||
|
||||
export const AttachmentIcon = ({ attachment }: { attachment: Attachment }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const IconColors: { [key in AttachmentType]: string } = {
|
||||
Archive: theme.color.gray,
|
||||
Audio: theme.color.pink,
|
||||
Image: theme.color.yellow,
|
||||
Presentation: theme.color.orange,
|
||||
Spreadsheet: theme.color.turquoise,
|
||||
TextDocument: theme.color.blue,
|
||||
Video: theme.color.purple,
|
||||
Other: theme.color.gray,
|
||||
};
|
||||
|
||||
const Icon = IconMapping[attachment.type];
|
||||
|
||||
return (
|
||||
<StyledIconContainer background={IconColors[attachment.type]}>
|
||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||
</StyledIconContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,76 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Attachment } from '@/activities/files/types/Attachment';
|
||||
|
||||
import { AttachmentRow } from './AttachmentRow';
|
||||
|
||||
type AttachmentListProps = {
|
||||
title: string;
|
||||
attachments: Attachment[];
|
||||
button?: ReactElement | false;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 8px 24px;
|
||||
`;
|
||||
|
||||
const StyledTitleBar = styled.h3`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
place-items: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.h3`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
`;
|
||||
|
||||
const StyledCount = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledAttachmentContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
disply: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const AttachmentList = ({
|
||||
title,
|
||||
attachments,
|
||||
button,
|
||||
}: AttachmentListProps) => (
|
||||
<>
|
||||
{attachments && attachments.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
<StyledTitle>
|
||||
{title} <StyledCount>{attachments.length}</StyledCount>
|
||||
</StyledTitle>
|
||||
{button}
|
||||
</StyledTitleBar>
|
||||
<StyledAttachmentContainer>
|
||||
{attachments.map((attachment) => (
|
||||
<AttachmentRow key={attachment.id} attachment={attachment} />
|
||||
))}
|
||||
</StyledAttachmentContainer>
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
104
front/src/modules/activities/files/components/AttachmentRow.tsx
Normal file
104
front/src/modules/activities/files/components/AttachmentRow.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown';
|
||||
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
|
||||
import { Attachment } from '@/activities/files/types/Attachment';
|
||||
import { downloadFile } from '@/activities/files/utils/downloadFile';
|
||||
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
|
||||
import { IconCalendar } from '@/ui/display/icon';
|
||||
import {
|
||||
FieldContext,
|
||||
GenericFieldContextType,
|
||||
} from '@/ui/object/field/contexts/FieldContext';
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
|
||||
const StyledRow = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledLeftContent = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledRightContent = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||
`;
|
||||
|
||||
const StyledCalendarIconContainer = styled.div`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledLink = styled.a`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
}
|
||||
`;
|
||||
|
||||
export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
|
||||
const theme = useTheme();
|
||||
const fieldContext = useMemo(
|
||||
() => ({ recoilScopeId: attachment?.id ?? '' }),
|
||||
[attachment?.id],
|
||||
);
|
||||
|
||||
const { deleteOneObject: deleteOneAttachment } =
|
||||
useDeleteOneObjectRecord<Attachment>({
|
||||
objectNameSingular: 'attachment',
|
||||
});
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteOneAttachment(attachment.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldContext.Provider value={fieldContext as GenericFieldContextType}>
|
||||
<StyledRow>
|
||||
<StyledLeftContent>
|
||||
<AttachmentIcon attachment={attachment} />
|
||||
<StyledLink
|
||||
href={
|
||||
process.env.REACT_APP_SERVER_BASE_URL +
|
||||
'/files/' +
|
||||
attachment.fullPath
|
||||
}
|
||||
target="__blank"
|
||||
>
|
||||
{attachment.name}
|
||||
</StyledLink>
|
||||
</StyledLeftContent>
|
||||
<StyledRightContent>
|
||||
<StyledCalendarIconContainer>
|
||||
<IconCalendar size={theme.icon.size.md} />
|
||||
</StyledCalendarIconContainer>
|
||||
{formatToHumanReadableDate(attachment.createdAt)}
|
||||
<AttachmentDropdown
|
||||
scopeKey={attachment.id}
|
||||
onDelete={handleDelete}
|
||||
onDownload={() => {
|
||||
downloadFile(attachment.fullPath, attachment.name);
|
||||
}}
|
||||
/>
|
||||
</StyledRightContent>
|
||||
</StyledRow>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
};
|
||||
152
front/src/modules/activities/files/components/Attachments.tsx
Normal file
152
front/src/modules/activities/files/components/Attachments.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import { ChangeEvent, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { AttachmentList } from '@/activities/files/components/AttachmentList';
|
||||
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
||||
import { Attachment } from '@/activities/files/types/Attachment';
|
||||
import { getFileType } from '@/activities/files/utils/getFileType';
|
||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { FileFolder, useUploadFileMutation } from '~/generated/graphql';
|
||||
|
||||
const StyledTaskGroupEmptyContainer = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
justify-content: center;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(16)};
|
||||
padding-left: ${({ theme }) => theme.spacing(4)};
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledEmptyTaskGroupTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
`;
|
||||
|
||||
const StyledEmptyTaskGroupSubTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledAttachmentsContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const StyledFileInput = styled.input`
|
||||
display: none;
|
||||
`;
|
||||
|
||||
export const Attachments = ({
|
||||
targetableEntity,
|
||||
}: {
|
||||
targetableEntity: ActivityTargetableEntity;
|
||||
}) => {
|
||||
const inputFileRef = useRef<HTMLInputElement>(null);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { attachments } = useAttachments(targetableEntity);
|
||||
|
||||
const [uploadFile] = useUploadFileMutation();
|
||||
|
||||
const { createOneObject: createOneAttachment } =
|
||||
useCreateOneObjectRecord<Attachment>({
|
||||
objectNameSingular: 'attachment',
|
||||
});
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) onUploadFile?.(e.target.files[0]);
|
||||
};
|
||||
|
||||
const handleUploadFileClick = () => {
|
||||
inputFileRef?.current?.click?.();
|
||||
};
|
||||
|
||||
const onUploadFile = async (file: File) => {
|
||||
const result = await uploadFile({
|
||||
variables: {
|
||||
file,
|
||||
fileFolder: FileFolder.Attachment,
|
||||
},
|
||||
});
|
||||
|
||||
const attachmentUrl = result?.data?.uploadFile;
|
||||
|
||||
if (!attachmentUrl) {
|
||||
return;
|
||||
}
|
||||
if (!createOneAttachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
await createOneAttachment({
|
||||
authorId: currentWorkspaceMember?.id,
|
||||
name: file.name,
|
||||
fullPath: attachmentUrl,
|
||||
type: getFileType(file.name),
|
||||
companyId:
|
||||
targetableEntity.type == 'Company' ? targetableEntity.id : null,
|
||||
personId: targetableEntity.type == 'Person' ? targetableEntity.id : null,
|
||||
});
|
||||
};
|
||||
|
||||
if (attachments?.length === 0 && targetableEntity.type !== 'Custom') {
|
||||
return (
|
||||
<StyledTaskGroupEmptyContainer>
|
||||
<StyledFileInput
|
||||
ref={inputFileRef}
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
/>
|
||||
|
||||
<StyledEmptyTaskGroupTitle>No files yet</StyledEmptyTaskGroupTitle>
|
||||
<StyledEmptyTaskGroupSubTitle>Upload one:</StyledEmptyTaskGroupSubTitle>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add file"
|
||||
variant="secondary"
|
||||
onClick={handleUploadFileClick}
|
||||
/>
|
||||
</StyledTaskGroupEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledAttachmentsContainer>
|
||||
<StyledFileInput
|
||||
ref={inputFileRef}
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
/>
|
||||
<AttachmentList
|
||||
title="All"
|
||||
attachments={attachments ?? []}
|
||||
button={
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
title="Add file"
|
||||
onClick={handleUploadFileClick}
|
||||
></Button>
|
||||
}
|
||||
/>
|
||||
</StyledAttachmentsContainer>
|
||||
);
|
||||
};
|
||||
20
front/src/modules/activities/files/hooks/useAttachments.tsx
Normal file
20
front/src/modules/activities/files/hooks/useAttachments.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { Attachment } from '@/activities/files/types/Attachment';
|
||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||
|
||||
import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
|
||||
|
||||
export const useAttachments = (entity: ActivityTargetableEntity) => {
|
||||
const { objects: attachments } = useFindManyObjectRecords({
|
||||
objectNamePlural: 'attachments',
|
||||
filter: {
|
||||
[entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id },
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'DescNullsFirst',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
attachments: attachments as Attachment[],
|
||||
};
|
||||
};
|
||||
20
front/src/modules/activities/files/types/Attachment.ts
Normal file
20
front/src/modules/activities/files/types/Attachment.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export type Attachment = {
|
||||
id: string;
|
||||
name: string;
|
||||
fullPath: string;
|
||||
type: AttachmentType;
|
||||
companyId: string;
|
||||
personId: string;
|
||||
activityId: string;
|
||||
authorId: string;
|
||||
createdAt: string;
|
||||
};
|
||||
export type AttachmentType =
|
||||
| 'Archive'
|
||||
| 'Audio'
|
||||
| 'Image'
|
||||
| 'Presentation'
|
||||
| 'Spreadsheet'
|
||||
| 'TextDocument'
|
||||
| 'Video'
|
||||
| 'Other';
|
||||
18
front/src/modules/activities/files/utils/downloadFile.ts
Normal file
18
front/src/modules/activities/files/utils/downloadFile.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const downloadFile = (fullPath: string, fileName: string) => {
|
||||
fetch(process.env.REACT_APP_SERVER_BASE_URL + '/files/' + fullPath)
|
||||
.then((resp) =>
|
||||
resp.status === 200
|
||||
? resp.blob()
|
||||
: Promise.reject('Failed downloading file'),
|
||||
)
|
||||
.then((blob) => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
};
|
||||
55
front/src/modules/activities/files/utils/getFileType.ts
Normal file
55
front/src/modules/activities/files/utils/getFileType.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { AttachmentType } from '@/activities/files/types/Attachment';
|
||||
|
||||
const FileExtensionMapping: { [key: string]: AttachmentType } = {
|
||||
doc: 'TextDocument',
|
||||
docm: 'TextDocument',
|
||||
docx: 'TextDocument',
|
||||
dot: 'TextDocument',
|
||||
dotx: 'TextDocument',
|
||||
odt: 'TextDocument',
|
||||
pdf: 'TextDocument',
|
||||
txt: 'TextDocument',
|
||||
rtf: 'TextDocument',
|
||||
xls: 'Spreadsheet',
|
||||
xlsb: 'Spreadsheet',
|
||||
xlsm: 'Spreadsheet',
|
||||
xlsx: 'Spreadsheet',
|
||||
xltx: 'Spreadsheet',
|
||||
csv: 'Spreadsheet',
|
||||
tsv: 'Spreadsheet',
|
||||
ods: 'Spreadsheet',
|
||||
ppt: 'Presentation',
|
||||
pptx: 'Presentation',
|
||||
potx: 'Presentation',
|
||||
odp: 'Presentation',
|
||||
html: 'Presentation',
|
||||
png: 'Image',
|
||||
jpg: 'Image',
|
||||
jpeg: 'Image',
|
||||
svg: 'Image',
|
||||
gif: 'Image',
|
||||
webp: 'Image',
|
||||
heif: 'Image',
|
||||
tif: 'Image',
|
||||
tiff: 'Image',
|
||||
bmp: 'Image',
|
||||
mp4: 'Video',
|
||||
avi: 'Video',
|
||||
mov: 'Video',
|
||||
wmv: 'Video',
|
||||
mpg: 'Video',
|
||||
mpeg: 'Video',
|
||||
mp3: 'Audio',
|
||||
zip: 'Archive',
|
||||
tar: 'Archive',
|
||||
iso: 'Archive',
|
||||
gz: 'Archive',
|
||||
};
|
||||
|
||||
export const getFileType = (fileName: string): AttachmentType => {
|
||||
const fileExtension = fileName.split('.').at(-1);
|
||||
if (!fileExtension) {
|
||||
return 'Other';
|
||||
}
|
||||
return FileExtensionMapping[fileExtension.toLowerCase()] ?? 'Other';
|
||||
};
|
||||
7
front/src/modules/files/graphql/queries/uploadFile.ts
Normal file
7
front/src/modules/files/graphql/queries/uploadFile.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPLOAD_FILE = gql`
|
||||
mutation uploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder)
|
||||
}
|
||||
`;
|
||||
@ -36,6 +36,7 @@ export {
|
||||
IconCurrencyDollar,
|
||||
IconDatabase,
|
||||
IconDotsVertical,
|
||||
IconDownload,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
IconFileCheck,
|
||||
@ -64,6 +65,7 @@ export {
|
||||
IconMouse2,
|
||||
IconNotes,
|
||||
IconNumbers,
|
||||
IconPaperclip,
|
||||
IconPencil,
|
||||
IconPhone,
|
||||
IconPlug,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Attachments } from '@/activities/files/components/Attachments';
|
||||
import { Notes } from '@/activities/notes/components/Notes';
|
||||
import { EntityTasks } from '@/activities/tasks/components/EntityTasks';
|
||||
import { Timeline } from '@/activities/timeline/components/Timeline';
|
||||
@ -8,6 +9,7 @@ import {
|
||||
IconCheckbox,
|
||||
IconMail,
|
||||
IconNotes,
|
||||
IconPaperclip,
|
||||
IconTimelineEvent,
|
||||
} from '@/ui/display/icon';
|
||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||
@ -72,6 +74,13 @@ export const ShowPageRightContainer = ({
|
||||
hide: !notes,
|
||||
disabled: entity.type === 'Custom',
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
title: 'Files',
|
||||
Icon: IconPaperclip,
|
||||
hide: !notes,
|
||||
disabled: entity.type === 'Custom',
|
||||
},
|
||||
{
|
||||
id: 'emails',
|
||||
title: 'Emails',
|
||||
@ -94,6 +103,7 @@ export const ShowPageRightContainer = ({
|
||||
{activeTabId === 'timeline' && <Timeline entity={entity} />}
|
||||
{activeTabId === 'tasks' && <EntityTasks entity={entity} />}
|
||||
{activeTabId === 'notes' && <Notes entity={entity} />}
|
||||
{activeTabId === 'files' && <Attachments targetableEntity={entity} />}
|
||||
</StyledShowPageRightContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user