feat: enhance document management UI
This commit is contained in:
@@ -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', {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user