refactor(composables): merge 3 history composables into generic (F2.2)

Create useEntityHistory.ts with parameterized entity type. Rewrite
useComponentHistory, usePieceHistory, useProductHistory as thin
backward-compatible wrappers (67→13 LOC each).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-02-09 11:13:31 +01:00
parent 86bb8af32d
commit 399ec1f7b4
4 changed files with 99 additions and 195 deletions

View File

@@ -1,67 +1,12 @@
import { ref } from 'vue'
import { useApi } from '~/composables/useApi'
/**
* Backward-compatible wrapper around useEntityHistory.
* Real logic lives in useEntityHistory.ts.
*/
import { useEntityHistory, type EntityHistoryActor, type EntityHistoryEntry } from './useEntityHistory'
export type ComponentHistoryActor = {
id: string
label: string
export type ComponentHistoryActor = EntityHistoryActor
export type ComponentHistoryEntry = EntityHistoryEntry
export function useComponentHistory() {
return useEntityHistory('composant')
}
export type ComponentHistoryEntry = {
id: string
action: 'create' | 'update' | 'delete' | string
createdAt: string
actor: ComponentHistoryActor | null
diff: Record<string, { from: unknown; to: unknown }> | null
snapshot: Record<string, unknown> | null
}
const extractItems = (payload: any): ComponentHistoryEntry[] => {
if (Array.isArray(payload?.items)) {
return payload.items
}
if (Array.isArray(payload?.member)) {
return payload.member
}
if (Array.isArray(payload?.['hydra:member'])) {
return payload['hydra:member']
}
return []
}
export function useComponentHistory () {
const { get } = useApi()
const history = ref<ComponentHistoryEntry[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const loadHistory = async (componentId: string) => {
loading.value = true
error.value = null
try {
const result = await get(`/composants/${componentId}/history`)
if (!result.success) {
error.value = result.error ?? 'Impossible de charger lhistorique.'
history.value = []
return result
}
history.value = extractItems(result.data) as ComponentHistoryEntry[]
return { success: true, data: history.value }
} catch (err: any) {
const message = err?.message ?? 'Erreur inconnue'
error.value = message
history.value = []
return { success: false, error: message }
} finally {
loading.value = false
}
}
return {
history,
loading,
error,
loadHistory,
}
}

View File

@@ -0,0 +1,69 @@
/**
* Generic entity history composable.
*
* Replaces useComponentHistory, usePieceHistory, useProductHistory which were
* 99% identical (only the API endpoint differed).
*/
import { ref } from 'vue'
import { useApi } from '~/composables/useApi'
export type EntityHistoryActor = {
id: string
label: string
}
export type EntityHistoryEntry = {
id: string
action: 'create' | 'update' | 'delete' | string
createdAt: string
actor: EntityHistoryActor | null
diff: Record<string, { from: unknown; to: unknown }> | null
snapshot: Record<string, unknown> | null
}
const ENTITY_ENDPOINTS: Record<string, string> = {
composant: '/composants',
piece: '/pieces',
product: '/products',
}
const extractItems = (payload: any): EntityHistoryEntry[] => {
if (Array.isArray(payload?.items)) return payload.items
if (Array.isArray(payload?.member)) return payload.member
if (Array.isArray(payload?.['hydra:member'])) return payload['hydra:member']
return []
}
export function useEntityHistory(entityType: 'composant' | 'piece' | 'product') {
const { get } = useApi()
const basePath = ENTITY_ENDPOINTS[entityType]
const history = ref<EntityHistoryEntry[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const loadHistory = async (entityId: string) => {
loading.value = true
error.value = null
try {
const result = await get(`${basePath}/${entityId}/history`)
if (!result.success) {
error.value = result.error ?? 'Impossible de charger l\'historique.'
history.value = []
return result
}
history.value = extractItems(result.data)
return { success: true, data: history.value }
} catch (err: any) {
const message = err?.message ?? 'Erreur inconnue'
error.value = message
history.value = []
return { success: false, error: message }
} finally {
loading.value = false
}
}
return { history, loading, error, loadHistory }
}

View File

@@ -1,67 +1,12 @@
import { ref } from 'vue'
import { useApi } from '~/composables/useApi'
/**
* Backward-compatible wrapper around useEntityHistory.
* Real logic lives in useEntityHistory.ts.
*/
import { useEntityHistory, type EntityHistoryActor, type EntityHistoryEntry } from './useEntityHistory'
export type PieceHistoryActor = {
id: string
label: string
export type PieceHistoryActor = EntityHistoryActor
export type PieceHistoryEntry = EntityHistoryEntry
export function usePieceHistory() {
return useEntityHistory('piece')
}
export type PieceHistoryEntry = {
id: string
action: 'create' | 'update' | 'delete' | string
createdAt: string
actor: PieceHistoryActor | null
diff: Record<string, { from: unknown; to: unknown }> | null
snapshot: Record<string, unknown> | null
}
const extractItems = (payload: any): PieceHistoryEntry[] => {
if (Array.isArray(payload?.items)) {
return payload.items
}
if (Array.isArray(payload?.member)) {
return payload.member
}
if (Array.isArray(payload?.['hydra:member'])) {
return payload['hydra:member']
}
return []
}
export function usePieceHistory () {
const { get } = useApi()
const history = ref<PieceHistoryEntry[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const loadHistory = async (pieceId: string) => {
loading.value = true
error.value = null
try {
const result = await get(`/pieces/${pieceId}/history`)
if (!result.success) {
error.value = result.error ?? 'Impossible de charger lhistorique.'
history.value = []
return result
}
history.value = extractItems(result.data) as PieceHistoryEntry[]
return { success: true, data: history.value }
} catch (err: any) {
const message = err?.message ?? 'Erreur inconnue'
error.value = message
history.value = []
return { success: false, error: message }
} finally {
loading.value = false
}
}
return {
history,
loading,
error,
loadHistory,
}
}

View File

@@ -1,67 +1,12 @@
import { ref } from 'vue'
import { useApi } from '~/composables/useApi'
/**
* Backward-compatible wrapper around useEntityHistory.
* Real logic lives in useEntityHistory.ts.
*/
import { useEntityHistory, type EntityHistoryActor, type EntityHistoryEntry } from './useEntityHistory'
export type ProductHistoryActor = {
id: string
label: string
export type ProductHistoryActor = EntityHistoryActor
export type ProductHistoryEntry = EntityHistoryEntry
export function useProductHistory() {
return useEntityHistory('product')
}
export type ProductHistoryEntry = {
id: string
action: 'create' | 'update' | 'delete' | string
createdAt: string
actor: ProductHistoryActor | null
diff: Record<string, { from: unknown; to: unknown }> | null
snapshot: Record<string, unknown> | null
}
const extractItems = (payload: any): ProductHistoryEntry[] => {
if (Array.isArray(payload?.items)) {
return payload.items
}
if (Array.isArray(payload?.member)) {
return payload.member
}
if (Array.isArray(payload?.['hydra:member'])) {
return payload['hydra:member']
}
return []
}
export function useProductHistory () {
const { get } = useApi()
const history = ref<ProductHistoryEntry[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const loadHistory = async (productId: string) => {
loading.value = true
error.value = null
try {
const result = await get(`/products/${productId}/history`)
if (!result.success) {
error.value = result.error ?? 'Impossible de charger lhistorique.'
history.value = []
return result
}
history.value = extractItems(result.data) as ProductHistoryEntry[]
return { success: true, data: history.value }
} catch (err: any) {
const message = err?.message ?? 'Erreur inconnue'
error.value = message
history.value = []
return { success: false, error: message }
} finally {
loading.value = false
}
}
return {
history,
loading,
error,
loadHistory,
}
}