Files
leocrm/frontend/src/components/dms/FilePreviewModal.tsx
T
2026-07-23 05:05:08 +02:00

109 lines
3.6 KiB
TypeScript

/**
* File preview modal for DMS.
* Renders PDF files in an iframe via the preview endpoint.
* Shows file metadata and download button for non-PDF files.
*/
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal } from '@/components/ui/Modal';
import { Button } from '@/components/ui/Button';
import { formatDateShort } from '@/utils/date';
import { Badge } from '@/components/ui/Badge';
import { getFilePreviewUrl, type DmsFile } from '@/api/dms';
import { FileText } from 'lucide-react';
export interface FilePreviewModalProps {
open: boolean;
file: DmsFile | null;
onClose: () => void;
}
function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
export function FilePreviewModal({ open, file, onClose }: FilePreviewModalProps) {
const { t } = useTranslation();
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
useEffect(() => {
if (open && file) {
setPreviewUrl(getFilePreviewUrl(file.id));
} else {
setPreviewUrl(null);
}
}, [open, file]);
if (!file) return null;
const isPdf = file.mime_type === 'application/pdf';
const isImage = file.mime_type.startsWith('image/');
return (
<Modal
open={open}
onClose={onClose}
title={file.name}
size="xl"
data-testid="file-preview-modal"
>
<div className="space-y-4">
<div className="flex items-center gap-4 flex-wrap">
<Badge variant="info">{file.mime_type.split('/')[1]?.toUpperCase() || t('dms.icon.other')}</Badge>
<span className="text-sm text-secondary-500">{formatFileSize(file.size_bytes ?? file.size ?? 0)}</span>
{file.created_at && (
<span className="text-sm text-secondary-500">
{t('dms.fileModified')}: {formatDateShort(file.created_at)}
</span>
)}
</div>
{isPdf && previewUrl && (
<div className="border border-secondary-200 rounded-lg overflow-hidden" data-testid="pdf-preview">
<iframe
src={previewUrl} className="w-full h-[60vh]"
title={file.name} aria-label={t('dms.preview')}
/>
</div>
)}
{isImage && previewUrl && (
<div className="flex justify-center border border-secondary-200 rounded-lg p-4" data-testid="image-preview">
<img
src={previewUrl}
alt={file.name}
className="max-h-[60vh] object-contain"
/>
</div>
)}
{!isPdf && !isImage && (
<div className="text-center py-12" data-testid="no-preview-available">
<FileText className="mx-auto h-12 w-12 text-secondary-300" aria-hidden="true" strokeWidth={1.5} />
<p className="mt-2 text-sm text-secondary-500">{t('dms.preview')}</p>
</div>
)}
<div className="flex justify-end gap-3">
{previewUrl && (
<a
href={previewUrl}
download={file.name}
className="inline-flex items-center px-4 py-2 rounded-md bg-primary-600 text-white text-sm font-medium hover:bg-primary-700 min-h-touch"
>
{t('dms.download')}
</a>
)}
<Button variant="secondary" onClick={onClose}>
{t('dms.cancel')}
</Button>
</div>
</div>
</Modal>
);
}