chore: update frontend configuration
This commit is contained in:
@@ -13,15 +13,17 @@
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-circle shrink-0 transition-transform"
|
||||
:class="{ 'rotate-90': !isCollapsed }"
|
||||
@click="toggleCollapse"
|
||||
:aria-expanded="!isCollapsed"
|
||||
:title="isCollapsed ? 'Déplier les détails du composant' : 'Replier les détails du composant'"
|
||||
@click="toggleCollapse"
|
||||
>
|
||||
<IconLucideChevronRight class="w-5 h-5 transition-transform" aria-hidden="true" />
|
||||
<span class="sr-only">{{ isCollapsed ? 'Déplier' : 'Replier' }} le composant</span>
|
||||
</button>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-semibold">{{ component.name }}</h3>
|
||||
<h3 class="text-lg font-semibold">
|
||||
{{ component.name }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
<span v-if="component.reference" class="badge badge-outline badge-sm">{{ component.reference }}</span>
|
||||
<span v-if="component.constructeur" class="badge badge-outline badge-sm">{{ component.constructeur?.name }}</span>
|
||||
@@ -55,8 +57,10 @@
|
||||
type="text"
|
||||
class="input input-bordered input-sm"
|
||||
@blur="updateComponent"
|
||||
/>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">{{ component.name }}</div>
|
||||
>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||
{{ component.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Référence</span></label>
|
||||
@@ -66,8 +70,10 @@
|
||||
type="text"
|
||||
class="input input-bordered input-sm"
|
||||
@blur="updateComponent"
|
||||
/>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">{{ component.reference || 'Non définie' }}</div>
|
||||
>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||
{{ component.reference || 'Non définie' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Prix</span></label>
|
||||
@@ -78,8 +84,10 @@
|
||||
step="0.01"
|
||||
class="input input-bordered input-sm"
|
||||
@blur="updateComponent"
|
||||
/>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">{{ component.prix ? `${component.prix}€` : 'Non défini' }}</div>
|
||||
>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||
{{ component.prix ? `${component.prix}€` : 'Non défini' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Constructeur</span></label>
|
||||
@@ -87,7 +95,7 @@
|
||||
v-if="isEditMode"
|
||||
class="w-full"
|
||||
:model-value="component.constructeurId || component.constructeur?.id || null"
|
||||
@update:modelValue="handleConstructeurChange"
|
||||
@update:model-value="handleConstructeurChange"
|
||||
/>
|
||||
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||
<div class="flex flex-col">
|
||||
@@ -117,7 +125,9 @@
|
||||
class="select select-bordered select-sm"
|
||||
@change="assignComponentModel($event.target.value)"
|
||||
>
|
||||
<option value="">Définir manuellement</option>
|
||||
<option value="">
|
||||
Définir manuellement
|
||||
</option>
|
||||
<option
|
||||
v-for="model in componentModelOptionsList"
|
||||
:key="model.id"
|
||||
@@ -141,7 +151,9 @@
|
||||
|
||||
<!-- Custom Fields Display - Editable or Read-only -->
|
||||
<div v-if="component.customFields && component.customFields.length > 0" class="mt-4 pt-4 border-t border-gray-200">
|
||||
<h4 class="font-semibold text-sm text-gray-700 mb-3">Champs personnalisés</h4>
|
||||
<h4 class="font-semibold text-sm text-gray-700 mb-3">
|
||||
Champs personnalisés
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="field in component.customFields" :key="field.id" class="form-control">
|
||||
<label class="label">
|
||||
@@ -156,7 +168,7 @@
|
||||
class="input input-bordered input-sm"
|
||||
:required="field.required"
|
||||
@blur="updateComponentCustomField(field)"
|
||||
/>
|
||||
>
|
||||
<input
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="field.value"
|
||||
@@ -164,7 +176,7 @@
|
||||
class="input input-bordered input-sm"
|
||||
:required="field.required"
|
||||
@blur="updateComponentCustomField(field)"
|
||||
/>
|
||||
>
|
||||
<select
|
||||
v-else-if="field.type === 'select'"
|
||||
v-model="field.value"
|
||||
@@ -172,8 +184,12 @@
|
||||
:required="field.required"
|
||||
@change="updateComponentCustomField(field)"
|
||||
>
|
||||
<option value="">Sélectionner...</option>
|
||||
<option v-for="option in field.options" :key="option" :value="option">{{ option }}</option>
|
||||
<option value="">
|
||||
Sélectionner...
|
||||
</option>
|
||||
<option v-for="option in field.options" :key="option" :value="option">
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-else-if="field.type === 'boolean'" class="flex items-center gap-2">
|
||||
<input
|
||||
@@ -183,7 +199,7 @@
|
||||
true-value="true"
|
||||
false-value="false"
|
||||
@change="updateComponentCustomField(field)"
|
||||
/>
|
||||
>
|
||||
<span class="text-sm">{{ field.value === 'true' ? 'Oui' : 'Non' }}</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -193,10 +209,12 @@
|
||||
class="input input-bordered input-sm"
|
||||
:required="field.required"
|
||||
@blur="updateComponentCustomField(field)"
|
||||
/>
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="input input-bordered input-sm bg-base-200">{{ field.value || 'Non défini' }}</div>
|
||||
<div class="input input-bordered input-sm bg-base-200">
|
||||
{{ field.value || 'Non défini' }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,13 +222,17 @@
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-gray-200 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="font-semibold text-sm text-gray-700">Documents</h4>
|
||||
<h4 class="font-semibold text-sm text-gray-700">
|
||||
Documents
|
||||
</h4>
|
||||
<span v-if="isEditMode && selectedFiles.length" class="badge badge-outline">
|
||||
{{ selectedFiles.length }} fichier{{ selectedFiles.length > 1 ? 's' : '' }} sélectionné{{ selectedFiles.length > 1 ? 's' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="loadingDocuments" class="text-xs text-gray-500">Chargement des documents...</p>
|
||||
<p v-if="loadingDocuments" class="text-xs text-gray-500">
|
||||
Chargement des documents...
|
||||
</p>
|
||||
|
||||
<DocumentUpload
|
||||
v-if="isEditMode"
|
||||
@@ -235,7 +257,9 @@
|
||||
/>
|
||||
</span>
|
||||
<div>
|
||||
<div class="font-medium">{{ document.name }}</div>
|
||||
<div class="font-medium">
|
||||
{{ document.name }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ document.mimeType || 'Inconnu' }} • {{ formatSize(document.size) }}
|
||||
</div>
|
||||
@@ -266,12 +290,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else-if="!loadingDocuments" class="text-xs text-gray-500">Aucun document lié à ce composant.</p>
|
||||
<p v-else-if="!loadingDocuments" class="text-xs text-gray-500">
|
||||
Aucun document lié à ce composant.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Component Pieces -->
|
||||
<div v-if="component.pieces && component.pieces.length > 0" class="space-y-2">
|
||||
<h4 class="font-semibold text-gray-700">Pièces du composant</h4>
|
||||
<h4 class="font-semibold text-gray-700">
|
||||
Pièces du composant
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<PieceItem
|
||||
v-for="piece in component.pieces"
|
||||
@@ -289,7 +317,9 @@
|
||||
|
||||
<!-- Sub Components -->
|
||||
<div v-if="component.subComponents && component.subComponents.length > 0" class="space-y-3">
|
||||
<h4 class="font-semibold text-gray-700">Sous-composants</h4>
|
||||
<h4 class="font-semibold text-gray-700">
|
||||
Sous-composants
|
||||
</h4>
|
||||
<div class="space-y-3 pl-4 border-l-2 border-gray-200">
|
||||
<ComponentItem
|
||||
v-for="subComponent in component.subComponents"
|
||||
@@ -326,32 +356,32 @@ import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
||||
const props = defineProps({
|
||||
component: {
|
||||
type: Object,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
isEditMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: false
|
||||
},
|
||||
collapseAll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: true
|
||||
},
|
||||
toggleToken: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
default: 0
|
||||
},
|
||||
componentModelOptions: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
default: () => []
|
||||
},
|
||||
componentModelOptionsProvider: {
|
||||
type: Function,
|
||||
default: () => [],
|
||||
default: () => []
|
||||
},
|
||||
pieceModelOptionsProvider: {
|
||||
type: Function,
|
||||
default: () => [],
|
||||
},
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -360,7 +390,7 @@ const emit = defineEmits([
|
||||
'custom-field-update',
|
||||
'assign-model',
|
||||
'assign-piece-model',
|
||||
'create-model-from-component',
|
||||
'create-model-from-component'
|
||||
])
|
||||
|
||||
const isCollapsed = ref(true)
|
||||
@@ -369,7 +399,7 @@ const uploadingDocuments = ref(false)
|
||||
const loadingDocuments = ref(false)
|
||||
const documentsLoaded = ref(!!(props.component.documents && props.component.documents.length))
|
||||
const componentDocuments = computed(() => props.component.documents || [])
|
||||
const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
|
||||
const documentIcon = doc => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
|
||||
const previewDocument = ref(null)
|
||||
const previewVisible = ref(false)
|
||||
|
||||
@@ -395,14 +425,14 @@ watch(
|
||||
ensureDocumentsLoaded()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.component.documents,
|
||||
(docs) => {
|
||||
documentsLoaded.value = !!(docs && docs.length)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const toggleCollapse = () => {
|
||||
@@ -444,7 +474,7 @@ const assignComponentModel = (value) => {
|
||||
componentId: props.component.id,
|
||||
composantModelId: value || null,
|
||||
previousModelId,
|
||||
previousModel,
|
||||
previousModel
|
||||
})
|
||||
}
|
||||
|
||||
@@ -453,7 +483,7 @@ const emitAssignPieceModel = (payload) => {
|
||||
}
|
||||
|
||||
const ensureDocumentsLoaded = async () => {
|
||||
if (documentsLoaded.value || !props.component?.id) return
|
||||
if (documentsLoaded.value || !props.component?.id) { return }
|
||||
await refreshDocuments()
|
||||
}
|
||||
|
||||
@@ -471,15 +501,15 @@ const refreshDocuments = async () => {
|
||||
}
|
||||
|
||||
const handleFilesAdded = async (files) => {
|
||||
if (!files.length || !props.component?.id) return
|
||||
if (!files.length || !props.component?.id) { return }
|
||||
uploadingDocuments.value = true
|
||||
try {
|
||||
const result = await uploadDocuments(
|
||||
{
|
||||
files,
|
||||
context: { composantId: props.component.id },
|
||||
context: { composantId: props.component.id }
|
||||
},
|
||||
{ updateStore: false },
|
||||
{ updateStore: false }
|
||||
)
|
||||
|
||||
if (result.success) {
|
||||
@@ -494,15 +524,15 @@ const handleFilesAdded = async (files) => {
|
||||
}
|
||||
|
||||
const removeDocument = async (documentId) => {
|
||||
if (!documentId) return
|
||||
if (!documentId) { return }
|
||||
const result = await deleteDocument(documentId, { updateStore: false })
|
||||
if (result.success) {
|
||||
props.component.documents = (props.component.documents || []).filter((doc) => doc.id !== documentId)
|
||||
props.component.documents = (props.component.documents || []).filter(doc => doc.id !== documentId)
|
||||
}
|
||||
}
|
||||
|
||||
const downloadDocument = (doc) => {
|
||||
if (!doc?.path) return
|
||||
if (!doc?.path) { return }
|
||||
|
||||
if (doc.path.startsWith('data:')) {
|
||||
const link = document.createElement('a')
|
||||
@@ -516,7 +546,7 @@ const downloadDocument = (doc) => {
|
||||
}
|
||||
|
||||
const openPreview = (doc) => {
|
||||
if (!canPreviewDocument(doc)) return
|
||||
if (!canPreviewDocument(doc)) { return }
|
||||
previewDocument.value = doc
|
||||
previewVisible.value = true
|
||||
}
|
||||
@@ -527,8 +557,8 @@ const closePreview = () => {
|
||||
}
|
||||
|
||||
const formatSize = (size) => {
|
||||
if (size === undefined || size === null) return '—'
|
||||
if (size === 0) return '0 B'
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user