diff --git a/app/composables/useApi.js b/app/composables/useApi.js deleted file mode 100644 index 14d4ec2..0000000 --- a/app/composables/useApi.js +++ /dev/null @@ -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 - } -} diff --git a/app/composables/useApi.ts b/app/composables/useApi.ts new file mode 100644 index 0000000..d9e2378 --- /dev/null +++ b/app/composables/useApi.ts @@ -0,0 +1,128 @@ +import { useToast } from './useToast' + +export interface ApiResponse { + success: boolean + data?: T + error?: string + status?: number +} + +interface ApiCallOptions extends RequestInit { + headers?: Record +} + +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 (endpoint: string, options: ApiCallOptions = {}): Promise> => { + 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 = {} + 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 (endpoint: string): Promise> => { + return apiCall(endpoint, { method: 'GET' }) + } + + const post = async (endpoint: string, data?: unknown): Promise> => { + return apiCall(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/ld+json', + }, + body: data !== undefined ? JSON.stringify(data) : undefined, + }) + } + + const patch = async (endpoint: string, data?: unknown): Promise> => { + return apiCall(endpoint, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/merge-patch+json', + }, + body: data !== undefined ? JSON.stringify(data) : undefined, + }) + } + + const put = async (endpoint: string, data?: unknown): Promise> => { + return apiCall(endpoint, { + method: 'PUT', + headers: { + 'Content-Type': 'application/ld+json', + }, + body: data !== undefined ? JSON.stringify(data) : undefined, + }) + } + + const del = async (endpoint: string): Promise> => { + return apiCall(endpoint, { method: 'DELETE' }) + } + + return { + apiCall, + get, + post, + patch, + put, + delete: del, + } +} diff --git a/app/composables/useComponentModels.js b/app/composables/useComponentModels.js deleted file mode 100644 index 1a4a1f9..0000000 --- a/app/composables/useComponentModels.js +++ /dev/null @@ -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 - } -} diff --git a/app/composables/useCustomFields.js b/app/composables/useCustomFields.ts similarity index 58% rename from app/composables/useCustomFields.js rename to app/composables/useCustomFields.ts index faa6d50..132a1a9 100644 --- a/app/composables/useCustomFields.js +++ b/app/composables/useCustomFields.ts @@ -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([]) const loading = ref(false) // Créer une valeur de champ personnalisé - const createCustomFieldValue = async (customFieldValueData) => { + const createCustomFieldValue = async (customFieldValueData: Record): Promise => { 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 => { 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): Promise => { 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 = {}, + ): Promise => { 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 => { 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, } } diff --git a/app/composables/useMachineTypesApi.js b/app/composables/useMachineTypesApi.ts similarity index 55% rename from app/composables/useMachineTypesApi.js rename to app/composables/useMachineTypesApi.ts index 5712f60..3e5a867 100644 --- a/app/composables/useMachineTypesApi.js +++ b/app/composables/useMachineTypesApi.ts @@ -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([]) 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, _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): 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 => { 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)) + .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): Promise => { 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) + 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): Promise => { 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) + 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 => { 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 => { // 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) // 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, } } diff --git a/app/composables/useMachines.js b/app/composables/useMachines.ts similarity index 61% rename from app/composables/useMachines.js rename to app/composables/useMachines.ts index 35141df..48ff8f4 100644 --- a/app/composables/useMachines.js +++ b/app/composables/useMachines.ts @@ -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([]) const loading = ref(false) -const resolveLinkCollection = (source, keys) => { +const resolveLinkCollection = (source: Record, 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 + const container = raw.machine && typeof raw.machine === 'object' + ? raw.machine as Record + : raw - const normalized = { ...container } + const normalized: Record = { ...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 => { 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): Promise => { 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)?.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, typeMachine: { id: string }): Promise => { 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): Promise => { 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)?.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 => { 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)?.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 => { 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 l’ajout 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 => { 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, } } diff --git a/app/composables/usePieceModels.js b/app/composables/usePieceModels.js deleted file mode 100644 index b6d5e0c..0000000 --- a/app/composables/usePieceModels.js +++ /dev/null @@ -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 - } -} diff --git a/app/composables/useProfileSession.js b/app/composables/useProfileSession.ts similarity index 53% rename from app/composables/useProfileSession.js rename to app/composables/useProfileSession.ts index 83be2f2..0d4c257 100644 --- a/app/composables/useProfileSession.js +++ b/app/composables/useProfileSession.ts @@ -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('profileSession:active', () => null) + const sessionLoaded = useState('profileSession:loaded', () => false) + const loading = useState('profileSession:loading', () => false) - const getSessionHeaders = () => { - if (!process.server) { return undefined } + const getSessionHeaders = (): Record | 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 => { loading.value = true try { - activeProfile.value = await $fetch(buildUrl('/session/profile'), { + activeProfile.value = await $fetch(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 => { if (!sessionLoaded.value) { return fetchCurrentProfile() } return Promise.resolve(activeProfile.value) } - const activateProfile = async (profileId) => { + const activateProfile = async (profileId: string): Promise => { await $fetch(buildUrl('/session/profile'), { method: 'POST', credentials: 'include', body: { profileId }, - headers: getSessionHeaders() + headers: getSessionHeaders(), }) await fetchCurrentProfile() } - const logout = async () => { + const logout = async (): Promise => { 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, } } diff --git a/app/composables/useProfiles.js b/app/composables/useProfiles.js deleted file mode 100644 index ad1c80d..0000000 --- a/app/composables/useProfiles.js +++ /dev/null @@ -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 - } -} diff --git a/app/composables/useProfiles.ts b/app/composables/useProfiles.ts new file mode 100644 index 0000000..945ec4c --- /dev/null +++ b/app/composables/useProfiles.ts @@ -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('profiles:list', () => []) + const loadingProfiles = useState('profiles:loading', () => false) + const profilesLoaded = useState('profiles:loaded', () => false) + + const getSessionHeaders = (): Record | undefined => { + if (!import.meta.server) { return undefined } + const headers = useRequestHeaders(['cookie']) + return headers?.cookie ? { cookie: headers.cookie } : undefined + } + + const fetchProfiles = async (): Promise => { + 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 }: { firstName: string; lastName: string }): Promise => { + const profile = await $fetch(buildUrl('/session/profiles'), { + method: 'POST', + credentials: 'include', + body: { firstName, lastName }, + headers: getSessionHeaders(), + }) + await fetchProfiles() + return profile + } + + const deleteProfile = async (profileId: string): Promise => { + await $fetch(buildUrl(`/session/profiles/${profileId}`), { + method: 'DELETE', + credentials: 'include', + headers: getSessionHeaders(), + }) + await fetchProfiles() + } + + return { + profiles, + loadingProfiles, + profilesLoaded, + fetchProfiles, + createProfile, + deleteProfile, + } +} diff --git a/app/composables/useToast.js b/app/composables/useToast.ts similarity index 51% rename from app/composables/useToast.js rename to app/composables/useToast.ts index 94bd036..a27dcc5 100644 --- a/app/composables/useToast.js +++ b/app/composables/useToast.ts @@ -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([]) 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, } }