191 lines
6.3 KiB
TypeScript
191 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useModules, refreshModules } from "@/lib/store";
|
|
import {
|
|
boardHref,
|
|
displayName,
|
|
envOf,
|
|
getModuleMeta,
|
|
getStatusInfo,
|
|
moduleUrl,
|
|
sectionOf,
|
|
SECTIONS,
|
|
type ModuleState,
|
|
} from "@/lib/sam";
|
|
|
|
export default function Home() {
|
|
const { data, error } = useModules();
|
|
const loading = data === null && error === null;
|
|
const fetchModules = refreshModules;
|
|
|
|
const modules = data?.modules ?? [];
|
|
|
|
const bySection: Record<string, ModuleState[]> = {};
|
|
modules.forEach((m) => {
|
|
const s = sectionOf(m.name, m.category);
|
|
if (!bySection[s]) bySection[s] = [];
|
|
bySection[s].push(m);
|
|
});
|
|
|
|
const renderModuleCard = (m: ModuleState) => {
|
|
const meta = getModuleMeta(m.name);
|
|
const status = getStatusInfo(m);
|
|
const env = envOf(m.branch);
|
|
const url = moduleUrl(m);
|
|
const name = displayName(m.name);
|
|
const board = boardHref(m);
|
|
const boardLink = board.external ? (
|
|
<a
|
|
href={board.href}
|
|
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
|
>
|
|
{name}
|
|
</a>
|
|
) : (
|
|
<Link
|
|
href={board.href}
|
|
className="text-decoration-none text-dark d-block py-2 stretched-link"
|
|
>
|
|
{name}
|
|
</Link>
|
|
);
|
|
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>
|
|
{boardLink}
|
|
<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>
|
|
<span className={`badge ${env.cls} badge-respaldo`}>
|
|
{env.label}
|
|
</span>
|
|
</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 (
|
|
<>
|
|
<div className="sidenav">
|
|
<div className="row text-light login-main-text">
|
|
<div className="col-12 text-center">
|
|
<div
|
|
className="mx-auto d-flex align-items-center justify-content-center bg-white rounded-3 overflow-hidden"
|
|
style={{ width: 80, height: 80 }}
|
|
>
|
|
<img src="/logo-sam.png" alt="SAM" style={{ width: 64, height: 64, objectFit: "contain" }} />
|
|
</div>
|
|
</div>
|
|
<div className="col-12 text-center mt-3">
|
|
<h1 className="fw-bold fs-2">Sistema SAM</h1>
|
|
</div>
|
|
<div className="col-12 text-center">
|
|
<h4 className="fw-semibold opacity-75">Aplicaciones</h4>
|
|
</div>
|
|
<div className="col-12 text-center mt-3">
|
|
<small className="opacity-50">Portal de integración</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="main">
|
|
<div className="row" id="modulos">
|
|
<div className="col-lg-1" />
|
|
<div className="col-lg-10 mt-4">
|
|
{loading && (
|
|
<div className="text-center py-5">
|
|
<div className="spinner-border text-primary" role="status" />
|
|
<p className="mt-2 text-muted">Cargando módulos...</p>
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="alert alert-danger d-flex align-items-center">
|
|
<i className="bx bx-error-circle me-2 fs-5" />
|
|
<div>
|
|
<strong>Error:</strong> {error}
|
|
<button
|
|
onClick={fetchModules}
|
|
className="btn btn-sm btn-outline-danger ms-3"
|
|
>
|
|
Reintentar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!loading && !error && (
|
|
<div className="row row-cards">
|
|
{SECTIONS.map((section) => {
|
|
const list = bySection[section.id] ?? [];
|
|
if (list.length === 0) return null;
|
|
return (
|
|
<div className="col-12" key={section.id} id={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)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
<div className="col-12 mt-4 mb-4 text-center">
|
|
<small className="text-muted">
|
|
Auto-actualizado cada 15s ·{" "}
|
|
<a href="#" onClick={(e) => { e.preventDefault(); fetchModules(); }}>
|
|
actualizar ahora
|
|
</a>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|