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 { 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 customFieldValues = ref([])
const customFieldValues = ref<CustomFieldValue[]>([])
const loading = ref(false)
// Créer une valeur de champ personnalisé
const createCustomFieldValue = async (customFieldValueData) => {
const createCustomFieldValue = async (customFieldValueData: Record<string, unknown>): Promise<ApiResponse> => {
try {
const result = await apiCall('/custom-fields/values', {
method: 'POST',
body: JSON.stringify(customFieldValueData)
body: JSON.stringify(customFieldValueData),
})
return result
} catch (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é
const getCustomFieldValuesByEntity = async (entityType, entityId) => {
const getCustomFieldValuesByEntity = async (entityType: string, entityId: string): Promise<ApiResponse> => {
try {
loading.value = true
const result = await apiCall(`/custom-fields/values/${entityType}/${entityId}`, {
method: 'GET'
method: 'GET',
})
if (result.success) {
customFieldValues.value = result.data
customFieldValues.value = result.data as CustomFieldValue[]
}
return result
} catch (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 {
loading.value = false
}
}
// Mettre à jour une valeur de champ personnalisé
const updateCustomFieldValue = async (id, updateData) => {
const updateCustomFieldValue = async (id: string, updateData: Record<string, unknown>): Promise<ApiResponse> => {
try {
const result = await apiCall(`/custom-fields/values/${id}`, {
method: 'PATCH',
body: JSON.stringify(updateData)
body: JSON.stringify(updateData),
})
return result
} catch (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é
const upsertCustomFieldValue = async (
customFieldId,
entityType,
entityId,
value,
metadata = {},
) => {
customFieldId: string | null,
entityType: string,
entityId: string,
value: unknown,
metadata: Record<string, unknown> = {},
): Promise<ApiResponse> => {
try {
const result = await apiCall('/custom-fields/values/upsert', {
method: 'POST',
@@ -69,26 +78,26 @@ export function useCustomFields () {
entityType,
entityId,
value,
...metadata
})
...metadata,
}),
})
return result
} catch (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é
const deleteCustomFieldValue = async (id) => {
const deleteCustomFieldValue = async (id: string): Promise<ApiResponse> => {
try {
const result = await apiCall(`/custom-fields/values/${id}`, {
method: 'DELETE'
method: 'DELETE',
})
return result
} catch (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,
updateCustomFieldValue,
upsertCustomFieldValue,
deleteCustomFieldValue
deleteCustomFieldValue,
}
}

View File

@@ -1,80 +1,76 @@
import { ref } from 'vue'
import { useToast } from './useToast'
import { useApi } from './useApi'
import { useApi, type ApiResponse } from './useApi'
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 normalizeRequirementList = (value, relationKey) => {
const normalizeRequirementList = (value: unknown, relationKey: string): MachineTypeRequirement[] => {
if (!Array.isArray(value)) {
return []
}
return value.map((entry, index) => {
return value.map((entry: Record<string, unknown>, _index: number) => {
if (!entry || typeof entry !== 'object') {
return entry
}
const normalized = { ...entry }
const relationField = relationKey.replace('Id', '')
const relationValue = normalized[relationField]
console.log(`[normalizeRequirementList] Entry ${index}:`, {
relationKey,
relationField,
hasRelationKey: !!normalized[relationKey],
relationValue,
relationValueType: typeof relationValue
})
if (relationKey && !normalized[relationKey]) {
const relationId = extractRelationId(relationValue)
console.log(`[normalizeRequirementList] Extracted ID:`, relationId)
if (relationId) {
normalized[relationKey] = relationId
}
}
console.log(`[normalizeRequirementList] Normalized entry:`, normalized)
return normalized
return normalized as MachineTypeRequirement
})
}
const normalizeMachineType = (type) => {
const normalizeMachineType = (type: Record<string, unknown>): MachineType | null => {
if (!type || typeof type !== 'object') {
return type
return null
}
return {
...type,
componentRequirements: normalizeRequirementList(type.componentRequirements, 'typeComposantId'),
pieceRequirements: normalizeRequirementList(type.pieceRequirements, 'typePieceId'),
productRequirements: normalizeRequirementList(type.productRequirements, 'typeProductId'),
}
} as MachineType
}
const extractCollection = (payload) => {
if (Array.isArray(payload)) {
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()
export function useMachineTypesApi() {
const { showSuccess, showInfo } = useToast()
const { get, post, put, delete: del } = useApi()
const loadMachineTypes = async () => {
const loadMachineTypes = async (): Promise<void> => {
loading.value = true
try {
const result = await get('/type_machines')
if (result.success) {
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`)
}
} catch (error) {
@@ -84,31 +80,32 @@ export function useMachineTypesApi () {
}
}
const createMachineType = async (typeData) => {
const createMachineType = async (typeData: Partial<MachineType>): Promise<ApiResponse> => {
loading.value = true
try {
const result = await post('/type_machines', typeData)
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`)
}
return result
} catch (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 {
loading.value = false
}
}
const updateMachineType = async (id, typeData) => {
const updateMachineType = async (id: string, typeData: Partial<MachineType>): Promise<ApiResponse> => {
loading.value = true
try {
const result = await put(`/type_machines/${id}`, typeData)
if (result.success) {
const normalized = normalizeMachineType(result.data)
const index = machineTypes.value.findIndex(type => type.id === id)
if (index !== -1) {
const normalized = normalizeMachineType(result.data as Record<string, unknown>)
const index = machineTypes.value.findIndex((type) => type.id === id)
if (index !== -1 && normalized) {
machineTypes.value[index] = normalized
}
showSuccess(`Type de machine "${typeData.name}" mis à jour avec succès`)
@@ -116,34 +113,34 @@ export function useMachineTypesApi () {
return result
} catch (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 {
loading.value = false
}
}
const deleteMachineType = async (id) => {
const deleteMachineType = async (id: string): Promise<ApiResponse> => {
loading.value = true
try {
const result = await del(`/type_machines/${id}`)
if (result.success) {
const deletedType = machineTypes.value.find(type => type.id === id)
machineTypes.value = machineTypes.value.filter(type => type.id !== id)
const deletedType = machineTypes.value.find((type) => type.id === id)
machineTypes.value = machineTypes.value.filter((type) => type.id !== id)
showSuccess(`Type de machine "${deletedType?.name || 'inconnu'}" supprimé avec succès`)
}
return result
} catch (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 {
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)
if (!forceRefresh) {
const localType = machineTypes.value.find(type => type.id === id)
const localType = machineTypes.value.find((type) => type.id === id)
if (localType) {
return { success: true, data: localType }
}
@@ -153,12 +150,12 @@ export function useMachineTypesApi () {
try {
const result = await get(`/type_machines/${id}`)
if (result.success) {
const normalized = normalizeMachineType(result.data)
const normalized = normalizeMachineType(result.data as Record<string, unknown>)
// Mettre à jour le cache local
const index = machineTypes.value.findIndex(type => type.id === id)
if (index !== -1) {
const index = machineTypes.value.findIndex((type) => type.id === id)
if (index !== -1 && normalized) {
machineTypes.value[index] = normalized
} else {
} else if (normalized) {
machineTypes.value.push(normalized)
}
return { success: true, data: normalized }
@@ -166,12 +163,12 @@ export function useMachineTypesApi () {
return result
} catch (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 isLoading = () => loading.value
const getMachineTypes = (): MachineType[] => machineTypes.value
const isLoading = (): boolean => loading.value
return {
machineTypes,
@@ -182,6 +179,6 @@ export function useMachineTypesApi () {
deleteMachineType,
getMachineTypeById,
getMachineTypes,
isLoading
isLoading,
}
}

View File

@@ -1,13 +1,24 @@
import { ref } from 'vue'
import { useToast } from './useToast'
import { useApi } from './useApi'
import { useApi, type ApiResponse } from './useApi'
import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils'
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 resolveLinkCollection = (source, keys) => {
const resolveLinkCollection = (source: Record<string, unknown>, keys: string[]): unknown[] | undefined => {
if (!source || typeof source !== 'object') {
return undefined
}
@@ -22,16 +33,17 @@ const resolveLinkCollection = (source, keys) => {
return undefined
}
const normalizeMachineResponse = (payload) => {
const normalizeMachineResponse = (payload: unknown): Machine | null => {
if (!payload || typeof payload !== 'object') {
return null
}
const container = payload.machine && typeof payload.machine === 'object'
? payload.machine
: payload
const raw = payload as Record<string, unknown>
const container = raw.machine && typeof raw.machine === 'object'
? raw.machine as Record<string, unknown>
: raw
const normalized = { ...container }
const normalized: Record<string, unknown> = { ...container }
if (!normalized.siteId) {
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']) ??
[]
const pieceLinks = resolveLinkCollection(payload, ['pieceLinks', 'machinePieceLinks']) ??
const pieceLinks = resolveLinkCollection(raw, ['pieceLinks', 'machinePieceLinks']) ??
resolveLinkCollection(container, ['pieceLinks', 'machinePieceLinks']) ??
[]
normalized.componentLinks = componentLinks
normalized.pieceLinks = pieceLinks
return normalized
return normalized as Machine
}
export function useMachines () {
export function useMachines() {
const { showSuccess, showError, showInfo } = useToast()
const { get, post, patch, delete: del } = useApi()
const loadMachines = async () => {
const loadMachines = async (): Promise<void> => {
loading.value = true
try {
const result = await get('/machines')
if (result.success) {
const machineList = Array.isArray(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 machineList = extractCollection(result.data)
const normalized = machineList
.map((item) => normalizeMachineResponse(item))
.filter(Boolean)
.filter((item): item is Machine => item !== null)
machines.value = normalized
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
try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const result = await post('/machines', normalizedPayload)
if (result.success) {
const createdMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse(result.data?.machine) ||
normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
null
if (createdMachine) {
machines.value.push(createdMachine)
@@ -111,34 +113,31 @@ export function useMachines () {
return result
} catch (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 {
loading.value = false
}
}
const createMachineFromType = async (machineData, typeMachine) => {
// Créer la machine avec la structure héritée du type
const createMachineFromType = async (machineData: Partial<Machine>, typeMachine: { id: string }): Promise<ApiResponse> => {
const machineWithStructure = {
...machineData,
typeMachineId: typeMachine.id
// La structure sera automatiquement héritée du type
// Les composants et pièces seront créés automatiquement
typeMachineId: typeMachine.id,
}
return await createMachine(machineWithStructure)
}
const updateMachineData = async (id, machineData) => {
const updateMachineData = async (id: string, machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true
try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const result = await patch(`/machines/${id}`, normalizedPayload)
if (result.success) {
const updatedMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse(result.data?.machine) ||
normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
null
const index = machines.value.findIndex(machine => machine.id === id)
const index = machines.value.findIndex((machine) => machine.id === id)
if (index !== -1 && updatedMachine) {
machines.value[index] = {
...machines.value[index],
@@ -150,13 +149,13 @@ export function useMachines () {
return result
} catch (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 {
loading.value = false
}
}
const reconfigureSkeleton = async (machineId, payload) => {
const reconfigureSkeleton = async (machineId: string, payload: unknown): Promise<ApiResponse> => {
if (!machineId) {
return { success: false, error: 'Identifiant de machine manquant' }
}
@@ -165,28 +164,28 @@ export function useMachines () {
try {
const result = await patch(`/machines/${machineId}/skeleton`, payload)
if (result.success) {
const index = machines.value.findIndex(machine => machine.id === machineId)
const index = machines.value.findIndex((machine) => machine.id === machineId)
if (index !== -1) {
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],
...(updatedMachine || {}),
}
...updatedMachine,
} as Machine
}
showSuccess('Structure de la machine mise à jour avec succès')
}
return result
} catch (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 {
loading.value = false
}
}
const addMissingCustomFields = async (machineId, { showToast: shouldShowToast = true } = {}) => {
const addMissingCustomFields = async (machineId: string, { showToast: shouldShowToast = true } = {}): Promise<ApiResponse> => {
if (!machineId) {
const error = 'Identifiant de machine manquant'
if (shouldShowToast) {
@@ -206,46 +205,46 @@ export function useMachines () {
}
return result
} 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) {
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
try {
const result = await del(`/machines/${id}`)
if (result.success) {
const deletedMachine = machines.value.find(machine => machine.id === id)
machines.value = machines.value.filter(machine => machine.id !== id)
const deletedMachine = machines.value.find((machine) => machine.id === id)
machines.value = machines.value.filter((machine) => machine.id !== id)
showSuccess(`Machine "${deletedMachine?.name || 'inconnu'}" supprimée avec succès`)
}
return result
} catch (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 {
loading.value = false
}
}
const getMachineById = (id) => {
return machines.value.find(machine => machine.id === id)
const getMachineById = (id: string): Machine | undefined => {
return machines.value.find((machine) => machine.id === id)
}
const getMachinesBySite = (siteId) => {
return machines.value.filter(machine => machine.siteId === siteId)
const getMachinesBySite = (siteId: string): Machine[] => {
return machines.value.filter((machine) => machine.siteId === siteId)
}
const getMachinesByType = (typeMachineId) => {
return machines.value.filter(machine => machine.typeMachineId === typeMachineId)
const getMachinesByType = (typeMachineId: string): Machine[] => {
return machines.value.filter((machine) => machine.typeMachineId === typeMachineId)
}
const getMachines = () => machines.value
const isLoading = () => loading.value
const getMachines = (): Machine[] => machines.value
const isLoading = (): boolean => loading.value
return {
machines,
@@ -261,6 +260,6 @@ export function useMachines () {
getMachinesByType,
getMachines,
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 type { Profile } from './useProfiles'
const buildUrl = (path) => {
const buildUrl = (path: string): string => {
const config = useRuntimeConfig()
const baseUrl = process.server
? (config.apiBaseUrl || config.public.apiBaseUrl || '')
: (config.public.apiBaseUrl || '')
const baseUrl = import.meta.server
? ((config.apiBaseUrl as string) || (config.public.apiBaseUrl as string) || '')
: ((config.public.apiBaseUrl as string) || '')
const base = baseUrl.replace(/\/$/, '')
return `${base}${path}`
}
export function useProfileSession () {
const activeProfile = useState('profileSession:active', () => null)
const sessionLoaded = useState('profileSession:loaded', () => false)
const loading = useState('profileSession:loading', () => false)
export function useProfileSession() {
const activeProfile = useState<Profile | null>('profileSession:active', () => null)
const sessionLoaded = useState<boolean>('profileSession:loaded', () => false)
const loading = useState<boolean>('profileSession:loading', () => false)
const getSessionHeaders = () => {
if (!process.server) { return undefined }
const getSessionHeaders = (): Record<string, string> | undefined => {
if (!import.meta.server) { return undefined }
const headers = useRequestHeaders(['cookie'])
return headers?.cookie ? { cookie: headers.cookie } : undefined
}
const fetchCurrentProfile = async () => {
const fetchCurrentProfile = async (): Promise<Profile | null> => {
loading.value = true
try {
activeProfile.value = await $fetch(buildUrl('/session/profile'), {
activeProfile.value = await $fetch<Profile>(buildUrl('/session/profile'), {
method: 'GET',
credentials: 'include',
headers: getSessionHeaders()
headers: getSessionHeaders(),
})
} catch (error) {
if (error?.status === 401) {
const err = error as { status?: number }
if (err?.status === 401) {
activeProfile.value = null
} else {
console.error('Erreur lors du chargement du profil actif', error)
@@ -42,29 +44,29 @@ export function useProfileSession () {
return activeProfile.value
}
const ensureSession = () => {
const ensureSession = (): Promise<Profile | null> => {
if (!sessionLoaded.value) {
return fetchCurrentProfile()
}
return Promise.resolve(activeProfile.value)
}
const activateProfile = async (profileId) => {
const activateProfile = async (profileId: string): Promise<void> => {
await $fetch(buildUrl('/session/profile'), {
method: 'POST',
credentials: 'include',
body: { profileId },
headers: getSessionHeaders()
headers: getSessionHeaders(),
})
await fetchCurrentProfile()
}
const logout = async () => {
const logout = async (): Promise<void> => {
try {
await $fetch(buildUrl('/session/profile'), {
method: 'DELETE',
credentials: 'include',
headers: getSessionHeaders()
headers: getSessionHeaders(),
})
} finally {
activeProfile.value = null
@@ -79,6 +81,6 @@ export function useProfileSession () {
ensureSession,
fetchCurrentProfile,
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'
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
let nextId = 1
export function useToast () {
const showToast = (message, type = 'info', duration = 3500) => {
export function useToast() {
const showToast = (message: string, type: ToastType = 'info', duration = 3500): number => {
const id = nextId++
const toast = {
const toast: Toast = {
id,
message,
type,
visible: true
visible: true,
}
if (toasts.value.length >= MAX_TOASTS) {
@@ -28,25 +37,25 @@ export function useToast () {
return id
}
const showSuccess = (message, duration = 5000) => {
const showSuccess = (message: string, duration = 5000): number => {
return showToast(message, 'success', duration)
}
const showError = (message, duration = 5000) => {
const showError = (message: string, duration = 5000): number => {
return showToast(message, 'error', duration)
}
const showWarning = (message, duration = 6000) => {
const showWarning = (message: string, duration = 6000): number => {
return showToast(message, 'warning', duration)
}
const showInfo = (message, duration = 5000) => {
const showInfo = (message: string, duration = 5000): number => {
return showToast(message, 'info', duration)
}
const removeToast = (id) => {
const index = toasts.value.findIndex(toast => toast.id === id)
if (index !== -1) {
const removeToast = (id: number): void => {
const index = toasts.value.findIndex((toast) => toast.id === id)
if (index !== -1 && toasts.value[index]) {
toasts.value[index].visible = false
setTimeout(() => {
toasts.value.splice(index, 1)
@@ -54,7 +63,7 @@ export function useToast () {
}
}
const clearAll = () => {
const clearAll = (): void => {
toasts.value = []
}
@@ -66,6 +75,6 @@ export function useToast () {
showWarning,
showInfo,
removeToast,
clearAll
clearAll,
}
}