feat: secciones configurables del indice (apps/system, manifiesto MODULE_SECTION)
This commit is contained in:
1 parent
5c6d6bc46e
commit
3787152ca0
2 files changed
+124
-68
No files matched your search
+90
-68
@@ -7,7 +7,10 @@ import {
|
|||||||
getModuleMeta,
|
getModuleMeta,
|
||||||
getStatusInfo,
|
getStatusInfo,
|
||||||
moduleUrl,
|
moduleUrl,
|
||||||
|
sectionOf,
|
||||||
|
SECTIONS,
|
||||||
type ApiResponse,
|
type ApiResponse,
|
||||||
|
type ModuleState,
|
||||||
} from "@/lib/sam";
|
} from "@/lib/sam";
|
||||||
|
|
||||||
// Store externo: evita setState en efectos (regla react-hooks)
|
// Store externo: evita setState en efectos (regla react-hooks)
|
||||||
@@ -69,6 +72,63 @@ export default function Home() {
|
|||||||
const modules = data?.modules ?? [];
|
const modules = data?.modules ?? [];
|
||||||
const backend = data?.backend;
|
const backend = data?.backend;
|
||||||
|
|
||||||
|
const bySection: Record<string, ModuleState[]> = {};
|
||||||
|
modules.forEach((m) => {
|
||||||
|
const s = sectionOf(m.name);
|
||||||
|
if (!bySection[s]) bySection[s] = [];
|
||||||
|
bySection[s].push(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderModuleCard = (m: ModuleState) => {
|
||||||
|
const meta = getModuleMeta(m.name);
|
||||||
|
const status = getStatusInfo(m);
|
||||||
|
const url = moduleUrl(m);
|
||||||
|
const name = displayName(m.name);
|
||||||
|
return (
|
||||||
|
<div className="col-md-6" key={m.name}>
|
||||||
|
<div className="app-card p-2">
|
||||||
|
<table className="table table-borderless mb-0 align-middle">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="ps-3" style={{ width: 56 }}>
|
||||||
|
<i
|
||||||
|
className={`bx ${meta.icon} bx-sm bx-border text-light ${meta.bg}`}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Link
|
||||||
|
href={`/m/${m.name}`}
|
||||||
|
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
<div className="d-flex align-items-center gap-2">
|
||||||
|
<span className={`status-dot ${status.cls}`} />
|
||||||
|
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||||
|
{status.label} · :{m.port} · {m.branch}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="text-end pe-3" style={{ width: 40 }}>
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title={`Abrir ${url}`}
|
||||||
|
className="position-relative"
|
||||||
|
style={{ zIndex: 2 }}
|
||||||
|
>
|
||||||
|
<i className="bx bx-window-open text-muted" />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="sidenav">
|
<div className="sidenav">
|
||||||
@@ -126,18 +186,7 @@ export default function Home() {
|
|||||||
|
|
||||||
{!loading && !error && (
|
{!loading && !error && (
|
||||||
<div className="row row-cards">
|
<div className="row row-cards">
|
||||||
<div className="col-12">
|
{modules.length === 0 && (
|
||||||
<div className="alert alert-light section-header mb-3 py-2 px-3 d-flex justify-content-between align-items-center">
|
|
||||||
<span>
|
|
||||||
<i className="bx bx-grid-alt me-1" /> Selección de Aplicación
|
|
||||||
</span>
|
|
||||||
<span className="badge bg-primary rounded-pill">
|
|
||||||
{modules.length} módulo{modules.length !== 1 ? "s" : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{modules.length === 0 ? (
|
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div className="app-card p-4 text-center">
|
<div className="app-card p-4 text-center">
|
||||||
<i className="bx bx-inbox fs-1 text-muted" />
|
<i className="bx bx-inbox fs-1 text-muted" />
|
||||||
@@ -149,65 +198,34 @@ export default function Home() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
modules.map((m) => {
|
|
||||||
const meta = getModuleMeta(m.name);
|
|
||||||
const status = getStatusInfo(m);
|
|
||||||
const url = moduleUrl(m);
|
|
||||||
const name = displayName(m.name);
|
|
||||||
return (
|
|
||||||
<div className="col-md-6" key={m.name}>
|
|
||||||
<div className="app-card p-2">
|
|
||||||
<table className="table table-borderless mb-0 align-middle">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td className="ps-3" style={{ width: 56 }}>
|
|
||||||
<i
|
|
||||||
className={`bx ${meta.icon} bx-sm bx-border text-light ${meta.bg}`}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Link
|
|
||||||
href={`/m/${m.name}`}
|
|
||||||
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</Link>
|
|
||||||
<div className="d-flex align-items-center gap-2">
|
|
||||||
<span className={`status-dot ${status.cls}`} />
|
|
||||||
<small className="text-muted" style={{ fontSize: "0.75rem" }}>
|
|
||||||
{status.label} · :{m.port} · {m.branch}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="text-end pe-3" style={{ width: 40 }}>
|
|
||||||
<a
|
|
||||||
href={url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
title={`Abrir ${url}`}
|
|
||||||
className="position-relative"
|
|
||||||
style={{ zIndex: 2 }}
|
|
||||||
>
|
|
||||||
<i className="bx bx-window-open text-muted" />
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{backend && (
|
{SECTIONS.map((section) => {
|
||||||
|
const list = bySection[section.id] ?? [];
|
||||||
|
const isSystem = section.id === "system";
|
||||||
|
if (!isSystem && list.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<div className="col-12" key={section.id}>
|
||||||
|
<div className="row row-cards">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="alert alert-light section-header mb-3 py-2 px-3 d-flex justify-content-between align-items-center">
|
||||||
|
<span>
|
||||||
|
<i className={`bx ${section.icon} me-1`} />{" "}
|
||||||
|
{section.title}
|
||||||
|
</span>
|
||||||
|
{list.length > 0 && (
|
||||||
|
<span className="badge bg-primary rounded-pill">
|
||||||
|
{list.length} módulo
|
||||||
|
{list.length !== 1 ? "s" : ""}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{list.map(renderModuleCard)}
|
||||||
|
|
||||||
|
{isSystem && backend && (
|
||||||
<>
|
<>
|
||||||
<div className="col-12 mt-4 mb-2">
|
|
||||||
<div className="alert alert-light section-header mb-3 py-2 px-3">
|
|
||||||
<i className="bx bx-server me-1" /> Backend & Sistema
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<div className="app-card p-2">
|
<div className="app-card p-2">
|
||||||
<table className="table table-borderless mb-0 align-middle">
|
<table className="table table-borderless mb-0 align-middle">
|
||||||
@@ -260,6 +278,10 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
<div className="col-12 mt-4 mb-4 text-center">
|
<div className="col-12 mt-4 mb-4 text-center">
|
||||||
<small className="text-muted">
|
<small className="text-muted">
|
||||||
|
|||||||
@@ -167,3 +167,37 @@ export function displayName(name: string) {
|
|||||||
export function moduleUrl(m: ModuleState) {
|
export function moduleUrl(m: ModuleState) {
|
||||||
return `https://${m.subdomain}.devops.norteamericano.cl`;
|
return `https://${m.subdomain}.devops.norteamericano.cl`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Secciones del índice: dónde aparece cada módulo.
|
||||||
|
// - Todo módulo nuevo cae en "apps" (Aplicaciones) automáticamente.
|
||||||
|
// - Para mover un módulo a otra sección, agrégalo a MODULE_SECTION.
|
||||||
|
// - Para crear una sección futura: agrega una entrada en SECTIONS
|
||||||
|
// (id, título, icono) y la regla en MODULE_SECTION o en
|
||||||
|
// sectionOf(). El índice la renderiza sola si tiene módulos.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
export interface Section {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SECTIONS: Section[] = [
|
||||||
|
{ id: "apps", title: "Selección de Aplicación", icon: "bx-grid-alt" },
|
||||||
|
{ id: "system", title: "Backend & Sistema", icon: "bx-server" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const MODULE_SECTION: Record<string, string> = {
|
||||||
|
// Ejemplos:
|
||||||
|
// contabilidad: "system",
|
||||||
|
// pagos: "system",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function sectionOf(name: string): string {
|
||||||
|
const key = name.toLowerCase();
|
||||||
|
if (MODULE_SECTION[key]) return MODULE_SECTION[key];
|
||||||
|
const known = SECTIONS.some((s) => s.id === key);
|
||||||
|
if (known) return key;
|
||||||
|
return "apps";
|
||||||
|
}
|
||||||
Reference in new issue
Block a user