# Séance 2 : Architecture et composables

## 🎯 Objectifs

* Comprendre la structure d'un projet Vue.js
* Comprendre l'utilisation des composables et des services pour découper et organiser le code
* Découvrir la factorisation de logique avec les composables.

## 📖 Partie théorique

### Organisation du projet

#### Structure d'un projet Vue 3

Organisation recommandée pour un projet Vue 3 professionnel :

```
mon-projet-vue/
├── public/                 # Fichiers statiques
│   ├── index.html         # Template HTML principal
│   └── favicon.ico
├── src/                   # Code source
│   ├── assets/           # Ressources (images, CSS, fonts)
│   │   ├── css/
│   │   ├── images/
│   │   └── fonts/
│   ├── components/       # Composants réutilisables
│   │   ├── common/      # Composants génériques (Button, Modal)
│   │   └── features/    # Composants spécifiques (TaskCard, UserProfile)
│   ├── composables/      # Logique réutilisable (hooks)
│   │   ├── useAuth.js
│   │   ├── useApi.js
│   │   └── useTasks.js
│   ├── stores/          # Gestion d'état Pinia
│   │   ├── auth.js
│   │   ├── tasks.js
│   │   └── index.js
│   ├── services/        # Services API et logique métier
│   │   ├── api.js
│   │   ├── taskService.js
│   │   └── authService.js
│   ├── router/          # Configuration des routes
│   │   └── index.js
│   ├── views/           # Pages principales
│   │   ├── Home.vue
│   │   ├── Login.vue
│   │   └── Dashboard.vue
│   ├── utils/           # Fonctions utilitaires
│   │   ├── helpers.js
│   │   └── constants.js
│   ├── App.vue          # Composant racine
│   └── main.js          # Point d'entrée
├── package.json         # Dépendances et scripts
├── vite.config.js       # Configuration Vite
└── README.md
```

**Conventions de nommage :**

```javascript
<!-- PascalCase pour les composants -->
<TaskCard />
<UserProfile />

<!-- camelCase pour les props et variables -->
<TaskCard :taskData="task" :isEditable="true" />

<!-- kebab-case pour les événements -->
@task-updated="handleUpdate"
@user-logged-in="onLogin"
```

#### Dossiers principaux :

* `components/` : composants Vue réutilisables
* `stores/` : gestion d’état globale (Pinia)
* `composables/` : fonctions réactives réutilisables
* `services/` : accès aux API et logique métier
* `views/` : pages principales de l’application

#### Tableau de synthèse des dossiers principaux

| Dossier        | Rôle                        | Contenu                                     | Exemple                              | Réutilisabilité | Gestion d'état        |
| -------------- | --------------------------- | ------------------------------------------- | ------------------------------------ | --------------- | --------------------- |
| `components/`  | Interface utilisateur       | Composants Vue avec template, script, style | `TaskCard.vue`, `Button.vue`         | ✅ Haute         | État local uniquement |
| `stores/`      | État global partagé         | Stores Pinia avec state, getters, actions   | `useAuthStore.js`, `useTaskStore.js` | ✅ Globale       | ✅ État global réactif |
| `composables/` | Logique métier réutilisable | Fonctions avec réactivité Vue               | `useTasks.js`, `useAuth.js`          | ✅ Très haute    | État réactif local    |
| `services/`    | Accès aux données           | Classes/fonctions pour API et logique       | `taskService.js`, `apiClient.js`     | ✅ Moyenne       | ❌ Pas d'état          |
| `views/`       | Pages de l'application      | Composants de page/route                    | `HomePage.vue`, `ProfilePage.vue`    | ❌ Faible        | État local de page    |

#### Détail des responsabilités

**`components/` - Composants Vue réutilisables**

* **Rôle** : Éléments d'interface utilisateur
* **Contenu** : Template, logique d'affichage, styles
* **Exemples** : `TaskCard.vue`, `Modal.vue`, `Button.vue`
* **Communication** : Props (entrée) et emits (sortie)

**`stores/` - Gestion d'état globale (Pinia)**

* **Rôle** : Centraliser l'état partagé entre composants
* **Contenu** : State, getters, actions
* **Exemples** : `useAuthStore.js`, `useTaskStore.js`
* **Avantages** : Persistance, réactivité globale, DevTools

**`composables/` - Fonctions réactives réutilisables**

* **Rôle** : Factoriser la logique métier avec réactivité
* **Contenu** : Fonctions utilisant ref, computed, watch
* **Exemples** : `useTasks.js`, `useLocalStorage.js`
* **Avantages** : Testabilité, réutilisabilité, séparation des préoccupations

**`services/` - Accès aux API et logique métier**

* **Rôle** : Interaction avec APIs et traitements de données
* **Contenu** : Classes/fonctions sans réactivité Vue
* **Exemples** : `taskService.js`, `authService.js`, `apiClient.js`
* **Avantages** : Abstraction des APIs, logique pure

**`views/` - Pages principales de l'application**

* **Rôle** : Composants de niveau page liés au routing
* **Contenu** : Layout de page, orchestration de composants
* **Exemples** : `HomePage.vue`, `TaskListPage.vue`
* **Particularité** : Souvent connectés aux routes du routerfixe `use` (ex: `useCounter`, `useAuth`)

### Les composables

#### Qu'est-ce qu'un composable ?

* Fonctions réutilisables exploitant la réactivité de Vue.
* Permettent de factoriser la logique métier et d'éviter la duplication de code.

#### Exemple complet : useTasks

**Créer `src/composables/useTasks.js` :**

```javascript
import { ref, computed } from 'vue'

export function useTasks() {
  // État réactif
  const tasks = ref([
    {
      id: 1,
      title: 'Apprendre Vue 3',
      description: 'Maîtriser la Composition API',
      priority: 'High',
      completed: false,
      createdAt: new Date('2024-09-01')
    },
    {
      id: 2,
      title: 'Créer un projet',
      description: 'Développer une application complète',
      priority: 'Medium',
      completed: true,
      createdAt: new Date('2024-09-15')
    }
  ])

  const loading = ref(false)
  const error = ref(null)

  // Computed properties
  const totalTasks = computed(() => tasks.value.length)
  
  const completedTasks = computed(() => 
    tasks.value.filter(task => task.completed)
  )
  
  const pendingTasks = computed(() => 
    tasks.value.filter(task => !task.completed)
  )
  
  const highPriorityTasks = computed(() =>
    tasks.value.filter(task => task.priority === 'High')
  )

  const completionRate = computed(() => {
    if (totalTasks.value === 0) return 0
    return Math.round((completedTasks.value.length / totalTasks.value) * 100)
  })

  // Méthodes
  const addTask = (taskData) => {
    const newTask = {
      id: Date.now(),
      title: taskData.title,
      description: taskData.description,
      priority: taskData.priority || 'Medium',
      completed: false,
      createdAt: new Date()
    }
    tasks.value.push(newTask)
  }

  const deleteTask = (taskId) => {
    tasks.value = tasks.value.filter(task => task.id !== taskId)
  }

  const toggleComplete = (taskId) => {
    const task = tasks.value.find(task => task.id === taskId)
    if (task) {
      task.completed = !task.completed
    }
  }

  const updateTask = (taskId, updates) => {
    const taskIndex = tasks.value.findIndex(task => task.id === taskId)
    if (taskIndex !== -1) {
      tasks.value[taskIndex] = { ...tasks.value[taskIndex], ...updates }
    }
  }

  const getTasksByPriority = (priority) => {
    return computed(() => 
      tasks.value.filter(task => task.priority === priority)
    )
  }

  const clearCompletedTasks = () => {
    tasks.value = tasks.value.filter(task => !task.completed)
  }

  // Simuler un appel API
  const fetchTasks = async () => {
    try {
      loading.value = true
      error.value = null
      
      // Simulation d'un appel API
      await new Promise(resolve => setTimeout(resolve, 1000))
      
      // En réalité, on ferait : 
      // const response = await fetch('/api/tasks')
      // tasks.value = await response.json()
      
      console.log('Tâches chargées depuis l\'API (simulé)')
    } catch (err) {
      error.value = 'Erreur lors du chargement des tâches'
      console.error(err)
    } finally {
      loading.value = false
    }
  }

  // Retourner l'état et les méthodes
  return {
    // État
    tasks: tasks.value, // ou tasks si on veut la réactivité complète
    loading,
    error,
    
    // Computed
    totalTasks,
    completedTasks,
    pendingTasks,
    highPriorityTasks,
    completionRate,
    
    // Méthodes
    addTask,
    deleteTask,
    toggleComplete,
    updateTask,
    getTasksByPriority,
    clearCompletedTasks,
    fetchTasks
  }
}
```

#### Utilisation du composable dans un composant

**Dans `TaskManager.vue` :**

```javascript
<template>
  <div class="task-manager">
    <div class="stats">
      <h2>📊 Statistiques</h2>
      <div class="stats-grid">
        <div class="stat-item">
          <span class="stat-number">{{ totalTasks }}</span>
          <span class="stat-label">Total</span>
        </div>
        <div class="stat-item">
          <span class="stat-number">{{ completedTasks.length }}</span>
          <span class="stat-label">Terminées</span>
        </div>
        <div class="stat-item">
          <span class="stat-number">{{ pendingTasks.length }}</span>
          <span class="stat-label">En cours</span>
        </div>
        <div class="stat-item">
          <span class="stat-number">{{ completionRate }}%</span>
          <span class="stat-label">Progression</span>
        </div>
      </div>
    </div>

    <div class="task-form">
      <h3>Ajouter une tâche</h3>
      <input v-model="newTaskTitle" placeholder="Titre">
      <textarea v-model="newTaskDescription" placeholder="Description"></textarea>
      <select v-model="newTaskPriority">
        <option value="Low">Basse</option>
        <option value="Medium">Moyenne</option>
        <option value="High">Haute</option>
      </select>
      <button @click="handleAddTask">Ajouter</button>
    </div>

    <div class="task-list">
      <div v-if="loading" class="loading">Chargement...</div>
      <div v-if="error" class="error">{{ error }}</div>
      
      <div v-for="task in pendingTasks" :key="task.id" class="task-item">
        <h4>{{ task.title }}</h4>
        <p>{{ task.description }}</p>
        <div class="task-actions">
          <button @click="toggleComplete(task.id)">
            {{ task.completed ? 'Rouvrir' : 'Terminer' }}
          </button>
          <button @click="deleteTask(task.id)" class="danger">
            Supprimer
          </button>
        </div>
      </div>
    </div>

    <button @click="clearCompletedTasks" v-if="completedTasks.length > 0">
      Effacer les tâches terminées ({{ completedTasks.length }})
    </button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useTasks } from '@/composables/useTasks'

// Utilisation du composable
const {
  tasks,
  loading,
  error,
  totalTasks,
  completedTasks,
  pendingTasks,
  completionRate,
  addTask,
  deleteTask,
  toggleComplete,
  clearCompletedTasks,
  fetchTasks
} = useTasks()

// État local du formulaire
const newTaskTitle = ref('')
const newTaskDescription = ref('')
const newTaskPriority = ref('Medium')

// Méthodes locales
const handleAddTask = () => {
  if (newTaskTitle.value.trim()) {
    addTask({
      title: newTaskTitle.value,
      description: newTaskDescription.value,
      priority: newTaskPriority.value
    })
    
    // Reset du formulaire
    newTaskTitle.value = ''
    newTaskDescription.value = ''
    newTaskPriority.value = 'Medium'
  }
}

// Charger les tâches au montage du composant
onMounted(() => {
  fetchTasks()
})
</script>
```

#### Avantages des composables

1. **Réutilisabilité** : Logique partagée entre plusieurs composants
2. **Testabilité** : Fonctions isolées faciles à tester
3. **Lisibilité** : Composants plus propres et focalisés sur l'UI
4. **Maintenance** : Logique centralisée et organiséetion d’un projet Vue.

### Les services

#### Qu'est-ce qu'un service ?

* Classes ou fonctions dédiées à l'accès aux API et à la logique métier.
* Séparent la logique de données de l'interface utilisateur.

#### Exemples de services

1. **Service API** : Gère les appels HTTP vers une API externe.

   ```js
   // src/services/api.js
   import axios from 'axios'

   const apiClient = axios.create({
     baseURL: 'https://api.example.com',
     headers: {
       'Content-Type': 'application/json'
     }
   })

   export default {
     getTasks() {
       return apiClient.get('/tasks')
     },
     createTask(task) {
       return apiClient.post('/tasks', task)
     },
     // Autres méthodes...
   }
   ```
2. **Service de gestion des tâches** : Contient la logique métier liée aux tâches.

   ```js
   // src/services/taskService.js
   import api from './api'

   export const fetchTasks = async () => {
     const response = await api.getTasks()
     return response.data
   }

   export const createTask = async (task) => {
     const response = await api.createTask(task)
     return response.data
   }

   // Autres méthodes...
   ```

#### Avantages des services

* **Séparation des préoccupations** : Distingue la logique métier de l'UI.
* **Réutilisabilité** : Services utilisables dans différents composants ou composables.
* **Testabilité** : Logique métier isolée, facilitant les tests unitaires.
* **Maintenance** : Centralisation de la logique métier et des interactions API.
* **Scalabilité** : Facilité d'ajout de nouvelles fonctionnalités sans impacter l'UI.
* **Abstraction** : Masque les détails d'implémentation des API.
* **Gestion des erreurs** : Centralise la gestion des erreurs liées aux API.
* **Performance** : Optimise les appels API (caching, batching).
* **Documentation** : Facilite la compréhension de la logique métier.
* **Collaboration** : Simplifie le travail en équipe en définissant des interfaces claires.
* **Flexibilité** : Permet de changer l'implémentation sans affecter les consommateurs.

### Design patterns

* Smart vs Dumb components :
  * Smart : gèrent la logique, les données et l’état
  * Dumb : affichent uniquement les données reçues
* Container vs Presentational components :
  * Container : connectés au store, gèrent la logique
  * Presentational : affichent l’UI, reçoivent les props

#### Smart vs Dumb components

**Smart Component (TaskManager.vue)**

* Gère la logique métier : utilise le composable useTasks()
* État local : formulaire avec newTaskTitle
* Computed properties : completedTasks, completionRate
* Méthodes avec logique : addNewTask(), handleToggleComplete(), handleDeleteTask()
* Orchestration : coordonne plusieurs TaskCard

**Dumb Component (TaskCard.vue)**

* Affichage pur : reçoit uniquement task via props
* Aucune logique métier : juste présentation des données
* Émissions d'événements : toggle-complete, delete sans traitement
* Validation de props : assure la structure des données reçues
* Styles intégrés : focus sur l'apparence

**Avantages expliqués**

1. Réutilisabilité : TaskCard peut être utilisé partout
2. Testabilité : Logique séparée, tests plus faciles
3. Maintenance : Responsabilités claires
4. Lisibilité : Code mieux organisé

Le pattern montre clairement la séparation entre :

**Smart** = Logique + Données + État

**Dumb** = Affichage + Props + Events

#### Container vs Presentational Components

Pattern similaire mais avec une approche plus axée sur la séparation des données.

**Container Components**

* Connectés aux sources de données (store, API, composables)
* Gèrent les effets de bord et la logique d'état
* Fournissent les données aux composants présentationnels

**Exemple de Container Component :**

```javascript
<!-- TaskListContainer.vue (Container) -->
<template>
  <TaskListPresentation
    :tasks="tasks"
    :loading="loading"
    :error="error"
    :filters="availableFilters"
    :current-filter="currentFilter"
    @filter-change="updateFilter"
    @task-action="handleTaskAction"
  />
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useTaskStore } from '@/stores/taskStore'
import TaskListPresentation from './TaskListPresentation.vue'

// Connexion au store (source de données)
const taskStore = useTaskStore()
const { tasks, loading, error } = taskStore

// État local du container
const currentFilter = ref('all')
const availableFilters = [
  { value: 'all', label: 'Toutes' },
  { value: 'pending', label: 'En cours' },
  { value: 'completed', label: 'Terminées' }
]

// Logique de gestion des données
const updateFilter = (newFilter) => {
  currentFilter.value = newFilter
  taskStore.filterTasks(newFilter)
}

const handleTaskAction = ({ action, taskId }) => {
  switch (action) {
    case 'complete':
      taskStore.completeTask(taskId)
      break
    case 'delete':
      taskStore.deleteTask(taskId)
      break
    case 'edit':
      taskStore.startEditTask(taskId)
      break
  }
}

// Chargement initial des données
onMounted(() => {
  taskStore.fetchTasks()
})
</script>
```

**Presentational Components**

* Reçoivent toutes les données via props
* Émettent des événements pour toute interaction
* Aucune connaissance du contexte métier

**Exemple de Presentational Component :**

```javascript
<!-- TaskListPresentation.vue (Presentational) -->
<template>
  <div class="task-list-wrapper">
    <!-- Filtres -->
    <div class="filter-bar">
      <button
        v-for="filter in filters"
        :key="filter.value"
        @click="$emit('filter-change', filter.value)"
        :class="{ active: filter.value === currentFilter }"
      >
        {{ filter.label }}
      </button>
    </div>

    <!-- États de chargement/erreur -->
    <div v-if="loading" class="loading-state">
      <span class="spinner"></span>
      Chargement des tâches...
    </div>

    <div v-else-if="error" class="error-state">
      <span class="error-icon">⚠️</span>
      {{ error }}
    </div>

    <!-- Liste des tâches -->
    <div v-else-if="tasks.length" class="task-grid">
      <div
        v-for="task in tasks"
        :key="task.id"
        class="task-item"
        @click="$emit('task-action', { action: 'view', taskId: task.id })"
      >
        <h4>{{ task.title }}</h4>
        <p>{{ task.description }}</p>
        
        <div class="task-actions" @click.stop>
          <button @click="$emit('task-action', { action: 'complete', taskId: task.id })">
            {{ task.completed ? 'Rouvrir' : 'Terminer' }}
          </button>
          <button @click="$emit('task-action', { action: 'edit', taskId: task.id })">
            Modifier
          </button>
          <button @click="$emit('task-action', { action: 'delete', taskId: task.id })">
            Supprimer
          </button>
        </div>
      </div>
    </div>

    <!-- État vide -->
    <div v-else class="empty-state">
      <span class="empty-icon">📝</span>
      <h3>Aucune tâche</h3>
      <p>Commencez par créer votre première tâche !</p>
    </div>
  </div>
</template>

<script setup>
// Props pures (pas de logique métier)
defineProps({
  tasks: Array,
  loading: Boolean,
  error: String,
  filters: Array,
  currentFilter: String
})

// Événements émis (délégation vers le container)
defineEmits(['filter-change', 'task-action'])
</script>

<style scoped>
.task-list-wrapper { /* styles */ }
.filter-bar button.active { background: #007bff; color: white; }
.loading-state, .error-state, .empty-state { 
  text-align: center; 
  padding: 2rem; 
}
</style>
```

#### Avantages de ces patterns

**Pour Smart/Dumb :**

* **Réutilisabilité** : Les Dumb components sont facilement réutilisables
* **Testabilité** : Logique séparée de l'affichage
* **Maintenance** : Responsabilités claires

**Pour Container/Presentational :**

* **Séparation des préoccupations** : Données vs présentation
* **Flexibilité** : Changement de source de données sans impact sur l'UI
* **Performance** : Optimisations possibles au niveau container(images, CSS, fonts)

## 📝 Travaux pratiques

* Refactor TP précédent avec un composable `useTasks`.
* Ajouter `useCounter` pour pratiquer.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://cours.davidannebicque.fr/vue.js-but/but3/seance-2-architecture-et-composables.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
