fix(machine-ui): restore machine detail layout and align api base url

This commit is contained in:
Matthieu
2025-09-19 15:00:56 +02:00
parent b0c3b2b646
commit e33e91ee26
4 changed files with 303 additions and 43 deletions

View File

@@ -3,7 +3,7 @@ import { useToast } from './useToast'
export function useApi() {
const { showSuccess, showError, showInfo } = useToast()
const { public: publicConfig } = useRuntimeConfig()
const API_BASE_URL = publicConfig.apiBaseUrl || 'http://localhost:3000/api'
const API_BASE_URL = publicConfig.apiBaseUrl || 'http://localhost:3000'
const parsedApiTimeout = Number(publicConfig.apiTimeout ?? 30000)
const API_TIMEOUT = Number.isNaN(parsedApiTimeout) ? 30000 : parsedApiTimeout

View File

@@ -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()

View File

@@ -1,5 +1,5 @@
# Configuration de l'API backend
NUXT_PUBLIC_API_BASE_URL=http://localhost:3000/api
NUXT_PUBLIC_API_BASE_URL=http://localhost:3000
# Configuration du serveur de développement
NUXT_PUBLIC_APP_URL=http://localhost:3001
@@ -21,4 +21,4 @@ NUXT_PUBLIC_LOG_LEVEL=debug
# Configuration des timeouts
NUXT_PUBLIC_API_TIMEOUT=30000
NUXT_PUBLIC_REQUEST_TIMEOUT=10000
NUXT_PUBLIC_REQUEST_TIMEOUT=10000

View File

@@ -19,7 +19,7 @@ export default defineNuxtConfig({
],
runtimeConfig: {
public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api',
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:3000',
appUrl: process.env.NUXT_PUBLIC_APP_URL || 'http://localhost:3001',
appName: process.env.NUXT_PUBLIC_APP_NAME || 'Inventory Management System',
apiTimeout: process.env.NUXT_PUBLIC_API_TIMEOUT || '30000',