fbcaed6816
- add note in DEV.md explaining basePath auto-deduction and breadcrumb slug convention - update README.md to document new `basePath` param in `registerNavItem` and detail active item/breadcrumb behavior - update navigation file listing in README.md to include new exported helpers - implement `getNavItemBasePath` and `findActiveNavContext` in navigation.js - use `basePath` in AdminSidebar to determine active item via longest-prefix match - use `basePath` in AdminTop to build breadcrumb with section, item, and action labels - expose new navigation helpers from admin index.js and registry.js
94 lines
3.4 KiB
JavaScript
94 lines
3.4 KiB
JavaScript
/**
|
|
* Registre unique pour étendre l'admin sans modifier le core.
|
|
*
|
|
* Trois types d'extensions :
|
|
* - widget : une tuile du tableau de bord. Côté serveur on enregistre un fetcher
|
|
* (registerWidgetFetcher), côté client le Composant (registerWidget).
|
|
* - navItem : une entrée de la sidebar admin (section optionnelle pour grouper).
|
|
* - page : un composant rendu sous /admin/<slug>.
|
|
*
|
|
* Les Maps sont stockées sur `globalThis` via `Symbol.for` pour survivre :
|
|
* 1. au hot-reload de Next.js dev (sinon les enregistrements disparaissent).
|
|
* 2. à la double-instanciation du fichier — l'instrumentation hook tourne en
|
|
* Node natif (require ESM), tandis que les Server Components passent par
|
|
* le bundle Turbopack/Webpack. Sans `globalThis`, les nav items poussés
|
|
* par `register-server.js` au boot ne seraient pas visibles côté Server
|
|
* Component qui rend la sidebar — la sidebar resterait vide.
|
|
*/
|
|
|
|
const REGISTRY_KEY = Symbol.for('__ZEN_ADMIN_REGISTRY__');
|
|
if (!globalThis[REGISTRY_KEY]) {
|
|
globalThis[REGISTRY_KEY] = {
|
|
widgetFetchers: new Map(), // id -> async () => data
|
|
widgetComponents: new Map(), // id -> { Component, order, permission }
|
|
navItems: new Map(), // id -> { id, label, icon, href, basePath, order, sectionId, position, permission }
|
|
navSections: new Map(), // id -> { id, title, icon, order }
|
|
pages: new Map(), // slug -> { slug, Component, title?, breadcrumbLabel? }
|
|
};
|
|
}
|
|
const { widgetFetchers, widgetComponents, navItems, navSections, pages } = globalThis[REGISTRY_KEY];
|
|
|
|
// ---- Widgets ---------------------------------------------------------------
|
|
|
|
export function registerWidgetFetcher(id, fetcher) {
|
|
widgetFetchers.set(id, fetcher);
|
|
}
|
|
|
|
export function registerWidget({ id, Component, order = 0, permission }) {
|
|
widgetComponents.set(id, { Component, order, permission });
|
|
}
|
|
|
|
export function getWidgets() {
|
|
return [...widgetComponents.entries()]
|
|
.map(([id, v]) => ({ id, ...v }))
|
|
.sort((a, b) => a.order - b.order);
|
|
}
|
|
|
|
// Un fetcher qui échoue n'empêche pas les autres de produire leur donnée.
|
|
export async function collectWidgetData() {
|
|
const entries = [...widgetFetchers.entries()];
|
|
const results = await Promise.allSettled(
|
|
entries.map(async ([id, fetch]) => [id, await fetch()])
|
|
);
|
|
const out = {};
|
|
for (const r of results) {
|
|
if (r.status === 'fulfilled') {
|
|
const [id, data] = r.value;
|
|
out[id] = data;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ---- Navigation ------------------------------------------------------------
|
|
|
|
export function registerNavSection({ id, title, icon, order = 0 }) {
|
|
navSections.set(id, { id, title, icon, order });
|
|
}
|
|
|
|
export function registerNavItem({ id, label, icon, href, basePath, order = 0, sectionId = 'main', position, permission }) {
|
|
navItems.set(id, { id, label, icon, href, basePath, order, sectionId, position, permission });
|
|
}
|
|
|
|
export function getNavSections() {
|
|
return [...navSections.values()].sort((a, b) => a.order - b.order);
|
|
}
|
|
|
|
export function getNavItems() {
|
|
return [...navItems.values()].sort((a, b) => a.order - b.order);
|
|
}
|
|
|
|
// ---- Pages -----------------------------------------------------------------
|
|
|
|
export function registerPage({ slug, Component, title, breadcrumbLabel }) {
|
|
pages.set(slug, { slug, Component, title, breadcrumbLabel });
|
|
}
|
|
|
|
export function getPage(slug) {
|
|
return pages.get(slug);
|
|
}
|
|
|
|
export function getPages() {
|
|
return [...pages.values()];
|
|
}
|