ComponentItem, PieceItem, and machine/[id] used 'as string' type assertions but were missing lang="ts" on their script tags. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
361 lines
14 KiB
Vue
361 lines
14 KiB
Vue
<template>
|
|
<div>
|
|
<main class="container mx-auto px-6 py-8">
|
|
<!-- Loading State -->
|
|
<div v-if="d.loading.value" class="flex justify-center items-center py-16">
|
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
</div>
|
|
|
|
<!-- Machine Details -->
|
|
<div v-else-if="d.machine.value" ref="d.printAreaRef" class="space-y-8">
|
|
<DocumentPreviewModal
|
|
:document="d.previewDocument.value"
|
|
:visible="d.previewVisible.value"
|
|
:documents="d.machineDocumentsList.value"
|
|
@close="d.closePreview"
|
|
/>
|
|
|
|
<!-- Header with actions -->
|
|
<MachineDetailHeader
|
|
:title="d.machine.value.name"
|
|
:description="d.machine.value.description"
|
|
:site-name="d.machine.value.site?.name"
|
|
:site-color="d.machine.value.site?.color"
|
|
:reference="d.machine.value.reference"
|
|
:is-edit-mode="d.isEditMode.value"
|
|
@toggle-edit="d.toggleEditMode"
|
|
@open-print="d.openPrintModal"
|
|
/>
|
|
|
|
<!-- Tabbed content -->
|
|
<EntityTabs v-model="activeTab" :tabs="machineTabs" aria-label="Sections machine">
|
|
<template #tab-general>
|
|
<div class="space-y-8">
|
|
<MachineInfoCard
|
|
ref="machineInfoCardRef"
|
|
:is-edit-mode="d.isEditMode.value"
|
|
:machine-name="d.machineName.value"
|
|
:machine-reference="d.machineReference.value"
|
|
:machine-site-id="d.machineSiteId.value"
|
|
:machine-site-name="d.machine.value?.site?.name ?? ''"
|
|
:sites="d.sites.value"
|
|
:machine-constructeur-ids="d.machineConstructeurIds.value"
|
|
:machine-constructeurs-display="d.machineConstructeursDisplay.value"
|
|
:has-machine-constructeur="d.hasMachineConstructeur.value"
|
|
:constructeur-links="d.constructeurLinks.value"
|
|
:visible-custom-fields="d.visibleMachineCustomFields.value"
|
|
:get-machine-field-id="d.getMachineFieldId"
|
|
:machine-id="machineId"
|
|
:machine-custom-field-defs="d.machine.value?.customFields ?? []"
|
|
@update:machine-name="d.machineName.value = $event"
|
|
@update:machine-reference="d.machineReference.value = $event"
|
|
@update:machine-site-id="d.machineSiteId.value = $event"
|
|
@update:constructeur-ids="d.handleMachineConstructeurChange"
|
|
@update:constructeur-links="d.constructeurLinks.value = $event"
|
|
@remove-constructeur-link="handleRemoveConstructeurLink"
|
|
@set-custom-field-value="d.setMachineCustomFieldValue"
|
|
@custom-fields-saved="() => { d.loadMachineData(); refreshVersions() }"
|
|
/>
|
|
<MachineProductsCard
|
|
v-if="d.isEditMode.value || d.machineDirectProducts.value.length > 0"
|
|
:products="d.machineDirectProducts.value"
|
|
:is-edit-mode="d.isEditMode.value"
|
|
@add-product="openAddModal('product')"
|
|
@remove-product="confirmRemoveProduct"
|
|
@fill-entity="(linkId, typeId) => handleFillEntity(linkId, 'product', typeId)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<template #tab-structure>
|
|
<div class="space-y-8">
|
|
<MachineComponentsCard
|
|
v-if="d.isEditMode.value || d.components.value.length > 0"
|
|
:components="d.components.value"
|
|
:is-edit-mode="d.isEditMode.value"
|
|
:collapsed="d.componentsCollapsed.value"
|
|
:collapse-toggle-token="d.collapseToggleToken.value"
|
|
@toggle-collapse="d.toggleAllComponents"
|
|
@update-component="d.updateComponent"
|
|
@edit-piece="d.updatePieceFromComponent"
|
|
@custom-field-update="d.handleCustomFieldUpdate"
|
|
@add-component="openAddModal('component')"
|
|
@remove-component="confirmRemoveComponent"
|
|
@fill-entity="(linkId, typeId) => handleFillEntity(linkId, 'component', typeId)"
|
|
/>
|
|
<MachinePiecesCard
|
|
v-if="d.isEditMode.value || d.machinePieces.value.length > 0"
|
|
:pieces="d.machinePieces.value"
|
|
:is-edit-mode="d.isEditMode.value"
|
|
:collapsed="d.piecesCollapsed.value"
|
|
:collapse-toggle-token="d.pieceCollapseToggleToken.value"
|
|
@update-piece="d.updatePieceInfo"
|
|
@edit-piece="d.editPiece"
|
|
@custom-field-update="d.handleCustomFieldUpdate"
|
|
@add-piece="openAddModal('piece')"
|
|
@remove-piece="confirmRemovePiece"
|
|
@fill-entity="(linkId, typeId) => handleFillEntity(linkId, 'piece', typeId)"
|
|
@toggle-collapse="d.toggleAllPieces"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<template #tab-documents>
|
|
<MachineDocumentsCard
|
|
v-if="d.isEditMode.value || d.machineDocumentsList.value.length > 0"
|
|
:documents="d.machineDocumentsList.value"
|
|
:is-edit-mode="d.isEditMode.value"
|
|
:uploading="d.machineDocumentsUploading.value"
|
|
:files="d.machineDocumentFiles.value"
|
|
@update:files="d.machineDocumentFiles.value = $event"
|
|
@files-added="d.handleMachineFilesAdded"
|
|
@preview="d.openPreview"
|
|
@download="d.downloadDocument"
|
|
@remove="confirmRemoveDocument"
|
|
/>
|
|
</template>
|
|
|
|
<template #tab-history>
|
|
<div class="space-y-8">
|
|
<EntityHistorySection
|
|
:entries="history"
|
|
:loading="historyLoading"
|
|
:error="historyError"
|
|
:field-labels="historyFieldLabels"
|
|
/>
|
|
<EntityVersionList
|
|
ref="versionListRef"
|
|
entity-type="machine"
|
|
:entity-id="String(machineId)"
|
|
:field-labels="historyFieldLabels"
|
|
:refresh-key="versionRefreshKey"
|
|
@restored="d.loadMachineData()"
|
|
/>
|
|
<CommentSection
|
|
entity-type="machine"
|
|
:entity-id="String(machineId)"
|
|
:entity-name="d.machine.value?.name"
|
|
show-resolved
|
|
/>
|
|
</div>
|
|
</template>
|
|
</EntityTabs>
|
|
|
|
<!-- Add Entity Modal -->
|
|
<AddEntityToMachineModal
|
|
:open="addModalOpen"
|
|
:entity-kind="addModalKind"
|
|
:prefill-type-id="fillTypeId"
|
|
@close="addModalOpen = false; fillLinkId = ''; fillTypeId = ''"
|
|
@confirm="handleAddEntity"
|
|
/>
|
|
|
|
<!-- Save / Cancel buttons -->
|
|
<div v-if="d.isEditMode.value" class="flex flex-col gap-3 md:flex-row md:justify-end">
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost"
|
|
:class="{ 'btn-disabled': d.saving.value }"
|
|
@click="d.cancelEdition()"
|
|
>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary"
|
|
:disabled="!d.canSubmit.value"
|
|
@click="submitMachineEdition"
|
|
>
|
|
<span v-if="d.saving.value" class="loading loading-spinner loading-sm mr-2" />
|
|
Enregistrer les modifications
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div v-else class="text-center py-12">
|
|
<div class="max-w-md mx-auto">
|
|
<div class="w-16 h-16 rounded-2xl bg-base-200 grid place-items-center mx-auto mb-5">
|
|
<IconLucideAlertTriangle class="w-8 h-8 text-base-content/30" aria-hidden="true" />
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-base-content mb-1">Machine non trouvée</h3>
|
|
<p class="text-sm text-base-content/50 mb-6">La machine avec l'ID "{{ machineId }}" n'existe pas ou a été supprimée.</p>
|
|
<button type="button" class="btn btn-primary" @click="$router.back()">
|
|
Retour aux machines
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<MachinePrintSelectionModal
|
|
:open="d.printModalOpen.value"
|
|
:selection="d.printSelection"
|
|
:components="d.components.value"
|
|
:pieces="d.machinePieces.value"
|
|
@close="d.closePrintModal"
|
|
@confirm="d.handlePrintConfirm"
|
|
@select-all="d.setAllPrintSelection(true)"
|
|
@deselect-all="d.setAllPrintSelection(false)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, watch, onMounted } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { useMachineDetailData } from '~/composables/useMachineDetailData'
|
|
import { useEntityHistory } from '~/composables/useEntityHistory'
|
|
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
|
import MachinePrintSelectionModal from '~/components/MachinePrintSelectionModal.vue'
|
|
import MachineDetailHeader from '~/components/machine/MachineDetailHeader.vue'
|
|
import MachineInfoCard from '~/components/machine/MachineInfoCard.vue'
|
|
import MachineDocumentsCard from '~/components/machine/MachineDocumentsCard.vue'
|
|
import MachineProductsCard from '~/components/machine/MachineProductsCard.vue'
|
|
import MachineComponentsCard from '~/components/machine/MachineComponentsCard.vue'
|
|
import MachinePiecesCard from '~/components/machine/MachinePiecesCard.vue'
|
|
import AddEntityToMachineModal from '~/components/machine/AddEntityToMachineModal.vue'
|
|
import EntityHistorySection from '~/components/common/EntityHistorySection.vue'
|
|
import EntityVersionList from '~/components/common/EntityVersionList.vue'
|
|
import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
|
|
|
|
const route = useRoute()
|
|
const machineId = route.params.id
|
|
const { canEdit } = usePermissions()
|
|
|
|
if (!machineId) {
|
|
console.error('ID de machine manquant')
|
|
}
|
|
|
|
const d = useMachineDetailData(machineId)
|
|
const machineInfoCardRef = ref(null)
|
|
const versionRefreshKey = ref(0)
|
|
const refreshVersions = () => { versionRefreshKey.value++ }
|
|
const { confirm: confirmDialog } = useConfirm()
|
|
|
|
const activeTab = ref(route.query.tab || 'general')
|
|
watch(activeTab, (val) => {
|
|
navigateTo({ query: { ...route.query, tab: val } }, { replace: true })
|
|
})
|
|
|
|
const machineTabs = computed(() => [
|
|
{ key: 'general', label: 'Général' },
|
|
{ key: 'structure', label: 'Structure', count: d.components.value.length + d.machinePieces.value.length },
|
|
{ key: 'documents', label: 'Documents', count: d.machineDocumentsList.value.length },
|
|
{ key: 'history', label: 'Historique' },
|
|
])
|
|
|
|
const {
|
|
history,
|
|
loading: historyLoading,
|
|
error: historyError,
|
|
loadHistory,
|
|
} = useEntityHistory('machine')
|
|
|
|
const historyFieldLabels = {
|
|
name: 'Nom',
|
|
reference: 'Référence',
|
|
prix: 'Prix',
|
|
site: 'Site',
|
|
constructeurIds: 'Fournisseurs',
|
|
addedComponent: 'Composant ajouté',
|
|
removedComponent: 'Composant supprimé',
|
|
addedPiece: 'Pièce ajoutée',
|
|
removedPiece: 'Pièce supprimée',
|
|
addedProduct: 'Produit ajouté',
|
|
removedProduct: 'Produit supprimé',
|
|
componentLinks: 'Composants liés',
|
|
pieceLinks: 'Pièces liées',
|
|
productLinks: 'Produits liés',
|
|
}
|
|
|
|
const addModalOpen = ref(false)
|
|
const addModalKind = ref('component')
|
|
const fillLinkId = ref('')
|
|
const fillTypeId = ref('')
|
|
|
|
const openAddModal = (kind) => {
|
|
addModalKind.value = kind
|
|
addModalOpen.value = true
|
|
}
|
|
|
|
const handleRemoveConstructeurLink = (constructeurId) => {
|
|
const ids = d.machineConstructeurIds.value.filter(id => id !== constructeurId)
|
|
d.handleMachineConstructeurChange(ids)
|
|
}
|
|
|
|
const handleAddEntity = async (payload) => {
|
|
const { entityId, modelTypeId } = payload
|
|
|
|
if (fillLinkId.value) {
|
|
await d.fillEntityLink(fillLinkId.value, entityId, addModalKind.value)
|
|
fillLinkId.value = ''
|
|
fillTypeId.value = ''
|
|
} else if (entityId) {
|
|
if (addModalKind.value === 'component') {
|
|
await d.addComponentLink(entityId)
|
|
} else if (addModalKind.value === 'piece') {
|
|
await d.addPieceLink(entityId)
|
|
} else {
|
|
await d.addProductLink(entityId)
|
|
}
|
|
} else {
|
|
if (addModalKind.value === 'component') {
|
|
await d.addComponentLinkCategoryOnly(modelTypeId)
|
|
} else if (addModalKind.value === 'piece') {
|
|
await d.addPieceLinkCategoryOnly(modelTypeId)
|
|
} else {
|
|
await d.addProductLinkCategoryOnly(modelTypeId)
|
|
}
|
|
}
|
|
refreshVersions()
|
|
}
|
|
|
|
const handleFillEntity = (linkId, entityKind, modelTypeId) => {
|
|
fillLinkId.value = linkId
|
|
fillTypeId.value = modelTypeId
|
|
addModalKind.value = entityKind
|
|
addModalOpen.value = true
|
|
}
|
|
|
|
const submitMachineEdition = async () => {
|
|
if (machineInfoCardRef.value?.saveFieldDefinitions) {
|
|
await machineInfoCardRef.value.saveFieldDefinitions()
|
|
}
|
|
await d.submitEdition()
|
|
refreshVersions()
|
|
}
|
|
|
|
const confirmRemoveProduct = async (id: string) => {
|
|
if (!await confirmDialog({ title: 'Retirer ce produit ?', message: 'Le produit sera dissocié de la machine.', confirmText: 'Retirer', dangerous: true })) return
|
|
await d.removeProductLink(id)
|
|
refreshVersions()
|
|
}
|
|
|
|
const confirmRemoveComponent = async (id: string) => {
|
|
if (!await confirmDialog({ title: 'Retirer ce composant ?', message: 'Le composant sera dissocié de la machine.', confirmText: 'Retirer', dangerous: true })) return
|
|
await d.removeComponentLink(id)
|
|
refreshVersions()
|
|
}
|
|
|
|
const confirmRemovePiece = async (id: string) => {
|
|
if (!await confirmDialog({ title: 'Retirer cette pièce ?', message: 'La pièce sera dissociée de la machine.', confirmText: 'Retirer', dangerous: true })) return
|
|
await d.removePieceLink(id)
|
|
refreshVersions()
|
|
}
|
|
|
|
const confirmRemoveDocument = async (id: string) => {
|
|
if (!await confirmDialog({ title: 'Supprimer ce document ?', message: 'Le fichier sera supprimé définitivement.', confirmText: 'Supprimer', dangerous: true })) return
|
|
d.removeMachineDocument(id)
|
|
}
|
|
|
|
onMounted(() => {
|
|
d.loadMachineData()
|
|
d.loadInitialData()
|
|
loadHistory(String(machineId)).catch(() => {})
|
|
|
|
if (route.query.edit === 'true' && canEdit.value) {
|
|
d.isEditMode.value = true
|
|
}
|
|
})
|
|
</script>
|