|
|
|
|
@@ -13,64 +13,324 @@
|
|
|
|
|
@close="closePreview"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- Header with Edit Button -->
|
|
|
|
|
<!-- Header with actions -->
|
|
|
|
|
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
|
|
|
|
<h1 class="text-3xl font-bold">Détails de la machine</h1>
|
|
|
|
|
<div class="flex items-center gap-2 print:hidden" data-print-hide>
|
|
|
|
|
<button
|
|
|
|
|
@click="toggleEditMode"
|
|
|
|
|
class="btn btn-primary btn-sm"
|
|
|
|
|
class="btn btn-primary"
|
|
|
|
|
:class="{ 'btn-outline': isEditMode }"
|
|
|
|
|
>
|
|
|
|
|
<IconLucideSquarePen
|
|
|
|
|
v-if="!isEditMode"
|
|
|
|
|
class="w-4 h-4 mr-2"
|
|
|
|
|
class="w-5 h-5 mr-2"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<IconLucideCheck
|
|
|
|
|
<IconLucideEye
|
|
|
|
|
v-else
|
|
|
|
|
class="w-4 h-4 mr-2"
|
|
|
|
|
class="w-5 h-5 mr-2"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<span class="text-sm">
|
|
|
|
|
{{ isEditMode ? 'Quitter le mode édition' : 'Activer le mode édition' }}
|
|
|
|
|
</span>
|
|
|
|
|
{{ isEditMode ? 'Voir détails' : 'Modifier' }}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="btn btn-ghost btn-sm"
|
|
|
|
|
@click="toggleAllComponents"
|
|
|
|
|
>
|
|
|
|
|
<IconLucideChevronDown
|
|
|
|
|
class="w-4 h-4 mr-2 transform transition-transform"
|
|
|
|
|
:class="componentsCollapsed ? '-rotate-90' : 'rotate-0'"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<span class="text-sm">
|
|
|
|
|
{{ componentsCollapsed ? 'Tout déplier' : 'Tout replier' }}
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="btn btn-outline btn-sm"
|
|
|
|
|
v-if="!isEditMode"
|
|
|
|
|
@click="openPrintModal"
|
|
|
|
|
type="button"
|
|
|
|
|
class="btn btn-outline btn-secondary"
|
|
|
|
|
>
|
|
|
|
|
<IconLucidePrinter class="w-4 h-4 mr-2" aria-hidden="true" />
|
|
|
|
|
<span class="text-sm">Imprimer</span>
|
|
|
|
|
<IconLucidePrinter class="w-5 h-5 mr-2" aria-hidden="true" />
|
|
|
|
|
Imprimer
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Components Tree -->
|
|
|
|
|
<ComponentHierarchy
|
|
|
|
|
:components="components"
|
|
|
|
|
:is-edit-mode="isEditMode"
|
|
|
|
|
:collapse-all="componentsCollapsed"
|
|
|
|
|
:toggle-token="collapseToggleToken"
|
|
|
|
|
@update="updateComponent"
|
|
|
|
|
@edit-piece="updatePieceFromComponent"
|
|
|
|
|
/>
|
|
|
|
|
<!-- Debug info -->
|
|
|
|
|
<div v-if="debug" class="bg-yellow-100 p-4 rounded-lg">
|
|
|
|
|
<p>Debug: Machine trouvée - {{ machine.name }}</p>
|
|
|
|
|
<p>Components count: {{ components.length }}</p>
|
|
|
|
|
<p>Pieces count: {{ pieces.length }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Hero -->
|
|
|
|
|
<PageHero
|
|
|
|
|
:title="machine.name"
|
|
|
|
|
:subtitle="machine.description || machine.typeMachine?.description"
|
|
|
|
|
min-height="min-h-[20vh]"
|
|
|
|
|
max-width="max-w-md"
|
|
|
|
|
rounded
|
|
|
|
|
>
|
|
|
|
|
<div class="flex justify-center gap-4">
|
|
|
|
|
<div class="badge badge-outline">{{ machine.typeMachine?.category || 'N/A' }}</div>
|
|
|
|
|
<div class="badge badge-outline">{{ machine.site?.name }}</div>
|
|
|
|
|
<div v-if="machine.reference" class="badge badge-outline">{{ machine.reference }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</PageHero>
|
|
|
|
|
|
|
|
|
|
<!-- Machine Info Card -->
|
|
|
|
|
<div class="card bg-base-100 shadow-lg">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<h2 class="card-title">Informations de la machine</h2>
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
<div class="form-control">
|
|
|
|
|
<label class="label">
|
|
|
|
|
<span class="label-text">Nom</span>
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
v-if="isEditMode"
|
|
|
|
|
:id="getMachineFieldId('name')"
|
|
|
|
|
v-model="machineName"
|
|
|
|
|
type="text"
|
|
|
|
|
class="input input-bordered"
|
|
|
|
|
@blur="updateMachineInfo"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="input input-bordered bg-base-200">
|
|
|
|
|
{{ machineName }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-control">
|
|
|
|
|
<label class="label">
|
|
|
|
|
<span class="label-text">Référence</span>
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
v-if="isEditMode"
|
|
|
|
|
:id="getMachineFieldId('reference')"
|
|
|
|
|
v-model="machineReference"
|
|
|
|
|
type="text"
|
|
|
|
|
class="input input-bordered"
|
|
|
|
|
@blur="updateMachineInfo"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="input input-bordered bg-base-200">
|
|
|
|
|
{{ machineReference || 'Non définie' }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-control">
|
|
|
|
|
<label class="label">
|
|
|
|
|
<span class="label-text">Emplacement</span>
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
v-if="isEditMode"
|
|
|
|
|
:id="getMachineFieldId('emplacement')"
|
|
|
|
|
v-model="machineEmplacement"
|
|
|
|
|
type="text"
|
|
|
|
|
class="input input-bordered"
|
|
|
|
|
@blur="updateMachineInfo"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="input input-bordered bg-base-200">
|
|
|
|
|
{{ machineEmplacement || 'Non défini' }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-control">
|
|
|
|
|
<label class="label">
|
|
|
|
|
<span class="label-text">Constructeur</span>
|
|
|
|
|
</label>
|
|
|
|
|
<ConstructeurSelect
|
|
|
|
|
v-if="isEditMode"
|
|
|
|
|
class="w-full"
|
|
|
|
|
:key="machine.value?.id"
|
|
|
|
|
:model-value="machineConstructeurId"
|
|
|
|
|
placeholder="Rechercher un constructeur..."
|
|
|
|
|
@update:modelValue="handleMachineConstructeurChange"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="input input-bordered bg-base-200">
|
|
|
|
|
<div class="flex flex-col">
|
|
|
|
|
<span class="font-medium">{{ machineConstructeurDisplay?.name || 'Non défini' }}</span>
|
|
|
|
|
<span class="text-xs text-gray-500">
|
|
|
|
|
{{ [machineConstructeurDisplay?.email, machineConstructeurDisplay?.phone].filter(Boolean).join(' • ') || '' }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Champs personnalisés -->
|
|
|
|
|
<div v-if="machine && machine.customFieldValues && machine.customFieldValues.length > 0" class="mt-6 pt-4 border-t border-gray-200">
|
|
|
|
|
<h4 class="font-semibold text-gray-700 mb-3">Champs personnalisés de la machine</h4>
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
<div
|
|
|
|
|
v-for="fieldValue in machine.customFieldValues"
|
|
|
|
|
:key="fieldValue.id"
|
|
|
|
|
class="form-control"
|
|
|
|
|
>
|
|
|
|
|
<label class="label">
|
|
|
|
|
<span class="label-text text-sm">{{ fieldValue.customField.name }}</span>
|
|
|
|
|
<span v-if="fieldValue.customField.required" class="label-text-alt text-error">*</span>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<template v-if="isEditMode">
|
|
|
|
|
<input
|
|
|
|
|
v-if="fieldValue.customField.type === 'text'"
|
|
|
|
|
:value="fieldValue.value"
|
|
|
|
|
@input="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
|
|
|
|
type="text"
|
|
|
|
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
|
|
|
|
class="input input-bordered input-sm"
|
|
|
|
|
:required="fieldValue.customField.required"
|
|
|
|
|
@blur="updateMachineCustomField(fieldValue.id)"
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
v-else-if="fieldValue.customField.type === 'number'"
|
|
|
|
|
:value="fieldValue.value"
|
|
|
|
|
@input="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
|
|
|
|
type="number"
|
|
|
|
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
|
|
|
|
class="input input-bordered input-sm"
|
|
|
|
|
:required="fieldValue.customField.required"
|
|
|
|
|
@blur="updateMachineCustomField(fieldValue.id)"
|
|
|
|
|
/>
|
|
|
|
|
<select
|
|
|
|
|
v-else-if="fieldValue.customField.type === 'select'"
|
|
|
|
|
:value="fieldValue.value"
|
|
|
|
|
@change="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
|
|
|
|
class="select select-bordered select-sm"
|
|
|
|
|
:required="fieldValue.customField.required"
|
|
|
|
|
@blur="updateMachineCustomField(fieldValue.id)"
|
|
|
|
|
>
|
|
|
|
|
<option value="">{{ fieldValue.customField.defaultValue || 'Sélectionner...' }}</option>
|
|
|
|
|
<option
|
|
|
|
|
v-for="option in fieldValue.customField.options"
|
|
|
|
|
:key="option"
|
|
|
|
|
:value="option"
|
|
|
|
|
>
|
|
|
|
|
{{ option }}
|
|
|
|
|
</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div v-else-if="fieldValue.customField.type === 'boolean'" class="flex items-center gap-2">
|
|
|
|
|
<input
|
|
|
|
|
:value="fieldValue.value"
|
|
|
|
|
@change="setMachineCustomFieldValue(fieldValue.id, $event.target.checked ? 'true' : 'false')"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
class="checkbox checkbox-sm"
|
|
|
|
|
:checked="fieldValue.value === 'true'"
|
|
|
|
|
@blur="updateMachineCustomField(fieldValue.id)"
|
|
|
|
|
/>
|
|
|
|
|
<span class="text-sm">{{ fieldValue.value === 'true' ? 'Oui' : 'Non' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
v-else-if="fieldValue.customField.type === 'date'"
|
|
|
|
|
:value="fieldValue.value"
|
|
|
|
|
@input="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
|
|
|
|
type="date"
|
|
|
|
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
|
|
|
|
class="input input-bordered input-sm"
|
|
|
|
|
:required="fieldValue.customField.required"
|
|
|
|
|
@blur="updateMachineCustomField(fieldValue.id)"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<div class="input input-bordered input-sm bg-base-200">
|
|
|
|
|
{{ fieldValue.value || fieldValue.customField.defaultValue || 'Non défini' }}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Documents -->
|
|
|
|
|
<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">
|
|
|
|
|
<component
|
|
|
|
|
:is="documentIcon(document).component"
|
|
|
|
|
class="h-6 w-6"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
</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"
|
|
|
|
|
:disabled="!canPreviewDocument(document)"
|
|
|
|
|
:title="canPreviewDocument(document) ? 'Consulter le document' : 'Aucun aperçu disponible pour ce type'"
|
|
|
|
|
@click="openPreview(document)"
|
|
|
|
|
>
|
|
|
|
|
Consulter
|
|
|
|
|
</button>
|
|
|
|
|
<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">
|
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
|
|
|
<h2 class="card-title">Composants</h2>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="btn btn-ghost btn-sm gap-2"
|
|
|
|
|
@click="toggleAllComponents"
|
|
|
|
|
:title="componentsCollapsed ? 'Déplier tous les composants' : 'Replier tous les composants'"
|
|
|
|
|
>
|
|
|
|
|
<IconLucideChevronRight
|
|
|
|
|
class="w-5 h-5 transition-transform"
|
|
|
|
|
:class="componentsCollapsed ? 'rotate-0' : 'rotate-90'"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<span class="text-sm">
|
|
|
|
|
{{ componentsCollapsed ? 'Tout déplier' : 'Tout replier' }}
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ComponentHierarchy
|
|
|
|
|
:components="components"
|
|
|
|
|
:is-edit-mode="isEditMode"
|
|
|
|
|
:collapse-all="componentsCollapsed"
|
|
|
|
|
:toggle-token="collapseToggleToken"
|
|
|
|
|
@update="updateComponent"
|
|
|
|
|
@edit-piece="updatePieceFromComponent"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Machine Pieces Section -->
|
|
|
|
|
@@ -142,8 +402,8 @@ import MachinePrintSelectionModal from '~/components/MachinePrintSelectionModal.
|
|
|
|
|
import { buildMachinePrintContext, buildMachinePrintHtml } from '~/utils/printTemplates/machineReport'
|
|
|
|
|
import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
|
|
|
|
|
import IconLucideSquarePen from '~icons/lucide/square-pen'
|
|
|
|
|
import IconLucideCheck from '~icons/lucide/check'
|
|
|
|
|
import IconLucideChevronDown from '~icons/lucide/chevron-down'
|
|
|
|
|
import IconLucideEye from '~icons/lucide/eye'
|
|
|
|
|
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
|
|
|
|
import IconLucidePrinter from '~icons/lucide/printer'
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|