feat: Composables pour la gestion des données

- useApi.js : Service API générique avec gestion d'erreurs
- useSites.js : Gestion des sites industriels
- useMachines.js : Gestion des machines et création depuis types
- useMachineTypes.js : Gestion des types de machines
- useMachineTypesApi.js : API pour les types de machines
- useComposants.js : Gestion des composants hiérarchiques
- usePieces.js : Gestion des pièces de machines
- useCustomFields.js : Gestion des champs personnalisés
- useToast.js : Système de notifications toast
This commit is contained in:
Matthieu
2025-07-30 08:15:35 +02:00
parent 7613374e1f
commit 1ca5c347b7
9 changed files with 1500 additions and 0 deletions

75
app/composables/useApi.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

100
app/composables/useSites.js Normal file
View File

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

View File

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