feat: enhance document management UI

This commit is contained in:
Matthieu
2025-09-17 12:41:51 +02:00
parent 0fbf77ab43
commit 3c0c22ad0f
8 changed files with 660 additions and 47 deletions

View File

@@ -70,8 +70,15 @@
<tbody>
<tr v-for="document in filteredDocuments" :key="document.id" class="text-sm">
<td>
<div class="font-semibold">{{ document.name }}</div>
<div class="text-xs text-gray-500">{{ document.filename }}</div>
<div class="flex items-center gap-3">
<span class="text-xl" :class="documentIcon(document).colorClass">
{{ documentIcon(document).icon }}
</span>
<div>
<div class="font-semibold">{{ document.name }}</div>
<div class="text-xs text-gray-500">{{ document.filename }}</div>
</div>
</div>
</td>
<td>{{ document.mimeType || 'Inconnu' }}</td>
<td>{{ formatSize(document.size) }}</td>
@@ -104,6 +111,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useDocuments } from '~/composables/useDocuments'
import { getFileIcon } from '~/utils/fileIcons'
const { documents, loading, loadDocuments } = useDocuments()
@@ -155,6 +163,8 @@ const formatSize = (size) => {
return `${formatted.toFixed(1)} ${units[index]}`
}
const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
const formatDate = (date) => {
if (!date) return '—'
return new Intl.DateTimeFormat('fr-FR', {

View File

@@ -216,6 +216,63 @@
</div>
</div>
<div class="card bg-base-100 shadow-lg mt-6">
<div class="card-body space-y-4">
<div class="flex items-center justify-between">
<div>
<h2 class="card-title">Documents de la machine</h2>
<p class="text-xs text-gray-500">Ajoutez ou consultez les documents liés à cette machine.</p>
</div>
<span v-if="isEditMode && machineDocumentFiles.length" class="badge badge-outline">
{{ machineDocumentFiles.length }} fichier{{ machineDocumentFiles.length > 1 ? 's' : '' }} sélectionné{{ machineDocumentFiles.length > 1 ? 's' : '' }}
</span>
</div>
<DocumentUpload
v-if="isEditMode"
v-model="machineDocumentFiles"
title="Déposer des fichiers pour la machine"
subtitle="Formats acceptés : PDF, images, documents..."
@files-added="handleMachineFilesAdded"
/>
<div v-if="machineDocumentsList.length" class="space-y-2">
<div
v-for="document in machineDocumentsList"
:key="document.id"
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
>
<div class="flex items-center gap-3 text-sm">
<span class="text-xl" :class="documentIcon(document).colorClass">
{{ documentIcon(document).icon }}
</span>
<div>
<div class="font-medium">{{ document.name }}</div>
<div class="text-xs text-gray-500">
{{ document.mimeType || 'Inconnu' }} {{ formatSize(document.size) }}
</div>
</div>
</div>
<div class="flex items-center gap-2">
<button type="button" class="btn btn-ghost btn-xs" @click="downloadDocument(document)">
Télécharger
</button>
<button
v-if="isEditMode"
type="button"
class="btn btn-error btn-xs"
:disabled="machineDocumentsUploading"
@click="removeMachineDocument(document.id)"
>
Supprimer
</button>
</div>
</div>
</div>
<p v-else class="text-xs text-gray-500">Aucun document lié à cette machine.</p>
</div>
</div>
<!-- Components Section -->
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
@@ -301,7 +358,10 @@ import { usePieces } from '~/composables/usePieces'
import { useCustomFields } from '~/composables/useCustomFields'
import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { useDocuments } from '~/composables/useDocuments'
import { getFileIcon } from '~/utils/fileIcons'
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
import DocumentUpload from '~/components/DocumentUpload.vue'
const route = useRoute()
const machineId = route.params.id
@@ -323,6 +383,13 @@ const {
} = usePieces()
const { upsertCustomFieldValue } = useCustomFields()
const {
uploadDocuments,
deleteDocument,
loadDocumentsByMachine,
loadDocumentsByComponent,
loadDocumentsByPiece
} = useDocuments()
// Data
const loading = ref(true)
@@ -339,6 +406,10 @@ const machineConstructeur = ref('')
// Valeurs des champs personnalisés de la machine
const machineCustomFieldValues = reactive({})
const machineDocumentFiles = ref([])
const machineDocumentsUploading = ref(false)
const machineDocumentsLoaded = ref(false)
// Mode d'édition
const isEditMode = ref(false)
const debug = ref(false) // Ajout de debug pour afficher les infos de debug
@@ -379,6 +450,9 @@ const machinePieces = computed(() => {
return filteredPieces
})
const machineDocumentsList = computed(() => machine.value?.documents || [])
const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
const allComponents = computed(() => {
return components.value
})
@@ -391,6 +465,68 @@ const componentPieces = (composantId) => {
return pieces.value.filter(piece => piece.composantId === composantId)
}
const refreshMachineDocuments = async () => {
if (!machine.value?.id) return
const result = await loadDocumentsByMachine(machine.value.id, { updateStore: false })
if (result.success && machine.value) {
machine.value.documents = result.data || []
machineDocumentsLoaded.value = true
}
}
const handleMachineFilesAdded = async (files) => {
if (!files.length || !machine.value?.id) return
machineDocumentsUploading.value = true
try {
const result = await uploadDocuments(
{
files,
context: { machineId: machine.value.id }
},
{ updateStore: false }
)
if (result.success && machine.value) {
const newDocs = result.data || []
machine.value.documents = [...newDocs, ...(machine.value.documents || [])]
machineDocumentFiles.value = []
}
} finally {
machineDocumentsUploading.value = false
}
}
const removeMachineDocument = async (documentId) => {
if (!documentId) return
const result = await deleteDocument(documentId, { updateStore: false })
if (result.success && machine.value) {
machine.value.documents = (machine.value.documents || []).filter(doc => doc.id !== documentId)
}
}
const downloadDocument = (doc) => {
if (!doc?.path) return
if (doc.path.startsWith('data:')) {
const link = document.createElement('a')
link.href = doc.path
link.download = doc.filename || doc.name || 'document'
link.click()
return
}
window.open(doc.path, '_blank')
}
const formatSize = (size) => {
if (size === undefined || size === null) return '—'
if (size === 0) return '0 B'
const units = ['B', 'KB', 'MB', 'GB']
const index = Math.min(units.length - 1, Math.floor(Math.log(size) / Math.log(1024)))
const formatted = size / Math.pow(1024, index)
return `${formatted.toFixed(1)} ${units[index]}`
}
// Transform custom field values to custom fields format
const transformCustomFields = (pieces) => {
return pieces.map(piece => {
@@ -406,7 +542,8 @@ const transformCustomFields = (pieces) => {
return {
...piece,
customFields
customFields,
documents: piece.documents || []
}
})
}
@@ -439,7 +576,8 @@ const transformComponentCustomFields = (componentsData) => {
...component,
customFields, // Use customFields for frontend display
pieces,
subComponents // Use the transformed sousComposants as subComponents
subComponents, // Use the transformed sousComposants as subComponents
documents: component.documents || []
};
console.log('Transformed component:', result.name, 'with subComponents:', result.subComponents?.length || 0)
@@ -466,6 +604,8 @@ const loadMachineData = async () => {
if (machineResult.success) {
machine.value = machineResult.data
machine.value.documents = machine.value.documents || []
machineDocumentsLoaded.value = !!(machine.value.documents?.length)
console.log('Machine trouvée et assignée:', machine.value)
} else {
console.error('Machine non trouvée:', machineId)
@@ -515,7 +655,11 @@ const loadMachineData = async () => {
} else {
console.log('Aucune pièce trouvée dans la réponse de la machine')
}
if (!machineDocumentsLoaded.value) {
await refreshMachineDocuments()
}
console.log('Chargement terminé avec succès')
} catch (error) {
console.error('Erreur lors du chargement des données:', error)
@@ -666,6 +810,9 @@ const editPiece = (piece) => {
const toggleEditMode = () => {
isEditMode.value = !isEditMode.value
debug.value = !debug.value // Inversez la valeur de debug
if (isEditMode.value && !machineDocumentsLoaded.value) {
refreshMachineDocuments()
}
}
// Lifecycle

View File

@@ -316,10 +316,15 @@
:key="document.id"
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
>
<div class="text-sm">
<div class="font-medium">{{ document.name }}</div>
<div class="text-xs text-gray-500">
{{ document.mimeType || 'Inconnu' }} {{ formatSize(document.size) }}
<div class="flex items-center gap-3 text-sm">
<span class="text-xl" :class="documentIcon(document).colorClass">
{{ documentIcon(document).icon }}
</span>
<div>
<div class="font-medium">{{ document.name }}</div>
<div class="text-xs text-gray-500">
{{ document.mimeType || 'Inconnu' }} {{ formatSize(document.size) }}
</div>
</div>
</div>
<div class="flex items-center gap-2">
@@ -359,10 +364,11 @@ import { ref, reactive, onMounted, computed } from 'vue'
import { useSites } from '~/composables/useSites'
import { useToast } from '~/composables/useToast'
import { useDocuments } from '~/composables/useDocuments'
import { getFileIcon } from '~/utils/fileIcons'
import DocumentUpload from '~/components/DocumentUpload.vue'
const { sites, loading, loadSites, createSite, updateSite, deleteSite } = useSites()
const { uploadDocuments, deleteDocument, loadDocumentsBySite, documents: documentStore } = useDocuments()
const { uploadDocuments, deleteDocument, loadDocumentsBySite } = useDocuments()
// Data
const showAddSiteModal = ref(false)
@@ -392,6 +398,7 @@ const selectedFiles = ref([])
const uploadingDocuments = ref(false)
const siteDocuments = computed(() => siteBeingEdited.value?.documents || [])
const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
// Methods
const handleCreateSite = async () => {
@@ -446,10 +453,13 @@ const handleUpdateSite = async () => {
let uploadedDocuments = []
if (selectedFiles.value.length) {
uploadingDocuments.value = true
const uploadResult = await uploadDocuments({
files: selectedFiles.value,
context: { siteId: siteBeingEdited.value.id }
})
const uploadResult = await uploadDocuments(
{
files: selectedFiles.value,
context: { siteId: siteBeingEdited.value.id }
},
{ updateStore: false }
)
uploadingDocuments.value = false
if (uploadResult.success) {
@@ -502,7 +512,7 @@ const closeEditModal = () => {
const handleRemoveSiteDocument = async (documentId) => {
if (!documentId) return
const result = await deleteDocument(documentId)
const result = await deleteDocument(documentId, { updateStore: false })
if (result.success) {
if (siteBeingEdited.value) {
siteBeingEdited.value.documents = (siteBeingEdited.value.documents || []).filter(doc => doc.id !== documentId)
@@ -535,9 +545,9 @@ const downloadDocument = (doc) => {
const refreshSiteDocuments = async (siteId) => {
if (!siteId) return
const result = await loadDocumentsBySite(siteId)
const result = await loadDocumentsBySite(siteId, { updateStore: false })
if (result.success && siteBeingEdited.value && siteBeingEdited.value.id === siteId) {
const cloned = [...documentStore.value]
const cloned = Array.isArray(result.data) ? [...result.data] : []
siteBeingEdited.value.documents = cloned
const index = sites.value.findIndex(site => site.id === siteId)
if (index !== -1) {