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,87 @@
|
||||
import { formatDistanceToNow, format, differenceInSeconds } from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
|
||||
/**
|
||||
* Formate une date ISO en temps relatif lisible en français.
|
||||
* Ex : "il y a 3 minutes", "dans 2 heures"
|
||||
*
|
||||
* @param dateString - Date au format ISO 8601
|
||||
* @returns Chaîne relative en français, ou "date inconnue" si parsing impossible
|
||||
*/
|
||||
export const formatRelativeDate = (dateString: string): string => {
|
||||
try {
|
||||
return formatDistanceToNow(new Date(dateString), { addSuffix: true, locale: fr });
|
||||
} catch {
|
||||
return 'date inconnue';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formate une date ISO en format court DD/MM/YYYY HH:mm.
|
||||
*
|
||||
* @param dateString - Date au format ISO 8601
|
||||
* @returns Chaîne formatée, ou "date inconnue" si parsing impossible
|
||||
*/
|
||||
export const formatShortDate = (dateString: string): string => {
|
||||
try {
|
||||
return format(new Date(dateString), 'dd/MM/yyyy HH:mm', { locale: fr });
|
||||
} catch {
|
||||
return 'date inconnue';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Calcule et formate la durée entre deux dates.
|
||||
* Si stoppedAt est absent, calcule jusqu'à maintenant (exécution en cours).
|
||||
*
|
||||
* @param startedAt - Date de début ISO 8601
|
||||
* @param stoppedAt - Date de fin ISO 8601 (optionnelle)
|
||||
* @returns Durée formatée ex : "2m 34s", "45s"
|
||||
*/
|
||||
export const formatDuration = (startedAt: string, stoppedAt?: string): string => {
|
||||
try {
|
||||
const start = new Date(startedAt);
|
||||
const end = stoppedAt ? new Date(stoppedAt) : new Date();
|
||||
const seconds = differenceInSeconds(end, start);
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
||||
} catch {
|
||||
return 'durée inconnue';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Traduit le statut d'une exécution n8n en libellé français.
|
||||
*
|
||||
* @param status - Statut brut de l'API n8n
|
||||
* @returns Libellé français correspondant
|
||||
*/
|
||||
export const formatStatus = (status: string): string => {
|
||||
const statusMap: Record<string, string> = {
|
||||
success: 'Succès',
|
||||
error: 'Erreur',
|
||||
waiting: 'En attente',
|
||||
running: 'En cours',
|
||||
canceled: 'Annulé',
|
||||
};
|
||||
return statusMap[status] ?? status;
|
||||
};
|
||||
|
||||
/**
|
||||
* Traduit le mode de déclenchement d'une exécution n8n.
|
||||
*
|
||||
* @param mode - Mode brut retourné par l'API
|
||||
* @returns Libellé français correspondant
|
||||
*/
|
||||
export const formatMode = (mode: string): string => {
|
||||
const modeMap: Record<string, string> = {
|
||||
manual: 'Manuel',
|
||||
trigger: 'Déclencheur',
|
||||
webhook: 'Webhook',
|
||||
internal: 'Interne',
|
||||
retry: 'Réessai',
|
||||
};
|
||||
return modeMap[mode] ?? mode;
|
||||
};
|
||||
Reference in New Issue
Block a user