'use client'; import * as React from 'react'; import { Download, FileText, Loader2, Trash2 } from 'lucide-react'; import { useAuth } from '@/components/id/auth-provider'; import { attachmentDisplayName, attachmentFileExtension, inferAttachmentContentType, isImageAttachment, isPdfAttachment, type DocumentAttachmentMeta } from '@/lib/document-attachments'; import { fetchDocumentPhotoUrl } from '@/lib/api'; import { resolveBrowserMediaUrl } from '@/lib/resilient-media-fetch'; import { cn } from '@/lib/utils'; import { DocumentAttachmentViewer, DocumentPhotoThumbnail } from './document-photo-viewer'; export type ResolvedDocumentAttachment = { storageKey: string; url: string; contentType: string; fileName: string; }; interface DocumentPhotoGalleryProps { userId: string; token: string | null; storageKeys: string[]; attachmentMeta?: Record; readOnly?: boolean; onRemove?: (storageKey: string) => void; layout?: 'grid' | 'strip'; className?: string; } export function DocumentPhotoGallery({ userId, token, storageKeys, attachmentMeta = {}, readOnly, onRemove, layout = 'grid', className }: DocumentPhotoGalleryProps) { const { isPinLocked } = useAuth(); const [attachments, setAttachments] = React.useState([]); const [loadingKeys, setLoadingKeys] = React.useState>(new Set()); const [viewerOpen, setViewerOpen] = React.useState(false); const [viewerIndex, setViewerIndex] = React.useState(0); React.useEffect(() => { if (!token || storageKeys.length === 0 || isPinLocked) { setAttachments([]); return; } let cancelled = false; setLoadingKeys(new Set(storageKeys)); void Promise.all( storageKeys.map(async (storageKey) => { const meta = attachmentMeta[storageKey]; const contentType = inferAttachmentContentType(storageKey, meta); const fileName = attachmentDisplayName(storageKey, meta); try { const response = await fetchDocumentPhotoUrl(userId, storageKey, token, fileName); return { storageKey, accessUrl: resolveBrowserMediaUrl(response.accessUrl), contentType, fileName }; } catch { return { storageKey, accessUrl: '', contentType, fileName }; } }) ).then((results) => { if (cancelled) return; setAttachments( results .filter((item) => Boolean(item.accessUrl)) .map((item) => ({ storageKey: item.storageKey, url: item.accessUrl, contentType: item.contentType, fileName: item.fileName })) ); setLoadingKeys(new Set()); }); return () => { cancelled = true; }; }, [attachmentMeta, isPinLocked, storageKeys.join('|'), token, userId]); function openViewer(storageKey: string) { const resolvedIndex = attachments.findIndex((item) => item.storageKey === storageKey); if (resolvedIndex >= 0) { setViewerIndex(resolvedIndex); setViewerOpen(true); } } if (storageKeys.length === 0) return null; const isStrip = layout === 'strip'; return ( <>
event.stopPropagation()} > {storageKeys.map((storageKey) => { const meta = attachmentMeta[storageKey]; const contentType = inferAttachmentContentType(storageKey, meta); const fileName = attachmentDisplayName(storageKey, meta); const resolved = attachments.find((item) => item.storageKey === storageKey); const loading = loadingKeys.has(storageKey); if (isImageAttachment(contentType)) { return (
openViewer(storageKey)} /> {!readOnly && onRemove ? ( ) : null}
); } return (
{resolved ? ( event.stopPropagation()} > ) : null} {!readOnly && onRemove ? ( ) : null}
); })}
); } function RemoveAttachmentButton({ storageKey, onRemove, className }: { storageKey: string; onRemove: (storageKey: string) => void; className?: string; }) { return ( ); } interface DocumentPhotosInlineProps { userId: string; token: string | null; storageKeys: string[]; attachmentMeta?: Record; className?: string; } /** Горизонтальная полоска файлов документа для списков. */ export function DocumentPhotosInline({ userId, token, storageKeys, attachmentMeta, className }: DocumentPhotosInlineProps) { if (!storageKeys.length) return null; return ( ); }