feat: add profile management flow

This commit is contained in:
Matthieu
2025-09-17 23:11:13 +02:00
parent 37c66ac3d6
commit 316dcb6339
27 changed files with 2717 additions and 1556 deletions

View File

@@ -6,7 +6,7 @@
</div>
<!-- Machine Details -->
<div v-else-if="machine" class="space-y-8">
<div v-else-if="machine" ref="printAreaRef" class="space-y-8">
<DocumentPreviewModal
:document="previewDocument"
:visible="previewVisible"
@@ -14,22 +14,37 @@
/>
<!-- Header with Edit Button -->
<div class="flex justify-between items-center">
<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>
<button
@click="toggleEditMode"
class="btn btn-primary"
: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>
<div class="flex items-center gap-2 print:hidden" data-print-hide>
<button
@click="toggleEditMode"
class="btn btn-primary"
: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 -->
@@ -40,19 +55,19 @@
</div>
<!-- Header -->
<div class="hero bg-gradient-to-r from-primary to-secondary min-h-[20vh] rounded-lg">
<div class="hero-content text-center text-neutral-content">
<div class="max-w-md">
<h1 class="mb-5 text-4xl font-bold">{{ machine.name }}</h1>
<p class="mb-5">{{ machine.description || machine.typeMachine?.description }}</p>
<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>
</div>
<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>
</div>
</PageHero>
<!-- Machine Info Card -->
<div class="card bg-base-100 shadow-lg">
@@ -367,10 +382,21 @@
</div>
</div>
</main>
<MachinePrintSelectionModal
:open="printModalOpen"
:selection="printSelection"
:components="components"
:pieces="machinePieces"
@close="closePrintModal"
@confirm="handlePrintConfirm"
@select-all="setAllPrintSelection(true)"
@deselect-all="setAllPrintSelection(false)"
/>
</template>
<script setup>
import { ref, reactive, onMounted, computed, watch } from 'vue'
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { useMachines } from '~/composables/useMachines'
import { useComposants } from '~/composables/useComposants'
@@ -386,6 +412,9 @@ import DocumentUpload from '~/components/DocumentUpload.vue'
import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
import { useConstructeurs } from '~/composables/useConstructeurs'
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'
const route = useRoute()
const machineId = route.params.id
@@ -420,6 +449,7 @@ const loading = ref(true)
const machine = ref(null)
const components = ref([])
const pieces = ref([])
const printAreaRef = ref(null)
const { constructeurs, loadConstructeurs } = useConstructeurs()
@@ -442,6 +472,16 @@ const machineDocumentsUploading = ref(false)
const machineDocumentsLoaded = ref(false)
const previewDocument = ref(null)
const previewVisible = ref(false)
const printModalOpen = ref(false)
const printSelection = reactive({
machine: {
info: true,
customFields: true,
documents: true,
},
components: {},
pieces: {},
})
const handleMachineConstructeurChange = async (value) => {
machineConstructeurId.value = value
@@ -567,6 +607,114 @@ const closePreview = () => {
previewDocument.value = null
}
const ensurePrintSelectionEntries = () => {
printSelection.machine.info ??= true
printSelection.machine.customFields ??= true
printSelection.machine.documents ??= true
const ensureComponent = (component) => {
if (component?.id !== undefined && printSelection.components[component.id] === undefined) {
printSelection.components[component.id] = true
}
;(component.pieces || []).forEach((piece) => {
if (piece?.id !== undefined && printSelection.pieces[piece.id] === undefined) {
printSelection.pieces[piece.id] = true
}
})
;(component.subComponents || []).forEach(ensureComponent)
}
components.value.forEach(ensureComponent)
machinePieces.value.forEach((piece) => {
if (piece?.id !== undefined && printSelection.pieces[piece.id] === undefined) {
printSelection.pieces[piece.id] = true
}
})
}
const setAllPrintSelection = (value) => {
ensurePrintSelectionEntries()
printSelection.machine.info = value
printSelection.machine.customFields = value
printSelection.machine.documents = value
Object.keys(printSelection.components).forEach((key) => {
printSelection.components[key] = value
})
Object.keys(printSelection.pieces).forEach((key) => {
printSelection.pieces[key] = value
})
}
const openPrintModal = () => {
ensurePrintSelectionEntries()
printModalOpen.value = true
}
const closePrintModal = () => {
printModalOpen.value = false
}
const handlePrintConfirm = async () => {
closePrintModal()
await nextTick()
printMachine(printSelection)
}
const printMachine = (currentSelection = printSelection) => {
if (typeof window === 'undefined') return
const context = buildMachinePrintContext({
machine: machine.value,
machineName: machineName.value,
machineReference: machineReference.value,
machineEmplacement: machineEmplacement.value,
machinePieces: machinePieces.value,
components: components.value,
selection: currentSelection,
})
const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style'))
.map(node => node.outerHTML)
.join('')
const htmlContent = buildMachinePrintHtml(context, styles)
const iframe = document.createElement('iframe')
iframe.style.position = 'fixed'
iframe.style.right = '0'
iframe.style.bottom = '0'
iframe.style.width = '0'
iframe.style.height = '0'
iframe.style.border = '0'
iframe.setAttribute('title', 'print-frame')
document.body.appendChild(iframe)
const iframeWindow = iframe.contentWindow
const iframeDocument = iframe.contentDocument || iframeWindow?.document
if (!iframeDocument || !iframeWindow) {
iframe.remove()
return
}
iframeDocument.open()
iframeDocument.write(htmlContent)
iframeDocument.close()
const triggerPrint = () => {
iframeWindow.focus()
iframeWindow.print()
setTimeout(() => {
iframe.remove()
}, 1000)
}
if (iframeDocument.readyState === 'complete') {
setTimeout(triggerPrint, 50)
} else {
iframe.onload = () => setTimeout(triggerPrint, 50)
}
}
const formatSize = (size) => {
if (size === undefined || size === null) return '—'
if (size === 0) return '0 B'
@@ -868,6 +1016,14 @@ const toggleEditMode = () => {
}
}
watch(
() => [components.value.length, machinePieces.value.length],
() => {
ensurePrintSelectionEntries()
},
{ immediate: true }
)
// Lifecycle
onMounted(() => {
loadMachineData()