diff --git a/app/composables/useApi.js b/app/composables/useApi.js new file mode 100644 index 0000000..232c416 --- /dev/null +++ b/app/composables/useApi.js @@ -0,0 +1,75 @@ +import { useToast } from './useToast' + +export function useApi() { + const { showSuccess, showError, showInfo } = useToast() + const API_BASE_URL = process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api' + const API_TIMEOUT = parseInt(process.env.NUXT_PUBLIC_API_TIMEOUT || '30000') + + const apiCall = async (endpoint, options = {}) => { + const url = `${API_BASE_URL}${endpoint}` + const defaultOptions = { + 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, + signal: controller.signal + }) + + clearTimeout(timeoutId) + + if (response.ok) { + const data = await response.json() + return { success: true, data } + } else { + const errorData = await response.json().catch(() => ({})) + 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', + body: JSON.stringify(data) + }) + } + + const patch = async (endpoint, data) => { + return apiCall(endpoint, { + method: 'PATCH', + body: JSON.stringify(data) + }) + } + + const del = async (endpoint) => { + return apiCall(endpoint, { method: 'DELETE' }) + } + + return { + apiCall, + get, + post, + patch, + delete: del + } +} \ No newline at end of file diff --git a/app/composables/useComposants.js b/app/composables/useComposants.js new file mode 100644 index 0000000..54e3b26 --- /dev/null +++ b/app/composables/useComposants.js @@ -0,0 +1,134 @@ +import { ref } from 'vue' +import { useToast } from './useToast' +import { useApi } from './useApi' + +const composants = ref([]) +const loading = ref(false) + +export function useComposants() { + const { showSuccess, showError, showInfo } = useToast() + const { get, post, patch, delete: del } = useApi() + + const loadComposants = async () => { + loading.value = true + try { + const result = await get('/composants') + if (result.success) { + composants.value = result.data + showInfo(`Chargement de ${composants.value.length} composant(s) réussi`) + } + } catch (error) { + console.error('Erreur lors du chargement des composants:', error) + } finally { + loading.value = false + } + } + + const getComposantsByMachine = async (machineId) => { + loading.value = true + try { + const result = await get(`/composants/machine/${machineId}`) + if (result.success) { + return { success: true, data: result.data } + } + return { success: false, error: result.error } + } catch (error) { + console.error('Erreur lors du chargement des composants:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const getComposantHierarchy = async (machineId) => { + loading.value = true + try { + const result = await get(`/composants/hierarchy/${machineId}`) + if (result.success) { + return { success: true, data: result.data } + } + return { success: false, error: result.error } + } catch (error) { + console.error('Erreur lors du chargement de la hiérarchie:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const createComposant = async (composantData) => { + loading.value = true + try { + const result = await post('/composants', composantData) + if (result.success) { + composants.value.push(result.data) + showSuccess(`Composant "${composantData.name}" créé avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la création du composant:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const updateComposantData = async (id, composantData) => { + loading.value = true + try { + const result = await patch(`/composants/${id}`, composantData) + if (result.success) { + const index = composants.value.findIndex(comp => comp.id === id) + if (index !== -1) { + composants.value[index] = result.data + } + showSuccess(`Composant "${composantData.name}" mis à jour avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la mise à jour du composant:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const deleteComposant = async (id) => { + loading.value = true + try { + const result = await del(`/composants/${id}`) + if (result.success) { + const deletedComposant = composants.value.find(comp => comp.id === id) + composants.value = composants.value.filter(comp => comp.id !== id) + showSuccess(`Composant "${deletedComposant?.name || 'inconnu'}" supprimé avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la suppression du composant:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const getComposantById = (id) => { + return composants.value.find(comp => comp.id === id) + } + + const getComposants = () => composants.value + const isLoading = () => loading.value + + return { + composants, + loading, + loadComposants, + getComposantsByMachine, + getComposantHierarchy, + createComposant, + updateComposant: updateComposantData, + deleteComposant, + getComposantById, + getComposants, + isLoading + } +} \ No newline at end of file diff --git a/app/composables/useCustomFields.js b/app/composables/useCustomFields.js new file mode 100644 index 0000000..b010129 --- /dev/null +++ b/app/composables/useCustomFields.js @@ -0,0 +1,97 @@ +import { ref } from 'vue' +import { useApi } from './useApi' + +export function useCustomFields() { + const { apiCall } = useApi() + const customFieldValues = ref([]) + const loading = ref(false) + + // Créer une valeur de champ personnalisé + const createCustomFieldValue = async (customFieldValueData) => { + try { + const result = await apiCall('/custom-fields/values', { + method: 'POST', + 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 } + } + } + + // Obtenir les valeurs de champs personnalisés pour une entité + const getCustomFieldValuesByEntity = async (entityType, entityId) => { + try { + loading.value = true + const result = await apiCall(`/custom-fields/values/${entityType}/${entityId}`, { + method: 'GET' + }) + if (result.success) { + customFieldValues.value = result.data + } + return result + } catch (error) { + console.error('Erreur lors de la récupération des valeurs de champs personnalisés:', error) + return { success: false, error } + } finally { + loading.value = false + } + } + + // Mettre à jour une valeur de champ personnalisé + const updateCustomFieldValue = async (id, updateData) => { + try { + const result = await apiCall(`/custom-fields/values/${id}`, { + method: 'PATCH', + 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 } + } + } + + // Créer ou mettre à jour une valeur de champ personnalisé + const upsertCustomFieldValue = async (customFieldId, entityType, entityId, value) => { + try { + const result = await apiCall('/custom-fields/values/upsert', { + method: 'POST', + body: JSON.stringify({ + customFieldId, + entityType, + entityId, + value + }) + }) + 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 } + } + } + + // Supprimer une valeur de champ personnalisé + const deleteCustomFieldValue = async (id) => { + try { + const result = await apiCall(`/custom-fields/values/${id}`, { + 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 { + customFieldValues, + loading, + createCustomFieldValue, + getCustomFieldValuesByEntity, + updateCustomFieldValue, + upsertCustomFieldValue, + deleteCustomFieldValue + } +} \ No newline at end of file diff --git a/app/composables/useMachineTypes.js b/app/composables/useMachineTypes.js new file mode 100644 index 0000000..d81d0e7 --- /dev/null +++ b/app/composables/useMachineTypes.js @@ -0,0 +1,654 @@ +import { ref } from 'vue' + +// Types de machines prédéfinis avec structure hiérarchique +const machineTypes = ref([ + // Machines de production + { + id: 1, + name: 'Presse hydraulique', + category: 'Production', + description: 'Machine de formage par compression hydraulique', + maintenanceFrequency: 'Mensuelle', + components: [ + { + name: 'Système hydraulique', + subComponents: [ + { + name: 'Pompe hydraulique', + subComponents: [ + { name: 'Rotor' }, + { name: 'Stator' }, + { name: 'Joint d\'étanchéité' } + ] + }, + { + name: 'Cylindre principal', + subComponents: [ + { name: 'Piston' }, + { name: 'Tige' }, + { name: 'Joint de piston' } + ] + }, + { + name: 'Soupapes de sécurité', + subComponents: [ + { name: 'Soupape de surpression' }, + { name: 'Soupape de décharge' } + ] + } + ] + }, + { + name: 'Système mécanique', + subComponents: [ + { + name: 'Banc de machine', + subComponents: [ + { name: 'Poutre supérieure' }, + { name: 'Poutre inférieure' }, + { name: 'Colonnes' } + ] + }, + { + name: 'Système de guidage', + subComponents: [ + { name: 'Rails de guidage' }, + { name: 'Patins' } + ] + } + ] + } + ], + criticalParts: ['Pompe hydraulique', 'Cylindre principal', 'Soupapes de sécurité'], + specifications: { + force: '100-5000 tonnes', + course: '100-800 mm', + vitesse: '5-50 mm/s' + } + }, + { + id: 2, + name: 'Convoyeur à bande', + category: 'Production', + description: 'Système de transport continu de matériaux', + maintenanceFrequency: 'Hebdomadaire', + components: [ + { + name: 'Système de transport', + subComponents: [ + { + name: 'Bande transporteuse', + subComponents: [ + { name: 'Carcasse' }, + { name: 'Revêtement' }, + { name: 'Armature' } + ] + }, + { + name: 'Rouleaux', + subComponents: [ + { name: 'Rouleaux porteurs' }, + { name: 'Rouleaux de retour' }, + { name: 'Rouleaux d\'impact' } + ] + } + ] + }, + { + name: 'Système d\'entraînement', + subComponents: [ + { + name: 'Moteur d\'entraînement', + subComponents: [ + { name: 'Rotor' }, + { name: 'Stator' }, + { name: 'Roulements' } + ] + }, + { + name: 'Réducteur', + subComponents: [ + { name: 'Engrenages' }, + { name: 'Arbre de sortie' } + ] + } + ] + } + ], + criticalParts: ['Bande transporteuse', 'Rouleaux', 'Moteur d\'entraînement'], + specifications: { + longueur: '5-100 m', + largeur: '400-2000 mm', + vitesse: '0.5-3 m/s' + } + }, + { + id: 3, + name: 'Robot de soudage', + category: 'Production', + description: 'Robot industriel pour opérations de soudage automatisé', + maintenanceFrequency: 'Trimestrielle', + components: [ + { + name: 'Bras robotique', + subComponents: [ + { + name: 'Base rotative', + subComponents: [ + { name: 'Moteur de rotation' }, + { name: 'Réducteur' }, + { name: 'Capteur de position' } + ] + }, + { + name: 'Bras articulé', + subComponents: [ + { name: 'Joint 1' }, + { name: 'Joint 2' }, + { name: 'Joint 3' } + ] + } + ] + }, + { + name: 'Système de soudage', + subComponents: [ + { + name: 'Torche de soudage', + subComponents: [ + { name: 'Électrode' }, + { name: 'Gainage' }, + { name: 'Conduit de gaz' } + ] + }, + { + name: 'Alimentation électrique', + subComponents: [ + { name: 'Transformateur' }, + { name: 'Régulateur de courant' } + ] + } + ] + } + ], + criticalParts: ['Bras robotique', 'Torche de soudage', 'Contrôleur'], + specifications: { + portée: '1.5-3 m', + charge: '5-200 kg', + précision: '±0.1 mm' + } + }, + + // Machines de transformation + { + id: 4, + name: 'Tour CNC', + category: 'Transformation', + description: 'Machine-outil pour usinage de pièces cylindriques', + maintenanceFrequency: 'Mensuelle', + components: [ + { + name: 'Banc de machine', + subComponents: [ + { + name: 'Banc principal', + subComponents: [ + { name: 'Poutre' }, + { name: 'Guidages' }, + { name: 'Vis à billes' } + ] + } + ] + }, + { + name: 'Système de broche', + subComponents: [ + { + name: 'Broche principale', + subComponents: [ + { name: 'Arbre de broche' }, + { name: 'Roulements' }, + { name: 'Moteur de broche' } + ] + }, + { + name: 'Contre-pointe', + subComponents: [ + { name: 'Pointe' }, + { name: 'Cylindre' } + ] + } + ] + } + ], + criticalParts: ['Banc de machine', 'Broche', 'Contre-pointe'], + specifications: { + diamètre: '200-1000 mm', + longueur: '500-3000 mm', + puissance: '5-50 kW' + } + }, + { + id: 5, + name: 'Fraiseuse', + category: 'Transformation', + description: 'Machine-outil pour usinage par enlèvement de copeaux', + maintenanceFrequency: 'Mensuelle', + components: [ + { + name: 'Table de travail', + subComponents: [ + { + name: 'Table X', + subComponents: [ + { name: 'Guidages X' }, + { name: 'Vis à billes X' }, + { name: 'Moteur X' } + ] + }, + { + name: 'Table Y', + subComponents: [ + { name: 'Guidages Y' }, + { name: 'Vis à billes Y' }, + { name: 'Moteur Y' } + ] + } + ] + }, + { + name: 'Système de broche', + subComponents: [ + { + name: 'Broche verticale', + subComponents: [ + { name: 'Arbre de broche' }, + { name: 'Roulements' }, + { name: 'Moteur de broche' } + ] + } + ] + } + ], + criticalParts: ['Table de travail', 'Broche', 'Guidages'], + specifications: { + courseX: '400-2000 mm', + courseY: '300-1500 mm', + courseZ: '200-800 mm' + } + }, + + // Machines de manutention + { + id: 6, + name: 'Pont roulant', + category: 'Manutention', + description: 'Système de levage et transport de charges lourdes', + maintenanceFrequency: 'Mensuelle', + components: [ + { + name: 'Poutre principale', + subComponents: [ + { + name: 'Poutre de roulement', + subComponents: [ + { name: 'Profilé principal' }, + { name: 'Rails de roulement' }, + { name: 'Renforts' } + ] + } + ] + }, + { + name: 'Système de palans', + subComponents: [ + { + name: 'Palans', + subComponents: [ + { name: 'Moteur de levage' }, + { name: 'Treuil' }, + { name: 'Crochet' } + ] + }, + { + name: 'Système de translation', + subComponents: [ + { name: 'Moteur de translation' }, + { name: 'Roues de roulement' } + ] + } + ] + } + ], + criticalParts: ['Poutre principale', 'Palans', 'Rails de guidage'], + specifications: { + capacité: '1-500 tonnes', + portée: '5-50 m', + hauteur: '3-20 m' + } + }, + { + id: 7, + name: 'Chariot élévateur', + category: 'Manutention', + description: 'Véhicule de manutention pour charges palettisées', + maintenanceFrequency: 'Hebdomadaire', + components: [ + { + name: 'Système de levage', + subComponents: [ + { + name: 'Mast', + subComponents: [ + { name: 'Mât extérieur' }, + { name: 'Mât intérieur' }, + { name: 'Cylindres de levage' } + ] + }, + { + name: 'Fourches', + subComponents: [ + { name: 'Fourche gauche' }, + { name: 'Fourche droite' }, + { name: 'Système de réglage' } + ] + } + ] + }, + { + name: 'Groupe motopropulseur', + subComponents: [ + { + name: 'Moteur', + subComponents: [ + { name: 'Bloc moteur' }, + { name: 'Système d\'injection' }, + { name: 'Système de refroidissement' } + ] + }, + { + name: 'Transmission', + subComponents: [ + { name: 'Boîte de vitesses' }, + { name: 'Arbre de transmission' }, + { name: 'Pont arrière' } + ] + } + ] + } + ], + criticalParts: ['Mast', 'Fourches', 'Moteur'], + specifications: { + capacité: '1-10 tonnes', + hauteur: '3-6 m', + type: 'Électrique/Diesel/Gaz' + } + }, + + // Machines de traitement + { + id: 8, + name: 'Compresseur d\'air', + category: 'Traitement', + description: 'Générateur d\'air comprimé pour applications industrielles', + maintenanceFrequency: 'Hebdomadaire', + components: [ + { + name: 'Système de compression', + subComponents: [ + { + name: 'Compresseur', + subComponents: [ + { name: 'Pistons' }, + { name: 'Cylindres' }, + { name: 'Soupapes' } + ] + }, + { + name: 'Réservoir', + subComponents: [ + { name: 'Cuve' }, + { name: 'Soupape de sécurité' }, + { name: 'Manomètre' } + ] + } + ] + }, + { + name: 'Système de filtration', + subComponents: [ + { + name: 'Filtres', + subComponents: [ + { name: 'Filtre à air' }, + { name: 'Filtre à huile' }, + { name: 'Séparateur d\'eau' } + ] + } + ] + } + ], + criticalParts: ['Compresseur', 'Réservoir', 'Filtres'], + specifications: { + débit: '100-10000 L/min', + pression: '7-10 bar', + puissance: '5-500 kW' + } + }, + { + id: 9, + name: 'Pompe hydraulique', + category: 'Traitement', + description: 'Pompe pour circuits hydrauliques industriels', + maintenanceFrequency: 'Mensuelle', + components: [ + { + name: 'Système de pompage', + subComponents: [ + { + name: 'Rotor', + subComponents: [ + { name: 'Ailettes' }, + { name: 'Arbre' } + ] + }, + { + name: 'Stator', + subComponents: [ + { name: 'Corps' }, + { name: 'Chambres' } + ] + } + ] + }, + { + name: 'Système d\'étanchéité', + subComponents: [ + { + name: 'Joint d\'étanchéité', + subComponents: [ + { name: 'Joint radial' }, + { name: 'Joint axial' } + ] + } + ] + } + ], + criticalParts: ['Rotor', 'Stator', 'Joint d\'étanchéité'], + specifications: { + débit: '10-500 L/min', + pression: '50-350 bar', + type: 'Piston/Palette/Engrenage' + } + }, + + // Machines de contrôle + { + id: 10, + name: 'Capteur de température', + category: 'Contrôle', + description: 'Instrument de mesure de température industrielle', + maintenanceFrequency: 'Annuelle', + components: [ + { + name: 'Système de mesure', + subComponents: [ + { + name: 'Élément sensible', + subComponents: [ + { name: 'Fil de platine' }, + { name: 'Isolation' } + ] + }, + { + name: 'Câblage', + subComponents: [ + { name: 'Fils de connexion' }, + { name: 'Gaine de protection' } + ] + } + ] + }, + { + name: 'Système de transmission', + subComponents: [ + { + name: 'Transmetteur', + subComponents: [ + { name: 'Circuit électronique' }, + { name: 'Affichage' } + ] + } + ] + } + ], + criticalParts: ['Élément sensible', 'Câblage', 'Transmetteur'], + specifications: { + plage: '-50 à +500°C', + précision: '±0.5°C', + type: 'PT100/PT1000/Thermocouple' + } + }, + { + id: 11, + name: 'Manomètre', + category: 'Contrôle', + description: 'Instrument de mesure de pression', + maintenanceFrequency: 'Annuelle', + components: [ + { + name: 'Système de mesure', + subComponents: [ + { + name: 'Tube de Bourdon', + subComponents: [ + { name: 'Tube' }, + { name: 'Extrémité fixe' }, + { name: 'Extrémité mobile' } + ] + }, + { + name: 'Cadran', + subComponents: [ + { name: 'Échelle' }, + { name: 'Aiguille' } + ] + } + ] + }, + { + name: 'Système de connexion', + subComponents: [ + { + name: 'Joint', + subComponents: [ + { name: 'Joint d\'étanchéité' }, + { name: 'Filetage' } + ] + } + ] + } + ], + criticalParts: ['Tube de Bourdon', 'Cadran', 'Joint'], + specifications: { + plage: '0-600 bar', + précision: '±1%', + type: 'Analogique/Numérique' + } + } +]) + +// Catégories disponibles +const categories = ref([ + 'Production', + 'Transformation', + 'Manutention', + 'Traitement', + 'Contrôle' +]) + +export function useMachineTypes() { + const getTypes = () => machineTypes.value + + const getTypeById = (id) => { + return machineTypes.value.find(type => type.id === id) + } + + const getTypesByCategory = (category) => { + return machineTypes.value.filter(type => type.category === category) + } + + const getCategories = () => categories.value + + const addType = (newType) => { + const id = Math.max(...machineTypes.value.map(t => t.id)) + 1 + machineTypes.value.push({ + id, + ...newType + }) + } + + const updateType = (id, updatedType) => { + const index = machineTypes.value.findIndex(type => type.id === id) + if (index !== -1) { + machineTypes.value[index] = { ...machineTypes.value[index], ...updatedType } + } + } + + const deleteType = (id) => { + const index = machineTypes.value.findIndex(type => type.id === id) + if (index !== -1) { + machineTypes.value.splice(index, 1) + } + } + + // Méthodes pour la hiérarchie + const flattenComponents = (components, level = 0) => { + let flat = [] + components.forEach(comp => { + flat.push({ ...comp, level }) + if (comp.subComponents && comp.subComponents.length > 0) { + flat = flat.concat(flattenComponents(comp.subComponents, level + 1)) + } + }) + return flat + } + + const getComponentHierarchy = (typeId) => { + const type = getTypeById(typeId) + if (!type || !type.components) return [] + return flattenComponents(type.components) + } + + return { + getTypes, + getTypeById, + getTypesByCategory, + getCategories, + addType, + updateType, + deleteType, + flattenComponents, + getComponentHierarchy + } +} \ No newline at end of file diff --git a/app/composables/useMachineTypesApi.js b/app/composables/useMachineTypesApi.js new file mode 100644 index 0000000..5e6a830 --- /dev/null +++ b/app/composables/useMachineTypesApi.js @@ -0,0 +1,117 @@ +import { ref } from 'vue' +import { useToast } from './useToast' +import { useApi } from './useApi' + +const machineTypes = ref([]) +const loading = ref(false) + +export function useMachineTypesApi() { + const { showSuccess, showError, showInfo } = useToast() + const { get, post, patch, delete: del } = useApi() + + const loadMachineTypes = async () => { + loading.value = true + try { + const result = await get('/types/machines') + if (result.success) { + machineTypes.value = result.data + showInfo(`Chargement de ${machineTypes.value.length} type(s) de machine réussi`) + } + } catch (error) { + console.error('Erreur lors du chargement des types de machines:', error) + } finally { + loading.value = false + } + } + + const createMachineType = async (typeData) => { + loading.value = true + try { + const result = await post('/types/machines', typeData) + if (result.success) { + machineTypes.value.push(result.data) + 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 } + } finally { + loading.value = false + } + } + + const updateMachineType = async (id, typeData) => { + loading.value = true + try { + const result = await patch(`/types/machines/${id}`, typeData) + if (result.success) { + const index = machineTypes.value.findIndex(type => type.id === id) + if (index !== -1) { + machineTypes.value[index] = result.data + } + showSuccess(`Type de machine "${typeData.name}" mis à jour avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la mise à jour du type de machine:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const deleteMachineType = async (id) => { + loading.value = true + try { + const result = await del(`/types/machines/${id}`) + if (result.success) { + 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 } + } finally { + loading.value = false + } + } + + const getMachineTypeById = async (id) => { + // D'abord chercher dans le cache local + const localType = machineTypes.value.find(type => type.id === id) + if (localType) { + return { success: true, data: localType } + } + + // Si pas trouvé localement, récupérer depuis l'API + try { + const result = await get(`/types/machines/${id}`) + if (result.success) { + // Ajouter au cache local + machineTypes.value.push(result.data) + } + return result + } catch (error) { + console.error('Erreur lors de la récupération du type de machine:', error) + return { success: false, error: error.message } + } + } + + const getMachineTypes = () => machineTypes.value + const isLoading = () => loading.value + + return { + machineTypes, + loading, + loadMachineTypes, + createMachineType, + updateMachineType, + deleteMachineType, + getMachineTypeById, + getMachineTypes, + isLoading + } +} \ No newline at end of file diff --git a/app/composables/useMachines.js b/app/composables/useMachines.js new file mode 100644 index 0000000..adfda1e --- /dev/null +++ b/app/composables/useMachines.js @@ -0,0 +1,123 @@ +import { ref } from 'vue' +import { useToast } from './useToast' +import { useApi } from './useApi' + +const machines = ref([]) +const loading = ref(false) + +export function useMachines() { + const { showSuccess, showError, showInfo } = useToast() + const { get, post, patch, delete: del } = useApi() + + const loadMachines = async () => { + loading.value = true + try { + const result = await get('/machines') + if (result.success) { + machines.value = result.data + showInfo(`Chargement de ${machines.value.length} machine(s) réussi`) + } + } catch (error) { + console.error('Erreur lors du chargement des machines:', error) + } finally { + loading.value = false + } + } + + const createMachine = async (machineData) => { + loading.value = true + try { + const result = await post('/machines', machineData) + if (result.success) { + machines.value.push(result.data) + showSuccess(`Machine "${machineData.name}" créée avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la création de la machine:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const createMachineFromType = async (machineData, typeMachine) => { + // Créer la machine avec la structure héritée du type + const machineWithStructure = { + ...machineData, + 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) + } + + const updateMachineData = async (id, machineData) => { + loading.value = true + try { + const result = await patch(`/machines/${id}`, machineData) + if (result.success) { + const index = machines.value.findIndex(machine => machine.id === id) + if (index !== -1) { + machines.value[index] = result.data + } + showSuccess(`Machine "${machineData.name}" mise à jour avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la mise à jour de la machine:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const deleteMachine = async (id) => { + 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) + 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 } + } finally { + loading.value = false + } + } + + const getMachineById = (id) => { + return machines.value.find(machine => machine.id === id) + } + + const getMachinesBySite = (siteId) => { + return machines.value.filter(machine => machine.siteId === siteId) + } + + const getMachinesByType = (typeMachineId) => { + return machines.value.filter(machine => machine.typeMachineId === typeMachineId) + } + + const getMachines = () => machines.value + const isLoading = () => loading.value + + return { + machines, + loading, + loadMachines, + createMachine, + createMachineFromType, + updateMachine: updateMachineData, + deleteMachine, + getMachineById, + getMachinesBySite, + getMachinesByType, + getMachines, + isLoading + } +} \ No newline at end of file diff --git a/app/composables/usePieces.js b/app/composables/usePieces.js new file mode 100644 index 0000000..337ec21 --- /dev/null +++ b/app/composables/usePieces.js @@ -0,0 +1,134 @@ +import { ref } from 'vue' +import { useToast } from './useToast' +import { useApi } from './useApi' + +const pieces = ref([]) +const loading = ref(false) + +export function usePieces() { + const { showSuccess, showError, showInfo } = useToast() + const { get, post, patch, delete: del } = useApi() + + const loadPieces = async () => { + loading.value = true + try { + const result = await get('/pieces') + if (result.success) { + pieces.value = result.data + showInfo(`Chargement de ${pieces.value.length} pièce(s) réussi`) + } + } catch (error) { + console.error('Erreur lors du chargement des pièces:', error) + } finally { + loading.value = false + } + } + + const getPiecesByMachine = async (machineId) => { + loading.value = true + try { + const result = await get(`/pieces/machine/${machineId}`) + if (result.success) { + return { success: true, data: result.data } + } + return { success: false, error: result.error } + } catch (error) { + console.error('Erreur lors du chargement des pièces:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const getPiecesByComposant = async (composantId) => { + loading.value = true + try { + const result = await get(`/pieces/composant/${composantId}`) + if (result.success) { + return { success: true, data: result.data } + } + return { success: false, error: result.error } + } catch (error) { + console.error('Erreur lors du chargement des pièces:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const createPiece = async (pieceData) => { + loading.value = true + try { + const result = await post('/pieces', pieceData) + if (result.success) { + pieces.value.push(result.data) + showSuccess(`Pièce "${pieceData.name}" créée avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la création de la pièce:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const updatePieceData = async (id, pieceData) => { + loading.value = true + try { + const result = await patch(`/pieces/${id}`, pieceData) + if (result.success) { + const index = pieces.value.findIndex(piece => piece.id === id) + if (index !== -1) { + pieces.value[index] = result.data + } + showSuccess(`Pièce "${pieceData.name}" mise à jour avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la mise à jour de la pièce:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const deletePiece = async (id) => { + loading.value = true + try { + const result = await del(`/pieces/${id}`) + if (result.success) { + const deletedPiece = pieces.value.find(piece => piece.id === id) + pieces.value = pieces.value.filter(piece => piece.id !== id) + showSuccess(`Pièce "${deletedPiece?.name || 'inconnu'}" supprimée avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la suppression de la pièce:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const getPieceById = (id) => { + return pieces.value.find(piece => piece.id === id) + } + + const getPieces = () => pieces.value + const isLoading = () => loading.value + + return { + pieces, + loading, + loadPieces, + getPiecesByMachine, + getPiecesByComposant, + createPiece, + updatePiece: updatePieceData, + deletePiece, + getPieceById, + getPieces, + isLoading + } +} \ No newline at end of file diff --git a/app/composables/useSites.js b/app/composables/useSites.js new file mode 100644 index 0000000..eb7b829 --- /dev/null +++ b/app/composables/useSites.js @@ -0,0 +1,100 @@ +import { ref } from 'vue' +import { useToast } from './useToast' +import { useApi } from './useApi' + +const sites = ref([]) +const loading = ref(false) + +export function useSites() { + const { showSuccess, showInfo } = useToast() + const { get, post, patch, delete: del } = useApi() + + const loadSites = async () => { + loading.value = true + try { + const result = await get('/sites') + if (result.success) { + sites.value = result.data + showInfo(`Chargement de ${sites.value.length} site(s) réussi`) + } + } catch (error) { + console.error('Erreur lors du chargement des sites:', error) + } finally { + loading.value = false + } + } + + const createSite = async (siteData) => { + loading.value = true + try { + const result = await post('/sites', siteData) + if (result.success) { + sites.value.push(result.data) + showSuccess(`Site "${siteData.name}" créé avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la création du site:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const updateSite = async (id, siteData) => { + loading.value = true + try { + const result = await patch(`/sites/${id}`, siteData) + if (result.success) { + const index = sites.value.findIndex(site => site.id === id) + if (index !== -1) { + sites.value[index] = result.data + } + showSuccess(`Site "${siteData.name}" mis à jour avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la mise à jour du site:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const deleteSite = async (id) => { + loading.value = true + try { + const result = await del(`/sites/${id}`) + if (result.success) { + const deletedSite = sites.value.find(site => site.id === id) + sites.value = sites.value.filter(site => site.id !== id) + showSuccess(`Site "${deletedSite?.name || 'inconnu'}" supprimé avec succès`) + } + return result + } catch (error) { + console.error('Erreur lors de la suppression du site:', error) + return { success: false, error: error.message } + } finally { + loading.value = false + } + } + + const getSiteById = (id) => { + return sites.value.find(site => site.id === id) + } + + const getSites = () => sites.value + const isLoading = () => loading.value + + return { + sites, + loading, + loadSites, + createSite, + updateSite, + deleteSite, + getSiteById, + getSites, + isLoading + } +} \ No newline at end of file diff --git a/app/composables/useToast.js b/app/composables/useToast.js new file mode 100644 index 0000000..4a08662 --- /dev/null +++ b/app/composables/useToast.js @@ -0,0 +1,66 @@ +import { ref } from 'vue' + +const toasts = ref([]) +let nextId = 1 + +export function useToast() { + const showToast = (message, type = 'info', duration = 5000) => { + const id = nextId++ + const toast = { + id, + message, + type, + visible: true + } + + toasts.value.push(toast) + + // Auto-remove after duration + setTimeout(() => { + removeToast(id) + }, duration) + + return id + } + + const showSuccess = (message, duration = 5000) => { + return showToast(message, 'success', duration) + } + + const showError = (message, duration = 7000) => { + return showToast(message, 'error', duration) + } + + const showWarning = (message, duration = 6000) => { + return showToast(message, 'warning', duration) + } + + const showInfo = (message, duration = 5000) => { + return showToast(message, 'info', duration) + } + + const removeToast = (id) => { + const index = toasts.value.findIndex(toast => toast.id === id) + if (index !== -1) { + toasts.value[index].visible = false + setTimeout(() => { + toasts.value.splice(index, 1) + }, 300) // Animation duration + } + } + + const clearAll = () => { + toasts.value = [] + } + + return { + toasts, + showToast, + showSuccess, + showError, + showWarning, + showInfo, + removeToast, + clearAll + } +} \ No newline at end of file