> ## 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.

# Feature Workflows - Parcours Guidés

> Tutoriels étape par étape pour la transformation digitale

## Vue d'ensemble

Les **Workflows** (Parcours Guidés) sont des tutoriels structurés qui accompagnent les associations dans leurs projets digitaux étape par étape.

**Objectif :** Démystifier les outils numériques avec des guides concrets et actionnables.

**Statistiques :**

* 3 niveaux de difficulté (Débutant, Intermédiaire, Expert)
* Durée estimée par workflow (30min à 2h)
* Recommandations d'outils intégrées
* Templates et checklists inclus

***

## Architecture technique

### Tables de base de données

#### Table `workflows`

```sql theme={null}
CREATE TABLE workflows (
  id UUID PRIMARY KEY,
  title TEXT NOT NULL,
  description TEXT NOT NULL,
  difficulty TEXT CHECK (difficulty IN ('débutant', 'intermédiaire', 'expert')),
  duration TEXT NOT NULL,
  category TEXT NOT NULL,
  icon TEXT NOT NULL,
  status TEXT CHECK (status IN ('active', 'draft')),
  steps JSONB NOT NULL,
  display_order INTEGER DEFAULT 0,
  objective TEXT,
  completion_message TEXT,
  next_steps JSONB DEFAULT '[]',
  resources JSONB DEFAULT '[]',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);
```

#### Table `workflow_steps`

```sql theme={null}
CREATE TABLE workflow_steps (
  id UUID PRIMARY KEY,
  workflow_id UUID REFERENCES workflows(id) ON DELETE CASCADE,
  step_number INTEGER NOT NULL,
  tool_name TEXT NOT NULL,
  action TEXT NOT NULL,
  tool_url TEXT,
  detailed_instructions JSONB DEFAULT '[]',
  practical_tip TEXT,
  completion_checklist TEXT[],
  created_at TIMESTAMPTZ DEFAULT NOW()
);
```

***

### API Layer (GraphQL)

**Fichier :** `src/api/workflows.ts`

Toutes les opérations passent par `apiCall()` / `apiCallVoid()` + `nhost.graphql.request()`.

```typescript theme={null}
import { apiCall, apiCallVoid } from './base';
import { nhost } from './client';

export const workflowsApi = {
  /**
   * Liste tous les workflows actifs
   */
  async list(): Promise<Workflow[]> {
    const query = `
      query GetWorkflows {
        workflows(
          where: { status: { _eq: "active" } }
          order_by: { display_order: asc }
        ) {
          id title description difficulty duration
          category icon status steps display_order
        }
      }
    `;
    return apiCall(
      () => nhost.graphql.request({ query }),
      'workflows.list'
    );
  },

  /**
   * Récupère un workflow par ID avec ses steps
   */
  async getById(id: string): Promise<Workflow> {
    const query = `
      query GetWorkflow($id: uuid!) {
        workflows_by_pk(id: $id) {
          id title description difficulty duration
          category icon status steps display_order
          objective completion_message
          workflow_steps(order_by: { step_number: asc }) {
            id step_number tool_name action tool_url
            detailed_instructions practical_tip completion_checklist
          }
        }
      }
    `;
    return apiCall(
      () => nhost.graphql.request({ query, variables: { id } }),
      'workflows.getById'
    );
  },

  /**
   * Met à jour un workflow (convention Hasura : _set)
   */
  async update(id: string, updates: Partial<Workflow>): Promise<Workflow> {
    const mutation = `
      mutation UpdateWorkflow($id: uuid!, $set: workflows_set_input!) {
        update_workflows_by_pk(pk_columns: { id: $id }, _set: $set) {
          id title status
        }
      }
    `;
    return apiCall(
      () => nhost.graphql.request({
        query: mutation,
        variables: { id, set: { ...updates, updated_at: new Date().toISOString() } }
      }),
      'workflows.update'
    );
  },

  /**
   * Supprime un workflow (cascade sur steps)
   */
  async delete(id: string): Promise<void> {
    const mutation = `
      mutation DeleteWorkflow($id: uuid!) {
        delete_workflows_by_pk(id: $id) { id }
      }
    `;
    await apiCallVoid(
      () => nhost.graphql.request({ query: mutation, variables: { id } }),
      'workflows.delete'
    );
  },
};
```

***

## Structure d'un workflow

### Métadonnées

```typescript theme={null}
interface Workflow {
  id: string;
  title: string;                    // "Créer ma première newsletter"
  description: string;               // Résumé du parcours
  difficulty: 'débutant' | 'intermédiaire' | 'expert';
  duration: string;                  // "30min", "1h", "2h"
  category: string;                  // "Communication", "Site Web", etc.
  icon: string;                      // Nom d'icône Lucide (ex: "Mail")
  status: 'active' | 'draft';
  display_order: number;
  objective?: string;
  completion_message?: string;
}
```

### Steps (étapes)

```typescript theme={null}
interface WorkflowStep {
  step_number: number;
  tool_name: string;                 // "Mailchimp"
  action: string;                    // "Créer votre compte"
  tool_url?: string;
  detailed_instructions: Array<{
    text: string;
    type: 'text' | 'code' | 'link';
  }>;
  practical_tip?: string;
  completion_checklist?: string[];
}
```

***

## Permissions Hasura

* **public** : SELECT des workflows actifs uniquement (`status = 'active'`)
* **public** : `workflow_steps` visibles uniquement si le workflow parent est actif
* **admin** : CRUD complet sur toutes les lignes (y compris drafts)

***

## Composants UI

### WorkflowCard

```typescript theme={null}
// src/components/workflows/WorkflowCard.tsx
import { Clock } from 'lucide-react';
import { getIconComponent } from '@/utils/icons';

export function WorkflowCard({ workflow, onClick }: WorkflowCardProps) {
  const Icon = getIconComponent(workflow.icon);

  const difficultyColors = {
    'débutant': 'bg-green-100 text-green-800',
    'intermédiaire': 'bg-orange-100 text-orange-800',
    'expert': 'bg-red-100 text-red-800',
  };

  return (
    <div onClick={onClick} className="p-6 border rounded-lg hover:shadow-lg transition cursor-pointer">
      <div className="flex items-start gap-4">
        <div className="p-3 bg-indigo-100 rounded-lg">
          <Icon className="w-6 h-6 text-indigo-600" />
        </div>
        <div className="flex-1">
          <h3 className="font-semibold text-lg mb-2">{workflow.title}</h3>
          <p className="text-gray-600 text-sm mb-4">{workflow.description}</p>
          <div className="flex items-center gap-4 text-sm">
            <span className={`px-2 py-1 rounded ${difficultyColors[workflow.difficulty]}`}>
              {workflow.difficulty}
            </span>
            <span className="flex items-center gap-1 text-gray-500">
              <Clock className="w-4 h-4" /> {workflow.duration}
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}
```

### WorkflowStepper

```typescript theme={null}
// src/components/workflows/WorkflowStepper.tsx
export function WorkflowStepper({ steps, currentStep, onStepClick }) {
  return (
    <div className="flex items-center justify-between mb-8">
      {steps.map((step, index) => {
        const isActive = currentStep === index;
        const isCompleted = currentStep > index;

        return (
          <div key={step.id} className="flex items-center flex-1">
            <button
              onClick={() => onStepClick(index)}
              className={`w-10 h-10 rounded-full flex items-center justify-center
                ${isCompleted ? 'bg-green-500 text-white' :
                  isActive ? 'bg-indigo-600 text-white' :
                  'bg-gray-200 text-gray-600'}`}
            >
              {isCompleted ? '✓' : index + 1}
            </button>
            {index < steps.length - 1 && (
              <div className={`h-1 flex-1 mx-2 ${isCompleted ? 'bg-green-500' : 'bg-gray-200'}`} />
            )}
          </div>
        );
      })}
    </div>
  );
}
```

***

## Hooks

### useWorkflowProgress

```typescript theme={null}
// src/features/workflows/hooks/useWorkflowProgress.ts
export function useWorkflowProgress(totalSteps: number) {
  const [currentStep, setCurrentStep] = useState(0);
  const [completedSteps, setCompletedSteps] = useState<Set<number>>(new Set());

  const nextStep = () => { /* ... */ };
  const prevStep = () => { /* ... */ };
  const goToStep = (step: number) => { /* ... */ };
  const progress = (completedSteps.size / totalSteps) * 100;

  return {
    currentStep, completedSteps, progress,
    nextStep, prevStep, goToStep,
    isFirstStep: currentStep === 0,
    isLastStep: currentStep === totalSteps - 1,
  };
}
```

***

## Bonnes pratiques

### ✅ À faire

* Instructions claires et actionnables (un objectif par étape)
* 3-7 étapes max par workflow
* Durée réaliste (tester avant de publier)
* Checklist de validation par étape

### ❌ À éviter

* Instructions vagues ("Configurez l'outil")
* Étapes trop longues (> 15 min)
* Oubli des prérequis
* Jargon technique sans explication

***

## Ressources

<CardGroup cols={2}>
  <Card title="Database Schema" icon="database" href="/database/schema">
    Structure des tables workflows
  </Card>

  <Card title="API Layer" icon="plug" href="/development/api-layer">
    Utiliser l'API GraphQL
  </Card>

  <Card title="Admin Dashboard" icon="shield" href="/features/admin">
    Créer et gérer des workflows
  </Card>

  <Card title="Patterns" icon="code" href="/architecture/patterns">
    Modals, forms, hooks
  </Card>
</CardGroup>
