Files
worklenz/worklenz-frontend/src/pages/projects/projectView/files/project-view-files.tsx
chamikaJ 8825b0410a init
2025-04-17 18:28:54 +05:30

272 lines
7.9 KiB
TypeScript

import {
Button,
Card,
Flex,
Popconfirm,
Segmented,
Table,
TableProps,
Tooltip,
Typography,
} from 'antd';
import { useEffect, useState } from 'react';
import { colors } from '@/styles/colors';
import {
AppstoreOutlined,
BarsOutlined,
CloudDownloadOutlined,
DeleteOutlined,
ExclamationCircleFilled,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import { durationDateFormat } from '@utils/durationDateFormat';
import { DEFAULT_PAGE_SIZE, IconsMap } from '@/shared/constants';
import {
IProjectAttachmentsViewModel,
ITaskAttachmentViewModel,
} from '@/types/tasks/task-attachment-view-model';
import { useAppSelector } from '@/hooks/useAppSelector';
import { attachmentsApiService } from '@/api/attachments/attachments.api.service';
import logger from '@/utils/errorLogger';
import { evt_project_files_visit } from '@/shared/worklenz-analytics-events';
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
const ProjectViewFiles = () => {
const { t } = useTranslation('project-view-files');
const { trackMixpanelEvent } = useMixpanelTracking();
const { projectId, refreshTimestamp } = useAppSelector(state => state.projectReducer);
const [attachments, setAttachments] = useState<IProjectAttachmentsViewModel>({});
const [loading, setLoading] = useState(false);
const [downloading, setDownloading] = useState(false);
const [paginationConfig, setPaginationConfig] = useState({
total: 0,
pageIndex: 1,
showSizeChanger: true,
defaultPageSize: DEFAULT_PAGE_SIZE,
});
const fetchAttachments = async () => {
if (!projectId) return;
try {
setLoading(true);
const response = await attachmentsApiService.getProjectAttachments(
projectId,
paginationConfig.pageIndex,
paginationConfig.defaultPageSize
);
if (response.done) {
setAttachments(response.body || {});
setPaginationConfig(prev => ({ ...prev, total: response.body?.total || 0 }));
}
} catch (error) {
logger.error('Error fetching project attachments', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchAttachments();
}, [refreshTimestamp]);
const getFileTypeIcon = (type: string | undefined) => {
if (!type) return IconsMap['search'];
return IconsMap[type as string] || IconsMap['search'];
};
const downloadAttachment = async (id: string | undefined, filename: string | undefined) => {
if (!id || !filename) return;
try {
setDownloading(true);
const response = await attachmentsApiService.downloadAttachment(id, filename);
if (response.done) {
const link = document.createElement('a');
link.href = response.body || '';
link.download = filename;
link.click();
link.remove();
}
} catch (error) {
logger.error('Error downloading attachment', error);
} finally {
setDownloading(false);
}
};
const deleteAttachment = async (id: string | undefined) => {
if (!id) return;
try {
const response = await attachmentsApiService.deleteAttachment(id);
if (response.done) {
fetchAttachments();
}
} catch (error) {
logger.error('Error deleting attachment', error);
}
};
const openAttachment = (url: string | undefined) => {
if (!url) return;
const a = document.createElement('a');
a.href = url;
a.target = '_blank';
a.style.display = 'none';
a.click();
};
useEffect(() => {
trackMixpanelEvent(evt_project_files_visit);
fetchAttachments();
}, [paginationConfig.pageIndex, projectId]);
const columns: TableProps<ITaskAttachmentViewModel>['columns'] = [
{
key: 'fileName',
title: t('nameColumn'),
render: (record: ITaskAttachmentViewModel) => (
<Flex
gap={4}
align="center"
style={{ cursor: 'pointer' }}
onClick={() => openAttachment(record.url)}
>
<img
src={`/file-types/${getFileTypeIcon(record.type)}`}
alt={t('fileIconAlt')}
style={{ width: '100%', maxWidth: 25 }}
/>
<Typography.Text>
[{record.task_key}] {record.name}
</Typography.Text>
</Flex>
),
},
{
key: 'attachedTask',
title: t('attachedTaskColumn'),
render: (record: ITaskAttachmentViewModel) => (
<Typography.Text style={{ cursor: 'pointer' }} onClick={() => openAttachment(record.url)}>
{record.task_name}
</Typography.Text>
),
},
{
key: 'size',
title: t('sizeColumn'),
render: (record: ITaskAttachmentViewModel) => (
<Typography.Text style={{ cursor: 'pointer' }} onClick={() => openAttachment(record.url)}>
{record.size}
</Typography.Text>
),
},
{
key: 'uploadedBy',
title: t('uploadedByColumn'),
render: (record: ITaskAttachmentViewModel) => (
<Typography.Text style={{ cursor: 'pointer' }} onClick={() => openAttachment(record.url)}>
{record.uploader_name}
</Typography.Text>
),
},
{
key: 'uploadedAt',
title: t('uploadedAtColumn'),
render: (record: ITaskAttachmentViewModel) => (
<Typography.Text style={{ cursor: 'pointer' }} onClick={() => openAttachment(record.url)}>
<Tooltip title={record.created_at}>{durationDateFormat(record.created_at)}</Tooltip>
</Typography.Text>
),
},
{
key: 'actionBtns',
width: 80,
render: (record: ITaskAttachmentViewModel) => (
<Flex gap={8} style={{ padding: 0 }}>
<Popconfirm
title={t('deleteConfirmationTitle')}
icon={<ExclamationCircleFilled style={{ color: colors.vibrantOrange }} />}
okText={t('deleteConfirmationOk')}
cancelText={t('deleteConfirmationCancel')}
onConfirm={() => deleteAttachment(record.id)}
>
<Tooltip title="Delete">
<Button shape="default" icon={<DeleteOutlined />} size="small" />
</Tooltip>
</Popconfirm>
<Tooltip title="Download">
<Button
size="small"
icon={<CloudDownloadOutlined />}
onClick={() => downloadAttachment(record.id, record.name)}
loading={downloading}
/>
</Tooltip>
</Flex>
),
},
];
return (
<Card
style={{ width: '100%' }}
title={
<Flex justify="space-between">
<Typography.Text
style={{
display: 'flex',
gap: 4,
alignItems: 'center',
color: colors.lightGray,
fontSize: 13,
lineHeight: 1,
}}
>
<ExclamationCircleOutlined />
{t('titleDescriptionText')}
</Typography.Text>
<Tooltip title={t('segmentedTooltip')}>
<Segmented
options={[
{ value: 'listView', icon: <BarsOutlined /> },
{ value: 'thumbnailView', icon: <AppstoreOutlined /> },
]}
defaultValue={'listView'}
disabled={true}
/>
</Tooltip>
</Flex>
}
>
<Table<ITaskAttachmentViewModel>
className="custom-two-colors-row-table"
dataSource={attachments.data}
columns={columns}
rowKey={record => record.id || ''}
loading={loading}
pagination={{
showSizeChanger: paginationConfig.showSizeChanger,
defaultPageSize: paginationConfig.defaultPageSize,
total: paginationConfig.total,
current: paginationConfig.pageIndex,
onChange: (page, pageSize) =>
setPaginationConfig(prev => ({
...prev,
pageIndex: page,
defaultPageSize: pageSize,
})),
}}
/>
</Card>
);
};
export default ProjectViewFiles;