building-admin-dashboard-customizations

Par medusajs · medusa-agent-skills

Chargé automatiquement lors de la planification, de la recherche ou de l'implémentation de l'interface utilisateur du tableau de bord Medusa Admin (widgets, pages personnalisées, formulaires, tableaux, chargement de données, navigation). OBLIGATOIRE pour tout travail sur l'interface admin dans TOUS les modes (planification, implémentation, exploration). Contient les design patterns, l'utilisation des composants et les patterns de chargement de données que les serveurs MCP ne fournissent pas.

npx skills add https://github.com/medusajs/medusa-agent-skills --skill building-admin-dashboard-customizations

Personnalisations du tableau de bord d'administration Medusa

Créez des extensions d'interface utilisateur personnalisées pour le tableau de bord d'administration de Medusa en utilisant l'Admin SDK et les composants Medusa UI.

Remarque : « UI Routes » sont des pages d'administration personnalisées, différentes des routes API backend (qui utilisent la compétence building-with-medusa).

Quand appliquer cette compétence

Chargez cette compétence pour TOUTE tâche de développement d'interface d'administration, notamment :

  • Créer des widgets pour les pages de produits/commandes/clients
  • Créer des pages d'administration personnalisées
  • Implémenter des formulaires et des modales
  • Afficher des données avec des tableaux ou des listes
  • Ajouter une navigation entre les pages

Chargez également ces compétences quand :

  • building-with-medusa : Créer des routes API backend que l'interface d'administration appelle
  • building-storefronts : Si vous travaillez sur la devanture plutôt que sur le tableau de bord d'administration

CRITIQUE : Charger les fichiers de référence quand nécessaire

La référence rapide ci-dessous N'EST PAS suffisante pour l'implémentation. Vous DEVEZ charger les fichiers de référence pertinents avant d'écrire du code pour ce composant.

Chargez ces références selon ce que vous implémentez :

  • Créer des widgets ? → DOIT charger references/data-loading.md en premier
  • Créer des formulaires/modales ? → DOIT charger references/forms.md en premier
  • Afficher des données dans des tableaux/listes ? → DOIT charger references/display-patterns.md en premier
  • Sélectionner dans de grands ensembles de données ? → DOIT charger references/table-selection.md en premier
  • Ajouter une navigation ? → DOIT charger references/navigation.md en premier
  • Styliser des composants ? → DOIT charger references/typography.md en premier

Exigence minimale : Chargez au moins 1 à 2 fichiers de référence pertinents à votre tâche spécifique avant l'implémentation.

Quand utiliser cette compétence vs serveur MCP MedusaDocs

⚠️ CRITIQUE : Cette compétence doit être consultée EN PREMIER pour la planification et l'implémentation.

Utilisez cette compétence pour (SOURCE PRIMAIRE) :

  • Planification - Comprendre comment structurer les fonctionnalités d'interface d'administration
  • Modèles de composants - Widgets, pages, formulaires, tableaux, modales
  • Système de conception - Typographie, couleurs, espacement, classes sémantiques
  • Chargement de données - Modèle de requête distincte critique, invalidation du cache
  • Bonnes pratiques - Modèles corrects vs incorrects (par ex., afficher des requêtes au montage)
  • Règles critiques - Ce qu'il NE FAUT PAS faire (erreurs courantes comme les requêtes d'affichage conditionnelles)

Utilisez le serveur MCP MedusaDocs pour (SOURCE SECONDAIRE) :

  • Signatures de props de composants spécifiques après avoir choisi le composant
  • Liste des zones de widget disponibles
  • Détails de la méthode SDK JS
  • Référence des options de configuration

Pourquoi les compétences viennent en premier :

  • Les compétences contiennent des modèles critiques comme des requêtes d'affichage/modal séparées que le MCP n'insiste pas
  • Les compétences montrent les modèles corrects vs incorrects ; le MCP montre ce qui est possible
  • La planification nécessite de comprendre les modèles, pas seulement la référence API

Règles de configuration critiques

Configuration du client SDK

CRITIQUE : Utilisez toujours la configuration exacte - différentes valeurs causent des erreurs :

// src/admin/lib/client.ts
import Medusa from "@medusajs/js-sdk"

export const sdk = new Medusa({
  baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
  debug: import.meta.env.DEV,
  auth: {
    type: "session",
  },
})

Utilisateurs pnpm UNIQUEMENT

CRITIQUE : Installez les dépendances peer AVANT d'écrire du code :

# Trouvez la version exacte du tableau de bord
pnpm list @tanstack/react-query --depth=10 | grep @medusajs/dashboard
# Installez cette version exacte
pnpm add @tanstack/react-query@[exact-version]

# Si vous utilisez la navigation (composant Link)
pnpm list react-router-dom --depth=10 | grep @medusajs/dashboard
pnpm add react-router-dom@[exact-version]

Utilisateurs npm/yarn : N'INSTALLEZ PAS ces packages - déjà disponibles.

Catégories de règles par priorité

Priorité Catégorie Impact Préfixe
1 Chargement de données CRITIQUE data-
2 Système de conception CRITIQUE design-
3 Affichage de données ÉLEVÉ (inclut la règle de prix CRITIQUE) display-
4 Typographie ÉLEVÉ typo-
5 Formulaires et modales MOYEN form-
6 Modèles de sélection MOYEN select-

Référence rapide

1. Chargement de données (CRITIQUE)

  • data-sdk-always - UTILISEZ TOUJOURS le SDK JS Medusa pour TOUTES les requêtes API - NE JAMAIS utiliser fetch() ordinaire (les en-têtes d'authentification manquants causent des erreurs)
  • data-sdk-method-choice - Utilisez les méthodes SDK existantes pour les endpoints intégrés (sdk.admin.product.list()), utilisez sdk.client.fetch() pour les routes personnalisées
  • data-display-on-mount - Les requêtes d'affichage DOIVENT se charger au montage (pas de condition enabled basée sur l'état UI)
  • data-separate-queries - Séparez les requêtes d'affichage des requêtes de modal/formulaire
  • data-invalidate-display - Invalidez les requêtes d'affichage après les mutations, pas seulement les requêtes de modal
  • data-loading-states - Affichez toujours les états de chargement (Spinner), pas les états vides
  • data-pnpm-install-first - Les utilisateurs pnpm DOIVENT installer @tanstack/react-query AVANT de coder

2. Système de conception (CRITIQUE)

  • design-semantic-colors - Utilisez toujours les classes de couleur sémantiques (bg-ui-bg-base, text-ui-fg-subtle), jamais codées en dur
  • design-spacing - Utilisez px-6 py-4 pour le remplissage des sections, gap-2 pour les listes, gap-3 pour les éléments
  • design-button-size - Utilisez toujours size="small" pour les boutons dans les widgets et tableaux
  • design-medusa-components - Utilisez toujours les composants Medusa UI (Container, Button, Text), pas du HTML brut

3. Affichage de données (ÉLEVÉ)

  • display-price-format - CRITIQUE : Les prix de Medusa sont stockés tels quels ($49,99 = 49,99, PAS en centimes). Affichez-les directement - NE JAMAIS diviser par 100

4. Typographie (ÉLEVÉ)

  • typo-text-component - Utilisez toujours le composant Text de @medusajs/ui, jamais des balises span/p brutes
  • typo-labels - Utilisez <Text size="small" leading="compact" weight="plus"> pour les étiquettes/en-têtes
  • typo-descriptions - Utilisez <Text size="small" leading="compact" className="text-ui-fg-subtle"> pour les descriptions
  • typo-no-heading-widgets - N'utilisez jamais Heading pour les petites sections dans les widgets (utilisez Text à la place)

5. Formulaires et modales (MOYEN)

  • form-focusmodal-create - Utilisez FocusModal pour créer de nouvelles entités
  • form-drawer-edit - Utilisez Drawer pour modifier des entités existantes
  • form-disable-pending - Désactivez toujours les actions pendant les mutations (disabled={mutation.isPending})
  • form-show-loading - Afficher l'état de chargement sur le bouton d'envoi (isLoading={mutation.isPending})

6. Modèles de sélection (MOYEN)

  • select-small-datasets - Utilisez le composant Select pour 2-10 options (statuts, types, etc.)
  • select-large-datasets - Utilisez DataTable avec FocusModal pour les grands ensembles de données (produits, catégories, etc.)
  • select-search-config - Doit transmettre la configuration de recherche à useDataTable pour éviter l'erreur « search not enabled »

Modèle critique de chargement de données

SUIVEZ TOUJOURS ce modèle - ne chargez jamais les données d'affichage de manière conditionnelle :

// ✅ CORRECT - Requêtes séparées avec responsabilités appropriées
const RelatedProductsWidget = ({ data: product }) => {
  const [modalOpen, setModalOpen] = useState(false)

  // Requête d'affichage - se charge au montage
  const { data: displayProducts } = useQuery({
    queryFn: () => fetchSelectedProducts(selectedIds),
    queryKey: ["related-products-display", product.id],
    // Pas de condition 'enabled' - se charge immédiatement
  })

  // Requête modale - se charge si nécessaire
  const { data: modalProducts } = useQuery({
    queryFn: () => sdk.admin.product.list({ limit: 10, offset: 0 }),
    queryKey: ["products-selection"],
    enabled: modalOpen, // OK pour les données réservées à la modale
  })

  // Mutation avec invalidation appropriée
  const updateProduct = useMutation({
    mutationFn: updateFunction,
    onSuccess: () => {
      // Invalidez la requête de données d'affichage pour actualiser l'interface
      queryClient.invalidateQueries({ queryKey: ["related-products-display", product.id] })
      // Invalidez aussi la requête d'entité
      queryClient.invalidateQueries({ queryKey: ["product", product.id] })
      // Remarque : Pas besoin d'invalider la requête de sélection modale
    },
  })

  return (
    <Container>
      {/* L'affichage utilise displayProducts */}
      {displayProducts?.map(p => <div key={p.id}>{p.title}</div>)}

      <FocusModal open={modalOpen} onOpenChange={setModalOpen}>
        {/* La modale utilise modalProducts */}
      </FocusModal>
    </Container>
  )
}

// ❌ MAUVAIS - Requête unique avec chargement conditionnel
const BrokenWidget = ({ data: product }) => {
  const [modalOpen, setModalOpen] = useState(false)

  const { data } = useQuery({
    queryFn: () => sdk.admin.product.list(),
    enabled: modalOpen, // ❌ L'affichage se casse à l'actualisation de la page !
  })

  // Essai d'affichage à partir de la requête modale
  const displayItems = data?.filter(item => ids.includes(item.id)) // Pas de données jusqu'à l'ouverture de la modale

  return <div>{displayItems?.map(...)}</div> // Vide au montage !
}

Pourquoi c'est important :

  • À l'actualisation de la page, la modale est fermée, donc la requête conditionnelle ne s'exécute pas
  • L'utilisateur voit un état vide au lieu de ses données
  • L'affichage dépend de l'interaction modale (UX cassée)

Liste de vérification des erreurs courantes

Avant l'implémentation, vérifiez que vous NE FAITES PAS ceci :

Chargement de données :

  • [ ] Utiliser fetch() ordinaire au lieu du SDK JS Medusa (cause des erreurs d'en-tête d'autorisation manquant)
  • [ ] Ne pas utiliser les méthodes SDK existantes pour les endpoints intégrés (par ex., utiliser sdk.client.fetch("/admin/products") au lieu de sdk.admin.product.list())
  • [ ] Charger les données d'affichage de manière conditionnelle basée sur l'état modale/UI
  • [ ] Utiliser une requête unique pour l'affichage et la modale
  • [ ] Oublier d'invalider les requêtes d'affichage après les mutations
  • [ ] Ne pas gérer les états de chargement (afficher vide au lieu d'un spinner)
  • [ ] Utilisateurs pnpm : Ne pas installer @tanstack/react-query avant de coder

Système de conception :

  • [ ] Utiliser des couleurs codées en dur au lieu de classes sémantiques
  • [ ] Oublier size="small" sur les boutons dans les widgets
  • [ ] Ne pas utiliser px-6 py-4 pour le remplissage des sections
  • [ ] Utiliser des éléments HTML bruts au lieu de composants Medusa UI

Affichage de données :

  • [ ] CRITIQUE : Diviser les prix par 100 lors de l'affichage (les prix sont stockés tels quels : $49,99 = 49,99, PAS en centimes)

Typographie :

  • [ ] Utiliser des balises span/p brutes au lieu du composant Text
  • [ ] Ne pas utiliser weight="plus" pour les étiquettes
  • [ ] Ne pas utiliser text-ui-fg-subtle pour les descriptions
  • [ ] Utiliser Heading dans les petites sections de widget

Formulaires :

  • [ ] Utiliser Drawer pour créer (devrait utiliser FocusModal)
  • [ ] Utiliser FocusModal pour modifier (devrait utiliser Drawer)
  • [ ] Ne pas désactiver les boutons pendant les mutations
  • [ ] Ne pas afficher l'état de chargement lors de l'envoi

Sélection :

  • [ ] Utiliser DataTable pour <10 éléments (excessif)
  • [ ] Utiliser Select pour >10 éléments (mauvaise UX)
  • [ ] Ne pas configurer la recherche dans useDataTable (cause une erreur)

Fichiers de référence disponibles

Chargez-les pour des modèles détaillés :

references/data-loading.md       - Modèles useQuery/useMutation, invalidation du cache
references/forms.md              - Modèles FocusModal/Drawer, validation
references/table-selection.md    - Modèle complet de sélection DataTable
references/display-patterns.md   - Listes, tableaux, cartes pour les entités
references/typography.md         - Modèles de composant Text
references/navigation.md         - Modèles Link, useNavigate, useParams

Chaque référence contient :

  • Guides d'implémentation étape par étape
  • Exemples de code corrects vs incorrects
  • Erreurs courantes et solutions
  • Exemples de travail complets

Intégration avec le backend

⚠️ CRITIQUE : UTILISEZ TOUJOURS le SDK JS Medusa pour TOUTES les requêtes API - NE JAMAIS utiliser fetch() ordinaire

L'interface d'administration se connecte aux routes API backend en utilisant le SDK :

import { sdk } from "[LOCALISEZ L'INSTANCE SDK DANS LE PROJET]"

// ✅ CORRECT - Endpoint intégré : Utilisez la méthode SDK existante
const { data: product } = useQuery({
  queryKey: ["product", productId],
  queryFn: () => sdk.admin.product.retrieve(productId),
})

// ✅ CORRECT - Endpoint personnalisé : Utilisez sdk.client.fetch()
const { data: reviews } = useQuery({
  queryKey: ["reviews", product.id],
  queryFn: () => sdk.client.fetch(`/admin/products/${product.id}/reviews`),
})

// ❌ MAUVAIS - Utiliser fetch() ordinaire
const { data } = useQuery({
  queryKey: ["reviews", product.id],
  queryFn: () => fetch(`http://localhost:9000/admin/products/${product.id}/reviews`),
  // ❌ Erreur : En-tête Authorization manquant !
})

// Mutation vers une route backend personnalisée
const createReview = useMutation({
  mutationFn: (data) => sdk.client.fetch("/admin/reviews", {
    method: "POST",
    body: data
  }),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ["reviews", product.id] })
    toast.success("Review created")
  },
})

Pourquoi le SDK est nécessaire :

  • Les routes d'administration ont besoin des en-têtes Authorization et du cookie de session
  • Les routes de magasin ont besoin de l'en-tête x-publishable-api-key
  • Le SDK gère automatiquement tous les en-têtes requis
  • fetch() ordinaire sans en-têtes → erreurs d'authentification/autorisation
  • L'utilisation des méthodes SDK existantes fournit une meilleure sécurité de type

Quand utiliser quoi :

  • Endpoints intégrés : Utilisez les méthodes SDK existantes (sdk.admin.product.list(), sdk.store.product.list())
  • Endpoints personnalisés : Utilisez sdk.client.fetch() pour vos routes API personnalisées

Pour implémenter les routes API backend, chargez la compétence building-with-medusa.

Widget vs UI Route

Les widgets étendent les pages d'administration existantes :

// src/admin/widgets/custom-widget.tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { DetailWidgetProps } from "@medusajs/framework/types"

const MyWidget = ({ data }: DetailWidgetProps<HttpTypes.AdminProduct>) => {
  return <Container>Widget content</Container>
}

export const config = defineWidgetConfig({
  zone: "product.details.after",
})

export default MyWidget

Les UI Routes créent de nouvelles pages d'administration :

// src/admin/routes/custom-page/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"

const CustomPage = () => {
  return <div>Page content</div>
}

export const config = defineRouteConfig({
  label: "Custom Page",
})

export default CustomPage

Problèmes courants et solutions

Erreurs « Cannot find module » (utilisateurs pnpm) :

  • Installez les dépendances peer AVANT de coder
  • Utilisez les versions exactes du tableau de bord

Erreur « No QueryClient set » :

  • pnpm : Installez @tanstack/react-query
  • npm/yarn : Supprimez le package installé incorrectement

« DataTable.Search not enabled » :

  • Doit transmettre la configuration de recherche à useDataTable

Le widget ne s'actualise pas :

  • Invalidez les requêtes d'affichage, pas seulement les requêtes modales
  • Incluez toutes les dépendances dans les clés de requête

Affichage vide à l'actualisation :

  • La requête d'affichage a une enabled conditionnelle basée sur l'état UI
  • Supprimez la condition - les données d'affichage doivent se charger au montage

Étapes suivantes - Tester votre implémentation

Après une implémentation réussie d'une fonctionnalité, fournissez toujours ces prochaines étapes à l'utilisateur :

1. Démarrer le serveur de développement

Si le serveur n'est pas déjà en cours d'exécution, démarrez-le :

npm run dev      # ou pnpm dev / yarn dev

2. Accéder au tableau de bord d'administration

Ouvrez votre navigateur et accédez à :

Connectez-vous avec vos identifiants administrateur.

3. Naviguer vers votre interface utilisateur personnalisée

Pour les widgets : Accédez à la page où votre widget est affiché. Zones de widget courantes :

  • Widgets de produit : Allez à Produits → Sélectionnez un produit → Votre widget apparaît dans la zone que vous avez configurée (par ex., product.details.after)
  • Widgets de commande : Allez à Commandes → Sélectionnez une commande → Votre widget apparaît dans la zone configurée
  • Widgets de client : Allez à Clients → Sélectionnez un client → Votre widget apparaît dans la zone configurée

Pour les UI Routes (pages personnalisées) :

  • Recherchez votre page personnalisée dans la barre latérale/navigation d'administration (basé sur le label que vous avez configuré)
  • Ou naviguez directement vers : http://localhost:9000/app/[your-route-path]

4. Tester la fonctionnalité

Selon ce qui a été implémenté, testez :

  • Formulaires : Essayez de créer/modifier des entités, vérifiez la validation et les messages d'erreur
  • Tableaux : Testez la pagination, la recherche, le tri et la sélection de lignes
  • Affichage de données : Vérifiez que les données se chargent correctement et s'actualisent après les mutations
  • Modales : Ouvrez FocusModal/Drawer, testez l'envoi de formulaire, vérifiez que les données se mettent à jour
  • Navigation : Cliquez sur les liens et vérifiez que le routage fonctionne correctement

Format pour présenter les prochaines étapes

Présentez toujours les prochaines étapes dans un format clair et actionnable après l'implémentation :

## Implémentation terminée

La [nom de la fonctionnalité] a été implémentée avec succès. Voici comment la voir :

### Démarrer le serveur de développement
[commande basée sur le gestionnaire de packages]

### Accéder au tableau de bord d'administration
Ouvrez http://localhost:9000/app dans votre navigateur et connectez-vous.

### Voir votre interface utilisateur personnalisée

**Pour les widgets :**
1. Naviguez vers [page d'administration spécifique, par ex., « Produits »]
2. Sélectionnez [une entité, par ex., « n'importe quel produit »]
3. Faites défiler jusqu'à [emplacement de la zone, par ex., « le bas de la page »]
4. Vous verrez votre widget « [nom du widget] »

**Pour les UI Routes :**
1. Recherchez « [étiquette de page] » dans la navigation d'administration
2. Ou naviguez directement vers http://localhost:9000/app/[route-path]

### Ce qu'il faut tester
1. [Cas de test spécifique 1]
2. [Cas de test spécifique 2]
3. [Cas de test spécifique 3]

Skills similaires