import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@/lib/queryClient"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useToast } from "@/hooks/use-toast"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Database, Plus, Edit, Trash2, Settings, Eye, EyeOff, CheckCircle, XCircle, AlertTriangle, Link, } from "lucide-react"; import type { NocodbConfig } from "@shared/schema"; // Form schemas const nocodbConfigFormSchema = z.object({ name: z.string().min(1, "Le nom est obligatoire"), baseUrl: z.string().url("L'URL doit être valide").min(1, "L'URL de base est obligatoire"), apiToken: z.string().min(1, "Le token API est obligatoire"), projectId: z.string().min(1, "L'ID du projet est obligatoire"), description: z.string().optional(), isActive: z.boolean().default(true), }); type NocodbConfigForm = z.infer; export default function NocoDBConfig() { const { user } = useAuthUnified(); const { toast } = useToast(); const queryClient = useQueryClient(); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [selectedConfig, setSelectedConfig] = useState(null); const [showTokens, setShowTokens] = useState<{ [key: number]: boolean }>({}); // Queries avec protection complète const { data: rawConfigs, isLoading, error } = useQuery({ queryKey: ['/api/nocodb-config'], enabled: user?.role === 'admin', }); // Protection quadruple couche pour éviter les erreurs TypeError const configs = rawConfigs || []; const safeConfigs = Array.isArray(configs) ? configs : []; // Log pour debug production avec plus de détails console.log('🔍 NocoDBConfig Debug:', { rawConfigs, rawConfigsType: typeof rawConfigs, configs, configsType: typeof configs, isArray: Array.isArray(configs), safeConfigs, safeConfigsType: typeof safeConfigs, length: safeConfigs.length, error, userRole: user?.role, environment: window.location.hostname }); // Mutations const createConfigMutation = useMutation({ mutationFn: (data: NocodbConfigForm) => apiRequest('/api/nocodb-config', 'POST', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/nocodb-config'] }); setShowCreateModal(false); toast({ title: "Configuration créée", description: "La configuration NocoDB a été créée avec succès.", }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de la création de la configuration.", variant: "destructive", }); }, }); const updateConfigMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: Partial }) => apiRequest(`/api/nocodb-config/${id}`, 'PUT', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/nocodb-config'] }); setShowEditModal(false); setSelectedConfig(null); toast({ title: "Configuration mise à jour", description: "La configuration NocoDB a été mise à jour avec succès.", }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de la mise à jour de la configuration.", variant: "destructive", }); }, }); const deleteConfigMutation = useMutation({ mutationFn: (id: number) => apiRequest(`/api/nocodb-config/${id}`, 'DELETE'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/nocodb-config'] }); setShowDeleteModal(false); setSelectedConfig(null); toast({ title: "Configuration supprimée", description: "La configuration NocoDB a été supprimée avec succès.", }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de la suppression de la configuration.", variant: "destructive", }); }, }); // Forms const createForm = useForm({ resolver: zodResolver(nocodbConfigFormSchema), defaultValues: { name: "", baseUrl: "", apiToken: "", projectId: "", description: "", isActive: true, }, }); const editForm = useForm({ resolver: zodResolver(nocodbConfigFormSchema), }); // Handle create config const handleCreateConfig = (data: NocodbConfigForm) => { createConfigMutation.mutate(data); }; // Handle edit config const handleEditConfig = (data: NocodbConfigForm) => { if (!selectedConfig) return; updateConfigMutation.mutate({ id: selectedConfig.id, data }); }; // Handle delete config const handleDeleteConfig = () => { if (!selectedConfig) return; deleteConfigMutation.mutate(selectedConfig.id); }; // Open edit modal const openEditModal = (config: NocodbConfig) => { setSelectedConfig(config); editForm.reset({ name: config.name, baseUrl: config.baseUrl, apiToken: config.apiToken, projectId: config.projectId, description: config.description || "", isActive: config.isActive, }); setShowEditModal(true); }; // Open delete modal const openDeleteModal = (config: NocodbConfig) => { setSelectedConfig(config); setShowDeleteModal(true); }; // Toggle token visibility const toggleTokenVisibility = (configId: number) => { setShowTokens(prev => ({ ...prev, [configId]: !prev[configId] })); }; // Format token for display const formatToken = (token: string, isVisible: boolean) => { if (isVisible) return token; return '•'.repeat(Math.min(token.length, 20)); }; if (user?.role !== 'admin') { return (

Accès restreint

Seuls les administrateurs peuvent gérer les configurations NocoDB.

); } if (isLoading) { return (
); } return (
{/* Header */}

Configuration NocoDB

Gérez les connexions aux bases de données NocoDB externes pour récupérer des informations.

{/* Configurations List */}
{safeConfigs.length === 0 ? (

Aucune configuration

Commencez par créer votre première configuration NocoDB.

) : ( safeConfigs.map((config: NocodbConfig) => (
{config.name} {config.isActive ? ( <> Actif ) : ( <> Inactif )}
{config.description && ( {config.description} )}
{config.baseUrl}
{config.projectId}
{formatToken(config.apiToken, showTokens[config.id])}
)) )}
{/* Create Modal */} Nouvelle Configuration NocoDB
Créer une nouvelle configuration pour se connecter à une base de données NocoDB externe
( Nom de la configuration )} /> ( URL de base )} /> ( Personal API Token )} /> ( ID du projet )} /> ( Description (optionnel)