> ## Documentation Index
> Fetch the complete documentation index at: https://docs-kit.lucdidion.lu/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Dashboard

> Interface de gestion pour outils, workflows, packs et quiz

## Vue d'ensemble

Le dashboard admin offre une interface complète de gestion de contenu pour Kit'Asso. Accessible uniquement aux utilisateurs authentifiés via Nhost Auth, il permet de gérer l'intégralité du catalogue.

**Fonctionnalités principales :**

* 4 panels de gestion (Tools, Workflows, Packs, Quiz)
* CRUD complet sur toutes les entités via GraphQL
* Upload d'images vers Nhost Storage
* Interface responsive avec recherche et filtres
* Modals lazy-loaded pour meilleures performances
* Notifications de succès/erreur (toast)
* Preview avant publication

***

## Architecture technique

### Structure des fichiers

```
src/pages/Admin.tsx                    # Page principale (tabs)
src/features/
  ├── tools/modals/ToolModal/          # Modal de gestion d'outils
  │   ├── index.tsx                    # Container
  │   ├── ToolForm.tsx                 # Formulaire
  │   ├── ToolPreview.tsx              # Preview
  │   └── useToolModal.ts             # Logique métier
  ├── workflows/modals/WorkflowModal/  # Modal de workflows
  ├── tool-packs/modals/PackModal/     # Modal de packs
  └── quiz/admin/                      # Panel admin quiz
      ├── QuizList.tsx                 # Liste des quiz
      ├── QuizForm.tsx                 # Formulaire création/édition quiz
      ├── QuizQuestionManager.tsx      # Gestion des questions avec réordonnancement
      ├── QuestionForm.tsx             # Éditeur de question + scoring weights
      ├── QuizRecommendationManager.tsx # Gestion des recommandations (V1)
      └── RecommendationForm.tsx       # Éditeur de conditions (V1)
```

### Sécurité et authentification

**Route protégée :**

```typescript theme={null}
// src/router.tsx
{
  path: '/admin',
  element: (
    <ProtectedRoute>
      <Admin />
    </ProtectedRoute>
  )
}
```

**ProtectedRoute** vérifie l'authentification via `useAuth()` (Nhost Auth). Si non connecté, redirection vers `/login`.

**Permissions Hasura :**

* Seuls les utilisateurs avec le rôle `admin` peuvent effectuer des INSERT/UPDATE/DELETE
* Accès complet à tous les contenus (y compris drafts)

***

## Panel 1 : Tools Management

### Interface principale

```typescript theme={null}
function ToolsPanel() {
  const [tools, setTools] = useState<EnhancedTool[]>([]);

  const loadTools = async () => {
    try {
      const data = await toolsApi.list();
      setTools(data);
    } catch (error) {
      toast.error('Erreur lors du chargement des outils');
    }
  };

  // Filtrage local par recherche + catégorie
  const filteredTools = tools.filter(tool => {
    const matchesSearch = tool.name.toLowerCase().includes(searchQuery);
    const matchesCategory = selectedCategory === 'all' || tool.category_id === selectedCategory;
    return matchesSearch && matchesCategory;
  });
}
```

### Upload de logo (Nhost Storage)

```typescript theme={null}
async function uploadLogo(file: File): Promise<string> {
  // Upload vers Nhost Storage
  const { error, fileMetadata } = await nhost.storage.upload({
    file,
    bucketId: 'default',
  });

  if (error) throw new Error('Erreur lors de l\'upload du logo');

  // Construire l'URL publique Nhost Storage
  const nhostSubdomain = import.meta.env.VITE_NHOST_SUBDOMAIN || 'gqvlmqwbsmkhlllmgbyw';
  const nhostRegion = import.meta.env.VITE_NHOST_REGION || 'eu-central-1';
  return `https://${nhostSubdomain}.storage.${nhostRegion}.nhost.run/v1/files/${fileMetadata.id}`;
}
```

### Modal de création/édition

Le formulaire utilise `react-hook-form` + `zod` pour la validation :

```typescript theme={null}
const toolSchema = z.object({
  name: z.string().min(1, 'Le nom est requis'),
  description: z.string().min(10, 'Description trop courte'),
  pricing_tier: z.enum(['Gratuit', 'Freemium', 'Payant', 'Entreprise']),
  category_id: z.string().uuid(),
  website_url: z.string().url().optional(),
  logo: z.instanceof(File).optional()
});
```

***

## Panel 2 : Workflows Management

Gestion des parcours guidés avec filtrage par statut (active/draft). Chaque workflow est éditable via un modal avec un step editor pour gérer les étapes.

**Création de workflow :** Le formulaire permet de définir les métadonnées (titre, difficulté, durée, icône) et d'ajouter/réordonner les étapes avec le `StepEditor`.

**Toggle de statut :** Un workflow peut basculer entre `active` (visible publiquement) et `draft` (admin uniquement) via une mutation GraphQL `update_workflows_by_pk` avec `_set`.

***

## Panel 3 : Tool Packs Management

Gestion des collections curées d'outils. Le `PackForm` permet de sélectionner les outils à inclure dans un pack via un système de checkboxes avec la liste complète des outils disponibles.

***

## Panel 4 : Quiz Management

Le panel quiz permet de gérer le cycle complet du diagnostic :

**QuizList** — Liste tous les quiz avec statut actif/inactif, boutons créer/éditer/supprimer.

**QuizForm** — Formulaire de création/édition d'un quiz (titre, description, slug, statut actif).

**QuizQuestionManager** — Gestion des questions avec :

* Réordonnancement par drag & drop
* 3 types : choix unique, choix multiple, échelle
* Configuration des règles de branchement (`next_question_rules`)

**QuestionForm** — Éditeur de question avec :

* Texte, type, aide contextuelle
* Options de réponse avec `scoring_weights` (clé V2 : pondération par outil/catégorie/pricing)
* Configuration du branchement conditionnel

**QuizRecommendationManager / RecommendationForm** — Gestion des règles de recommandation legacy V1 (condition\_logic). Le moteur V2 utilise les `scoring_weights` sur les réponses à la place.

<Info>
  Pour les nouveaux quiz, configurez les `scoring_weights` directement sur les réponses via le **QuestionForm**. Le système de recommandations V1 est conservé pour rétro-compatibilité.
</Info>

***

## Bonnes pratiques

### ✅ À faire

**Lazy loading des modals**

```typescript theme={null}
const ToolModal = lazy(() => import('./features/tools/modals/ToolModal'));

<Suspense fallback={<LoadingSpinner />}>
  {isOpen && <ToolModal />}
</Suspense>
```

**Charger uniquement l'onglet actif**

```typescript theme={null}
useEffect(() => {
  if (activeTab === 'tools') loadTools();
  if (activeTab === 'workflows') loadWorkflows();
}, [activeTab]);
```

**Confirmation avant suppression**

```typescript theme={null}
const handleDelete = async (id: string) => {
  if (!confirm('Supprimer cet élément ?')) return;
  try {
    await toolsApi.delete(id);
    toast.success('Suppression réussie');
    reloadData();
  } catch (error) {
    toast.error('Erreur lors de la suppression');
  }
};
```

### ❌ À éviter

* Charger toutes les données au mount (uniquement l'onglet actif)
* Appeler `nhost.graphql.request()` directement (passer par l'API layer)
* Oublier le cleanup des listeners

***

## Ressources

<CardGroup cols={2}>
  <Card title="Tools Feature" icon="wrench" href="/features/tools">
    Documentation des outils
  </Card>

  <Card title="Workflows Feature" icon="route" href="/features/workflows">
    Documentation des parcours guidés
  </Card>

  <Card title="Tool Packs" icon="package" href="/features/tool-packs">
    Gestion des collections d'outils
  </Card>

  <Card title="Quiz System" icon="clipboard-question" href="/features/quiz">
    Système de diagnostic interactif
  </Card>
</CardGroup>
