# 🚀 Guide - Formulaires AJAX (Version Avancée)

## 📌 Introduction

Cette version utilise **AJAX** pour soumettre les formulaires **sans rechargement de page**, offrant une meilleure expérience utilisateur.

---

## ✨ Avantages de la version AJAX

| Classique | AJAX |
|-----------|------|
| Rechargement de page | ✅ Pas de rechargement |
| Modal se ferme puis se rouvre si erreur | ✅ Modal reste ouvert |
| Perte du scroll | ✅ Position préservée |
| Plus lent | ✅ Plus rapide |
| Simple à implémenter | Nécessite JavaScript |

---

## 🎯 Implémentation AJAX pour les catégories

### 1. Modifier le modal pour utiliser AJAX

```blade
<!-- resources/views/components/modals/category.blade.php -->
@php $modalId = $modalId ?? 'modal-new-category'; @endphp
@php $inputClass = 'w-full px-3 py-2.5 text-sm border border-gray-200 rounded-lg bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors'; @endphp

<form id="form-category" class="space-y-6">
    @csrf
    
    <!-- Messages d'erreur globaux -->
    <div id="form-errors" class="hidden bg-red-50 border-l-4 border-red-500 p-4 rounded">
        <ul class="list-disc list-inside text-sm text-red-700"></ul>
    </div>
    
    <!-- Message de succès -->
    <div id="form-success" class="hidden bg-green-50 border-l-4 border-green-500 p-4 rounded">
        <p class="text-sm text-green-700"></p>
    </div>
    
    <div>
        <label class="block text-sm font-medium text-gray-700 mb-2">
            Nom <span class="text-red-500">*</span>
        </label>
        <input type="text" 
               name="name" 
               id="category-name"
               class="{{ $inputClass }}" 
               placeholder="Entrez le nom de la catégorie" 
               required>
        <p class="hidden text-sm text-red-600 mt-1" id="error-name"></p>
    </div>
    
    <div>
        <label class="block text-sm font-medium text-gray-700 mb-2">Description</label>
        <textarea name="description" 
                  id="category-description"
                  rows="3" 
                  class="{{ $inputClass }} resize-none" 
                  placeholder="Entrez la description de la catégorie"></textarea>
        <p class="hidden text-sm text-red-600 mt-1" id="error-description"></p>
    </div>
    
    @include('components.modals.modal-actions', ['modalId' => $modalId])
</form>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const form = document.getElementById('form-category');
    const formErrors = document.getElementById('form-errors');
    const formSuccess = document.getElementById('form-success');
    
    if (form) {
        form.addEventListener('submit', async function(e) {
            e.preventDefault();
            
            // Réinitialiser les erreurs
            clearErrors();
            
            // Désactiver le bouton pour éviter les doubles clics
            const submitBtn = form.querySelector('button[type="submit"]');
            const originalText = submitBtn.textContent;
            submitBtn.disabled = true;
            submitBtn.textContent = 'Enregistrement...';
            
            try {
                const formData = new FormData(form);
                
                const response = await fetch('{{ route("categories.store") }}', {
                    method: 'POST',
                    body: formData,
                    headers: {
                        'X-Requested-With': 'XMLHttpRequest',
                        'Accept': 'application/json'
                    }
                });
                
                const data = await response.json();
                
                if (response.ok) {
                    // Succès
                    showSuccess(data.message || 'Catégorie créée avec succès');
                    form.reset();
                    
                    // Fermer le modal après 1 seconde
                    setTimeout(() => {
                        closeModal('{{ $modalId }}');
                        // Recharger la page pour afficher la nouvelle catégorie
                        window.location.reload();
                    }, 1000);
                } else {
                    // Erreurs de validation
                    if (data.errors) {
                        showErrors(data.errors);
                    } else {
                        showGlobalError(data.message || 'Une erreur est survenue');
                    }
                }
            } catch (error) {
                showGlobalError('Erreur de connexion au serveur');
            } finally {
                // Réactiver le bouton
                submitBtn.disabled = false;
                submitBtn.textContent = originalText;
            }
        });
    }
    
    function clearErrors() {
        // Masquer les messages d'erreur
        formErrors.classList.add('hidden');
        formSuccess.classList.add('hidden');
        
        // Retirer les bordures rouges
        form.querySelectorAll('input, textarea').forEach(input => {
            input.classList.remove('border-red-500');
        });
        
        // Masquer les messages d'erreur individuels
        form.querySelectorAll('[id^="error-"]').forEach(el => {
            el.classList.add('hidden');
        });
    }
    
    function showErrors(errors) {
        for (const [field, messages] of Object.entries(errors)) {
            const input = form.querySelector(`[name="${field}"]`);
            const errorEl = document.getElementById(`error-${field}`);
            
            if (input) {
                input.classList.add('border-red-500');
            }
            
            if (errorEl) {
                errorEl.textContent = messages[0];
                errorEl.classList.remove('hidden');
            }
        }
    }
    
    function showGlobalError(message) {
        formErrors.querySelector('ul').innerHTML = `<li>${message}</li>`;
        formErrors.classList.remove('hidden');
    }
    
    function showSuccess(message) {
        formSuccess.querySelector('p').textContent = message;
        formSuccess.classList.remove('hidden');
    }
    
    function closeModal(modalId) {
        const modal = document.getElementById(modalId);
        if (modal) {
            modal.classList.add('hidden');
            modal.classList.remove('flex');
        }
    }
});
</script>
```

---

### 2. Modifier le contrôleur pour retourner du JSON

```php
// app/Http/Controllers/CategoryController.php

public function store(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'description' => 'nullable|string|max:255',
    ]);

    $validated['reference'] = Category::generateReference();
    $validated['user_id'] = Auth::id();

    $category = Category::create($validated);

    // Si la requête est AJAX
    if ($request->wantsJson()) {
        return response()->json([
            'success' => true,
            'message' => 'Catégorie créée avec succès',
            'data' => $category
        ], 201);
    }

    // Si la requête est normale (formulaire classique)
    return redirect()->route('categories.index')
        ->with('success', 'Catégorie créée avec succès');
}
```

---

## 🎨 Version avec Fetch API moderne

### Méthode plus simple et moderne

```javascript
// Dans votre modal
document.getElementById('form-category').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(e.target);
    
    try {
        const response = await fetch('/categories', {
            method: 'POST',
            body: formData,
            headers: {
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
                'Accept': 'application/json'
            }
        });
        
        const data = await response.json();
        
        if (response.ok) {
            // Succès - Afficher message et fermer modal
            alert(data.message);
            window.location.reload();
        } else {
            // Erreur - Afficher les messages
            console.error(data.errors);
        }
    } catch (error) {
        console.error('Erreur:', error);
    }
});
```

---

## 🔥 Version avec Axios (Recommandé)

### Installation d'Axios

Axios est déjà inclus dans Laravel si vous utilisez Vite.

```javascript
// Dans votre modal
document.getElementById('form-category').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(e.target);
    
    try {
        const response = await axios.post('/categories', formData);
        
        // Succès
        Swal.fire({
            icon: 'success',
            title: 'Succès !',
            text: response.data.message,
            timer: 2000
        }).then(() => {
            window.location.reload();
        });
        
    } catch (error) {
        if (error.response && error.response.status === 422) {
            // Erreurs de validation
            const errors = error.response.data.errors;
            
            Object.keys(errors).forEach(field => {
                const errorMsg = errors[field][0];
                document.getElementById(`error-${field}`).textContent = errorMsg;
                document.getElementById(`error-${field}`).classList.remove('hidden');
            });
        } else {
            // Autre erreur
            Swal.fire({
                icon: 'error',
                title: 'Erreur',
                text: 'Une erreur est survenue'
            });
        }
    }
});
```

---

## 📦 Avec SweetAlert2 pour les notifications

### Installation

```bash
npm install sweetalert2
```

### Utilisation dans le formulaire

```javascript
import Swal from 'sweetalert2';

// Succès
Swal.fire({
    icon: 'success',
    title: 'Catégorie créée !',
    text: data.message,
    showConfirmButton: false,
    timer: 1500
}).then(() => {
    window.location.reload();
});

// Erreur
Swal.fire({
    icon: 'error',
    title: 'Erreur',
    text: 'Veuillez corriger les erreurs'
});
```

---

## 🎯 Ajout dynamique dans le tableau (sans rechargement)

### Au lieu de recharger la page, ajoutez la ligne au tableau

```javascript
// Après succès de la création
const tbody = document.querySelector('tbody');
const newRow = `
    <tr class="hover:bg-blue-50/50 transition-colors">
        <td class="px-6 py-4">
            <div class="text-sm font-medium">${data.data.name}</div>
            <div class="text-xs text-gray-500">${data.data.reference}</div>
        </td>
        <td class="px-6 py-4">
            ${data.data.description || '-'}
        </td>
        <td class="px-6 py-4">
            <span class="badge">0 produit(s)</span>
        </td>
        <td class="px-6 py-4">
            ${new Date().toLocaleDateString('fr-FR')}
        </td>
        <td class="px-6 py-4">
            <button>Modifier</button>
            <button>Supprimer</button>
        </td>
    </tr>
`;

// Si le tableau est vide (message "Aucune catégorie")
if (tbody.querySelector('td[colspan]')) {
    tbody.innerHTML = newRow;
} else {
    tbody.insertAdjacentHTML('afterbegin', newRow);
}
```

---

## ⚡ Comparaison des approches

| Caractéristique | Classique | AJAX Simple | AJAX + Axios | AJAX + SweetAlert |
|-----------------|-----------|-------------|--------------|-------------------|
| Rechargement page | Oui | Non | Non | Non |
| Complexité | ⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| UX | Basique | Bonne | Excellente | Excellente++ |
| Notifications | Flash | Inline | Inline | Popup élégant |
| Gestion erreurs | Automatique | Manuelle | Automatique | Automatique |
| Dépendances | Aucune | Aucune | Axios | Axios + SweetAlert2 |

---

## 🎨 Recommandation

### Pour débuter (✅ Déjà implémenté)
**Utilisez la version classique** - Simple, fiable, et fonctionne out of the box

### Pour production
**Utilisez AJAX + Axios** - Meilleure UX, moderne, et facile à maintenir

### Pour un projet premium
**Utilisez AJAX + Axios + SweetAlert2** - Experience utilisateur exceptionnelle

---

## 🔧 Configuration Axios dans Laravel

### Dans `resources/js/app.js`

```javascript
import axios from 'axios';
window.axios = axios;

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = 
    document.querySelector('meta[name="csrf-token"]').getAttribute('content');
```

### Dans votre layout `app.blade.php`

```blade
<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
```

---

## ✅ Checklist AJAX

- [ ] Empêcher la soumission par défaut avec `e.preventDefault()`
- [ ] Inclure le token CSRF
- [ ] Gérer les erreurs de validation (422)
- [ ] Gérer les erreurs serveur (500)
- [ ] Désactiver le bouton pendant la soumission
- [ ] Afficher un loader ou message "Enregistrement..."
- [ ] Afficher un message de succès
- [ ] Fermer le modal après succès
- [ ] Mettre à jour l'interface (ajouter la ligne ou recharger)
- [ ] Réinitialiser le formulaire après succès

---

## 🎉 Conclusion

Vous avez maintenant **deux versions** au choix :

1. **Version Classique** (✅ Implémentée) - Simple et efficace
2. **Version AJAX** (📄 Documentée) - Moderne et élégante

Choisissez selon vos besoins et votre niveau de confort avec JavaScript !

---

_Guide créé pour le système de gestion des stocks hospitaliers_
