This commit is contained in:
Matthieu
2026-03-31 17:53:30 +02:00
parent e0f761da2b
commit 958a00c8fc
21 changed files with 281 additions and 144 deletions

View File

@@ -1,5 +1,6 @@
import { useApi } from '~/composables/useApi'
import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils'
import { extractCollection } from '~/shared/utils/apiHelpers'
type EntityType = 'machine' | 'piece' | 'composant' | 'product'
@@ -25,7 +26,7 @@ const ENTITY_PLURALS: Record<EntityType, string> = {
}
export function useConstructeurLinks() {
const { get, post, patch, del } = useApi()
const { get, post, patch, delete: del } = useApi()
const fetchLinks = async (
entityType: EntityType,
@@ -34,11 +35,11 @@ export function useConstructeurLinks() {
const endpoint = ENDPOINTS[entityType]
const key = ENTITY_KEYS[entityType]
const plural = ENTITY_PLURALS[entityType]
const result = await get(`${endpoint}?${key}=/api/${plural}/${entityId}`)
const url = `${endpoint}?${key}=/api/${plural}/${entityId}`
const result = await get(url)
if (!result.success || !result.data) return []
const data = result.data as Record<string, any>
const members = data['hydra:member'] ?? (Array.isArray(data) ? data : [])
const members = extractCollection(result.data)
if (!Array.isArray(members)) return []
return members.map((link: any) => ({

View File

@@ -1,4 +1,4 @@
import { ref, computed, type Ref, type ComputedRef } from 'vue'
import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'
import { useUrlState } from './useUrlState'
import type { DataTableSort, DataTablePagination, DataTableColumnFilters, SortDirection } from '~/shared/types/dataTable'
@@ -22,6 +22,8 @@ export interface UseDataTableOptions {
persistToUrl?: boolean
/** Extra URL state params for page-specific filters */
extraParams?: Record<string, { default: string | number; type?: 'string' | 'number' }>
/** Column filter keys to persist in URL (prefixed with `f.` in query string) */
columnFilterKeys?: string[]
}
export interface UseDataTableReturn {
@@ -56,6 +58,7 @@ export function useDataTable(
searchDebounceMs = 300,
persistToUrl = true,
extraParams = {},
columnFilterKeys = [],
} = options
let searchTerm: Ref<string>
@@ -64,6 +67,7 @@ export function useDataTable(
let currentPage: Ref<number>
let itemsPerPage: Ref<number>
const filters: Record<string, Ref<string | number>> = {}
const columnFilterRefs: Record<string, Ref<string>> = {}
if (persistToUrl) {
const paramDefs: Record<string, { default: string | number; type?: 'string' | 'number'; debounce?: number }> = {
@@ -75,6 +79,10 @@ export function useDataTable(
...extraParams,
}
for (const key of columnFilterKeys) {
paramDefs[`f.${key}`] = { default: '', debounce: 300 }
}
const state = useUrlState(paramDefs, {
onRestore: () => deps.fetchData(),
})
@@ -88,6 +96,10 @@ export function useDataTable(
for (const key of Object.keys(extraParams)) {
filters[key] = (state as Record<string, Ref<string | number>>)[key]!
}
for (const key of columnFilterKeys) {
columnFilterRefs[key] = (state as Record<string, Ref<string>>)[`f.${key}`]!
}
}
else {
searchTerm = ref('')
@@ -137,8 +149,31 @@ export function useDataTable(
deps.fetchData()
}
// Column filters
const columnFilters = ref<DataTableColumnFilters>({})
// Column filters — seed from URL-persisted refs
const initialColumnFilters: DataTableColumnFilters = {}
for (const [key, r] of Object.entries(columnFilterRefs)) {
if (r.value) initialColumnFilters[key] = r.value
}
const columnFilters = ref<DataTableColumnFilters>(initialColumnFilters)
// Sync columnFilters → URL refs
if (persistToUrl && columnFilterKeys.length > 0) {
watch(columnFilters, (val) => {
for (const key of columnFilterKeys) {
columnFilterRefs[key]!.value = val[key] || ''
}
}, { deep: true })
// Sync URL refs → columnFilters (back/forward navigation)
for (const key of columnFilterKeys) {
watch(columnFilterRefs[key]!, (urlVal) => {
const current = columnFilters.value[key] || ''
if (current !== urlVal) {
columnFilters.value = { ...columnFilters.value, [key]: urlVal }
}
})
}
}
const handleColumnFiltersChange = (newFilters: DataTableColumnFilters) => {
columnFilters.value = newFilters

View File

@@ -21,7 +21,11 @@ import {
resolveConstructeurs,
uniqueConstructeurIds,
formatConstructeurContact as formatConstructeurContactSummary,
parseConstructeurLinksFromApi,
constructeurIdsFromLinks,
} from '~/shared/constructeurUtils'
import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils'
import { useConstructeurLinks } from '~/composables/useConstructeurLinks'
import { useMachineDetailDocuments } from '~/composables/useMachineDetailDocuments'
import { useMachineDetailCustomFields } from '~/composables/useMachineDetailCustomFields'
import { useMachineDetailHierarchy } from '~/composables/useMachineDetailHierarchy'
@@ -64,6 +68,11 @@ export function useMachineDetailData(machineId: string) {
const printAreaRef = ref<HTMLElement | null>(null)
const saving = ref(false)
// Constructeur links
const { fetchLinks, syncLinks } = useConstructeurLinks()
const constructeurLinks = ref<ConstructeurLinkEntry[]>([])
const originalConstructeurLinks = ref<ConstructeurLinkEntry[]>([])
// Machine fields
const machineName = ref('')
const machineReference = ref('')
@@ -78,20 +87,15 @@ export function useMachineDetailData(machineId: string) {
})
const machineConstructeursDisplay = computed(() => {
const ids = uniqueConstructeurIds(
machineConstructeurIds.value,
(machine.value as AnyRecord)?.constructeurIds,
(machine.value as AnyRecord)?.constructeurs,
(machine.value as AnyRecord)?.constructeur,
)
const ids = machineConstructeurIds.value
if (!ids.length) return [] as any[]
// Extract nested constructeur objects from link entries as candidate pool
const linkConstructeurs = constructeurLinks.value
.filter(l => l.constructeur && l.constructeur.id)
.map(l => l.constructeur!) as any[]
return resolveConstructeurs(
ids,
Array.isArray((machine.value as AnyRecord)?.constructeurs)
? ((machine.value as AnyRecord).constructeurs as any[])
: [],
(machine.value as AnyRecord)?.constructeur
? [(machine.value as AnyRecord).constructeur as any]
: [],
linkConstructeurs,
constructeurs.value as any,
) as any[]
})
@@ -235,11 +239,12 @@ export function useMachineDetailData(machineId: string) {
if (machine.value) {
machineName.value = (machine.value.name as string) || ''
machineReference.value = (machine.value.reference as string) || ''
machineConstructeurIds.value = uniqueConstructeurIds(
machine.value.constructeurIds,
machine.value.constructeurs,
machine.value.constructeur,
)
// Parse constructeur links from structure response
const rawLinks = Array.isArray(machine.value.constructeurs) ? machine.value.constructeurs as any[] : []
const parsed = parseConstructeurLinksFromApi(rawLinks)
constructeurLinks.value = parsed
originalConstructeurLinks.value = parsed.map(l => ({ ...l }))
machineConstructeurIds.value = constructeurIdsFromLinks(parsed)
machineSiteId.value = (machine.value.siteId as string) || (machine.value.site as AnyRecord)?.id as string || ''
}
}
@@ -269,6 +274,8 @@ export function useMachineDetailData(machineId: string) {
machineReference,
machineSiteId,
machineConstructeurIds,
constructeurLinks,
originalConstructeurLinks,
machineDocumentsLoaded,
machineComponentLinks,
machinePieceLinks,
@@ -284,6 +291,7 @@ export function useMachineDetailData(machineId: string) {
updatePieceApi,
apiPatch,
toast,
syncLinks,
})
// UI methods
@@ -338,6 +346,8 @@ export function useMachineDetailData(machineId: string) {
const cancelEdition = () => {
initMachineFields()
syncMachineCustomFields()
constructeurLinks.value = originalConstructeurLinks.value.map(l => ({ ...l }))
machineConstructeurIds.value = constructeurIdsFromLinks(constructeurLinks.value)
isEditMode.value = false
}
@@ -467,6 +477,7 @@ export function useMachineDetailData(machineId: string) {
// Machine fields
machineName, machineReference, machineSiteId, machineConstructeurIds, machineConstructeurId,
machineConstructeursDisplay, machineConstructeurContact, hasMachineConstructeur,
constructeurLinks, originalConstructeurLinks,
sites,
// UI state

View File

@@ -5,7 +5,8 @@
*/
import type { Ref } from 'vue'
import { uniqueConstructeurIds } from '~/shared/constructeurUtils'
import { uniqueConstructeurIds, constructeurIdsFromLinks } from '~/shared/constructeurUtils'
import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils'
type AnyRecord = Record<string, unknown>
@@ -15,6 +16,8 @@ export interface UseMachineDetailUpdatesDeps {
machineReference: Ref<string>
machineSiteId: Ref<string>
machineConstructeurIds: Ref<string[]>
constructeurLinks: Ref<ConstructeurLinkEntry[]>
originalConstructeurLinks: Ref<ConstructeurLinkEntry[]>
machineDocumentsLoaded: Ref<boolean>
machineComponentLinks: Ref<AnyRecord[]>
machinePieceLinks: Ref<AnyRecord[]>
@@ -35,6 +38,12 @@ export interface UseMachineDetailUpdatesDeps {
updatePieceApi: (id: string, data: any) => Promise<unknown>
apiPatch: (endpoint: string, data?: unknown) => Promise<any>
toast: { showInfo: (msg: string) => void }
syncLinks: (
entityType: 'machine' | 'piece' | 'composant' | 'product',
entityId: string,
originalLinks: ConstructeurLinkEntry[],
formLinks: ConstructeurLinkEntry[],
) => Promise<void>
}
export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
@@ -44,6 +53,8 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
machineReference,
machineSiteId,
machineConstructeurIds,
constructeurLinks,
originalConstructeurLinks,
machineComponentLinks,
machinePieceLinks,
applyMachineLinks,
@@ -56,19 +67,16 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
updatePieceApi,
apiPatch,
toast,
syncLinks,
} = deps
const updateMachineInfo = async () => {
if (!machine.value) return
try {
const cIds = uniqueConstructeurIds(machineConstructeurIds.value)
machineConstructeurIds.value = cIds
const result: any = await updateMachineApi(machine.value.id as string, {
name: machineName.value,
reference: machineReference.value,
siteId: machineSiteId.value || undefined,
constructeurIds: cIds,
} as any)
if (result.success) {
const machinePayload =
@@ -82,11 +90,6 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
documents: machinePayload.documents || machine.value.documents || [],
customFieldValues: machinePayload.customFieldValues || machine.value.customFieldValues || [],
}
machineConstructeurIds.value = uniqueConstructeurIds(
machine.value!.constructeurIds,
machine.value!.constructeurs,
machine.value!.constructeur,
)
const linksApplied = applyMachineLinks(result.data)
if (linksApplied && machine.value) {
machine.value.componentLinks = machineComponentLinks.value
@@ -95,6 +98,9 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
loadProductDocuments().catch(() => {})
}
}
// Sync constructeur links after entity save
await syncLinks('machine', machine.value!.id as string, originalConstructeurLinks.value, constructeurLinks.value)
originalConstructeurLinks.value = constructeurLinks.value.map(l => ({ ...l }))
} catch (error) {
console.error('Erreur lors de la mise à jour de la machine:', error)
}
@@ -209,7 +215,13 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
}
const handleMachineConstructeurChange = (value: unknown) => {
machineConstructeurIds.value = uniqueConstructeurIds(value)
const newIds = uniqueConstructeurIds(value)
machineConstructeurIds.value = newIds
// Sync constructeurLinks: keep existing entries, add new ones
const existingMap = new Map(constructeurLinks.value.map(l => [l.constructeurId, l]))
constructeurLinks.value = newIds.map(id =>
existingMap.get(id) ?? { constructeurId: id, supplierReference: null },
)
}
const editComponent = () => {

View File

@@ -1,7 +1,6 @@
import { ref } from 'vue'
import { useToast } from './useToast'
import { useApi, type ApiResponse } from './useApi'
import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils'
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
import { extractCollection } from '~/shared/utils/apiHelpers'
@@ -92,7 +91,7 @@ export function useMachines() {
const createMachine = async (machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true
try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const normalizedPayload = normalizeRelationIds(machineData as Record<string, unknown>)
const result = await post('/machines', normalizedPayload)
if (result.success) {
const createdMachine = normalizeMachineResponse(result.data) ||
@@ -116,7 +115,7 @@ export function useMachines() {
const updateMachineData = async (id: string, machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true
try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const normalizedPayload = normalizeRelationIds(machineData as Record<string, unknown>)
const result = await patch(`/machines/${id}`, normalizedPayload)
if (result.success) {
const updatedMachine = normalizeMachineResponse(result.data) ||