DeenruvDeenruv
Rozszerzanie panelu administracyjnego

System pluginów

Szczegółowe omówienie systemu pluginów UI panelu administracyjnego Deenruv — wszystkie punkty rozszerzeń, identyfikatory lokalizacji i konwencje folderów

System pluginów UI panelu administracyjnego Deenruv pozwala rozszerzać praktycznie każdą część dashboardu administracyjnego. Pluginy definiuje się za pomocą funkcji createDeenruvUIPlugin, która zapewnia pełne bezpieczeństwo typów TypeScript dla wszystkich punktów rozszerzeń.

createDeenruvUIPlugin

Jest to funkcja tożsamościowa, która waliduje i typuje definicję pluginu:

import { createDeenruvUIPlugin } from '@deenruv/react-ui-devkit';

export const MyPlugin = createDeenruvUIPlugin({
  name: 'my-plugin-ui',
  version: '1.0.0',
  // ...punkty rozszerzeń
});

Punkty rozszerzeń

Definicja pluginu obsługuje 15 punktów rozszerzeń:

Punkt rozszerzeniaTypOpis
namestringNazwa pluginu (wymagana)
versionstringWersja pluginu (wymagana)
configTNiestandardowy obiekt konfiguracji pluginu
pagesPluginPage[]Niestandardowe trasy (automatyczny prefiks: admin-ui/extensions/{name}/{path})
tablesDeenruvUITable[]Kolumny widoku listy, akcje wierszy, akcje masowe
tabsDeenruvTabs[]Zakładki widoku szczegółów
actions{ inline?, dropdown? }Przyciski akcji widoku szczegółów
componentsDeenruvUIDetailComponent[]Wstrzykiwanie do widoków szczegółów i pasków bocznych
modalsDeenruvUIModalComponent[]Wstrzykiwanie do modali
widgetsWidget[]Widgety dashboardu
inputsPluginComponent[]Nadpisywanie inputów pól niestandardowych
navMenuGroupsPluginNavigationGroup[]Grupy menu nawigacji
navMenuLinksPluginNavigationLink[]Linki nawigacji
notificationsNotification[]Powiadomienia oparte na odpytywaniu
translations{ ns, data }Paczki tłumaczeń i18n

Pages

Definiuj niestandardowe trasy dla swojego pluginu. Ścieżki są automatycznie prefiksowane admin-ui/extensions/{plugin-name}/:

pages: [
  { path: '', element: <MyListPage /> },         // /admin-ui/extensions/my-plugin-ui/
  { path: 'new', element: <MyCreatePage /> },     // /admin-ui/extensions/my-plugin-ui/new
  { path: ':id', element: <MyDetailPage /> },     // /admin-ui/extensions/my-plugin-ui/:id
],

Tables

Rozszerzaj istniejące widoki list o dodatkowe kolumny, akcje wierszy i akcje masowe:

tables: [
  {
    locationId: 'products-list-view',
    columns: [
      {
        header: 'Review Score',
        accessorKey: 'customFields.reviewScore',
        cell: ({ row }) => <StarRating value={row.original.customFields?.reviewScore} />,
      },
    ],
    rowActions: [
      {
        label: 'View Reviews',
        onClick: (row) => navigate(`/reviews/${row.original.id}`),
      },
    ],
    bulkActions: [
      {
        label: 'Export Reviews',
        onClick: (selectedRows) => exportReviews(selectedRows),
      },
    ],
  },
],

Tabs

Dodawaj niestandardowe zakładki do widoków szczegółów:

tabs: [
  {
    locationId: 'products-detail-view',
    tab: {
      id: 'reviews',
      label: 'Reviews',
      component: ProductReviewsTab,
    },
    hideSidebar: false,              // Ukryj pasek boczny gdy ta zakładka jest aktywna
    sidebarReplacement: null,        // Zastąp zawartość paska bocznego
    disabled: false,                 // Dezaktywuj zakładkę
  },
],

Actions

Dodawaj przyciski akcji do widoków szczegółów:

actions: {
  inline: [
    {
      locationId: 'products-detail-view',
      component: ({ entity }) => (
        <Button onClick={() => generateReport(entity.id)}>
          Generate Report
        </Button>
      ),
    },
  ],
  dropdown: [
    {
      locationId: 'orders-detail-view',
      label: 'Send custom email',
      onClick: (entity) => sendEmail(entity.id),
    },
  ],
},

Components

Wstrzykuj niestandardowe komponenty do widoków szczegółów i pasków bocznych:

components: [
  // Wstrzyknięcie do głównego obszaru widoku szczegółów produktu
  {
    id: 'products-detail-view',
    tab: 'product',                    // Pokaż tylko na konkretnej zakładce
    component: MyProductComponent,
  },
  // Wstrzyknięcie do paska bocznego (dodaj -sidebar do identyfikatora lokalizacji)
  {
    id: 'products-detail-view-sidebar',
    tab: 'product',
    component: MyProductSidebar,
  },
],

Aby wstrzyknąć do paska bocznego widoku szczegółów, dodaj -sidebar do identyfikatora lokalizacji. Opcjonalnie możesz filtrować po tab, aby pokazywać komponent tylko na konkretnej zakładce.

Modals

Wstrzykuj komponenty do modali panelu administracyjnego:

modals: [
  {
    id: 'manual-order-state',
    component: MyOrderStateComponent,
  },
],

Widgets

Rejestruj widgety dashboardu:

widgets: [
  {
    id: 'sales-chart',
    title: 'Sales Overview',
    component: SalesChartWidget,
    size: { width: 2, height: 1 },
    sizes: [
      { width: 1, height: 1 },
      { width: 2, height: 1 },
      { width: 2, height: 2 },
    ],
  },
],

Inputs

Nadpisuj inputy pól niestandardowych własnymi komponentami:

inputs: [
  {
    id: 'my-custom-color-picker',
    component: ColorPickerInput,
  },
],

Nawigacja

Dodawaj grupy i linki nawigacji:

navMenuGroups: [
  {
    id: 'my-plugin-group',
    label: 'My Plugin',
    placement: 'after:assortment-group',
  },
],
navMenuLinks: [
  {
    id: 'my-plugin-link',
    labelId: 'nav.myPlugin',
    href: '',
    groupId: 'assortment-group',  // Użyj wartości BASE_GROUP_ID
    icon: ListIcon,
  },
],

Nawigacja górna

Dodawaj komponenty do górnego paska nawigacji:

topNavigationComponents: [
  { id: 'my-status-indicator', component: StatusIndicator },
],
topNavigationActionsMenu: [
  { label: 'Quick Action', onClick: () => doSomething(), icon: ZapIcon },
],

Notifications

Rejestruj powiadomienia oparte na odpytywaniu:

notifications: [
  {
    id: 'pending-reviews',
    fetch: async () => {
      const result = await apiClient('query')({
        pendingReviews: [
          {},
          { totalItems: true },
        ],
      });
      return { count: result.pendingReviews.totalItems };
    },
    interval: 30000,   // Odpytywanie co 30 sekund
    placements: {
      main: (data) => ({
        name: 'pending-reviews',
        title: 'Pending Reviews',
        description: `${data.count} reviews need approval`,
        icon: <StarIcon />,
        when: (data) => data.count > 0,
      }),
      navigation: [
        {
          id: 'my-plugin-link',
          component: (data) => <Badge>{data.count}</Badge>,
        },
      ],
    },
  },
],

Translations

Rejestruj paczki tłumaczeń i18n dla swojego pluginu:

translations: {
  ns: 'my-plugin',
  data: {
    en: { 'my-plugin': { nav: { myPlugin: 'My Plugin' } } },
    pl: { 'my-plugin': { nav: { myPlugin: 'Moja wtyczka' } } },
  },
},

Identyfikatory lokalizacji

Identyfikatory lokalizacji to ciągi znaków wskazujące na konkretne widoki w panelu administracyjnym.

Lokalizacje list (21)

Używane z tables do rozszerzania widoków list:

Identyfikator lokalizacjiTyp encji
assets-list-viewAsset
admins-list-viewAdministrator
channels-list-viewChannel
collections-list-viewCollection
countries-list-viewCountry
customerGroups-list-viewCustomerGroup
customers-list-viewCustomer
facets-list-viewFacet
facet-values-listFacetValue
orders-list-viewOrder
paymentMethods-list-viewPaymentMethod
products-list-viewProduct
productVariants-list-viewProductVariant
promotions-list-viewPromotion
roles-list-viewRole
sellers-list-viewSeller
shippingMethods-list-viewShippingMethod
stockLocations-listStockLocation
stockLocations-list-viewStockLocation
taxCategories-list-viewTaxCategory
taxRates-list-viewTaxRate
zones-list-viewZone

Lokalizacje szczegółów (20)

Używane z tabs, actions i components do rozszerzania widoków szczegółów:

Identyfikator lokalizacjiTyp encji
admins-detail-viewAdministrator
channels-detail-viewChannel
collections-detail-viewCollection
countries-detail-viewCountry
customerGroups-detail-viewCustomerGroup
customers-detail-viewCustomer
facets-detail-viewFacet
globalSettings-detail-viewGlobalSettings
orders-detail-viewOrder
orders-summaryOrder
paymentMethods-detail-viewPaymentMethod
products-detail-viewProduct
promotions-detail-viewPromotion
roles-detail-viewRole
sellers-detail-viewSeller
shippingMethods-detail-viewShippingMethod
stockLocations-detail-viewStockLocation
taxCategories-detail-viewTaxCategory
taxRates-detail-viewTaxRate
zones-detail-viewZone

Aby wstrzyknąć do paska bocznego widoku szczegółów, dodaj -sidebar do identyfikatora lokalizacji. Na przykład products-detail-view-sidebar.

Lokalizacje modali

Identyfikator lokalizacjiTyp
manual-order-stateModal zmiany stanu zamówienia

Identyfikatory grup nawigacji

Użyj wartości enuma BASE_GROUP_ID, aby celować w istniejące grupy nawigacji:

import { BASE_GROUP_ID } from '@deenruv/react-ui-devkit';

// Dostępne identyfikatory grup:
BASE_GROUP_ID.SHOP          // 'shop-group'
BASE_GROUP_ID.ASSORTMENT    // 'assortment-group'
BASE_GROUP_ID.USERS         // 'users-group'
BASE_GROUP_ID.PROMOTIONS    // 'promotions-group'
BASE_GROUP_ID.SHIPPING      // 'shipping-group'
BASE_GROUP_ID.SETTINGS      // 'settings-group'

Pełny przykład pluginu

Oto kompletny przykład pluginu, który wykorzystuje wszystkie główne punkty rozszerzeń:

src/plugin-ui/index.tsx
import { createDeenruvUIPlugin, BASE_GROUP_ID, apiClient } from '@deenruv/react-ui-devkit';
import { StarIcon, ListIcon } from 'lucide-react';
import { ReviewsListPage } from './pages/ReviewsListPage';
import { ReviewDetailPage } from './pages/ReviewDetailPage';
import { ProductReviewsTab } from './components/ProductReviewsTab';
import { ReviewsSidebar } from './components/ReviewsSidebar';
import { ReviewsWidget } from './components/ReviewsWidget';
import en from './locales/en';
import pl from './locales/pl';

const PLUGIN_NAME = 'reviews-plugin-ui';

export const ReviewsPlugin = createDeenruvUIPlugin({
  name: PLUGIN_NAME,
  version: '1.0.0',

  translations: {
    ns: 'reviews',
    data: { en, pl },
  },

  pages: [
    { path: '', element: <ReviewsListPage /> },
    { path: ':id', element: <ReviewDetailPage /> },
  ],

  navMenuLinks: [
    {
      id: 'reviews-link',
      labelId: 'nav.reviews',
      href: '',
      groupId: BASE_GROUP_ID.ASSORTMENT,
      icon: StarIcon,
    },
  ],

  tabs: [
    {
      locationId: 'products-detail-view',
      tab: {
        id: 'reviews',
        label: 'Reviews',
        component: ProductReviewsTab,
      },
    },
  ],

  components: [
    {
      id: 'products-detail-view-sidebar',
      tab: 'reviews',
      component: ReviewsSidebar,
    },
  ],

  widgets: [
    {
      id: 'recent-reviews',
      title: 'Recent Reviews',
      component: ReviewsWidget,
      size: { width: 2, height: 1 },
    },
  ],

  notifications: [
    {
      id: 'pending-reviews',
      fetch: async () => {
        const result = await apiClient('query')({
          pendingReviews: [{}, { totalItems: true }],
        });
        return { count: result.pendingReviews.totalItems };
      },
      interval: 30000,
      placements: {
        main: (data) => ({
          name: 'pending-reviews',
          title: 'Pending Reviews',
          description: `${data.count} reviews awaiting approval`,
          icon: <StarIcon />,
          when: (data) => data.count > 0,
        }),
      },
    },
  ],
});

Markery widoku pluginów

Naciśnij Ctrl+Q w panelu administracyjnym, aby przełączyć markery widoku pluginów. Podświetla to wszystkie punkty wstrzykiwania w bieżącym widoku, ułatwiając zobaczenie, gdzie będą renderowane komponenty Twojego pluginu.

Rejestr pluginów i zmienna środowiskowa

Jak to działa

Panel administracyjny korzysta z centralnego manifestu w pliku apps/panel/src/plugins/registry.ts, który deklaruje każdy dostępny plugin UI. Każdy wpis manifestu zawiera:

PoleTypOpis
idstringUnikalny identyfikator używany w zmiennej środowiskowej
pluginDeenruvUIPluginInstancja pluginu (importowana z pakietu pluginu)
enabledByDefaultbooleanCzy plugin ładuje się, gdy zmienna środowiskowa nie jest ustawiona

Podczas budowania aplikacji plik apps/panel/src/plugins/enabled.ts odczytuje zmienną środowiskową VITE_ADMIN_UI_PLUGINS i ustala, które pluginy aktywować.

Semantyka VITE_ADMIN_UI_PLUGINS

WartośćZachowanie
nieustawiona (undefined)Ładowane są tylko pluginy z enabledByDefault: true
"" (pusty string)Żadne pluginy nie są ładowane
"all" lub "*"Ładowane są wszystkie pluginy z manifestu
Lista CSV (np. "dashboard-widgets,badges")Ładowane są tylko wymienione identyfikatory

Nieznane identyfikatory (literówki, usunięte pluginy) wywołują ostrzeżenie w konsoli z listą dostępnych ID.

# Przykłady
VITE_ADMIN_UI_PLUGINS="dashboard-widgets,badges" pnpm start:admin-ui
VITE_ADMIN_UI_PLUGINS="all" pnpm start:admin-ui
VITE_ADMIN_UI_PLUGINS="" pnpm start:admin-ui   # brak pluginów

Dodawanie pluginu do manifestu

  1. Zainstaluj pakiet pluginu w apps/panel/package.json.
  2. Importuj eksport pluginu UI na górze pliku apps/panel/src/plugins/registry.ts.
  3. Dodaj wpis do tablicy pluginManifest z unikalnym id.
apps/panel/src/plugins/registry.ts
import { BadgesUiPlugin } from '@deenruv/product-badges-plugin/plugin-ui';

export const pluginManifest = [
  // ...istniejące wpisy
  { id: 'badges', plugin: BadgesUiPlugin, enabledByDefault: false },
];

Współistnienie z rejestracją pluginów po stronie serwera

Manifest w registry.ts kontroluje jedynie stronę UI panelu administracyjnego. Pluginy serwerowe rejestrowane są osobno w tablicy DeenruvConfig.plugins. Typowa konfiguracja obejmuje oba:

  • Plugin serwerowyDeenruvConfig.plugins (obsługuje rozszerzenia GraphQL, serwisy itp.)
  • Plugin UIpluginManifest w registry.ts (obsługuje rozszerzenia UI panelu administracyjnego)

Obie strony są niezależne — można mieć plugin serwerowy bez pluginu UI i odwrotnie.

Na tej stronie