Files
Inventory/app/components/DocumentThumbnail.vue
Matthieu e88ed5b8f2 feat(documents): migrate storage to filesystem, add server-side pagination
- Replace Base64 data URIs with file-based storage served via dedicated endpoints
- Add DocumentPreviewModal navigation, DocumentThumbnail fileUrl support
- Refactor documents page with server-side pagination, search, sort and filters
- Update all components to use fileUrl/downloadUrl instead of raw path
- Add pagination composable support (total, page, itemsPerPage, attachmentFilter)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:17:59 +01:00

106 lines
2.6 KiB
Vue

<template>
<div
v-if="document"
class="flex items-center justify-center overflow-hidden rounded-md border border-base-200 bg-base-200/70"
:class="thumbnailClass"
>
<img
v-if="canRenderImage"
:src="previewSrc"
class="h-full w-full object-cover"
:alt="altText"
loading="lazy"
decoding="async"
>
<component
v-else
:is="icon.component"
class="h-6 w-6"
:class="icon.colorClass"
aria-hidden="true"
/>
</div>
<div
v-else
class="flex h-16 w-16 items-center justify-center rounded-md border border-dashed border-base-200 bg-base-200/40 text-xs text-base-content/40"
aria-hidden="true"
>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getFileIcon } from '~/utils/fileIcons';
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview';
type GenericDocument = {
id?: string | number;
name?: string | null;
filename?: string | null;
mimeType?: string | null;
path?: string | null;
fileUrl?: string | null;
downloadUrl?: string | null;
size?: number | null;
};
const props = defineProps<{
document: GenericDocument | null | undefined;
alt?: string;
}>();
const normalizedDocument = computed(() => props.document ?? null);
const canRenderImage = computed(() => {
const doc = normalizedDocument.value;
return !!(doc && isImageDocument(doc) && (doc.fileUrl || doc.path));
});
const canRenderPdf = computed(() => {
// Rendering many PDF iframes in a list is very heavy for the browser.
// We intentionally disable inline PDF previews and fall back to an icon.
return false;
});
const appendPdfViewerParams = (src: string) => {
if (!src || src.startsWith('data:')) {
return src;
}
if (src.includes('#')) {
return `${src}&toolbar=0&navpanes=0`;
}
return `${src}#toolbar=0&navpanes=0`;
};
const previewSrc = computed(() => {
const doc = normalizedDocument.value;
const url = doc?.fileUrl || doc?.path;
if (!doc || !url) {
return '';
}
if (isPdfDocument(doc)) {
return appendPdfViewerParams(url);
}
return url;
});
const thumbnailClass = computed(() => (canRenderImage.value || canRenderPdf.value ? 'h-20 w-16' : 'h-16 w-16'));
const icon = computed(() => {
const doc = normalizedDocument.value;
return getFileIcon({
name: doc?.filename || doc?.name || '',
mime: doc?.mimeType || undefined,
});
});
const altText = computed(() => {
if (props.alt) {
return props.alt;
}
const doc = normalizedDocument.value;
return doc?.name ? `Aperçu de ${doc.name}` : 'Aperçu du document';
});
</script>