# Chapitre 3 : Flux de données ## Vue d'ensemble Comprendre comment les données circulent dans SmartMaker est essentiel. Ce chapitre trace le chemin complet d'une donnée, du clic utilisateur jusqu'au rendu final. ## Les 3 types de données ^ Type ^ Stockage ^ Persistance ^ Hook ^ | État local | Mémoire composant | Non | useState, useStates | | État global | Redux store | Optionnel (localStorage) | useGlobalStates | | Données serveur | API → Dolibarr | Base de données | useApi | ## Flux d'une requête API ``` ┌──────────────────────────────────────────────────────────────┐ │ FRONTEND │ ├──────────────────────────────────────────────────────────────┤ │ 1. Utilisateur clique "Charger" │ │ ↓ │ │ 2. Composant appelle useApi() │ │ ↓ │ │ 3. api.private.get('items') │ │ ↓ │ │ 4. ky ajoute automatiquement : │ │ - Authorization: Bearer │ │ - X-DEVICEID: │ └──────────────────────────────────────────────────────────────┘ ↓ HTTPS ┌──────────────────────────────────────────────────────────────┐ │ BACKEND │ ├──────────────────────────────────────────────────────────────┤ │ 5. pwa/api.php reçoit la requête │ │ ↓ │ │ 6. SmartAuth valide le JWT │ │ ↓ │ │ 7. Router appelle ItemController::index() │ │ ↓ │ │ 8. Controller charge les objets Dolibarr │ │ ↓ │ │ 9. Mapper convertit en DTO │ │ ↓ │ │ 10. Retourne JSON │ └──────────────────────────────────────────────────────────────┘ ↓ HTTPS ┌──────────────────────────────────────────────────────────────┐ │ FRONTEND │ ├──────────────────────────────────────────────────────────────┤ │ 11. ky parse la réponse JSON │ │ ↓ │ │ 12. Composant met à jour l'état │ │ ↓ │ │ 13. React re-rend avec les nouvelles données │ └──────────────────────────────────────────────────────────────┘ ``` ## Exemple concret : Liste de produits ### 1. Le composant React ```javascript // components/pages/private/ProductsPage/index.jsx import { useEffect } from 'react'; import { Page, Block, List, ListItem, Spinner } from '@cap-rel/smartcommon'; import { useApi, useStates } from '@cap-rel/smartcommon'; export const ProductsPage = () => { const api = useApi(); const st = useStates({ initialStates: { products: [], loading: true, error: null } }); // Chargement au montage useEffect(() => { loadProducts(); }, []); const loadProducts = async () => { st.set('loading', true); st.set('error', null); try { // Requête API avec JWT automatique const data = await api.private.get('products').json(); st.set('products', data.products); } catch (err) { st.set('error', err.message); } finally { st.set('loading', false); } }; // Affichage conditionnel if (st.get('loading')) { return ; } if (st.get('error')) { return ( Erreur : {st.get('error')} ); } return ( {st.get('products').map(product => ( ))} ); }; ``` ### 2. Le routeur API (PHP) ```php query($sql); $products = []; if ($result) { $mapper = new dmProduct(); while ($obj = $db->fetch_object($result)) { $product = new \Product($db); $product->fetch($obj->rowid); // Mapper vers DTO React $products[] = $mapper->exportMappedData($product); } } return [['products' => $products], 200]; } public function show($payload = null) { global $db; $id = $payload['id']; require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; $product = new \Product($db); if ($product->fetch($id) <= 0) { return [['error' => 'Produit non trouvé'], 404]; } $mapper = new dmProduct(); return [$mapper->exportMappedData($product), 200]; } } ``` ### 4. Le Mapper (PHP) ```php (int) $product->id, 'ref' => $product->ref, 'label' => $product->label, 'description' => $product->description, 'price' => (float) $product->price, 'price_ttc' => (float) $product->price_ttc, 'stock' => (float) $product->stock_reel, 'status' => (int) $product->status, 'created_at' => $product->datec, 'updated_at' => $product->tms ]; } public function importMappedData($data) { // Conversion DTO → Dolibarr pour création/modification return [ 'ref' => $data['ref'] ?? '', 'label' => $data['label'] ?? '', 'description' => $data['description'] ?? '', 'price' => $data['price'] ?? 0 ]; } } ``` ## Authentification JWT ### Flux de login ``` 1. Utilisateur saisit email/password ↓ 2. api.login({ login, password }) ↓ 3. SmartAuth vérifie les credentials ↓ 4. Retourne { accessToken, refreshToken, user } ↓ 5. SmartCommon stocke dans session (localStorage) ↓ 6. Toutes les requêtes futures ont le token ``` ### Exemple login ```javascript // components/pages/public/LoginPage/index.jsx import { Page, Block, Form, Input, Button } from '@cap-rel/smartcommon'; import { useApi, useNavigation } from '@cap-rel/smartcommon'; export const LoginPage = () => { const api = useApi(); const navigate = useNavigation(); const handleSubmit = async (values) => { try { // api.login gère tout automatiquement await api.login({ login: values.email, password: values.password }); // Redirige vers l'accueil navigate('/'); } catch (err) { alert('Identifiants incorrects'); } }; return (
); }; ``` ### Refresh automatique Le token d'accès expire après 15 minutes. SmartCommon gère automatiquement : 1. Détecte l'erreur 401 (token expiré) 2. Appelle `/refresh` avec le refreshToken 3. Obtient un nouveau accessToken 4. Relance la requête originale Tout est transparent pour le développeur. ## État global vs État local ### Quand utiliser useStates (local) - Données propres à un composant - État de formulaire - État de chargement - Données temporaires ```javascript const st = useStates({ initialStates: { loading: false, formData: { name: '', email: '' } } }); ``` ### Quand utiliser useGlobalStates - Données partagées entre composants - Session utilisateur - Préférences - Données métier principales ```javascript const [session, setSession] = useGlobalStates('session'); const [cart, setCart] = useGlobalStates('cart'); ``` ## Création de données ### Flux de création ``` 1. Formulaire rempli ↓ 2. api.private.post('products', { json: data }) ↓ 3. Controller::create() valide et crée ↓ 4. Retourne le produit créé ↓ 5. Mise à jour de l'état local/global ``` ### Exemple ```javascript const handleCreate = async (formData) => { try { const newProduct = await api.private .post('products', { json: formData }) .json(); // Ajouter à la liste locale st.set('products', [...st.get('products'), newProduct]); // Ou mettre à jour l'état global // const [products, setProducts] = useGlobalStates('products'); // setProducts([...products, newProduct]); navigate('/products'); } catch (err) { st.set('error', err.message); } }; ``` ## Résumé du flux complet ^ Étape ^ Couche ^ Action ^ | 1 | React | Utilisateur interagit | | 2 | Hook | useApi() prépare la requête | | 3 | ky | Ajoute JWT + envoie | | 4 | PHP | api.php route la requête | | 5 | SmartAuth | Valide le token | | 6 | Controller | Exécute la logique métier | | 7 | Dolibarr | Accès base de données | | 8 | Mapper | Convertit en DTO | | 9 | PHP | Retourne JSON | | 10 | ky | Parse la réponse | | 11 | Hook | Met à jour l'état | | 12 | React | Re-rend l'interface | ## Points clés à retenir 1. **useApi** gère automatiquement l'authentification JWT 2. **Controller** charge les objets Dolibarr 3. **Mapper** convertit Dolibarr → DTO React 4. **useStates** pour l'état local 5. **useGlobalStates** pour l'état partagé 6. Le refresh token est géré automatiquement [[:15_training:module5-architecture-smartmaker:configuration|← Chapitre précédent]] | [[:15_training:module5-architecture-smartmaker:start|Retour au module]] | [[:15_training:module6-smartcommon-composants:start|Module suivant : SmartCommon Composants →]]