140 lines
6.1 KiB
Vue
140 lines
6.1 KiB
Vue
<script setup>
|
|
import { FwbTable, FwbTableHead, FwbTableHeadCell, FwbTableBody, FwbTableRow, FwbTableCell, FwbBadge } from 'flowbite-vue'
|
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
|
import { faFilePdf, faFileWord, faFileExcel, faFileLines, faFileImage, faFile, faCircleInfo } from '@fortawesome/free-solid-svg-icons'
|
|
import { ref } from 'vue'
|
|
|
|
const props = defineProps({
|
|
documents: { type: Array, default: () => [] },
|
|
viewUrlBuilder: { type: Function, default: null },
|
|
})
|
|
const emit = defineEmits(['view'])
|
|
|
|
const formatSize = (bytes) => {
|
|
if (bytes == null) return '-'
|
|
const thresh = 1024
|
|
if (Math.abs(bytes) < thresh) return bytes + ' B'
|
|
const units = ['KB', 'MB', 'GB', 'TB']
|
|
let u = -1
|
|
do { bytes /= thresh; ++u } while (Math.abs(bytes) >= thresh && u < units.length - 1)
|
|
return bytes.toFixed(1) + ' ' + units[u]
|
|
}
|
|
|
|
const extFrom = (doc) => {
|
|
let ext = (doc?.extension || '').toLowerCase()
|
|
if (!ext && doc?.original_name) {
|
|
const parts = String(doc.original_name).toLowerCase().split('.')
|
|
if (parts.length > 1) ext = parts.pop()
|
|
}
|
|
// derive from mime
|
|
if (!ext && doc?.mime_type) {
|
|
const mime = String(doc.mime_type).toLowerCase()
|
|
if (mime.includes('pdf')) ext = 'pdf'
|
|
else if (mime.includes('word') || mime.includes('msword') || mime.includes('doc')) ext = 'docx'
|
|
else if (mime.includes('excel') || mime.includes('sheet')) ext = 'xlsx'
|
|
else if (mime.includes('csv')) ext = 'csv'
|
|
else if (mime.startsWith('image/')) ext = 'img'
|
|
else if (mime.includes('text')) ext = 'txt'
|
|
}
|
|
return ext
|
|
}
|
|
|
|
const fileTypeInfo = (doc) => {
|
|
const ext = extFrom(doc)
|
|
const mime = (doc?.mime_type || '').toLowerCase()
|
|
switch (ext) {
|
|
case 'pdf':
|
|
return { icon: faFilePdf, color: 'text-red-600', label: 'PDF' }
|
|
case 'doc':
|
|
case 'docx':
|
|
return { icon: faFileWord, color: 'text-blue-600', label: (ext || 'DOCX').toUpperCase() }
|
|
case 'xls':
|
|
case 'xlsx':
|
|
return { icon: faFileExcel, color: 'text-green-600', label: (ext || 'XLSX').toUpperCase() }
|
|
case 'csv':
|
|
// treat CSV as spreadsheet-like
|
|
return { icon: faFileExcel, color: 'text-emerald-600', label: 'CSV' }
|
|
case 'txt':
|
|
return { icon: faFileLines, color: 'text-slate-600', label: 'TXT' }
|
|
case 'jpg':
|
|
case 'jpeg':
|
|
case 'png':
|
|
case 'img':
|
|
return { icon: faFileImage, color: 'text-fuchsia-600', label: (ext === 'img' ? 'IMG' : (ext || 'IMG').toUpperCase()) }
|
|
default:
|
|
if (mime.startsWith('image/')) return { icon: faFileImage, color: 'text-fuchsia-600', label: 'IMG' }
|
|
return { icon: faFile, color: 'text-gray-600', label: (ext || 'FILE').toUpperCase() }
|
|
}
|
|
}
|
|
|
|
const hasDesc = (doc) => {
|
|
const d = doc?.description
|
|
return typeof d === 'string' && d.trim().length > 0
|
|
}
|
|
|
|
const expandedDescKey = ref(null)
|
|
const rowKey = (doc, i) => doc?.uuid ?? i
|
|
const toggleDesc = (doc, i) => {
|
|
const key = rowKey(doc, i)
|
|
expandedDescKey.value = expandedDescKey.value === key ? null : key
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative overflow-x-auto rounded-lg border border-gray-200 bg-white shadow-sm">
|
|
<FwbTable hoverable striped class="text-sm">
|
|
<FwbTableHead class="sticky top-0 z-10 bg-gray-50/90 backdrop-blur border-b border-gray-200 shadow-sm">
|
|
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Name</FwbTableHeadCell>
|
|
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Type</FwbTableHeadCell>
|
|
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Size</FwbTableHeadCell>
|
|
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Added</FwbTableHeadCell>
|
|
<FwbTableHeadCell class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3">Drugo</FwbTableHeadCell>
|
|
<FwbTableHeadCell class="w-px" />
|
|
</FwbTableHead>
|
|
<FwbTableBody>
|
|
<template v-for="(doc, i) in documents" :key="doc.uuid || i">
|
|
<FwbTableRow>
|
|
<FwbTableCell>
|
|
<div class="flex items-center gap-2">
|
|
<button type="button" class="text-indigo-600 hover:underline" @click="$emit('view', doc)">{{ doc.original_name || doc.name }}</button>
|
|
<FwbBadge v-if="doc.is_public" type="green">Public</FwbBadge>
|
|
</div>
|
|
</FwbTableCell>
|
|
<FwbTableCell>
|
|
<div class="flex items-center gap-2">
|
|
<FontAwesomeIcon :icon="fileTypeInfo(doc).icon" :class="['h-5 w-5', fileTypeInfo(doc).color]" />
|
|
<span class="text-gray-700">{{ fileTypeInfo(doc).label }}</span>
|
|
</div>
|
|
</FwbTableCell>
|
|
<FwbTableCell>{{ formatSize(doc.size) }}</FwbTableCell>
|
|
<FwbTableCell>{{ new Date(doc.created_at).toLocaleString() }}</FwbTableCell>
|
|
<FwbTableCell class="text-center">
|
|
<button
|
|
class="inline-flex items-center justify-center h-8 w-8 rounded-full hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
:disabled="!hasDesc(doc)"
|
|
:title="hasDesc(doc) ? 'Pokaži opis' : 'Ni opisa'"
|
|
type="button"
|
|
@click="toggleDesc(doc, i)"
|
|
>
|
|
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-700" />
|
|
</button>
|
|
</FwbTableCell>
|
|
<FwbTableCell class="text-right whitespace-nowrap">
|
|
<!-- future actions: download/delete -->
|
|
</FwbTableCell>
|
|
</FwbTableRow>
|
|
<!-- Expanded description row directly below the item -->
|
|
<FwbTableRow :key="'desc-' + (doc.uuid || i)" v-if="expandedDescKey === rowKey(doc, i)">
|
|
<FwbTableCell :colspan="6" class="bg-gray-50">
|
|
<div class="px-4 py-3 text-sm text-gray-700 whitespace-pre-wrap border-l-2 border-indigo-400">
|
|
{{ doc.description }}
|
|
</div>
|
|
</FwbTableCell>
|
|
</FwbTableRow>
|
|
</template>
|
|
</FwbTableBody>
|
|
</FwbTable>
|
|
<div v-if="!documents || documents.length === 0" class="p-6 text-center text-sm text-gray-500">No documents.</div>
|
|
</div>
|
|
</template>
|