Files
Inventory/app/composables/useApi.ts
Matthieu 78718b85ae 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>
2026-02-09 11:13:09 +01:00

129 lines
4.1 KiB
TypeScript

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,
}
}