refactor(frontend) : extract StructureSkeletonPreview shared component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
162
app/components/common/StructureSkeletonPreview.vue
Normal file
162
app/components/common/StructureSkeletonPreview.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="space-y-3 rounded-lg border border-base-200 bg-base-200/40 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="font-semibold text-base-content">Squelette sélectionné</h2>
|
||||
<p class="text-xs text-base-content/70">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="badge badge-outline">{{ previewBadge }}</span>
|
||||
</div>
|
||||
|
||||
<details v-if="structure" class="collapse collapse-arrow bg-base-100">
|
||||
<summary class="collapse-title text-sm font-medium">
|
||||
Consulter le détail du squelette
|
||||
</summary>
|
||||
<div class="collapse-content text-sm text-base-content/80" :class="variant === 'component' ? 'space-y-4' : 'space-y-2'">
|
||||
<!-- Custom fields: component variant (rich display) -->
|
||||
<div v-if="variant === 'component' && customFields.length" class="space-y-2">
|
||||
<h3 class="font-semibold text-sm text-base-content">Champs personnalisés</h3>
|
||||
<ul class="space-y-2">
|
||||
<li
|
||||
v-for="field in customFields"
|
||||
:key="field.customFieldId || field.id || field.name"
|
||||
class="rounded bg-base-200/60 px-3 py-2"
|
||||
>
|
||||
<p class="font-medium text-sm text-base-content">
|
||||
{{ field.name || field.key }}
|
||||
</p>
|
||||
<p class="text-xs text-base-content/70 mt-1">
|
||||
Type : {{ field.type || 'text' }}<span v-if="field.required"> • Obligatoire</span>
|
||||
<span v-if="Array.isArray(field.options) && field.options.length">
|
||||
• Options : {{ field.options.join(', ') }}
|
||||
</span>
|
||||
<span v-if="field.defaultValue">
|
||||
• Défaut : {{ field.defaultValue }}
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Custom fields: piece variant (simple display) -->
|
||||
<div v-if="variant === 'piece' && customFields.length" class="space-y-1">
|
||||
<h3 class="font-semibold text-sm text-base-content">Champs personnalisés</h3>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li v-for="field in customFields" :key="field.name">
|
||||
<span class="font-medium">{{ field.name }}</span>
|
||||
<span v-if="field.value !== undefined && field.value !== null"> : {{ field.value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Pieces: component variant only -->
|
||||
<div v-if="variant === 'component' && pieces.length" class="space-y-2">
|
||||
<h3 class="font-semibold text-sm text-base-content">Pièces imposées</h3>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li
|
||||
v-for="(piece, index) in pieces"
|
||||
:key="piece.role || piece.typePieceId || piece.familyCode || index"
|
||||
>
|
||||
{{ resolvePieceLabelFn(piece) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Products: component variant only -->
|
||||
<div v-if="variant === 'component' && products.length" class="space-y-2">
|
||||
<h3 class="font-semibold text-sm text-base-content">Produits imposés</h3>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li
|
||||
v-for="(product, index) in products"
|
||||
:key="product.role || product.typeProductId || product.familyCode || index"
|
||||
>
|
||||
{{ resolveProductLabelFn(product) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Subcomponents: component variant only -->
|
||||
<div v-if="variant === 'component' && subcomponents.length" class="space-y-2">
|
||||
<h3 class="font-semibold text-sm text-base-content">Sous-composants</h3>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li
|
||||
v-for="(subcomponent, index) in subcomponents"
|
||||
:key="subcomponent.alias || subcomponent.typeComposantId || subcomponent.familyCode || index"
|
||||
>
|
||||
{{ resolveSubcomponentLabelFn(subcomponent) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Empty state: component variant -->
|
||||
<p
|
||||
v-if="variant === 'component' && showEmptyState && !customFields.length && !pieces.length && !products.length && !subcomponents.length"
|
||||
class="text-xs text-gray-500"
|
||||
>
|
||||
Ce squelette ne définit pas encore de pièces, sous-composants ou valeurs par défaut.
|
||||
</p>
|
||||
|
||||
<!-- Empty state: piece variant -->
|
||||
<p v-if="variant === 'piece' && !customFields.length" class="text-xs text-base-content/70">
|
||||
Ce squelette ne définit pas encore de champs personnalisés.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getStructureCustomFields,
|
||||
getStructurePieces,
|
||||
getStructureProducts,
|
||||
getStructureSubcomponents,
|
||||
} from '~/shared/utils/structureDisplayUtils'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
structure: Record<string, any> | null
|
||||
description?: string
|
||||
previewBadge: string
|
||||
variant: 'component' | 'piece'
|
||||
showEmptyState?: boolean
|
||||
resolvePieceLabel?: (piece: Record<string, any>) => string
|
||||
resolveProductLabel?: (product: Record<string, any>) => string
|
||||
resolveSubcomponentLabel?: (subcomponent: Record<string, any>) => string
|
||||
}>(), {
|
||||
description: '',
|
||||
showEmptyState: false,
|
||||
resolvePieceLabel: undefined,
|
||||
resolveProductLabel: undefined,
|
||||
resolveSubcomponentLabel: undefined,
|
||||
})
|
||||
|
||||
const customFields = computed(() =>
|
||||
getStructureCustomFields(props.structure),
|
||||
)
|
||||
|
||||
const pieces = computed(() =>
|
||||
props.variant === 'component' ? getStructurePieces(props.structure) : [],
|
||||
)
|
||||
|
||||
const products = computed(() =>
|
||||
props.variant === 'component' ? getStructureProducts(props.structure) : [],
|
||||
)
|
||||
|
||||
const subcomponents = computed(() =>
|
||||
props.variant === 'component' ? getStructureSubcomponents(props.structure) : [],
|
||||
)
|
||||
|
||||
const fallbackLabel = (item: Record<string, any>) =>
|
||||
item?.name || item?.label || item?.role || item?.alias || 'N/A'
|
||||
|
||||
const resolvePieceLabelFn = (piece: Record<string, any>) =>
|
||||
props.resolvePieceLabel ? props.resolvePieceLabel(piece) : fallbackLabel(piece)
|
||||
|
||||
const resolveProductLabelFn = (product: Record<string, any>) =>
|
||||
props.resolveProductLabel ? props.resolveProductLabel(product) : fallbackLabel(product)
|
||||
|
||||
const resolveSubcomponentLabelFn = (subcomponent: Record<string, any>) =>
|
||||
props.resolveSubcomponentLabel ? props.resolveSubcomponentLabel(subcomponent) : fallbackLabel(subcomponent)
|
||||
</script>
|
||||
Reference in New Issue
Block a user