refactor(composables): migrate JS composables to TypeScript (F3.2)

Convert 7 composables from JS to TS with proper type annotations:
useApi, useCustomFields, useProfileSession, useProfiles, useToast,
useMachineTypesApi, useMachines. Remove deprecated stubs
useComponentModels.js and usePieceModels.js.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-02-09 11:13:09 +01:00
parent 9ee348fff0
commit 78718b85ae
11 changed files with 396 additions and 444 deletions

View File

@@ -1,105 +0,0 @@
import { useToast } from './useToast'
export function useApi () {
const { showSuccess, showError, showInfo } = useToast()
const { public: publicConfig } = useRuntimeConfig()
const API_BASE_URL = publicConfig.apiBaseUrl || 'http://localhost:3000'
const parsedApiTimeout = Number(publicConfig.apiTimeout ?? 30000)
const API_TIMEOUT = Number.isNaN(parsedApiTimeout) ? 30000 : parsedApiTimeout
const apiCall = async (endpoint, options = {}) => {
const url = `${API_BASE_URL}${endpoint}`
const defaultOptions = {
credentials: 'include',
headers: {
'Content-Type': 'application/json'
}
}
// Ajouter un timeout à la requête
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT)
try {
const response = await fetch(url, {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers
},
signal: controller.signal
})
clearTimeout(timeoutId)
if (response.ok) {
let data = null
if (response.status !== 204) {
const contentType = response.headers.get('content-type') || ''
if (contentType.includes('application/json') || contentType.includes('application/ld+json') || contentType.includes('+json')) {
const text = await response.text()
data = text ? JSON.parse(text) : null
} else {
const text = await response.text()
data = text || null
}
}
return { success: true, data }
} else {
const contentType = response.headers.get('content-type') || ''
let errorData = {}
if (contentType.includes('application/json')) {
errorData = await response.json().catch(() => ({}))
} else {
const text = await response.text().catch(() => '')
errorData = text ? { message: text } : {}
}
const errorMessage = errorData.message || `Erreur ${response.status}: ${response.statusText}`
showError(errorMessage)
return { success: false, error: errorMessage, status: response.status }
}
} catch (error) {
clearTimeout(timeoutId)
const errorMessage = error.name === 'AbortError' ? 'Timeout de la requête' : error.message || 'Erreur réseau'
showError(`Erreur réseau: ${errorMessage}`)
return { success: false, error: errorMessage }
}
}
const get = async (endpoint) => {
return apiCall(endpoint, { method: 'GET' })
}
const post = async (endpoint, data) => {
return apiCall(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/ld+json'
},
body: JSON.stringify(data)
})
}
const patch = async (endpoint, data) => {
return apiCall(endpoint, {
method: 'PATCH',
headers: {
'Content-Type': 'application/merge-patch+json'
},
body: JSON.stringify(data)
})
}
const del = async (endpoint) => {
return apiCall(endpoint, { method: 'DELETE' })
}
return {
apiCall,
get,
post,
patch,
delete: del
}
}

128
app/composables/useApi.ts Normal file
View File

@@ -0,0 +1,128 @@
import { useToast } from './useToast'
export interface ApiResponse<T = any> {
success: boolean
data?: T
error?: string
status?: number
}
interface ApiCallOptions extends RequestInit {
headers?: Record<string, string>
}
export function useApi() {
const { showError } = useToast()
const { public: publicConfig } = useRuntimeConfig()
const API_BASE_URL = (publicConfig.apiBaseUrl as string) || 'http://localhost:3000'
const parsedApiTimeout = Number(publicConfig.apiTimeout ?? 30000)
const API_TIMEOUT = Number.isNaN(parsedApiTimeout) ? 30000 : parsedApiTimeout
const apiCall = async <T = any>(endpoint: string, options: ApiCallOptions = {}): Promise<ApiResponse<T>> => {
const url = `${API_BASE_URL}${endpoint}`
const defaultOptions: ApiCallOptions = {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
}
// Ajouter un timeout à la requête
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT)
try {
const response = await fetch(url, {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers,
},
signal: controller.signal,
})
clearTimeout(timeoutId)
if (response.ok) {
let data: T | null = null
if (response.status !== 204) {
const contentType = response.headers.get('content-type') || ''
if (contentType.includes('application/json') || contentType.includes('application/ld+json') || contentType.includes('+json')) {
const text = await response.text()
data = text ? JSON.parse(text) : null
} else {
const text = await response.text()
data = (text || null) as T | null
}
}
return { success: true, data: data as T }
} else {
const contentType = response.headers.get('content-type') || ''
let errorData: Record<string, unknown> = {}
if (contentType.includes('application/json')) {
errorData = await response.json().catch(() => ({}))
} else {
const text = await response.text().catch(() => '')
errorData = text ? { message: text } : {}
}
const errorMessage = (errorData.message as string) || `Erreur ${response.status}: ${response.statusText}`
showError(errorMessage)
return { success: false, error: errorMessage, status: response.status }
}
} catch (error) {
clearTimeout(timeoutId)
const err = error as Error & { name?: string }
const errorMessage = err.name === 'AbortError' ? 'Timeout de la requête' : err.message || 'Erreur réseau'
showError(`Erreur réseau: ${errorMessage}`)
return { success: false, error: errorMessage }
}
}
const get = async <T = any>(endpoint: string): Promise<ApiResponse<T>> => {
return apiCall<T>(endpoint, { method: 'GET' })
}
const post = async <T = any>(endpoint: string, data?: unknown): Promise<ApiResponse<T>> => {
return apiCall<T>(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/ld+json',
},
body: data !== undefined ? JSON.stringify(data) : undefined,
})
}
const patch = async <T = any>(endpoint: string, data?: unknown): Promise<ApiResponse<T>> => {
return apiCall<T>(endpoint, {
method: 'PATCH',
headers: {
'Content-Type': 'application/merge-patch+json',
},
body: data !== undefined ? JSON.stringify(data) : undefined,
})
}
const put = async <T = any>(endpoint: string, data?: unknown): Promise<ApiResponse<T>> => {
return apiCall<T>(endpoint, {
method: 'PUT',
headers: {
'Content-Type': 'application/ld+json',
},
body: data !== undefined ? JSON.stringify(data) : undefined,
})
}
const del = async <T = any>(endpoint: string): Promise<ApiResponse<T>> => {
return apiCall<T>(endpoint, { method: 'DELETE' })
}
return {
apiCall,
get,
post,
patch,
put,
delete: del,
}
}

View File

@@ -1,47 +0,0 @@
import { ref, computed } from 'vue'
let hasWarned = false
const warnDeprecated = () => {
if (hasWarned) return
if (process.dev) {
console.warn('[useComponentModels] Ce composable est conservé pour compatibilité mais les modèles ont été remplacés par les catégories enrichies de squelette. Utilisez useComponentTypes / useComposants à la place.')
}
hasWarned = true
}
const buildUnsupportedResult = () => ({
success: false,
error: 'Les modèles de composants ont été retirés. Gérez les squelettes via les catégories et utilisez les requirements machine pour instancier des composants.'
})
export function useComponentModels () {
warnDeprecated()
const componentModelsBuckets = ref({})
const loadingComponentModels = ref(false)
const componentModels = computed(() => [])
const noLongerSupported = async () => {
warnDeprecated()
return buildUnsupportedResult()
}
const getComponentModels = () => componentModels.value
const getComponentModelsForType = () => []
const isComponentModelLoading = () => loadingComponentModels.value
return {
componentModels,
componentModelsBuckets,
loadingComponentModels,
loadComponentModels: noLongerSupported,
createComponentModel: noLongerSupported,
updateComponentModel: noLongerSupported,
deleteComponentModel: noLongerSupported,
getComponentModels,
getComponentModelsForType,
isComponentModelLoading
}
}

View File

@@ -1,66 +1,75 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useApi } from './useApi' import { useApi, type ApiResponse } from './useApi'
export function useCustomFields () { export interface CustomFieldValue {
id: string
customFieldId: string
entityType: string
entityId: string
value: unknown
[key: string]: unknown
}
export function useCustomFields() {
const { apiCall } = useApi() const { apiCall } = useApi()
const customFieldValues = ref([]) const customFieldValues = ref<CustomFieldValue[]>([])
const loading = ref(false) const loading = ref(false)
// Créer une valeur de champ personnalisé // Créer une valeur de champ personnalisé
const createCustomFieldValue = async (customFieldValueData) => { const createCustomFieldValue = async (customFieldValueData: Record<string, unknown>): Promise<ApiResponse> => {
try { try {
const result = await apiCall('/custom-fields/values', { const result = await apiCall('/custom-fields/values', {
method: 'POST', method: 'POST',
body: JSON.stringify(customFieldValueData) body: JSON.stringify(customFieldValueData),
}) })
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la création de la valeur de champ personnalisé:', error) console.error('Erreur lors de la création de la valeur de champ personnalisé:', error)
return { success: false, error } return { success: false, error: (error as Error).message }
} }
} }
// Obtenir les valeurs de champs personnalisés pour une entité // Obtenir les valeurs de champs personnalisés pour une entité
const getCustomFieldValuesByEntity = async (entityType, entityId) => { const getCustomFieldValuesByEntity = async (entityType: string, entityId: string): Promise<ApiResponse> => {
try { try {
loading.value = true loading.value = true
const result = await apiCall(`/custom-fields/values/${entityType}/${entityId}`, { const result = await apiCall(`/custom-fields/values/${entityType}/${entityId}`, {
method: 'GET' method: 'GET',
}) })
if (result.success) { if (result.success) {
customFieldValues.value = result.data customFieldValues.value = result.data as CustomFieldValue[]
} }
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la récupération des valeurs de champs personnalisés:', error) console.error('Erreur lors de la récupération des valeurs de champs personnalisés:', error)
return { success: false, error } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// Mettre à jour une valeur de champ personnalisé // Mettre à jour une valeur de champ personnalisé
const updateCustomFieldValue = async (id, updateData) => { const updateCustomFieldValue = async (id: string, updateData: Record<string, unknown>): Promise<ApiResponse> => {
try { try {
const result = await apiCall(`/custom-fields/values/${id}`, { const result = await apiCall(`/custom-fields/values/${id}`, {
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(updateData) body: JSON.stringify(updateData),
}) })
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la mise à jour de la valeur de champ personnalisé:', error) console.error('Erreur lors de la mise à jour de la valeur de champ personnalisé:', error)
return { success: false, error } return { success: false, error: (error as Error).message }
} }
} }
// Créer ou mettre à jour une valeur de champ personnalisé // Créer ou mettre à jour une valeur de champ personnalisé
const upsertCustomFieldValue = async ( const upsertCustomFieldValue = async (
customFieldId, customFieldId: string | null,
entityType, entityType: string,
entityId, entityId: string,
value, value: unknown,
metadata = {}, metadata: Record<string, unknown> = {},
) => { ): Promise<ApiResponse> => {
try { try {
const result = await apiCall('/custom-fields/values/upsert', { const result = await apiCall('/custom-fields/values/upsert', {
method: 'POST', method: 'POST',
@@ -69,26 +78,26 @@ export function useCustomFields () {
entityType, entityType,
entityId, entityId,
value, value,
...metadata ...metadata,
}) }),
}) })
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la création/mise à jour de la valeur de champ personnalisé:', error) console.error('Erreur lors de la création/mise à jour de la valeur de champ personnalisé:', error)
return { success: false, error } return { success: false, error: (error as Error).message }
} }
} }
// Supprimer une valeur de champ personnalisé // Supprimer une valeur de champ personnalisé
const deleteCustomFieldValue = async (id) => { const deleteCustomFieldValue = async (id: string): Promise<ApiResponse> => {
try { try {
const result = await apiCall(`/custom-fields/values/${id}`, { const result = await apiCall(`/custom-fields/values/${id}`, {
method: 'DELETE' method: 'DELETE',
}) })
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la suppression de la valeur de champ personnalisé:', error) console.error('Erreur lors de la suppression de la valeur de champ personnalisé:', error)
return { success: false, error } return { success: false, error: (error as Error).message }
} }
} }
@@ -99,6 +108,6 @@ export function useCustomFields () {
getCustomFieldValuesByEntity, getCustomFieldValuesByEntity,
updateCustomFieldValue, updateCustomFieldValue,
upsertCustomFieldValue, upsertCustomFieldValue,
deleteCustomFieldValue deleteCustomFieldValue,
} }
} }

View File

@@ -1,80 +1,76 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useToast } from './useToast' import { useToast } from './useToast'
import { useApi } from './useApi' import { useApi, type ApiResponse } from './useApi'
import { extractRelationId } from '~/shared/apiRelations' import { extractRelationId } from '~/shared/apiRelations'
import { extractCollection } from '~/shared/utils/apiHelpers'
const machineTypes = ref([]) export interface MachineTypeRequirement {
id?: string
label?: string
minCount?: number
maxCount?: number
required?: boolean
[key: string]: unknown
}
export interface MachineType {
id: string
name: string
componentRequirements: MachineTypeRequirement[]
pieceRequirements: MachineTypeRequirement[]
productRequirements: MachineTypeRequirement[]
[key: string]: unknown
}
const machineTypes = ref<MachineType[]>([])
const loading = ref(false) const loading = ref(false)
const normalizeRequirementList = (value, relationKey) => { const normalizeRequirementList = (value: unknown, relationKey: string): MachineTypeRequirement[] => {
if (!Array.isArray(value)) { if (!Array.isArray(value)) {
return [] return []
} }
return value.map((entry, index) => { return value.map((entry: Record<string, unknown>, _index: number) => {
if (!entry || typeof entry !== 'object') { if (!entry || typeof entry !== 'object') {
return entry return entry
} }
const normalized = { ...entry } const normalized = { ...entry }
const relationField = relationKey.replace('Id', '') const relationField = relationKey.replace('Id', '')
const relationValue = normalized[relationField] const relationValue = normalized[relationField]
console.log(`[normalizeRequirementList] Entry ${index}:`, {
relationKey,
relationField,
hasRelationKey: !!normalized[relationKey],
relationValue,
relationValueType: typeof relationValue
})
if (relationKey && !normalized[relationKey]) { if (relationKey && !normalized[relationKey]) {
const relationId = extractRelationId(relationValue) const relationId = extractRelationId(relationValue)
console.log(`[normalizeRequirementList] Extracted ID:`, relationId)
if (relationId) { if (relationId) {
normalized[relationKey] = relationId normalized[relationKey] = relationId
} }
} }
console.log(`[normalizeRequirementList] Normalized entry:`, normalized) return normalized as MachineTypeRequirement
return normalized
}) })
} }
const normalizeMachineType = (type) => { const normalizeMachineType = (type: Record<string, unknown>): MachineType | null => {
if (!type || typeof type !== 'object') { if (!type || typeof type !== 'object') {
return type return null
} }
return { return {
...type, ...type,
componentRequirements: normalizeRequirementList(type.componentRequirements, 'typeComposantId'), componentRequirements: normalizeRequirementList(type.componentRequirements, 'typeComposantId'),
pieceRequirements: normalizeRequirementList(type.pieceRequirements, 'typePieceId'), pieceRequirements: normalizeRequirementList(type.pieceRequirements, 'typePieceId'),
productRequirements: normalizeRequirementList(type.productRequirements, 'typeProductId'), productRequirements: normalizeRequirementList(type.productRequirements, 'typeProductId'),
} } as MachineType
} }
const extractCollection = (payload) => { export function useMachineTypesApi() {
if (Array.isArray(payload)) { const { showSuccess, showInfo } = useToast()
return payload
}
if (Array.isArray(payload?.member)) {
return payload.member
}
if (Array.isArray(payload?.['hydra:member'])) {
return payload['hydra:member']
}
if (Array.isArray(payload?.data)) {
return payload.data
}
return []
}
export function useMachineTypesApi () {
const { showSuccess, showError, showInfo } = useToast()
const { get, post, put, delete: del } = useApi() const { get, post, put, delete: del } = useApi()
const loadMachineTypes = async () => { const loadMachineTypes = async (): Promise<void> => {
loading.value = true loading.value = true
try { try {
const result = await get('/type_machines') const result = await get('/type_machines')
if (result.success) { if (result.success) {
const items = extractCollection(result.data) const items = extractCollection(result.data)
machineTypes.value = items.map(normalizeMachineType) machineTypes.value = items
.map((item) => normalizeMachineType(item as Record<string, unknown>))
.filter((item): item is MachineType => item !== null)
showInfo(`Chargement de ${machineTypes.value.length} type(s) de machine réussi`) showInfo(`Chargement de ${machineTypes.value.length} type(s) de machine réussi`)
} }
} catch (error) { } catch (error) {
@@ -84,31 +80,32 @@ export function useMachineTypesApi () {
} }
} }
const createMachineType = async (typeData) => { const createMachineType = async (typeData: Partial<MachineType>): Promise<ApiResponse> => {
loading.value = true loading.value = true
try { try {
const result = await post('/type_machines', typeData) const result = await post('/type_machines', typeData)
if (result.success) { if (result.success) {
machineTypes.value.push(normalizeMachineType(result.data)) const normalized = normalizeMachineType(result.data as Record<string, unknown>)
if (normalized) machineTypes.value.push(normalized)
showSuccess(`Type de machine "${typeData.name}" créé avec succès`) showSuccess(`Type de machine "${typeData.name}" créé avec succès`)
} }
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la création du type de machine:', error) console.error('Erreur lors de la création du type de machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const updateMachineType = async (id, typeData) => { const updateMachineType = async (id: string, typeData: Partial<MachineType>): Promise<ApiResponse> => {
loading.value = true loading.value = true
try { try {
const result = await put(`/type_machines/${id}`, typeData) const result = await put(`/type_machines/${id}`, typeData)
if (result.success) { if (result.success) {
const normalized = normalizeMachineType(result.data) const normalized = normalizeMachineType(result.data as Record<string, unknown>)
const index = machineTypes.value.findIndex(type => type.id === id) const index = machineTypes.value.findIndex((type) => type.id === id)
if (index !== -1) { if (index !== -1 && normalized) {
machineTypes.value[index] = normalized machineTypes.value[index] = normalized
} }
showSuccess(`Type de machine "${typeData.name}" mis à jour avec succès`) showSuccess(`Type de machine "${typeData.name}" mis à jour avec succès`)
@@ -116,34 +113,34 @@ export function useMachineTypesApi () {
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la mise à jour du type de machine:', error) console.error('Erreur lors de la mise à jour du type de machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const deleteMachineType = async (id) => { const deleteMachineType = async (id: string): Promise<ApiResponse> => {
loading.value = true loading.value = true
try { try {
const result = await del(`/type_machines/${id}`) const result = await del(`/type_machines/${id}`)
if (result.success) { if (result.success) {
const deletedType = machineTypes.value.find(type => type.id === id) const deletedType = machineTypes.value.find((type) => type.id === id)
machineTypes.value = machineTypes.value.filter(type => type.id !== id) machineTypes.value = machineTypes.value.filter((type) => type.id !== id)
showSuccess(`Type de machine "${deletedType?.name || 'inconnu'}" supprimé avec succès`) showSuccess(`Type de machine "${deletedType?.name || 'inconnu'}" supprimé avec succès`)
} }
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la suppression du type de machine:', error) console.error('Erreur lors de la suppression du type de machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const getMachineTypeById = async (id, forceRefresh = false) => { const getMachineTypeById = async (id: string, forceRefresh = false): Promise<ApiResponse> => {
// D'abord chercher dans le cache local (sauf si forceRefresh) // D'abord chercher dans le cache local (sauf si forceRefresh)
if (!forceRefresh) { if (!forceRefresh) {
const localType = machineTypes.value.find(type => type.id === id) const localType = machineTypes.value.find((type) => type.id === id)
if (localType) { if (localType) {
return { success: true, data: localType } return { success: true, data: localType }
} }
@@ -153,12 +150,12 @@ export function useMachineTypesApi () {
try { try {
const result = await get(`/type_machines/${id}`) const result = await get(`/type_machines/${id}`)
if (result.success) { if (result.success) {
const normalized = normalizeMachineType(result.data) const normalized = normalizeMachineType(result.data as Record<string, unknown>)
// Mettre à jour le cache local // Mettre à jour le cache local
const index = machineTypes.value.findIndex(type => type.id === id) const index = machineTypes.value.findIndex((type) => type.id === id)
if (index !== -1) { if (index !== -1 && normalized) {
machineTypes.value[index] = normalized machineTypes.value[index] = normalized
} else { } else if (normalized) {
machineTypes.value.push(normalized) machineTypes.value.push(normalized)
} }
return { success: true, data: normalized } return { success: true, data: normalized }
@@ -166,12 +163,12 @@ export function useMachineTypesApi () {
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la récupération du type de machine:', error) console.error('Erreur lors de la récupération du type de machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} }
} }
const getMachineTypes = () => machineTypes.value const getMachineTypes = (): MachineType[] => machineTypes.value
const isLoading = () => loading.value const isLoading = (): boolean => loading.value
return { return {
machineTypes, machineTypes,
@@ -182,6 +179,6 @@ export function useMachineTypesApi () {
deleteMachineType, deleteMachineType,
getMachineTypeById, getMachineTypeById,
getMachineTypes, getMachineTypes,
isLoading isLoading,
} }
} }

View File

@@ -1,13 +1,24 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useToast } from './useToast' import { useToast } from './useToast'
import { useApi } from './useApi' import { useApi, type ApiResponse } from './useApi'
import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils' import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils'
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations' import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
import { extractCollection } from '~/shared/utils/apiHelpers'
const machines = ref([]) export interface Machine {
id: string
name?: string
siteId?: string | null
typeMachineId?: string | null
componentLinks?: unknown[]
pieceLinks?: unknown[]
[key: string]: unknown
}
const machines = ref<Machine[]>([])
const loading = ref(false) const loading = ref(false)
const resolveLinkCollection = (source, keys) => { const resolveLinkCollection = (source: Record<string, unknown>, keys: string[]): unknown[] | undefined => {
if (!source || typeof source !== 'object') { if (!source || typeof source !== 'object') {
return undefined return undefined
} }
@@ -22,16 +33,17 @@ const resolveLinkCollection = (source, keys) => {
return undefined return undefined
} }
const normalizeMachineResponse = (payload) => { const normalizeMachineResponse = (payload: unknown): Machine | null => {
if (!payload || typeof payload !== 'object') { if (!payload || typeof payload !== 'object') {
return null return null
} }
const container = payload.machine && typeof payload.machine === 'object' const raw = payload as Record<string, unknown>
? payload.machine const container = raw.machine && typeof raw.machine === 'object'
: payload ? raw.machine as Record<string, unknown>
: raw
const normalized = { ...container } const normalized: Record<string, unknown> = { ...container }
if (!normalized.siteId) { if (!normalized.siteId) {
const siteId = extractRelationId(container.site) const siteId = extractRelationId(container.site)
@@ -47,42 +59,32 @@ const normalizeMachineResponse = (payload) => {
} }
} }
const componentLinks = resolveLinkCollection(payload, ['componentLinks', 'machineComponentLinks']) ?? const componentLinks = resolveLinkCollection(raw, ['componentLinks', 'machineComponentLinks']) ??
resolveLinkCollection(container, ['componentLinks', 'machineComponentLinks']) ?? resolveLinkCollection(container, ['componentLinks', 'machineComponentLinks']) ??
[] []
const pieceLinks = resolveLinkCollection(payload, ['pieceLinks', 'machinePieceLinks']) ?? const pieceLinks = resolveLinkCollection(raw, ['pieceLinks', 'machinePieceLinks']) ??
resolveLinkCollection(container, ['pieceLinks', 'machinePieceLinks']) ?? resolveLinkCollection(container, ['pieceLinks', 'machinePieceLinks']) ??
[] []
normalized.componentLinks = componentLinks normalized.componentLinks = componentLinks
normalized.pieceLinks = pieceLinks normalized.pieceLinks = pieceLinks
return normalized return normalized as Machine
} }
export function useMachines () { export function useMachines() {
const { showSuccess, showError, showInfo } = useToast() const { showSuccess, showError, showInfo } = useToast()
const { get, post, patch, delete: del } = useApi() const { get, post, patch, delete: del } = useApi()
const loadMachines = async () => { const loadMachines = async (): Promise<void> => {
loading.value = true loading.value = true
try { try {
const result = await get('/machines') const result = await get('/machines')
if (result.success) { if (result.success) {
const machineList = Array.isArray(result.data) const machineList = extractCollection(result.data)
? result.data
: Array.isArray(result.data?.member)
? result.data.member
: Array.isArray(result.data?.['hydra:member'])
? result.data['hydra:member']
: Array.isArray(result.data?.machines)
? result.data.machines
: Array.isArray(result.data?.data)
? result.data.data
: []
const normalized = machineList const normalized = machineList
.map((item) => normalizeMachineResponse(item)) .map((item) => normalizeMachineResponse(item))
.filter(Boolean) .filter((item): item is Machine => item !== null)
machines.value = normalized machines.value = normalized
showInfo(`Chargement de ${normalized.length} machine(s) réussi`) showInfo(`Chargement de ${normalized.length} machine(s) réussi`)
} }
@@ -93,14 +95,14 @@ export function useMachines () {
} }
} }
const createMachine = async (machineData) => { const createMachine = async (machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true loading.value = true
try { try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData)) const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const result = await post('/machines', normalizedPayload) const result = await post('/machines', normalizedPayload)
if (result.success) { if (result.success) {
const createdMachine = normalizeMachineResponse(result.data) || const createdMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse(result.data?.machine) || normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
null null
if (createdMachine) { if (createdMachine) {
machines.value.push(createdMachine) machines.value.push(createdMachine)
@@ -111,34 +113,31 @@ export function useMachines () {
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la création de la machine:', error) console.error('Erreur lors de la création de la machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const createMachineFromType = async (machineData, typeMachine) => { const createMachineFromType = async (machineData: Partial<Machine>, typeMachine: { id: string }): Promise<ApiResponse> => {
// Créer la machine avec la structure héritée du type
const machineWithStructure = { const machineWithStructure = {
...machineData, ...machineData,
typeMachineId: typeMachine.id typeMachineId: typeMachine.id,
// La structure sera automatiquement héritée du type
// Les composants et pièces seront créés automatiquement
} }
return await createMachine(machineWithStructure) return await createMachine(machineWithStructure)
} }
const updateMachineData = async (id, machineData) => { const updateMachineData = async (id: string, machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true loading.value = true
try { try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData)) const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const result = await patch(`/machines/${id}`, normalizedPayload) const result = await patch(`/machines/${id}`, normalizedPayload)
if (result.success) { if (result.success) {
const updatedMachine = normalizeMachineResponse(result.data) || const updatedMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse(result.data?.machine) || normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
null null
const index = machines.value.findIndex(machine => machine.id === id) const index = machines.value.findIndex((machine) => machine.id === id)
if (index !== -1 && updatedMachine) { if (index !== -1 && updatedMachine) {
machines.value[index] = { machines.value[index] = {
...machines.value[index], ...machines.value[index],
@@ -150,13 +149,13 @@ export function useMachines () {
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la mise à jour de la machine:', error) console.error('Erreur lors de la mise à jour de la machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const reconfigureSkeleton = async (machineId, payload) => { const reconfigureSkeleton = async (machineId: string, payload: unknown): Promise<ApiResponse> => {
if (!machineId) { if (!machineId) {
return { success: false, error: 'Identifiant de machine manquant' } return { success: false, error: 'Identifiant de machine manquant' }
} }
@@ -165,28 +164,28 @@ export function useMachines () {
try { try {
const result = await patch(`/machines/${machineId}/skeleton`, payload) const result = await patch(`/machines/${machineId}/skeleton`, payload)
if (result.success) { if (result.success) {
const index = machines.value.findIndex(machine => machine.id === machineId) const index = machines.value.findIndex((machine) => machine.id === machineId)
if (index !== -1) { if (index !== -1) {
const updatedMachine = normalizeMachineResponse(result.data) || const updatedMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse(result.data?.machine) || normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
machines.value[index] machines.value[index]
machines.value[index] = { machines.value[index] = {
...machines.value[index], ...machines.value[index],
...(updatedMachine || {}), ...updatedMachine,
} } as Machine
} }
showSuccess('Structure de la machine mise à jour avec succès') showSuccess('Structure de la machine mise à jour avec succès')
} }
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la reconfiguration du squelette de la machine:', error) console.error('Erreur lors de la reconfiguration du squelette de la machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const addMissingCustomFields = async (machineId, { showToast: shouldShowToast = true } = {}) => { const addMissingCustomFields = async (machineId: string, { showToast: shouldShowToast = true } = {}): Promise<ApiResponse> => {
if (!machineId) { if (!machineId) {
const error = 'Identifiant de machine manquant' const error = 'Identifiant de machine manquant'
if (shouldShowToast) { if (shouldShowToast) {
@@ -206,46 +205,46 @@ export function useMachines () {
} }
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de lajout des champs personnalisés manquants:', error) console.error('Erreur lors de l\'ajout des champs personnalisés manquants:', error)
if (shouldShowToast) { if (shouldShowToast) {
showError('Erreur lors de la complétion des champs personnalisés') showError('Erreur lors de la complétion des champs personnalisés')
} }
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} }
} }
const deleteMachine = async (id) => { const deleteMachine = async (id: string): Promise<ApiResponse> => {
loading.value = true loading.value = true
try { try {
const result = await del(`/machines/${id}`) const result = await del(`/machines/${id}`)
if (result.success) { if (result.success) {
const deletedMachine = machines.value.find(machine => machine.id === id) const deletedMachine = machines.value.find((machine) => machine.id === id)
machines.value = machines.value.filter(machine => machine.id !== id) machines.value = machines.value.filter((machine) => machine.id !== id)
showSuccess(`Machine "${deletedMachine?.name || 'inconnu'}" supprimée avec succès`) showSuccess(`Machine "${deletedMachine?.name || 'inconnu'}" supprimée avec succès`)
} }
return result return result
} catch (error) { } catch (error) {
console.error('Erreur lors de la suppression de la machine:', error) console.error('Erreur lors de la suppression de la machine:', error)
return { success: false, error: error.message } return { success: false, error: (error as Error).message }
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const getMachineById = (id) => { const getMachineById = (id: string): Machine | undefined => {
return machines.value.find(machine => machine.id === id) return machines.value.find((machine) => machine.id === id)
} }
const getMachinesBySite = (siteId) => { const getMachinesBySite = (siteId: string): Machine[] => {
return machines.value.filter(machine => machine.siteId === siteId) return machines.value.filter((machine) => machine.siteId === siteId)
} }
const getMachinesByType = (typeMachineId) => { const getMachinesByType = (typeMachineId: string): Machine[] => {
return machines.value.filter(machine => machine.typeMachineId === typeMachineId) return machines.value.filter((machine) => machine.typeMachineId === typeMachineId)
} }
const getMachines = () => machines.value const getMachines = (): Machine[] => machines.value
const isLoading = () => loading.value const isLoading = (): boolean => loading.value
return { return {
machines, machines,
@@ -261,6 +260,6 @@ export function useMachines () {
getMachinesByType, getMachinesByType,
getMachines, getMachines,
isLoading, isLoading,
addMissingCustomFields addMissingCustomFields,
} }
} }

View File

@@ -1,47 +0,0 @@
import { ref, computed } from 'vue'
let hasWarned = false
const warnDeprecated = () => {
if (hasWarned) return
if (process.dev) {
console.warn('[usePieceModels] Ce composable est conservé pour compatibilité mais les modèles ont été remplacés par les catégories enrichies de squelette. Utilisez usePieceTypes / usePieces à la place.')
}
hasWarned = true
}
const buildUnsupportedResult = () => ({
success: false,
error: 'Les modèles de pièces ont été retirés. Gérez les squelettes via les catégories et utilisez les requirements machine pour instancier des pièces.'
})
export function usePieceModels () {
warnDeprecated()
const pieceModelsBuckets = ref({})
const loadingPieceModels = ref(false)
const pieceModels = computed(() => [])
const noLongerSupported = async () => {
warnDeprecated()
return buildUnsupportedResult()
}
const getPieceModels = () => pieceModels.value
const getPieceModelsForType = () => []
const isPieceModelLoading = () => loadingPieceModels.value
return {
pieceModels,
pieceModelsBuckets,
loadingPieceModels,
loadPieceModels: noLongerSupported,
createPieceModel: noLongerSupported,
updatePieceModel: noLongerSupported,
deletePieceModel: noLongerSupported,
getPieceModels,
getPieceModelsForType,
isPieceModelLoading
}
}

View File

@@ -1,35 +1,37 @@
import { useState, useRequestHeaders, useRuntimeConfig } from '#imports' import { useState, useRequestHeaders, useRuntimeConfig } from '#imports'
import type { Profile } from './useProfiles'
const buildUrl = (path) => { const buildUrl = (path: string): string => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseUrl = process.server const baseUrl = import.meta.server
? (config.apiBaseUrl || config.public.apiBaseUrl || '') ? ((config.apiBaseUrl as string) || (config.public.apiBaseUrl as string) || '')
: (config.public.apiBaseUrl || '') : ((config.public.apiBaseUrl as string) || '')
const base = baseUrl.replace(/\/$/, '') const base = baseUrl.replace(/\/$/, '')
return `${base}${path}` return `${base}${path}`
} }
export function useProfileSession () { export function useProfileSession() {
const activeProfile = useState('profileSession:active', () => null) const activeProfile = useState<Profile | null>('profileSession:active', () => null)
const sessionLoaded = useState('profileSession:loaded', () => false) const sessionLoaded = useState<boolean>('profileSession:loaded', () => false)
const loading = useState('profileSession:loading', () => false) const loading = useState<boolean>('profileSession:loading', () => false)
const getSessionHeaders = () => { const getSessionHeaders = (): Record<string, string> | undefined => {
if (!process.server) { return undefined } if (!import.meta.server) { return undefined }
const headers = useRequestHeaders(['cookie']) const headers = useRequestHeaders(['cookie'])
return headers?.cookie ? { cookie: headers.cookie } : undefined return headers?.cookie ? { cookie: headers.cookie } : undefined
} }
const fetchCurrentProfile = async () => { const fetchCurrentProfile = async (): Promise<Profile | null> => {
loading.value = true loading.value = true
try { try {
activeProfile.value = await $fetch(buildUrl('/session/profile'), { activeProfile.value = await $fetch<Profile>(buildUrl('/session/profile'), {
method: 'GET', method: 'GET',
credentials: 'include', credentials: 'include',
headers: getSessionHeaders() headers: getSessionHeaders(),
}) })
} catch (error) { } catch (error) {
if (error?.status === 401) { const err = error as { status?: number }
if (err?.status === 401) {
activeProfile.value = null activeProfile.value = null
} else { } else {
console.error('Erreur lors du chargement du profil actif', error) console.error('Erreur lors du chargement du profil actif', error)
@@ -42,29 +44,29 @@ export function useProfileSession () {
return activeProfile.value return activeProfile.value
} }
const ensureSession = () => { const ensureSession = (): Promise<Profile | null> => {
if (!sessionLoaded.value) { if (!sessionLoaded.value) {
return fetchCurrentProfile() return fetchCurrentProfile()
} }
return Promise.resolve(activeProfile.value) return Promise.resolve(activeProfile.value)
} }
const activateProfile = async (profileId) => { const activateProfile = async (profileId: string): Promise<void> => {
await $fetch(buildUrl('/session/profile'), { await $fetch(buildUrl('/session/profile'), {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
body: { profileId }, body: { profileId },
headers: getSessionHeaders() headers: getSessionHeaders(),
}) })
await fetchCurrentProfile() await fetchCurrentProfile()
} }
const logout = async () => { const logout = async (): Promise<void> => {
try { try {
await $fetch(buildUrl('/session/profile'), { await $fetch(buildUrl('/session/profile'), {
method: 'DELETE', method: 'DELETE',
credentials: 'include', credentials: 'include',
headers: getSessionHeaders() headers: getSessionHeaders(),
}) })
} finally { } finally {
activeProfile.value = null activeProfile.value = null
@@ -79,6 +81,6 @@ export function useProfileSession () {
ensureSession, ensureSession,
fetchCurrentProfile, fetchCurrentProfile,
activateProfile, activateProfile,
logout logout,
} }
} }

View File

@@ -1,67 +0,0 @@
import { useState, useRequestHeaders, useRuntimeConfig } from '#imports'
const buildUrl = (path) => {
const config = useRuntimeConfig()
const base = config.public.apiBaseUrl?.replace(/\/$/, '') || ''
return `${base}${path}`
}
export function useProfiles () {
const profiles = useState('profiles:list', () => [])
const loadingProfiles = useState('profiles:loading', () => false)
const profilesLoaded = useState('profiles:loaded', () => false)
const getSessionHeaders = () => {
if (!process.server) { return undefined }
const headers = useRequestHeaders(['cookie'])
return headers?.cookie ? { cookie: headers.cookie } : undefined
}
const fetchProfiles = async () => {
loadingProfiles.value = true
try {
profiles.value = await $fetch(buildUrl('/session/profiles'), {
method: 'GET',
credentials: 'include',
headers: getSessionHeaders()
})
profilesLoaded.value = true
} catch (error) {
console.error('Erreur lors du chargement des profils', error)
profiles.value = []
profilesLoaded.value = false
} finally {
loadingProfiles.value = false
}
return profiles.value
}
const createProfile = async ({ firstName, lastName }) => {
const profile = await $fetch(buildUrl('/session/profiles'), {
method: 'POST',
credentials: 'include',
body: { firstName, lastName },
headers: getSessionHeaders()
})
await fetchProfiles()
return profile
}
const deleteProfile = async (profileId) => {
await $fetch(buildUrl(`/session/profiles/${profileId}`), {
method: 'DELETE',
credentials: 'include',
headers: getSessionHeaders()
})
await fetchProfiles()
}
return {
profiles,
loadingProfiles,
profilesLoaded,
fetchProfiles,
createProfile,
deleteProfile
}
}

View File

@@ -0,0 +1,74 @@
import { useState, useRequestHeaders, useRuntimeConfig } from '#imports'
export interface Profile {
id: string
firstName: string
lastName: string
[key: string]: unknown
}
const buildUrl = (path: string): string => {
const config = useRuntimeConfig()
const base = (config.public.apiBaseUrl as string)?.replace(/\/$/, '') || ''
return `${base}${path}`
}
export function useProfiles() {
const profiles = useState<Profile[]>('profiles:list', () => [])
const loadingProfiles = useState<boolean>('profiles:loading', () => false)
const profilesLoaded = useState<boolean>('profiles:loaded', () => false)
const getSessionHeaders = (): Record<string, string> | undefined => {
if (!import.meta.server) { return undefined }
const headers = useRequestHeaders(['cookie'])
return headers?.cookie ? { cookie: headers.cookie } : undefined
}
const fetchProfiles = async (): Promise<Profile[]> => {
loadingProfiles.value = true
try {
profiles.value = await $fetch<Profile[]>(buildUrl('/session/profiles'), {
method: 'GET',
credentials: 'include',
headers: getSessionHeaders(),
})
profilesLoaded.value = true
} catch (error) {
console.error('Erreur lors du chargement des profils', error)
profiles.value = []
profilesLoaded.value = false
} finally {
loadingProfiles.value = false
}
return profiles.value
}
const createProfile = async ({ firstName, lastName }: { firstName: string; lastName: string }): Promise<Profile> => {
const profile = await $fetch<Profile>(buildUrl('/session/profiles'), {
method: 'POST',
credentials: 'include',
body: { firstName, lastName },
headers: getSessionHeaders(),
})
await fetchProfiles()
return profile
}
const deleteProfile = async (profileId: string): Promise<void> => {
await $fetch(buildUrl(`/session/profiles/${profileId}`), {
method: 'DELETE',
credentials: 'include',
headers: getSessionHeaders(),
})
await fetchProfiles()
}
return {
profiles,
loadingProfiles,
profilesLoaded,
fetchProfiles,
createProfile,
deleteProfile,
}
}

View File

@@ -1,17 +1,26 @@
import { ref } from 'vue' import { ref } from 'vue'
const toasts = ref([]) export type ToastType = 'success' | 'error' | 'warning' | 'info'
export interface Toast {
id: number
message: string
type: ToastType
visible: boolean
}
const toasts = ref<Toast[]>([])
const MAX_TOASTS = 3 const MAX_TOASTS = 3
let nextId = 1 let nextId = 1
export function useToast () { export function useToast() {
const showToast = (message, type = 'info', duration = 3500) => { const showToast = (message: string, type: ToastType = 'info', duration = 3500): number => {
const id = nextId++ const id = nextId++
const toast = { const toast: Toast = {
id, id,
message, message,
type, type,
visible: true visible: true,
} }
if (toasts.value.length >= MAX_TOASTS) { if (toasts.value.length >= MAX_TOASTS) {
@@ -28,25 +37,25 @@ export function useToast () {
return id return id
} }
const showSuccess = (message, duration = 5000) => { const showSuccess = (message: string, duration = 5000): number => {
return showToast(message, 'success', duration) return showToast(message, 'success', duration)
} }
const showError = (message, duration = 5000) => { const showError = (message: string, duration = 5000): number => {
return showToast(message, 'error', duration) return showToast(message, 'error', duration)
} }
const showWarning = (message, duration = 6000) => { const showWarning = (message: string, duration = 6000): number => {
return showToast(message, 'warning', duration) return showToast(message, 'warning', duration)
} }
const showInfo = (message, duration = 5000) => { const showInfo = (message: string, duration = 5000): number => {
return showToast(message, 'info', duration) return showToast(message, 'info', duration)
} }
const removeToast = (id) => { const removeToast = (id: number): void => {
const index = toasts.value.findIndex(toast => toast.id === id) const index = toasts.value.findIndex((toast) => toast.id === id)
if (index !== -1) { if (index !== -1 && toasts.value[index]) {
toasts.value[index].visible = false toasts.value[index].visible = false
setTimeout(() => { setTimeout(() => {
toasts.value.splice(index, 1) toasts.value.splice(index, 1)
@@ -54,7 +63,7 @@ export function useToast () {
} }
} }
const clearAll = () => { const clearAll = (): void => {
toasts.value = [] toasts.value = []
} }
@@ -66,6 +75,6 @@ export function useToast () {
showWarning, showWarning,
showInfo, showInfo,
removeToast, removeToast,
clearAll clearAll,
} }
} }