feat(frontend): replace inline icons with lucide components

This commit is contained in:
Matthieu
2025-09-19 08:19:09 +02:00
parent dec4d451bb
commit 32dd8fab58
24 changed files with 519 additions and 901 deletions

View File

@@ -45,10 +45,7 @@
</div>
<div v-else-if="filteredDocuments.length === 0" class="text-center py-16 text-sm text-gray-500">
<svg class="mx-auto mb-4 h-14 w-14 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 000 2.828L11.586 19a2 2 0 002.828 0L21 12.414V7H15.172z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 17L3 20m0 0l3 3m-3-3h12" />
</svg>
<IconLucideFileSearch class="mx-auto mb-4 h-14 w-14 text-gray-400" aria-hidden="true" />
Aucun document ne correspond à votre recherche pour l'instant.
</div>
@@ -69,7 +66,11 @@
<td>
<div class="flex items-center gap-3">
<span class="text-xl" :class="documentIcon(document).colorClass">
{{ documentIcon(document).icon }}
<component
:is="documentIcon(document).component"
class="h-6 w-6"
aria-hidden="true"
/>
</span>
<div>
<div class="font-semibold">{{ document.name }}</div>
@@ -120,6 +121,7 @@ import { useDocuments } from '~/composables/useDocuments'
import { getFileIcon } from '~/utils/fileIcons'
import { canPreviewDocument } from '~/utils/documentPreview'
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
import IconLucideFileSearch from '~icons/lucide/file-search'
const { documents, loading, loadDocuments } = useDocuments()

View File

@@ -6,9 +6,10 @@
<div class="card-body space-y-6">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<IconLucidePlus
class="h-5 w-5"
aria-hidden="true"
/>
</span>
<div>
<h2 class="card-title text-2xl">Nouveau type de machine</h2>
@@ -50,16 +51,12 @@
</div>
<p class="text-sm text-gray-600 line-clamp-3">{{ type.description || 'Aucune description' }}</p>
<div class="text-xs text-gray-500 flex items-center gap-2">
<span class="inline-flex items-center gap-1">
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-3-3v6m-7 5h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<span class="inline-flex items-center gap-1">
<IconLucideClipboardList class="h-4 w-4" aria-hidden="true" />
{{ type.components?.length || 0 }} composant(s)
</span>
<span class="inline-flex items-center gap-1">
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
<IconLucideList class="h-4 w-4" aria-hidden="true" />
{{ type.machinePieces?.length || 0 }} pièce(s) machine
</span>
</div>
@@ -79,6 +76,9 @@
import { ref, computed, onMounted } from 'vue'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucideClipboardList from '~icons/lucide/clipboard-list'
import IconLucideList from '~icons/lucide/list'
const { machineTypes, loadMachineTypes, createMachineType } = useMachineTypesApi()
const { showError } = useToast()

View File

@@ -70,9 +70,7 @@
<!-- Hierarchical Machines View -->
<div v-else-if="filteredSites.length === 0" class="text-center py-12">
<div class="max-w-md mx-auto">
<svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
<IconLucideFactory class="w-16 h-16 mx-auto text-gray-400 mb-4" aria-hidden="true" />
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucune machine trouvée</h3>
<p class="text-gray-500 mb-4">Commencez par ajouter des sites et des machines.</p>
<div class="flex gap-2 justify-center">
@@ -97,32 +95,23 @@
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="avatar placeholder">
<div class="bg-primary text-primary-content rounded-lg w-12">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<div class="bg-primary text-primary-content rounded-lg w-12 grid place-items-center">
<IconLucideMapPin class="w-6 h-6" aria-hidden="true" />
</div>
</div>
<div>
<h3 class="text-xl font-bold">{{ site.name }}</h3>
<div class="text-sm text-gray-600 space-y-1">
<div class="flex items-center gap-2">
<svg class="w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 15c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<IconLucideUser class="w-4 h-4 text-primary" aria-hidden="true" />
<span class="font-medium">{{ site.contactName }}</span>
</div>
<div class="flex items-center gap-2">
<svg class="w-4 h-4 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3l2 3h6l2-3h3a2 2 0 012 2v13a2 2 0 01-2 2H5a2 2 0 01-2-2V5z" />
</svg>
<IconLucidePhone class="w-4 h-4 text-secondary" aria-hidden="true" />
<span>{{ site.contactPhone }}</span>
</div>
<div class="flex items-start gap-2">
<svg class="w-4 h-4 text-accent mt-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c1.38 0 2.5-1.12 2.5-2.5S13.38 6 12 6s-2.5 1.12-2.5 2.5S10.62 11 12 11zm0 0c-2.21 0-4 1.343-4 3v1.5h8V14c0-1.657-1.79-3-4-3z" />
</svg>
<IconLucideMapPinned class="w-4 h-4 text-accent mt-1" aria-hidden="true" />
<span>
{{ site.contactAddress }}<br />
{{ site.contactPostalCode }} {{ site.contactCity }}
@@ -134,9 +123,11 @@
<div class="flex items-center gap-2">
<div class="badge badge-primary badge-lg">{{ site.machines?.length || 0 }} machines</div>
<button @click="toggleSiteCollapse(site.id)" class="btn btn-ghost btn-sm">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
<IconLucideChevronDown
class="w-5 h-5 transition-transform"
:class="collapsedSites.includes(site.id) ? 'rotate-180' : ''"
aria-hidden="true"
/>
</button>
</div>
</div>
@@ -161,23 +152,17 @@
<div class="space-y-1 text-xs text-gray-600">
<div class="flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
<IconLucideSettings2 class="w-3 h-3" aria-hidden="true" />
<span>{{ machine.typeMachine?.name || 'Type inconnu' }}</span>
</div>
<div v-if="machine.reference" class="flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
<IconLucideTag class="w-3 h-3" aria-hidden="true" />
<span>{{ machine.reference }}</span>
</div>
<div v-if="machine.emplacement" class="flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<IconLucideBuilding class="w-3 h-3" aria-hidden="true" />
<span>{{ machine.emplacement }}</span>
</div>
</div>
@@ -201,9 +186,7 @@
<!-- Empty Site State -->
<div v-else-if="!collapsedSites.includes(site.id) && (!site.machines || site.machines.length === 0)" class="text-center py-6">
<div class="text-gray-400 mb-2">
<svg class="w-8 h-8 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
<IconLucideFactory class="w-8 h-8 mx-auto" aria-hidden="true" />
</div>
<p class="text-sm text-gray-500 mb-3">Aucune machine dans ce site</p>
<button @click="addMachineToSite(site)" class="btn btn-sm btn-primary">
@@ -423,6 +406,15 @@ import { useSites } from '~/composables/useSites'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useMachines } from '~/composables/useMachines'
import { useToast } from '~/composables/useToast'
import IconLucideFactory from '~icons/lucide/factory'
import IconLucideMapPin from '~icons/lucide/map-pin'
import IconLucideUser from '~icons/lucide/user'
import IconLucidePhone from '~icons/lucide/phone'
import IconLucideMapPinned from '~icons/lucide/map-pinned'
import IconLucideChevronDown from '~icons/lucide/chevron-down'
import IconLucideSettings2 from '~icons/lucide/settings-2'
import IconLucideTag from '~icons/lucide/tag'
import IconLucideBuilding from '~icons/lucide/building'
const { sites, loading, loadSites, createSite } = useSites()

View File

@@ -19,331 +19,58 @@
<div class="flex items-center gap-2 print:hidden" data-print-hide>
<button
@click="toggleEditMode"
class="btn btn-primary"
class="btn btn-primary btn-sm"
:class="{ 'btn-outline': isEditMode }"
>
<svg v-if="!isEditMode" class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
<svg v-else class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
</svg>
{{ isEditMode ? 'Voir détails' : 'Modifier' }}
</button>
<button
v-if="!isEditMode"
@click="openPrintModal"
type="button"
class="btn btn-outline btn-secondary"
>
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2V9a2 2 0 00-2-2h-2V4a1 1 0 00-1-1H8a1 1 0 00-1 1v3H5a2 2 0 00-2 2v6a2 2 0 002 2h2">
</path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17H7v5h10v-5z"></path>
</svg>
Imprimer
</button>
</div>
</div>
<!-- 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>
<!-- Header -->
<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 de la machine -->
<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>
<!-- Mode édition -->
<template v-if="isEditMode">
<!-- Champ de type TEXT -->
<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)"
/>
<!-- Champ de type NUMBER -->
<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)"
/>
<!-- Champ de type SELECT -->
<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>
<!-- Champ de type BOOLEAN -->
<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>
<!-- Champ de type DATE -->
<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>
<!-- Mode lecture seule -->
<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>
<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' : '' }}
<IconLucideSquarePen
v-if="!isEditMode"
class="w-4 h-4 mr-2"
aria-hidden="true"
/>
<IconLucideCheck
v-else
class="w-4 h-4 mr-2"
aria-hidden="true"
/>
<span class="text-sm">
{{ isEditMode ? 'Quitter le mode édition' : 'Activer le mode édition' }}
</span>
</div>
</button>
<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"
/>
<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>
<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"
: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>
<button
type="button"
class="btn btn-outline btn-sm"
@click="openPrintModal"
>
<IconLucidePrinter class="w-4 h-4 mr-2" aria-hidden="true" />
<span class="text-sm">Imprimer</span>
</button>
</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'"
>
<svg
class="w-5 h-5 transition-transform"
:class="componentsCollapsed ? 'rotate-0' : 'rotate-90'"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
<span class="text-sm">
{{ componentsCollapsed ? 'Tout déplier' : 'Tout replier' }}
</span>
</button>
</div>
<!-- Components Tree -->
<ComponentHierarchy
:components="components"
:is-edit-mode="isEditMode"
:collapse-all="componentsCollapsed"
:toggle-token="collapseToggleToken"
@update="updateComponent"
@edit-piece="updatePieceFromComponent"
/>
</div>
<!-- Components Tree -->
<ComponentHierarchy
:components="components"
:is-edit-mode="isEditMode"
:collapse-all="componentsCollapsed"
:toggle-token="collapseToggleToken"
@update="updateComponent"
@edit-piece="updatePieceFromComponent"
/>
</div>
<!-- Machine Pieces Section -->
@@ -371,9 +98,7 @@
<!-- Error State -->
<div v-else class="text-center py-12">
<div class="max-w-md mx-auto">
<svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.47-.881-6.08-2.33"></path>
</svg>
<IconLucideAlertTriangle class="w-16 h-16 mx-auto text-gray-400 mb-4" aria-hidden="true" />
<h3 class="text-lg font-medium text-gray-900 mb-2">Machine non trouvée</h3>
<p class="text-gray-500 mb-4">La machine avec l'ID "{{ machineId }}" n'existe pas ou a été supprimée.</p>
<NuxtLink to="/machines" class="btn btn-primary">
@@ -415,6 +140,11 @@ import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
import PageHero from '~/components/PageHero.vue'
import MachinePrintSelectionModal from '~/components/MachinePrintSelectionModal.vue'
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 IconLucidePrinter from '~icons/lucide/printer'
const route = useRoute()
const machineId = route.params.id

View File

@@ -8,9 +8,7 @@
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Machines</h2>
<button @click="showAddMachineModal = true" class="btn btn-primary">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
<IconLucidePlus class="w-5 h-5 mr-2" aria-hidden="true" />
Ajouter une machine
</button>
</div>
@@ -63,9 +61,7 @@
<div v-else-if="filteredMachines.length === 0" class="text-center py-12">
<div class="max-w-md mx-auto">
<svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
<IconLucideFactory class="w-16 h-16 mx-auto text-gray-400 mb-4" aria-hidden="true" />
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucune machine trouvée</h3>
<p class="text-gray-500 mb-4">Commencez par ajouter votre première machine.</p>
<button @click="showAddMachineModal = true" class="btn btn-primary">
@@ -91,31 +87,22 @@
<div class="space-y-2 text-sm">
<div class="flex items-center gap-2">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<IconLucideMapPin class="w-4 h-4 text-blue-500" aria-hidden="true" />
<span class="text-gray-600">{{ machine.site?.name || 'Site inconnu' }}</span>
</div>
<div class="flex items-center gap-2">
<svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
<IconLucideSettings2 class="w-4 h-4 text-green-500" aria-hidden="true" />
<span class="text-gray-600">{{ machine.typeMachine?.name || 'Type inconnu' }}</span>
</div>
<div v-if="machine.reference" class="flex items-center gap-2">
<svg class="w-4 h-4 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
<IconLucideTag class="w-4 h-4 text-orange-500" aria-hidden="true" />
<span class="text-gray-600">{{ machine.reference }}</span>
</div>
<div v-if="machine.emplacement" class="flex items-center gap-2">
<svg class="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<IconLucideBuilding class="w-4 h-4 text-purple-500" aria-hidden="true" />
<span class="text-gray-600">{{ machine.emplacement }}</span>
</div>
</div>
@@ -245,6 +232,12 @@ import { useMachines } from '~/composables/useMachines'
import { useSites } from '~/composables/useSites'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucideFactory from '~icons/lucide/factory'
import IconLucideMapPin from '~icons/lucide/map-pin'
import IconLucideSettings2 from '~icons/lucide/settings-2'
import IconLucideTag from '~icons/lucide/tag'
import IconLucideBuilding from '~icons/lucide/building'
const { machines, loading, loadMachines, createMachineFromType, deleteMachine } = useMachines()
const { sites, loadSites } = useSites()

View File

@@ -12,9 +12,7 @@
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Sites</h2>
<button @click="showAddSiteModal = true" class="btn btn-primary">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
<IconLucidePlus class="w-5 h-5 mr-2" aria-hidden="true" />
Ajouter un site
</button>
</div>
@@ -26,10 +24,7 @@
<div v-else-if="sites.length === 0" class="text-center py-12">
<div class="max-w-md mx-auto">
<svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<IconLucideMapPin class="w-16 h-16 mx-auto text-gray-400 mb-4" aria-hidden="true" />
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucun site trouvé</h3>
<p class="text-gray-500 mb-4">Commencez par ajouter votre premier site.</p>
<button @click="showAddSiteModal = true" class="btn btn-primary">
@@ -52,23 +47,17 @@
<div class="space-y-3 text-sm">
<div class="flex items-center gap-2 text-gray-700">
<svg class="w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 15c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<IconLucideUser class="w-4 h-4 text-primary" aria-hidden="true" />
<span class="font-medium">{{ site.contactName }}</span>
</div>
<div class="flex items-center gap-2 text-gray-600">
<svg class="w-4 h-4 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3l2 3h6l2-3h3a2 2 0 012 2v13a2 2 0 01-2 2H5a2 2 0 01-2-2V5z" />
</svg>
<IconLucidePhone class="w-4 h-4 text-secondary" aria-hidden="true" />
<span>{{ site.contactPhone }}</span>
</div>
<div class="flex items-start gap-2 text-gray-600">
<svg class="w-4 h-4 text-accent mt-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c1.38 0 2.5-1.12 2.5-2.5S13.38 6 12 6s-2.5 1.12-2.5 2.5S10.62 11 12 11zm0 0c-2.21 0-4 1.343-4 3v1.5h8V14c0-1.657-1.79-3-4-3z" />
</svg>
<IconLucideMapPin class="w-4 h-4 text-accent mt-1" aria-hidden="true" />
<span>
{{ site.contactAddress }}<br />
{{ site.contactPostalCode }} {{ site.contactCity }}
@@ -76,9 +65,7 @@
</div>
<div class="flex items-center gap-2 text-gray-600">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
<IconLucideFactory class="w-4 h-4 text-blue-500" aria-hidden="true" />
<span>{{ site.machines?.length || 0 }} machine(s)</span>
</div>
</div>
@@ -312,7 +299,11 @@
>
<div class="flex items-center gap-3 text-sm">
<span class="text-xl" :class="documentIcon(document).colorClass">
{{ documentIcon(document).icon }}
<component
:is="documentIcon(document).component"
class="h-6 w-6"
aria-hidden="true"
/>
</span>
<div>
<div class="font-medium">{{ document.name }}</div>
@@ -371,6 +362,11 @@ import { getFileIcon } from '~/utils/fileIcons'
import { canPreviewDocument } from '~/utils/documentPreview'
import DocumentUpload from '~/components/DocumentUpload.vue'
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucideMapPin from '~icons/lucide/map-pin'
import IconLucideUser from '~icons/lucide/user'
import IconLucidePhone from '~icons/lucide/phone'
import IconLucideFactory from '~icons/lucide/factory'
const { sites, loading, loadSites, createSite, updateSite, deleteSite } = useSites()
const { uploadDocuments, deleteDocument, loadDocumentsBySite } = useDocuments()

View File

@@ -17,9 +17,7 @@
</h2>
<div class="flex gap-2">
<NuxtLink :to="`/type/edit/${type.id}`" class="btn btn-secondary">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
<IconLucideSquarePen class="w-4 h-4 mr-2" aria-hidden="true" />
Éditer complètement
</NuxtLink>
<NuxtLink to="/types" class="btn btn-outline">
@@ -37,27 +35,16 @@
class="btn btn-outline btn-sm"
@click="toggleGlobalExpand"
>
<svg
<IconLucideMinus
v-if="globalExpandState.expanded"
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
v-if="globalExpandState.expanded"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 12H6"
></path>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v12m6-6H6"
></path>
</svg>
aria-hidden="true"
/>
<IconLucidePlus
v-else
class="w-4 h-4 mr-2"
aria-hidden="true"
/>
{{ globalExpandState.expanded ? 'Tout plier' : 'Tout déplier' }}
</button>
</div>
@@ -111,6 +98,9 @@ import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast'
import IconLucideSquarePen from '~icons/lucide/square-pen'
import IconLucideMinus from '~icons/lucide/minus'
import IconLucidePlus from '~icons/lucide/plus'
const route = useRoute()
const { getMachineTypeById } = useMachineTypesApi()

View File

@@ -8,9 +8,10 @@
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Types de Machines</h2>
<NuxtLink to="/generator" class="btn btn-primary">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
<IconLucidePlus
class="w-5 h-5 mr-2"
aria-hidden="true"
/>
Créer un type
</NuxtLink>
</div>
@@ -43,21 +44,10 @@
<p class="text-gray-600 mb-4">{{ type.description }}</p>
<div class="space-y-2">
<div class="flex items-center text-sm text-gray-500">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
Maintenance: {{ type.maintenanceFrequency }}
</div>
<div class="flex items-center text-sm text-gray-500">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg>
{{ type.components?.length || 0 }} composants principaux
</div>
<div class="flex items-center text-sm text-gray-500">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
<IconLucidePackage
class="w-4 h-4 mr-2"
aria-hidden="true"
/>
{{ type.machinePieces?.length || 0 }} pièces totales
</div>
</div>
@@ -74,11 +64,9 @@
<!-- Empty State -->
<div v-if="filteredTypes.length === 0" class="text-center py-12">
<div class="avatar placeholder">
<div class="bg-neutral text-neutral-content rounded-full w-16">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path>
</svg>
<div class="avatar placeholder">
<div class="bg-neutral text-neutral-content rounded-full w-16">
<IconLucideLayoutGrid class="w-8 h-8" aria-hidden="true" />
</div>
</div>
<h3 class="text-lg font-semibold text-gray-600 mt-4">Aucun type trouvé</h3>
@@ -92,6 +80,9 @@
import { ref, computed, onMounted } from 'vue'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucidePackage from '~icons/lucide/package'
import IconLucideLayoutGrid from '~icons/lucide/layout-grid'
const { machineTypes, loading, loadMachineTypes, deleteMachineType } = useMachineTypesApi()