El panel por dentro
Cómo está hecho el panel de administración y qué usar para construir una vista suya. El front del panel usa Fomantic-UI y jQuery.
La norma es el esqueleto, no un módulo. Publications es el que mejor lo cumple y sirve de ejemplo.
El esqueleto
Una vista del panel se pinta en tres llamadas desde el controlador:
$this->helpController->render('panel/layout/header');
$this->render('list', $data);
$this->helpController->render('panel/layout/footer');
Lo que queda alrededor de tu vista:
body
├─ barra superior (panel/layout/topbar.php): usuario, noticias, opciones de administración, modales del sistema
└─ div.container-sidebar
├─ aside.sidebar (panel/layout/menu.php): el menú
└─ div.content
└─ TU VISTA → section.module-view-container
panel/layout/header-no-sidebar.php y footer-no-sidebar.php son la variante sin menú.
Dentro de tu vista
Una pantalla del panel se arma con las piezas de src/app/view/panel/built-in/content/: la cabecera
(page-header.php: migas, título, frase y acciones), la barra de guardar de los formularios y el estado vacío. Cómo se
usa cada una, con un ejemplo que se puede copiar, en «Pantallas de contenido»; el listado de publicaciones
(src/app/classes/Publications/Views/publications/list.php) es la referencia viva.
Qué usar en cada caso
| Necesito… | Se hace con |
|---|---|
| Una tabla paginada por el servidor | table[url].ui.basic.table + dataTableServerProccesing() (statics/core/js/helpers.js) |
| Enviar un formulario | form.ui.form + genericFormHandler() |
| Botón primario / secundario | ui primary button / ui basic button. ui button brand-color es la forma anterior: las pantallas la van dejando, familia a familia, en el rediseño de la 10 (el ui primary lleva el color de marca de la instalación a través del tema del panel, que lo lee como --accent) |
| Pestañas | .tabs-controls + ui tab con [data-tab] |
| Un desplegable | ui dropdown (con search si filtra) |
| Una fecha | el atributo calendar-js en el campo |
| Avisar del resultado de una acción | successMessage(), warningMessage(), infoMessage(), errorMessage(): un toast arriba al centro |
| Un modal | div.ui.modal y $(selector).modal('show') |
| Subir y recortar una imagen | modalImageUploaderForCropperAdminViews() |
| Traducir un texto fijo en HTML | el atributo lang-group="<grupo>" |
Estética
- Los colores son los tokens del tema: el de marca es
--accent(y--accent-2), y para fondos y textos que sigan el modo claro u oscuro, los de lectura (--theme-surface,--theme-text…; contrato en el README del tema). Úsalos; no pongas colores fijos. - En el SCSS del panel no se escribe un color a mano (
#707070,rgba(0, 0, 0, 0.2)): va en una variable de SCSS con el nombre de su papel. Si ya existe en la paleta del panel (src/statics/core/sass/includes/_variables.scss, sección «Paleta del panel»:$colorTextStrong,$colorTextMuted,$colorBorderSoft,$colorShadowSoft,$colorSuccess,$colorError,$colorWhite…), úsala; si es solo de tu hoja, decláralo arriba de ella. Lo compruebabin/cli verify-integrity(la comprobación 54), que falla con la hoja y la línea. La zona pública (src/statics/sass/) queda fuera.
//En una hoja de un módulo, dentro de su carpeta Statics/sass
@use '../../../../../statics/core/sass/includes/variables_app_base' as *;
//Colores de esta hoja: si cambian, cambia su aspecto.
$markerColor: #E83D35;
.my-module-card {
border: 1px solid $colorBorderSoft;
color: $colorTextMuted;
.marker {
background: $markerColor;
}
}
El inicio: tarjetas que registra cada módulo
El inicio del panel no es una vista fija: son tarjetas que cada módulo registra, y cada usuario ve las que su
permiso le deja. Las registra PiecesPHP\AdminPanel\DashboardCardRegistry; cada una es un DashboardCard, y lo que
enseña es un DashboardCardBody. El inicio las pinta con las piezas de estadística de la hoja de diseño
(statistic-card.php, number-cards.php, action-card.php): una tarjeta no trae HTML propio, así que todas tienen la
misma forma.
new DashboardCard(
string $key, // minúsculas, cifras y guiones; única
string $title, // ya traducido; va en la barra de la tarjeta
string $icon, // icono de Fomantic, sin «icon»
string $permissionRoute, // el nombre de la ruta cuyo permiso hace visible la tarjeta
callable $content, // fn(UserDataPackage $user): ?DashboardCardBody — null oculta la tarjeta
int $position = 100, // de menor a mayor
bool $collapsible = false,
bool $collapsedByDefault = false, // solo con $collapsible; plegada, $content NO se llama
?string $collapsedFooterCaption = null, // el pie que se ve plegada (sin cifras)
string|\Closure|null $collapsedFooterUrl = null, // su URL; una Closure(): ?string se resuelve al pintar
string $help = '' // el texto del «?» de la barra; vacío, no hay «?»
);
Lo que enseña la tarjeta, con una de estas tres formas:
DashboardCardBody::numbers(array $items, ?string $footerCaption = null, ?string $footerUrl = null): de una a tres cifras, cada una['value' => …, 'concept' => …, 'subconcept' => …, 'icon' => …, 'color' => 'green'|'orange'](valueyconcept, obligatorios), con su pie y su flecha. Lanza con cero cifras o más de tres.DashboardCardBody::action(string $url, string $description, ?string $footerCaption = null): una tarjeta de acción, con el icono y el título de la tarjeta. Para llevar a una pantalla, no para enseñar cifras. Con$footerCaption, el pie lleva ese texto junto a la flecha («Ver el informe»); sin él, solo la flecha.DashboardCardBody::html(string $html, ?string $footerCaption = null, ?string $footerUrl = null): para lo que no sean cifras ni una acción. El HTML lo escapa quien llama. Úsalo poco: las otras dos ya tienen la forma del panel.
Así registra el framework «Mis sesiones» (UserSessionsController::registerDashboardCard(), llamado desde
UserSystemFeaturesRoutes):
DashboardCardRegistry::register(new DashboardCard(
'my-sessions',
__(self::LANG_GROUP, 'Mis sesiones'),
'desktop',
'my-space-admin-user-security',
fn(UserDataPackage $user): DashboardCardBody => DashboardCardBody::numbers(
[
['value' => count(UserSessions::openOfUser((int) $user->id)), 'concept' => __(self::LANG_GROUP, 'sesiones abiertas'), 'icon' => 'desktop'],
],
__(self::LANG_GROUP, 'Ver mis sesiones'),
MySpaceController::routeName('user-security')
),
10,
help: __(self::LANG_GROUP, 'Las sesiones abiertas con tu usuario en este y en otros dispositivos.')
));
- Los parámetros del final, por nombre (
help:,collapsedFooterCaption:…): son opcionales y así no hay que escribir los de en medio. - Un clon quita o sustituye las del framework desde sus Routes, después de que se registren:
DashboardCardRegistry::remove('general-report'), oDashboardCardRegistry::replace(new DashboardCard('general-report', …))(lanza si la clave no existe;register()lanza si ya existe). - Las del framework:
my-sessions(10),pending-tasks(20, las aprobaciones que ese usuario puede resolver; no sale si son 0),users-access(30, administradores: usuarios activos y accesos de hoy),errors-alerts(40, root; nace plegada y, plegada, enseña su barra y el enlace a los errores, sin cifras) ygeneral-report(50, el informe general, como tarjeta de acción). - Dos cifras en una tarjeta se apilan, como en la hoja de diseño: la rejilla de la pieza no tiene sitio para ponerlas en fila sin salirse de la pantalla.
- Lo plegado se guarda por usuario, en su
meta(dashboardCardsCollapsed). - Los repartos de siempre siguen delante: el administrador de organización y el usuario general entran a Mi perfil, y comunicaciones al listado de publicaciones.
Piezas que pone el sistema
No las reimplementes en tu módulo: ya están en la barra superior.
- Lateral de usuario: perfil, idioma, salir.
- Cambio de idioma: guarda la elección en una cookie y recarga.
- Conectarse como otro usuario (solo root): para ver el panel con los permisos de otro. Se empieza solo desde este
modal (un enlace no la activa), no alcanza a otro root, al usuario de sistema ni a un usuario inactivo o de una
organización inactiva, y caduca a los 60 minutos. Mientras dura, una franja arriba dice «Conectado como
» y lleva «Volver a mi cuenta». En código: PiecesPHP\UserSystem\Authentication\Impersonation(la regla) y las rutasuser-impersonation-startyuser-impersonation-end.