| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- import { useEffect, useState, useCallback } from 'react';
- import {
- Table,
- Button,
- Space,
- Modal,
- message,
- Typography,
- Tooltip,
- Popconfirm,
- Card,
- Descriptions,
- Tag,
- Empty
- } from 'antd';
- import {
- DownloadOutlined,
- DeleteOutlined,
- ReloadOutlined,
- EyeOutlined,
- FileWordOutlined,
- CheckCircleOutlined
- } from '@ant-design/icons';
- import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
- import type { Report } from '../types';
- import { reportService } from '../services/reports';
- import { usePagination } from '../hooks/usePagination';
- import { formatDateTime } from '../utils';
- const { Title, Text } = Typography;
- export default function Reports() {
- const [reports, setReports] = useState<Report[]>([]);
- const [loading, setLoading] = useState(false);
- const [detailModalVisible, setDetailModalVisible] = useState(false);
- const [selectedReport, setSelectedReport] = useState<Report | null>(null);
- const [deleting, setDeleting] = useState<number | null>(null);
- const [downloading, setDownloading] = useState<number | null>(null);
-
- const pagination = usePagination(10);
- // Fetch reports with specific page/pageSize
- const fetchReportsWithParams = useCallback(async (page: number, pageSize: number) => {
- try {
- setLoading(true);
- const response = await reportService.getReports({
- page,
- pageSize,
- });
- setReports(response.data);
- pagination.setTotal(response.pagination.total);
- } catch (error) {
- message.error('Failed to load reports');
- } finally {
- setLoading(false);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
- // Wrapper for refresh button
- const fetchReports = useCallback(() => {
- fetchReportsWithParams(pagination.page, pagination.pageSize);
- }, [fetchReportsWithParams, pagination.page, pagination.pageSize]);
- // Initial load only
- useEffect(() => {
- fetchReportsWithParams(pagination.page, pagination.pageSize);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
- // Handle table pagination change
- const handleTableChange = async (paginationConfig: TablePaginationConfig) => {
- const newPage = paginationConfig.current ?? pagination.page;
- const newPageSize = paginationConfig.pageSize ?? pagination.pageSize;
-
- pagination.setPage(newPage);
- if (newPageSize !== pagination.pageSize) {
- pagination.setPageSize(newPageSize);
- }
-
- // Directly fetch with new params
- await fetchReportsWithParams(newPage, newPageSize);
- };
- // Format file size
- const formatFileSize = (bytes: number): string => {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- };
- // Handle view report detail
- const handleViewReport = async (report: Report) => {
- try {
- const detail = await reportService.getReportDetail(report.id);
- setSelectedReport(detail);
- setDetailModalVisible(true);
- } catch (error) {
- message.error('Failed to load report details');
- }
- };
- // Handle download report
- const handleDownloadReport = async (report: Report) => {
- try {
- setDownloading(report.id);
- const blob = await reportService.downloadReport(report.id);
-
- // Create download link
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = report.file_name || `report-${report.id}.docx`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
-
- message.success('Report downloaded successfully');
- } catch (error) {
- message.error('Failed to download report');
- } finally {
- setDownloading(null);
- }
- };
- // Handle delete report
- const handleDeleteReport = async (reportId: number) => {
- try {
- setDeleting(reportId);
- await reportService.deleteReport(reportId);
- message.success('Report deleted successfully');
- fetchReports();
- } catch (error: unknown) {
- const err = error as { response?: { data?: { error?: { message?: string } } } };
- message.error(err.response?.data?.error?.message || 'Failed to delete report');
- } finally {
- setDeleting(null);
- }
- };
- // Table columns
- const columns: ColumnsType<Report> = [
- {
- title: 'ID',
- dataIndex: 'id',
- key: 'id',
- width: 80
- },
- {
- title: 'File Name',
- dataIndex: 'file_name',
- key: 'file_name',
- ellipsis: true,
- render: (fileName: string) => (
- <Space>
- <FileWordOutlined style={{ color: '#2b579a' }} />
- <Text ellipsis style={{ maxWidth: 300 }}>{fileName}</Text>
- </Space>
- ),
- },
- {
- title: 'Task ID',
- dataIndex: 'task_id',
- key: 'task_id',
- width: 100,
- render: (taskId: number) => (
- <Tag color="blue">#{taskId}</Tag>
- ),
- },
- {
- title: 'File Size',
- dataIndex: 'file_size',
- key: 'file_size',
- width: 120,
- render: (size: number) => formatFileSize(size),
- },
- {
- title: 'Created At',
- dataIndex: 'created_at',
- key: 'created_at',
- width: 180,
- render: (date: string) => formatDateTime(date),
- },
- {
- title: 'Actions',
- key: 'actions',
- width: 180,
- render: (_: unknown, record: Report) => (
- <Space>
- <Tooltip title="View Details">
- <Button
- size="small"
- icon={<EyeOutlined />}
- onClick={() => handleViewReport(record)}
- />
- </Tooltip>
- <Tooltip title="Download">
- <Button
- size="small"
- type="primary"
- icon={<DownloadOutlined />}
- loading={downloading === record.id}
- onClick={() => handleDownloadReport(record)}
- />
- </Tooltip>
- <Popconfirm
- title="Delete Report"
- description="Are you sure you want to delete this report?"
- onConfirm={() => handleDeleteReport(record.id)}
- okText="Yes"
- cancelText="No"
- >
- <Tooltip title="Delete">
- <Button
- size="small"
- danger
- icon={<DeleteOutlined />}
- loading={deleting === record.id}
- />
- </Tooltip>
- </Popconfirm>
- </Space>
- ),
- },
- ];
- return (
- <div>
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
- <Title level={2} style={{ margin: 0 }}>Reports</Title>
- <Button
- icon={<ReloadOutlined />}
- onClick={fetchReports}
- loading={loading}
- >
- Refresh
- </Button>
- </div>
-
- <Table
- columns={columns}
- dataSource={reports}
- rowKey="id"
- loading={loading}
- pagination={{
- current: pagination.page,
- pageSize: pagination.pageSize,
- total: pagination.total,
- showSizeChanger: true,
- showQuickJumper: false,
- pageSizeOptions: ['10', '20', '50', '100'],
- showTotal: (total: number) => `Total ${total} reports`,
- }}
- onChange={handleTableChange}
- locale={{
- emptyText: (
- <Empty
- image={Empty.PRESENTED_IMAGE_SIMPLE}
- description="No reports found"
- />
- ),
- }}
- />
- {/* Report Detail Modal */}
- <Modal
- title={
- <Space>
- <FileWordOutlined style={{ color: '#2b579a' }} />
- <span>Report Details</span>
- </Space>
- }
- open={detailModalVisible}
- onCancel={() => {
- setDetailModalVisible(false);
- setSelectedReport(null);
- }}
- footer={
- <Space>
- <Button onClick={() => setDetailModalVisible(false)}>
- Close
- </Button>
- {selectedReport && (
- <Button
- type="primary"
- icon={<DownloadOutlined />}
- loading={downloading === selectedReport.id}
- onClick={() => handleDownloadReport(selectedReport)}
- >
- Download
- </Button>
- )}
- </Space>
- }
- width={600}
- >
- {selectedReport && (
- <Card size="small">
- <Descriptions column={1} size="small" bordered>
- <Descriptions.Item label="Report ID">
- {selectedReport.id}
- </Descriptions.Item>
- <Descriptions.Item label="File Name">
- <Space>
- <FileWordOutlined style={{ color: '#2b579a' }} />
- {selectedReport.file_name}
- </Space>
- </Descriptions.Item>
- <Descriptions.Item label="Task ID">
- <Tag color="blue">#{selectedReport.task_id}</Tag>
- </Descriptions.Item>
- <Descriptions.Item label="File Size">
- {formatFileSize(selectedReport.file_size)}
- </Descriptions.Item>
- <Descriptions.Item label="File Path">
- <Text type="secondary" copyable>
- {selectedReport.file_path}
- </Text>
- </Descriptions.Item>
- <Descriptions.Item label="Created At">
- {formatDateTime(selectedReport.created_at)}
- </Descriptions.Item>
- <Descriptions.Item label="Status">
- <Tag color="success" icon={<CheckCircleOutlined />}>
- Available
- </Tag>
- </Descriptions.Item>
- </Descriptions>
- </Card>
- )}
- </Modal>
- </div>
- );
- }
|