Files
Inventory/app/pages/component-category/[id]/edit.vue
2026-03-31 09:51:22 +02:00

207 lines
6.4 KiB
Vue

<template>
<main class="mx-auto flex w-full max-w-4xl flex-col gap-8 px-4 py-8 sm:px-6 lg:px-8">
<header class="space-y-2">
<div class="flex items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-bold text-base-content">{{ title }}</h1>
<p class="text-base text-base-content/70">
Ajustez le squelette et les métadonnées de cette catégorie de composant. Les modifications seront appliquées lors des prochaines créations de composants.
</p>
</div>
<button type="button" class="btn btn-ghost" @click="$router.back()">
Retour au catalogue
</button>
</div>
</header>
<section class="rounded-xl border border-base-300 bg-base-100 p-6 shadow-sm">
<div v-if="loading" class="flex items-center justify-center py-16">
<span class="loading loading-spinner loading-lg" aria-hidden="true"></span>
<span class="ml-3 text-sm text-base-content/70">Chargement de la catégorie</span>
</div>
<ModelTypeForm
v-else
mode="edit"
initial-category="COMPONENT"
:initial-data="initialData"
:lock-category="true"
:saving="saving"
:readonly="!canEdit"
@submit="handleSubmit"
@cancel="handleCancel"
/>
</section>
<!-- Comments -->
<div class="mt-4">
<CommentSection
entity-type="component_category"
:entity-id="String(route.params.id)"
:entity-name="initialData?.name"
show-resolved
/>
</div>
<SyncConfirmationModal
:preview="syncPreviewData"
:open="showSyncModal"
:loading="syncLoading"
@confirm="handleSyncConfirm"
@cancel="handleSyncCancel"
/>
</main>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useHead, useRoute, useRouter } from '#imports'
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
import { getModelType, updateModelType, syncPreview, syncExecute, type ModelTypePayload, type SyncPreviewResult } from '~/services/modelTypes'
import type { ComponentModelStructure } from '~/shared/types/inventory'
import { useComponentTypes } from '~/composables/useComponentTypes'
import { useToast } from '~/composables/useToast'
const { canEdit } = usePermissions()
const route = useRoute()
const router = useRouter()
const { showError, showSuccess } = useToast()
const { loadComponentTypes } = useComponentTypes()
const loading = ref(true)
const saving = ref(false)
const initialData = ref<Partial<ModelTypePayload> | null>(null)
const showSyncModal = ref(false)
const syncLoading = ref(false)
const syncPreviewData = ref<SyncPreviewResult | null>(null)
const pendingPayload = ref<Partial<ModelTypePayload> | null>(null)
const title = computed(() =>
initialData.value?.name
? `Modifier « ${initialData.value.name} »`
: 'Modifier une catégorie de composant',
)
useHead(() => ({
title: title.value,
}))
const navigateBackToList = async () => {
await router.push('/component-category').catch(() => {
showError("Navigation impossible vers la liste des catégories.")
})
}
const normalizeError = (error: any) => {
const message = error?.data?.message || error?.message || 'Une erreur est survenue.'
return Array.isArray(message) ? message[0] : message
}
const loadCategory = async () => {
loading.value = true
try {
const id = String(route.params.id)
const response = await getModelType(id)
if (response.category !== 'COMPONENT') {
showError("Cette catégorie n'est pas un composant." )
await navigateBackToList()
return
}
initialData.value = {
name: response.name,
code: response.code,
category: response.category,
notes: response.notes ?? response.description ?? '',
structure: (response.structure as ComponentModelStructure | null) ?? undefined,
referenceFormula: response.referenceFormula ?? null,
requiredFieldsForReference: response.requiredFieldsForReference ?? null,
}
} catch (error) {
showError(normalizeError(error))
await navigateBackToList()
} finally {
loading.value = false
}
}
const handleCancel = () => {
navigateBackToList()
}
const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
if (!canEdit.value) return
const id = String(route.params.id)
saving.value = true
try {
const enrichedPayload = {
...payload,
description: payload?.notes ?? null,
}
// Get sync preview BEFORE saving
const preview = await syncPreview(id, enrichedPayload.structure || {})
const hasImpact = preview && (
Object.values(preview.additions || {}).some(v => v > 0)
|| Object.values(preview.deletions || {}).some(v => v > 0)
|| Object.values(preview.modifications || {}).some(v => v > 0)
)
if (hasImpact) {
// Show modal for confirmation
pendingPayload.value = enrichedPayload
syncPreviewData.value = preview
showSyncModal.value = true
} else {
// No impact — save directly + sync
await updateModelType(id, enrichedPayload)
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
await loadComponentTypes({ force: true })
showSuccess('Catégorie de composant mise à jour avec succès.')
}
} catch (error) {
showError(normalizeError(error))
} finally {
saving.value = false
}
}
const handleSyncConfirm = async () => {
if (!pendingPayload.value) return
const id = String(route.params.id)
syncLoading.value = true
try {
const hasDeletions = syncPreviewData.value && Object.values(syncPreviewData.value.deletions || {}).some(v => v > 0)
const hasModifications = syncPreviewData.value && Object.values(syncPreviewData.value.modifications || {}).some(v => v > 0)
await updateModelType(id, pendingPayload.value)
await syncExecute(id, {
confirmDeletions: !!hasDeletions,
confirmTypeChanges: !!hasModifications,
})
await loadComponentTypes({ force: true })
showSuccess('Catégorie de composant mise à jour avec succès.')
} catch (error) {
showError(normalizeError(error))
} finally {
syncLoading.value = false
showSyncModal.value = false
pendingPayload.value = null
syncPreviewData.value = null
}
}
const handleSyncCancel = () => {
showSyncModal.value = false
pendingPayload.value = null
syncPreviewData.value = null
}
onMounted(() => {
loadCategory()
})
</script>