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.mden premier - Créer des formulaires/modales ? → DOIT charger
references/forms.mden premier - Afficher des données dans des tableaux/listes ? → DOIT charger
references/display-patterns.mden premier - Sélectionner dans de grands ensembles de données ? → DOIT charger
references/table-selection.mden premier - Ajouter une navigation ? → DOIT charger
references/navigation.mden premier - Styliser des composants ? → DOIT charger
references/typography.mden 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()), utilisezsdk.client.fetch()pour les routes personnaliséesdata-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/formulairedata-invalidate-display- Invalidez les requêtes d'affichage après les mutations, pas seulement les requêtes de modaldata-loading-states- Affichez toujours les états de chargement (Spinner), pas les états videsdata-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 durdesign-spacing- Utilisez px-6 py-4 pour le remplissage des sections, gap-2 pour les listes, gap-3 pour les élémentsdesign-button-size- Utilisez toujours size="small" pour les boutons dans les widgets et tableauxdesign-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 brutestypo-labels- Utilisez<Text size="small" leading="compact" weight="plus">pour les étiquettes/en-têtestypo-descriptions- Utilisez<Text size="small" leading="compact" className="text-ui-fg-subtle">pour les descriptionstypo-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ésform-drawer-edit- Utilisez Drawer pour modifier des entités existantesform-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
Authorizationet 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
enabledconditionnelle 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 à :
- Tableau de bord d'administration : http://localhost:9000/app
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
labelque 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]