feat: application n8n Pilot complète (Expo managed workflow)
- Stack : Expo Router, Axios, Zustand, React Native Paper (thème sombre), date-fns - Sécurité : secrets dans Android Keystore via expo-secure-store, TLS obligatoire, headers X-N8N-API-KEY + X-App-Token injectés par intercepteur Axios - API : client.ts centralisé + workflows.ts + executions.ts (TypeScript strict) - Store : Zustand appStore avec chargement depuis secure store au démarrage - Hooks : usePolling (générique), useWorkflows, useExecutions - Composants : StatusBadge, WorkflowCard, ExecutionCard, SkeletonLoader - Screens : Dashboard, Workflows, Executions, Logs (détail exécution), Settings - Navigation Expo Router : 4 tabs + stack Logs + écran Setup initial - Docs : INSTALL.md, UPDATE.md, BACKUP.md, HAPROXY.md, SECURITY.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useAppStore } from '../store/appStore';
|
||||
import {
|
||||
fetchWorkflows,
|
||||
activateWorkflow,
|
||||
deactivateWorkflow,
|
||||
runWorkflow,
|
||||
Workflow,
|
||||
} from '../api/workflows';
|
||||
import { usePolling } from './usePolling';
|
||||
|
||||
/** Interface des valeurs et actions exposées par le hook */
|
||||
interface UseWorkflowsResult {
|
||||
workflows: Workflow[];
|
||||
isLoading: boolean;
|
||||
isRefreshing: boolean;
|
||||
/**
|
||||
* Recharge manuellement la liste des workflows.
|
||||
* Utilisé pour le pull-to-refresh.
|
||||
*/
|
||||
refresh: () => Promise<void>;
|
||||
/**
|
||||
* Bascule le statut actif/inactif d'un workflow.
|
||||
* Met à jour l'état local après confirmation serveur.
|
||||
*
|
||||
* @param workflow - Workflow à basculer
|
||||
*/
|
||||
toggleWorkflow: (workflow: Workflow) => Promise<void>;
|
||||
/**
|
||||
* Déclenche manuellement l'exécution d'un workflow.
|
||||
*
|
||||
* @param id - Identifiant du workflow à déclencher
|
||||
*/
|
||||
triggerWorkflow: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook de gestion des workflows : chargement, polling automatique et actions.
|
||||
* Délègue la gestion des erreurs à l'intercepteur Axios (errorHandler).
|
||||
* Les screens ne font pas d'appels API directs — tout passe par ce hook.
|
||||
*/
|
||||
export const useWorkflows = (): UseWorkflowsResult => {
|
||||
const { preferences, config } = useAppStore();
|
||||
const [workflows, setWorkflows] = useState<Workflow[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
/** Charge la liste des workflows depuis la couche API */
|
||||
const loadWorkflows = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
const data = await fetchWorkflows();
|
||||
setWorkflows(data);
|
||||
} catch {
|
||||
// Erreur déjà remontée via l'intercepteur Axios → toast affiché
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Polling automatique déclenché uniquement si l'app est configurée.
|
||||
* L'intervalle est lu depuis les préférences utilisateur (configurable dans Settings).
|
||||
*/
|
||||
usePolling({
|
||||
callback: loadWorkflows,
|
||||
interval: preferences.pollingInterval,
|
||||
enabled: config.isConfigured,
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
/** Pull-to-refresh : indicateur distinct du chargement initial */
|
||||
const refresh = useCallback(async (): Promise<void> => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await loadWorkflows();
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}, [loadWorkflows]);
|
||||
|
||||
/**
|
||||
* Bascule actif/inactif d'un workflow.
|
||||
* Met à jour l'entrée correspondante dans l'état local après retour serveur.
|
||||
*/
|
||||
const toggleWorkflow = useCallback(async (workflow: Workflow): Promise<void> => {
|
||||
try {
|
||||
const updated = workflow.active
|
||||
? await deactivateWorkflow(workflow.id)
|
||||
: await activateWorkflow(workflow.id);
|
||||
|
||||
// Mise à jour ciblée sans recharger toute la liste
|
||||
setWorkflows((prev) => prev.map((w) => (w.id === updated.id ? updated : w)));
|
||||
} catch {
|
||||
// Géré par l'intercepteur Axios
|
||||
}
|
||||
}, []);
|
||||
|
||||
const triggerWorkflow = useCallback(async (id: string): Promise<void> => {
|
||||
try {
|
||||
await runWorkflow(id);
|
||||
} catch {
|
||||
// Géré par l'intercepteur Axios
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
workflows,
|
||||
isLoading,
|
||||
isRefreshing,
|
||||
refresh,
|
||||
toggleWorkflow,
|
||||
triggerWorkflow,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user