Merge pull request 'feat(ui): refresh backoffice user experience' (#79) from feature/web/078-backoffice-ui-refresh into master
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 2m35s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 2m35s
Reviewed-on: #79
This commit is contained in:
commit
1d597f9791
@ -4,17 +4,23 @@
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
|
||||
<NotAuthorized>
|
||||
Disculpe, usted no esta autorizado a ver esta seccion.
|
||||
<div class="empty-state" role="alert">
|
||||
<strong>Acceso restringido</strong>
|
||||
<p class="mb-0">No tenés permisos para ver esta sección.</p>
|
||||
</div>
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<PageTitle>Página no encontrada | PhronCare</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Disculpe, no hay nada en esta dirección'.</p>
|
||||
<div class="empty-state" role="alert">
|
||||
<strong>Página no encontrada</strong>
|
||||
<p class="mb-0">No encontramos contenido en esta dirección.</p>
|
||||
</div>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingBlazoredModal>
|
||||
</CascadingAuthenticationState>
|
||||
</CascadingAuthenticationState>
|
||||
|
||||
@ -5,26 +5,24 @@
|
||||
<div class="page @GetMode()">
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div class="sidebar" style="@MinimizeMenuCss">
|
||||
<aside class="sidebar @MinimizeMenuClass">
|
||||
<NavMenu MinStatus="ToggleMinNavMenu"/>
|
||||
</div>
|
||||
</aside>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
<main>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div class="container-fluid" style="background-color: rgba(39, 54, 97, 0.5);">
|
||||
<div class="row">
|
||||
<div class="top-row px-1">
|
||||
<div class="col-md-4" style="zoom:50%">
|
||||
<PhToggle ToggleStateChanged="OnChangeToggleSwitchState" ToggleState=@state >
|
||||
<i class="fas fa-moon fa-2xl"/>
|
||||
</PhToggle>
|
||||
</div>
|
||||
<div class="col-md-8" style="text-align: right;">
|
||||
<PhLoginState/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar">
|
||||
<div class="topbar-context">
|
||||
<span class="topbar-kicker">PhronCare</span>
|
||||
<strong>Backoffice</strong>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<PhToggle ToggleStateChanged="OnChangeToggleSwitchState" ToggleState="@state" Caption="Tema oscuro">
|
||||
<i class="fas fa-moon" aria-hidden="true"></i>
|
||||
</PhToggle>
|
||||
<PhLoginState/>
|
||||
</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
@ -43,7 +41,7 @@
|
||||
ErrorIcon="fa fa-ban" />
|
||||
@code {
|
||||
private bool state=false;
|
||||
private string MinimizeMenuCss => navMenuService.Minimized ? "width:82px" : string.Empty;
|
||||
private string MinimizeMenuClass => navMenuService.Minimized ? "sidebar-minimized" : string.Empty;
|
||||
protected void ToggleMinNavMenu(bool status)
|
||||
{
|
||||
navMenuService.Minimized = status;
|
||||
@ -63,4 +61,4 @@
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,84 +1,16 @@
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: rgb(39, 54, 97);
|
||||
/*background-image: linear-gradient(180deg, rgb(39, 54, 97) 70%, rgb(39, 54, 97) 99%);*/
|
||||
}
|
||||
|
||||
.top-row {
|
||||
/* background-color: rgb(221, 226, 234);*/
|
||||
background-color: var(--fondo-resaltado);
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row:not(.auth) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top-row.auth {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 225px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
.page { position: relative; display: flex; min-height: 100vh; background: var(--ph-canvas); }
|
||||
main { flex: 1; min-width: 0; }
|
||||
.sidebar { position: sticky; top: 0; z-index: 20; width: 200px; height: 100vh; flex: 0 0 200px; overflow: hidden; background: var(--ph-sidebar); box-shadow: 4px 0 18px rgba(9, 18, 38, .08); transition: width .2s ease, flex-basis .2s ease; }
|
||||
.sidebar-minimized { width: 57px; flex-basis: 57px; }
|
||||
.topbar { position: sticky; top: 0; z-index: 15; display: flex; min-height: 4.25rem; align-items: center; justify-content: space-between; gap: 1rem; padding: .65rem 1.5rem; background: color-mix(in srgb, var(--ph-surface) 92%, transparent); border-bottom: 1px solid var(--ph-border); box-shadow: var(--ph-shadow-sm); backdrop-filter: blur(12px); }
|
||||
.topbar-context { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; }
|
||||
.topbar-context strong { color: var(--ph-text); font-size: .98rem; }
|
||||
.topbar-kicker { color: var(--ph-text-muted); font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
||||
.topbar-actions { display: flex; align-items: center; justify-content: flex-end; gap: 1rem; min-width: 0; }
|
||||
@media (max-width: 767.98px) {
|
||||
.page { display: block; }
|
||||
.sidebar, .sidebar-minimized { position: relative; width: 100%; height: auto; min-height: 4rem; }
|
||||
.topbar { position: relative; min-height: 3.75rem; padding: .5rem .85rem; }
|
||||
.topbar-context { display: none; }
|
||||
.topbar-actions { width: 100%; justify-content: space-between; gap: .5rem; }
|
||||
}
|
||||
|
||||
@ -1,27 +1,31 @@
|
||||
@inject NavMenuService navMenuService
|
||||
|
||||
<div class="top-row ps-3 navbar navbar-dark border-bottom" style="padding: 0; border:0px solid red;">
|
||||
<div class="container-fluid" style="padding:0; border:0px solid green;">
|
||||
<a class="navbar-brand d-flex align-items-center" href="" style="height: 56px; border:0px solid yellow; display: block;">
|
||||
<div class="sidebar-header navbar navbar-dark @(navMenuService.Minimized ? "brand-minimized" : string.Empty)">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="" aria-label="Ir al inicio de PhronCare">
|
||||
<img src="images/SaludLAB.png" alt="" />
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
<img src="images/Icono.png" alt="Logo" style="height: 50px; display: block; border:0px solid blue;" />
|
||||
<span class="brand-copy">
|
||||
<span class="brand-name"><strong>Phron</strong>Care</span>
|
||||
<small>by SaludLAB</small>
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<button type="button" title="Abrir menú de navegación" aria-label="Abrir menú de navegación" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@($"{NavMenuCssClass} nav-scrollable bg-dark text-white")" @onclick="ToggleNavMenu" style="padding-top:4px;">
|
||||
<nav class="flex-column text-center">
|
||||
<div class="@($"{NavMenuCssClass} nav-scrollable {(navMenuService.Minimized ? "nav-minimized" : string.Empty)}")" @onclick="ToggleNavMenu">
|
||||
<nav class="flex-column" aria-label="Navegación principal">
|
||||
<div class="nav-item px-2 py-0">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" href="/" Match="NavLinkMatch.All" activeClass="bg-secondary text-white">
|
||||
<span class="oi oi-home" aria-hidden="true"></span>
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
<label class="mb-0">Home</label>
|
||||
<label class="mb-0">Inicio</label>
|
||||
}
|
||||
</NavLink>
|
||||
</div>
|
||||
@ -47,7 +51,7 @@
|
||||
</div>
|
||||
</AuthorizeView>
|
||||
<div class="nav-item px-2 py-0">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" @onclick="() => expStock = !expStock">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" @onclick="ToggleStockMenu">
|
||||
<span class="oi oi-box" aria-hidden="true"></span>
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
@ -55,7 +59,7 @@
|
||||
}
|
||||
</NavLink>
|
||||
|
||||
@if (expStock)
|
||||
@if (expStock && !navMenuService.Minimized)
|
||||
{
|
||||
<ul class="nav flex-column">
|
||||
<!-- Expediciones -->
|
||||
@ -108,7 +112,7 @@
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-2 py-0">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" @onclick="() => expClientes = !expClientes">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" @onclick="ToggleSalesMenu">
|
||||
<span class="oi oi-briefcase" aria-hidden="true"></span>
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
@ -116,7 +120,7 @@
|
||||
}
|
||||
</NavLink>
|
||||
|
||||
@if (expClientes)
|
||||
@if (expClientes && !navMenuService.Minimized)
|
||||
{
|
||||
<ul class="nav flex-column">
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
@ -178,13 +182,34 @@
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
<div class="client-context" title="Cliente de esta instancia">
|
||||
<img src="images/Icono.png" alt="" />
|
||||
<span><small>Entorno</small><strong>BIODEC</strong></span>
|
||||
</div>
|
||||
}
|
||||
<button type="button"
|
||||
class="collapse-control"
|
||||
title="@(navMenuService.Minimized ? "Expandir menú" : "Contraer menú")"
|
||||
aria-label="@(navMenuService.Minimized ? "Expandir menú lateral" : "Contraer menú lateral")"
|
||||
aria-pressed="@navMenuService.Minimized"
|
||||
@onclick:stopPropagation
|
||||
@onclick="ToggleIconMenu">
|
||||
<i class="fas @(navMenuService.Minimized ? "fa-eye-slash" : "fa-eye")" aria-hidden="true"></i>
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
<span>Contraer menú</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
private bool minimizeNavMenu = false;
|
||||
private bool expStock = true;
|
||||
private bool expClientes = true;
|
||||
private bool expStock = false;
|
||||
private bool expClientes = false;
|
||||
|
||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
@ -199,9 +224,38 @@
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
|
||||
private async void ToggleIconMenu()
|
||||
private async Task ToggleStockMenu()
|
||||
{
|
||||
minimizeNavMenu = !minimizeNavMenu;
|
||||
await MinStatus.InvokeAsync(minimizeNavMenu);
|
||||
if (navMenuService.Minimized)
|
||||
{
|
||||
await MinStatus.InvokeAsync(false);
|
||||
expStock = true;
|
||||
return;
|
||||
}
|
||||
|
||||
expStock = !expStock;
|
||||
}
|
||||
|
||||
private async Task ToggleSalesMenu()
|
||||
{
|
||||
if (navMenuService.Minimized)
|
||||
{
|
||||
await MinStatus.InvokeAsync(false);
|
||||
expClientes = true;
|
||||
return;
|
||||
}
|
||||
|
||||
expClientes = !expClientes;
|
||||
}
|
||||
|
||||
private async Task ToggleIconMenu()
|
||||
{
|
||||
var minimized = !navMenuService.Minimized;
|
||||
if (minimized)
|
||||
{
|
||||
expStock = false;
|
||||
expClientes = false;
|
||||
}
|
||||
await MinStatus.InvokeAsync(minimized);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,83 +1,41 @@
|
||||
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.icon-white {
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
.top-row {
|
||||
height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.oi {
|
||||
width: 2rem;
|
||||
font-size: 1.1rem;
|
||||
vertical-align: text-top;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
color: #d7d7d7;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.25);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.nav-link .text-hide-on-small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Muestra el texto dentro de .nav-link en pantallas medianas o grandes ('lg' o más grande) */
|
||||
@media (min-width: 993px) {
|
||||
.nav-link .text-hide-on-small {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
@media (min-width: 641px) {
|
||||
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
.sidebar-header { min-height: 4.25rem; padding: .55rem .75rem; background: var(--ph-sidebar-deep); border-bottom: 1px solid rgba(255, 255, 255, .08); }
|
||||
.sidebar-header .container-fluid { flex-wrap: nowrap; padding: 0; }
|
||||
.sidebar-header.brand-minimized { padding-inline: .5rem; }
|
||||
.sidebar-header.brand-minimized .container-fluid { justify-content: center; }
|
||||
.navbar-brand { display: flex; min-width: 0; align-items: center; gap: .65rem; margin: 0; color: #fff; font-size: 1.08rem; letter-spacing: -.02em; }
|
||||
.navbar-brand img { display: block; width: 40px; height: 40px; flex: 0 0 40px; object-fit: contain; border-radius: .65rem; }
|
||||
.navbar-brand .brand-copy { display: flex; flex-direction: column; line-height: 1; white-space: nowrap; }
|
||||
.navbar-brand .brand-name { display: inline-block; white-space: nowrap; }
|
||||
.navbar-brand small { margin-top: .28rem; color: var(--ph-sidebar-muted); font-size: .58rem; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; }
|
||||
.navbar-toggler { flex: 0 0 auto; padding: .35rem .5rem; background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .14); border-radius: .5rem; }
|
||||
.nav-scrollable { display: flex; height: calc(100vh - 4.25rem); flex-direction: column; padding: .75rem .5rem .65rem; overflow: hidden; background: var(--ph-sidebar); }
|
||||
.nav-scrollable > nav { min-height: 0; flex: 1 1 auto; overflow-x: hidden; overflow-y: auto; -webkit-user-select: none; user-select: none; }
|
||||
.nav-item { padding: .12rem 0 !important; font-size: .875rem; }
|
||||
.nav-item ::deep a { display: flex; min-height: 2.65rem; align-items: center; gap: .65rem; padding: .55rem .72rem !important; color: var(--ph-sidebar-text) !important; line-height: 1.25; border-radius: .65rem; text-decoration: none; transition: color .15s ease, background-color .15s ease; }
|
||||
.nav-item ::deep a label { margin: 0; color: inherit; font-size: inherit; font-weight: 550; cursor: pointer; }
|
||||
.nav-item ::deep a .oi, .nav-item ::deep a > i { width: 1.35rem; flex: 0 0 1.35rem; color: var(--ph-sidebar-muted); font-size: 1rem; text-align: center; }
|
||||
.nav-item ::deep a:hover { color: #fff !important; background: rgba(255, 255, 255, .075); }
|
||||
.nav-item ::deep a:hover .oi, .nav-item ::deep a:hover > i { color: #fff; }
|
||||
.nav-item ::deep a.active { color: #fff !important; background: rgba(126, 162, 235, .2) !important; box-shadow: inset 3px 0 0 #8daef2; }
|
||||
.nav-item ::deep a.active .oi, .nav-item ::deep a.active > i { color: #a9c2f5; }
|
||||
.nav-scrollable.nav-minimized { padding-inline: .5rem; }
|
||||
.nav-scrollable.nav-minimized .nav-item ::deep a { justify-content: center; padding-inline: 0 !important; }
|
||||
.nav-scrollable.nav-minimized .nav-item ::deep a .oi, .nav-scrollable.nav-minimized .nav-item ::deep a > i { margin: 0 !important; }
|
||||
.nav-item ul { gap: .05rem; margin: .2rem 0 .4rem; padding: 0; border: 0; }
|
||||
.nav-item ul .nav-item { padding-left: 0 !important; border: 0 !important; }
|
||||
.nav-item ul .nav-item ::deep a { min-height: 2.25rem; padding: .4rem .72rem !important; color: var(--ph-sidebar-muted) !important; font-size: .78rem; font-weight: 500; }
|
||||
.nav-item ul .nav-item ::deep a .oi, .nav-item ul .nav-item ::deep a > i { color: color-mix(in srgb, var(--ph-sidebar-muted) 78%, transparent); font-size: .86rem; }
|
||||
.nav-item ul .nav-item ::deep a:hover { color: var(--ph-sidebar-text) !important; background: rgba(255, 255, 255, .055); }
|
||||
.nav-item ul .nav-item ::deep a.active { color: #fff !important; background: rgba(126, 162, 235, .13) !important; box-shadow: inset 2px 0 0 rgba(141, 174, 242, .75); }
|
||||
.sidebar-footer { flex: 0 0 auto; padding: .7rem .25rem 0; border-top: 1px solid rgba(255, 255, 255, .1); -webkit-user-select: none; user-select: none; }
|
||||
.client-context { display: flex; align-items: center; gap: .6rem; margin: 0 .35rem .65rem; padding: .45rem .5rem; color: var(--ph-sidebar-text); background: rgba(255, 255, 255, .04); border-radius: .55rem; }
|
||||
.client-context img { width: 2.25rem; height: 1.25rem; object-fit: contain; }
|
||||
.client-context span { display: flex; flex-direction: column; line-height: 1.05; }
|
||||
.client-context small { color: var(--ph-sidebar-muted); font-size: .58rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
||||
.client-context strong { margin-top: .2rem; color: var(--ph-sidebar-text); font-size: .72rem; letter-spacing: .04em; }
|
||||
.collapse-control { display: flex; width: 100%; min-height: 2.5rem; align-items: center; justify-content: center; gap: .6rem; padding: .5rem .65rem; color: var(--ph-sidebar-muted); background: transparent; border: 1px solid transparent; border-radius: .6rem; font-size: .78rem; font-weight: 650; transition: color .15s ease, background-color .15s ease, border-color .15s ease; }
|
||||
.collapse-control i { width: 1.2rem; font-size: .9rem; }
|
||||
.collapse-control:hover { color: #fff; background: rgba(255, 255, 255, .075); border-color: rgba(255, 255, 255, .08); }
|
||||
.collapse-control:focus-visible { outline: 3px solid rgba(141, 174, 242, .35); }
|
||||
@media (min-width: 768px) { .navbar-toggler { display: none; } .nav-scrollable.collapse { display: flex; } }
|
||||
@media (max-width: 767.98px) { .sidebar-header { min-height: 4rem; } .nav-scrollable { height: auto; max-height: calc(100vh - 4rem); } .nav-scrollable > nav { overflow-y: auto; } .sidebar-footer { display: none; } .collapse { display: none; } }
|
||||
|
||||
@ -12,75 +12,121 @@
|
||||
@inject ILoginService _iLoginService
|
||||
@inject IToastService toastService
|
||||
|
||||
<div class="row" style="margin-top: 30px;">
|
||||
<div class="col-md-3 col-sm-12" style="background-color: rgb(39, 54, 97); border-radius: 10px; color: white; padding: 20px; margin: 0 auto auto auto;">
|
||||
<img src="images/Logo.png" class="img-fluid" />
|
||||
<PageTitle>Ingresar | PhronCare · SaludLAB</PageTitle>
|
||||
|
||||
<section class="login-shell">
|
||||
<div class="login-showcase" aria-hidden="true">
|
||||
<div class="showcase-brand">
|
||||
<img src="images/SaludLAB.png" alt="" />
|
||||
<span class="brand-copy">
|
||||
<span class="brand-name"><strong>Phron</strong>Care</span>
|
||||
<small>by SaludLAB</small>
|
||||
</span>
|
||||
</div>
|
||||
<div class="showcase-copy">
|
||||
<span class="showcase-eyebrow">Gestión de salud</span>
|
||||
<h1>Una operación más clara, conectada y eficiente.</h1>
|
||||
<p>Centralizá la gestión asistencial, comercial y logística en un único espacio seguro.</p>
|
||||
</div>
|
||||
<div class="showcase-footer"><i class="fas fa-shield-halved"></i> Acceso seguro al Backoffice</div>
|
||||
</div>
|
||||
|
||||
<div class="login-panel">
|
||||
<div class="login-card">
|
||||
<div class="mobile-brand">
|
||||
<img src="images/SaludLAB.png" alt="Logo SaludLAB" />
|
||||
<span class="brand-copy">
|
||||
<span class="brand-name"><strong>Phron</strong>Care</span>
|
||||
<small>by SaludLAB</small>
|
||||
</span>
|
||||
</div>
|
||||
@if (!isSecret)
|
||||
{
|
||||
<h3>Iniciar sesión</h3>
|
||||
<EditForm Model="login2FAModel" OnInvalidSubmit="LoginAsync">
|
||||
{
|
||||
<div class="login-heading">
|
||||
<span class="login-eyebrow">Bienvenido</span>
|
||||
<h2>Iniciar sesión</h2>
|
||||
<p>Ingresá tus credenciales para continuar.</p>
|
||||
</div>
|
||||
<EditForm Model="login2FAModel" OnSubmit="LoginAsync">
|
||||
<div class="form-group">
|
||||
<label>Usuario</label>
|
||||
<div>
|
||||
<InputText Class="form-control" @bind-Value=@login2FAModel.UserName />
|
||||
<label for="username">Usuario</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-user" aria-hidden="true"></i>
|
||||
<InputText id="username" autocomplete="username" placeholder="Ingresá tu usuario" Class="form-control" @bind-Value="@login2FAModel.UserName" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Contraseña</label>
|
||||
<div>
|
||||
<InputText type="password" Class="form-control" @bind-Value=login2FAModel.Password />
|
||||
<label for="password">Contraseña</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-lock" aria-hidden="true"></i>
|
||||
<InputText id="password" type="password" autocomplete="current-password" placeholder="Ingresá tu contraseña" Class="form-control" @bind-Value="login2FAModel.Password" />
|
||||
</div>
|
||||
</div>
|
||||
@if (codeTwoFactor)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label for="code">Código</label>
|
||||
<InputText type="text" id="code" class="form-control" @bind-Value=login2FAModel.Code />
|
||||
<label for="code">Código de verificación</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-shield-halved" aria-hidden="true"></i>
|
||||
<InputText type="text" id="code" inputmode="numeric" autocomplete="one-time-code" placeholder="Código de seguridad" class="form-control" @bind-Value="login2FAModel.Code" />
|
||||
</div>
|
||||
<small class="field-hint">Ingresá el código de autenticación de dos factores.</small>
|
||||
</div>
|
||||
}
|
||||
<br />
|
||||
<div class="col-12" style="text-align:center;">
|
||||
<button type="submit" class="btn btn-primary" @onclick="LoginAsync">
|
||||
<span class="fa fa-key"></span> Entrar</button>
|
||||
<button class="btn btn-warning btn-circle" style="float:right;" @onclick="ToggleIsSecret">
|
||||
<span class="fa fa-user-secret"></span>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary login-submit" disabled="@isSubmitting">
|
||||
@if (isSubmitting) { <span class="spinner-border spinner-border-sm" aria-hidden="true"></span> }
|
||||
else { <span class="fa fa-arrow-right-to-bracket" aria-hidden="true"></span> }
|
||||
@(isSubmitting ? "Ingresando…" : "Ingresar")
|
||||
</button>
|
||||
<div class="login-help">
|
||||
<span>¿Problemas para ingresar?</span>
|
||||
<button type="button" class="reset-link" @onclick="ToggleIsSecret">Restablecer contraseña</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h3>Restablecer password</h3>
|
||||
<div class="login-heading">
|
||||
<span class="login-eyebrow">Recuperación de acceso</span>
|
||||
<h2>Restablecer contraseña</h2>
|
||||
<p>Completá los datos y el token recibido por correo.</p>
|
||||
</div>
|
||||
<EditForm Model="resetPassword" OnValidSubmit="ResetPasswordAsync">
|
||||
<DataAnnotationsValidator />
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<InputText @bind-Value="resetPassword.Email" class="form-control" />
|
||||
<label for="reset-email">Email</label>
|
||||
<InputText id="reset-email" type="email" autocomplete="email" @bind-Value="resetPassword.Email" class="form-control" />
|
||||
<ValidationMessage For="@(() => resetPassword.Email)" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nueva Contraseña</label>
|
||||
<InputText @bind-Value="resetPassword.Password" type="password" class="form-control" />
|
||||
<label for="new-password">Nueva contraseña</label>
|
||||
<InputText id="new-password" @bind-Value="resetPassword.Password" type="password" autocomplete="new-password" class="form-control" />
|
||||
<ValidationMessage For="@(() => resetPassword.Password)" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Confirmar Contraseña</label>
|
||||
<InputText @bind-Value="resetPassword.ConfirmPassword" type="password" class="form-control" />
|
||||
<label for="confirm-password">Confirmar contraseña</label>
|
||||
<InputText id="confirm-password" @bind-Value="resetPassword.ConfirmPassword" type="password" autocomplete="new-password" class="form-control" />
|
||||
<ValidationMessage For="@(() => resetPassword.ConfirmPassword)" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Token</label>
|
||||
<InputTextArea @bind-Value="resetPassword.Token" class="form-control" />
|
||||
<label for="reset-token">Token</label>
|
||||
<InputTextArea id="reset-token" rows="3" @bind-Value="resetPassword.Token" class="form-control" />
|
||||
<ValidationMessage For="@(() => resetPassword.Token)" />
|
||||
</div>
|
||||
<br/>
|
||||
<div class="col-12" style="text-align:center;">
|
||||
<button type="submit" class="btn btn-primary"><span class="fa fa-vault"></span> Restablecer</button>
|
||||
<button class="btn btn-warning btn-circle" style="fData.Login.Loginght;" @onclick="ToggleIsSecret">
|
||||
<span class="fa fa-circle-left"></span>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary login-submit" disabled="@isSubmitting">
|
||||
@if (isSubmitting) { <span class="spinner-border spinner-border-sm" aria-hidden="true"></span> }
|
||||
else { <span class="fa fa-key" aria-hidden="true"></span> }
|
||||
@(isSubmitting ? "Restableciendo…" : "Restablecer contraseña")
|
||||
</button>
|
||||
<div class="login-help">
|
||||
<button type="button" class="reset-link" @onclick="ToggleIsSecret"><i class="fas fa-arrow-left"></i> Volver al inicio de sesión</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
<div class="login-legal">PhronCare by SaludLAB · Entorno BIODEC</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
Login loginModel = new Login();
|
||||
@ -88,10 +134,15 @@
|
||||
ResetPassword resetPassword = new ResetPassword();
|
||||
public bool isSecret = false;
|
||||
bool codeTwoFactor = false;
|
||||
bool isSubmitting = false;
|
||||
string OTPCode = string.Empty;
|
||||
|
||||
private async Task LoginAsync()
|
||||
{
|
||||
if (isSubmitting) return;
|
||||
isSubmitting = true;
|
||||
try
|
||||
{
|
||||
if (codeTwoFactor && login2FAModel.Code.Length > 4)
|
||||
{
|
||||
string requestUrl = $"api/Authentication/login-2FA?code={login2FAModel.Code}&username={login2FAModel.UserName}";
|
||||
@ -136,9 +187,18 @@
|
||||
toastService.ShowError(tokenResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
private async Task ResetPasswordAsync()
|
||||
{
|
||||
if (isSubmitting) return;
|
||||
isSubmitting = true;
|
||||
try
|
||||
{
|
||||
if (resetPassword.Password != resetPassword.ConfirmPassword)
|
||||
{
|
||||
toastService.ShowError("Las contraseñas no coinciden. ¡Inténtalo de nuevo!");
|
||||
@ -151,6 +211,11 @@
|
||||
{
|
||||
await ResetingPassword();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
private async Task ResetingPassword()
|
||||
{
|
||||
|
||||
@ -1,9 +1,43 @@
|
||||
.btn-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 6px 0px;
|
||||
border-radius: 50px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
line-height: 1.42857;
|
||||
.login-shell { display: grid; min-height: calc(100vh - 3rem); grid-template-columns: minmax(360px, .9fr) minmax(430px, 1.1fr); overflow: hidden; background: var(--ph-surface); border: 1px solid var(--ph-border); border-radius: 1.25rem; box-shadow: var(--ph-shadow-md); }
|
||||
.login-showcase { position: relative; display: flex; min-height: 650px; flex-direction: column; justify-content: space-between; padding: 2.25rem; overflow: hidden; color: #fff; background: radial-gradient(circle at 85% 15%, rgba(83, 199, 184, .28), transparent 32%), linear-gradient(145deg, #263b70 0%, #1b2a50 52%, #15213e 100%); }
|
||||
.login-showcase::before, .login-showcase::after { position: absolute; content: ""; border: 1px solid rgba(255,255,255,.08); border-radius: 50%; }
|
||||
.login-showcase::before { right: -12rem; bottom: -12rem; width: 30rem; height: 30rem; }
|
||||
.login-showcase::after { right: -5rem; bottom: -5rem; width: 18rem; height: 18rem; }
|
||||
.showcase-brand { position: relative; z-index: 1; display: flex; align-items: center; gap: .75rem; font-size: 1.35rem; }
|
||||
.showcase-brand img { width: 46px; height: 46px; object-fit: contain; border-radius: .75rem; }
|
||||
.showcase-brand .brand-copy, .mobile-brand .brand-copy { display: flex; flex-direction: column; line-height: 1; }
|
||||
.showcase-brand .brand-name, .mobile-brand .brand-name { display: inline-block; white-space: nowrap; }
|
||||
.showcase-brand small, .mobile-brand small { margin-top: .3rem; color: rgba(232,239,251,.66); font-size: .58rem; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; }
|
||||
.showcase-copy { position: relative; z-index: 1; max-width: 32rem; }
|
||||
.showcase-eyebrow, .login-eyebrow { display: block; margin-bottom: .7rem; color: #72d6ca; font-size: .72rem; font-weight: 750; letter-spacing: .11em; text-transform: uppercase; }
|
||||
.showcase-copy h1 { margin: 0; color: #fff; font-size: clamp(2rem, 3.2vw, 3.15rem); font-weight: 720; line-height: 1.08; }
|
||||
.showcase-copy p { max-width: 28rem; margin: 1.15rem 0 0; color: rgba(232,239,251,.72); font-size: 1rem; line-height: 1.65; }
|
||||
.showcase-footer { position: relative; z-index: 1; display: flex; align-items: center; gap: .55rem; color: rgba(232,239,251,.68); font-size: .75rem; }
|
||||
.login-panel { display: flex; align-items: center; justify-content: center; padding: clamp(2rem, 5vw, 5rem); background: var(--ph-surface); }
|
||||
.login-card { width: 100%; max-width: 430px; }
|
||||
.mobile-brand { display: none; }
|
||||
.login-heading { margin-bottom: 1.75rem; }
|
||||
.login-heading h2 { margin: 0; color: var(--ph-text); font-size: 1.85rem; font-weight: 750; }
|
||||
.login-heading p { margin: .55rem 0 0; color: var(--ph-text-muted); font-size: .92rem; }
|
||||
.login-eyebrow { color: var(--ph-primary); }
|
||||
.form-group { margin-bottom: 1rem; }
|
||||
.input-with-icon { position: relative; }
|
||||
.input-with-icon > i { position: absolute; top: 50%; left: .95rem; z-index: 1; color: var(--ph-text-muted); font-size: .85rem; transform: translateY(-50%); pointer-events: none; }
|
||||
.input-with-icon ::deep .form-control { padding-left: 2.65rem; }
|
||||
.field-hint { display: block; margin-top: .4rem; color: var(--ph-text-muted); font-size: .75rem; }
|
||||
.login-submit { display: flex; width: 100%; min-height: 2.85rem; align-items: center; justify-content: center; gap: .55rem; margin-top: 1.35rem; }
|
||||
.login-help { display: flex; align-items: center; justify-content: center; gap: .35rem; margin-top: 1.2rem; color: var(--ph-text-muted); font-size: .78rem; text-align: center; }
|
||||
.reset-link { padding: .2rem; color: var(--ph-primary); background: transparent; border: 0; font: inherit; font-weight: 650; cursor: pointer; }
|
||||
.reset-link:hover { text-decoration: underline; }
|
||||
.reset-link:focus-visible { outline: 3px solid var(--ph-focus); border-radius: .25rem; }
|
||||
.login-legal { margin-top: 2rem; color: var(--ph-text-muted); font-size: .7rem; text-align: center; }
|
||||
@media (max-width: 991.98px) { .login-shell { grid-template-columns: minmax(300px, .75fr) minmax(400px, 1.25fr); } .login-showcase { padding: 1.75rem; } }
|
||||
@media (max-width: 767.98px) {
|
||||
.login-shell { display: block; min-height: auto; border: 0; border-radius: 0; box-shadow: none; }
|
||||
.login-showcase { display: none; }
|
||||
.login-panel { min-height: calc(100vh - 2rem); align-items: flex-start; padding: 1.5rem 1rem 2rem; }
|
||||
.mobile-brand { display: flex; align-items: center; gap: .65rem; margin-bottom: 2.5rem; color: var(--ph-text); font-size: 1.2rem; }
|
||||
.mobile-brand img { width: 42px; height: 42px; object-fit: contain; border-radius: .65rem; }
|
||||
.mobile-brand small { color: var(--ph-text-muted); }
|
||||
.login-heading h2 { font-size: 1.6rem; }
|
||||
}
|
||||
|
||||
@ -2,92 +2,120 @@
|
||||
@using phronCare.UIBlazor.Pages.Authorization
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>PhronCare</PageTitle>
|
||||
<PageTitle>PhronCare · SaludLAB</PageTitle>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<section class="home-dashboard">
|
||||
<div class="home-hero">
|
||||
<section class="home-dashboard" aria-label="Panel principal">
|
||||
<header class="home-intro">
|
||||
<div>
|
||||
<span class="home-eyebrow">BackOffice operativo</span>
|
||||
<h1>Bienvenido a PhronCare</h1>
|
||||
<p>
|
||||
Gestion integral para empresas de internacion domiciliaria,
|
||||
ortopedia y prestaciones medicas.
|
||||
</p>
|
||||
<span class="section-eyebrow">Centro operativo</span>
|
||||
<h1>Panel principal</h1>
|
||||
<p>Gestión asistencial, comercial y logística de PhronCare.</p>
|
||||
</div>
|
||||
<div class="home-hero-status" aria-label="Estado general">
|
||||
<span class="home-status-dot"></span>
|
||||
<div class="platform-status" aria-label="Estado general: plataforma operativa">
|
||||
<span class="status-dot"></span>
|
||||
Plataforma operativa
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="home-section-header">
|
||||
<h2>Accesos rapidos</h2>
|
||||
<span>Modulos principales</span>
|
||||
</div>
|
||||
|
||||
<div class="quick-access-grid">
|
||||
@foreach (var item in QuickAccessItems)
|
||||
{
|
||||
<button type="button" class="quick-access-card" @onclick="() => NavigateTo(item.Url)">
|
||||
<span class="quick-access-icon @item.AccentClass">
|
||||
<i class="@item.Icon" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="quick-access-content">
|
||||
<strong>@item.Title</strong>
|
||||
<small>@item.Description</small>
|
||||
</span>
|
||||
<i class="fas fa-chevron-right quick-access-arrow" aria-hidden="true"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="home-content-grid">
|
||||
<section>
|
||||
<div class="home-section-header">
|
||||
<h2>Resumen operativo</h2>
|
||||
<span>Valores temporales</span>
|
||||
<section class="dashboard-section" aria-labelledby="daily-title">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="section-eyebrow">Accesos principales</span>
|
||||
<h2 id="daily-title">Operación diaria</h2>
|
||||
</div>
|
||||
<p>Herramientas centrales para la atención y la gestión comercial.</p>
|
||||
</div>
|
||||
|
||||
<div class="kpi-grid">
|
||||
<div class="featured-grid">
|
||||
@foreach (var item in PrimaryItems)
|
||||
{
|
||||
<button type="button" class="featured-card @item.ToneClass" @onclick="() => NavigateTo(item.Url)">
|
||||
<span class="featured-card-topline">
|
||||
<span class="feature-icon"><i class="@item.Icon" aria-hidden="true"></i></span>
|
||||
<span class="feature-tag">@item.Category</span>
|
||||
</span>
|
||||
<span class="featured-copy">
|
||||
<strong>@item.Title</strong>
|
||||
<small>@item.Description</small>
|
||||
</span>
|
||||
<span class="card-action">@item.Action <i class="fas fa-arrow-right" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section" aria-labelledby="management-title">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="section-eyebrow">Administración</span>
|
||||
<h2 id="management-title">Gestión del backoffice</h2>
|
||||
</div>
|
||||
<p>Módulos que acompañan la operación comercial, documental y logística.</p>
|
||||
</div>
|
||||
|
||||
<div class="management-grid">
|
||||
@foreach (var item in ManagementItems)
|
||||
{
|
||||
<button type="button" class="management-card" @onclick="() => NavigateTo(item.Url)">
|
||||
<span class="management-icon @item.AccentClass"><i class="@item.Icon" aria-hidden="true"></i></span>
|
||||
<span class="management-copy">
|
||||
<strong>@item.Title</strong>
|
||||
<small>@item.Description</small>
|
||||
</span>
|
||||
<i class="fas fa-arrow-right management-arrow" aria-hidden="true"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section" aria-labelledby="overview-title">
|
||||
<div class="section-heading section-heading-compact">
|
||||
<div>
|
||||
<span class="section-eyebrow">Panorama actual</span>
|
||||
<h2 id="overview-title">Resumen operativo</h2>
|
||||
</div>
|
||||
<span class="sample-label">Datos de muestra</span>
|
||||
</div>
|
||||
|
||||
<div class="overview-grid">
|
||||
<div class="metrics-panel">
|
||||
@foreach (var item in SummaryItems)
|
||||
{
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-icon @item.AccentClass">
|
||||
<i class="@item.Icon" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div>
|
||||
<article class="metric-item">
|
||||
<span class="metric-icon @item.AccentClass"><i class="@item.Icon" aria-hidden="true"></i></span>
|
||||
<span class="metric-copy">
|
||||
<strong>@item.Value</strong>
|
||||
<span>@item.Title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="home-section-header">
|
||||
<h2>Actividad reciente</h2>
|
||||
<span>Simulada</span>
|
||||
</div>
|
||||
|
||||
<div class="activity-list">
|
||||
@foreach (var item in RecentActivity)
|
||||
{
|
||||
<div class="activity-item">
|
||||
<span class="activity-icon @item.AccentClass">
|
||||
<i class="@item.Icon" aria-hidden="true"></i>
|
||||
<small>@item.Title</small>
|
||||
</span>
|
||||
<div>
|
||||
<strong>@item.Title</strong>
|
||||
<small>@item.Description</small>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="activity-panel">
|
||||
<div class="activity-header">
|
||||
<div>
|
||||
<h3>Actividad reciente</h3>
|
||||
<p>Últimos movimientos registrados</p>
|
||||
</div>
|
||||
<i class="fas fa-clock" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="activity-list">
|
||||
@foreach (var item in RecentActivity)
|
||||
{
|
||||
<div class="activity-item">
|
||||
<span class="activity-icon @item.AccentClass"><i class="@item.Icon" aria-hidden="true"></i></span>
|
||||
<span>
|
||||
<strong>@item.Title</strong>
|
||||
<small>@item.Description</small>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@ -97,23 +125,27 @@
|
||||
|
||||
@code
|
||||
{
|
||||
private readonly List<QuickAccessItem> QuickAccessItems =
|
||||
private readonly List<PrimaryAccessItem> PrimaryItems =
|
||||
[
|
||||
new("Pacientes", "Gestion de pacientes y datos asistenciales.", "/sales/patients", "fas fa-user-injured", "accent-blue"),
|
||||
new("Presupuestos", "Consulta y seguimiento de presupuestos.", "/quotes/dashboard", "fas fa-file-invoice-dollar", "accent-green"),
|
||||
new("Ventas", "Clientes, productos y circuito comercial.", "/sales/customers", "fas fa-briefcase", "accent-indigo"),
|
||||
new("Remitos", "Emision y control de remitos.", "/deliverynotes", "fas fa-truck", "accent-orange"),
|
||||
new("Sales Documents", "Documentos comerciales y facturacion.", "/salesdocuments", "fas fa-receipt", "accent-teal"),
|
||||
new("Stock", "Productos medicos y trazabilidad.", "/stock/products", "fas fa-boxes-stacked", "accent-violet"),
|
||||
new("Tickets", "Alta y seguimiento de tickets.", "/dashboardpanel", "fas fa-ticket-alt", "accent-red")
|
||||
new("Presupuestos", "Creá, consultá y realizá el seguimiento del circuito de presupuestos.", "/quotes/dashboard", "fas fa-file-invoice-dollar", "Gestión comercial", "Ver presupuestos", "tone-green"),
|
||||
new("Notas de expedición", "Emití, consultá y controlá las entregas asociadas a la operación logística.", "/deliverynotes", "fas fa-truck", "Gestión logística", "Ver notas de expedición", "tone-blue")
|
||||
];
|
||||
|
||||
private readonly List<ManagementAccessItem> ManagementItems =
|
||||
[
|
||||
new("Pacientes", "Información asistencial", "/sales/patients", "fas fa-user-injured", "accent-blue"),
|
||||
new("Ventas", "Clientes y productos", "/sales/customers", "fas fa-briefcase", "accent-indigo"),
|
||||
new("Documentos comerciales", "Documentación y facturación", "/salesdocuments", "fas fa-receipt", "accent-teal"),
|
||||
new("Stock", "Productos y trazabilidad", "/stock/products", "fas fa-boxes-stacked", "accent-violet"),
|
||||
new("Tickets", "Alta y seguimiento", "/dashboardpanel", "fas fa-ticket-alt", "accent-red")
|
||||
];
|
||||
|
||||
private readonly List<SummaryItem> SummaryItems =
|
||||
[
|
||||
new("Presupuestos Activos", "128", "fas fa-file-signature", "accent-green"),
|
||||
new("Remitos Emitidos", "42", "fas fa-truck-fast", "accent-orange"),
|
||||
new("Facturas Pendientes", "15", "fas fa-file-invoice", "accent-teal"),
|
||||
new("Tickets Abiertos", "6", "fas fa-headset", "accent-red")
|
||||
new("Presupuestos activos", "128", "fas fa-file-signature", "accent-green"),
|
||||
new("Remitos emitidos", "42", "fas fa-truck-fast", "accent-orange"),
|
||||
new("Facturas pendientes", "15", "fas fa-file-invoice", "accent-teal"),
|
||||
new("Tickets abiertos", "6", "fas fa-headset", "accent-red")
|
||||
];
|
||||
|
||||
private readonly List<ActivityItem> RecentActivity =
|
||||
@ -124,12 +156,10 @@
|
||||
new("Ticket cerrado", "Hoy, 09:20", "fas fa-circle-check", "accent-red")
|
||||
];
|
||||
|
||||
private void NavigateTo(string url)
|
||||
{
|
||||
Navigation.NavigateTo(url);
|
||||
}
|
||||
private void NavigateTo(string url) => Navigation.NavigateTo(url);
|
||||
|
||||
private sealed record QuickAccessItem(string Title, string Description, string Url, string Icon, string AccentClass);
|
||||
private sealed record PrimaryAccessItem(string Title, string Description, string Url, string Icon, string Category, string Action, string ToneClass);
|
||||
private sealed record ManagementAccessItem(string Title, string Description, string Url, string Icon, string AccentClass);
|
||||
private sealed record SummaryItem(string Title, string Value, string Icon, string AccentClass);
|
||||
private sealed record ActivityItem(string Title, string Description, string Icon, string AccentClass);
|
||||
}
|
||||
|
||||
@ -1,318 +1,466 @@
|
||||
.home-dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding-bottom: 2rem;
|
||||
gap: clamp(2.2rem, 4vw, 3.75rem);
|
||||
padding: .25rem 0 3rem;
|
||||
}
|
||||
|
||||
.home-hero {
|
||||
align-items: flex-start;
|
||||
background: var(--background-highlight);
|
||||
border: 1px solid var(--background-highlight-light);
|
||||
border-radius: 8px;
|
||||
.home-intro,
|
||||
.section-heading {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 1.75rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.home-eyebrow {
|
||||
color: var(--link-color);
|
||||
.home-intro h1 {
|
||||
margin: .2rem 0 0;
|
||||
color: var(--ph-text);
|
||||
font-size: clamp(1.9rem, 3vw, 2.55rem);
|
||||
font-weight: 760;
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.home-intro p {
|
||||
margin: .55rem 0 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.section-eyebrow {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
margin-bottom: 0.45rem;
|
||||
color: var(--ph-primary);
|
||||
font-size: .72rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: .1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.home-hero h1 {
|
||||
color: var(--text-background);
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.home-hero p {
|
||||
color: var(--text-background);
|
||||
font-size: 1rem;
|
||||
line-height: 1.55;
|
||||
margin: 0.75rem 0 0;
|
||||
max-width: 760px;
|
||||
opacity: 0.78;
|
||||
}
|
||||
|
||||
.home-hero-status {
|
||||
align-items: center;
|
||||
background: var(--background);
|
||||
border: 1px solid var(--background-highlight-light);
|
||||
border-radius: 999px;
|
||||
color: var(--text-background);
|
||||
.platform-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
gap: 0.5rem;
|
||||
padding: 0.45rem 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.home-status-dot {
|
||||
background: #16a085;
|
||||
gap: .55rem;
|
||||
padding: .48rem .75rem;
|
||||
color: var(--ph-text-muted);
|
||||
background: var(--ph-surface);
|
||||
border: 1px solid var(--ph-border);
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 0 0 4px rgba(22, 160, 133, 0.14);
|
||||
height: 0.6rem;
|
||||
width: 0.6rem;
|
||||
font-size: .78rem;
|
||||
font-weight: 650;
|
||||
box-shadow: var(--ph-shadow-sm);
|
||||
}
|
||||
|
||||
.home-section-header {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
.status-dot {
|
||||
width: .52rem;
|
||||
height: .52rem;
|
||||
background: #35a58a;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, #35a58a 14%, transparent);
|
||||
}
|
||||
|
||||
.home-section-header h2 {
|
||||
color: var(--text-background);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.home-section-header span {
|
||||
color: var(--text-background);
|
||||
font-size: 0.82rem;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.quick-access-grid {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.quick-access-card {
|
||||
align-items: center;
|
||||
background: var(--background-highlight);
|
||||
border: 1px solid var(--background-highlight-light);
|
||||
border-radius: 8px;
|
||||
color: var(--text-background);
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
min-height: 86px;
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.quick-access-card:hover,
|
||||
.quick-access-card:focus {
|
||||
border-color: var(--link-color);
|
||||
box-shadow: 0 0.35rem 1rem rgba(0, 0, 0, 0.12);
|
||||
outline: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.quick-access-icon,
|
||||
.kpi-icon,
|
||||
.activity-icon {
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quick-access-icon {
|
||||
font-size: 1rem;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
.quick-access-content {
|
||||
.dashboard-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
gap: 1.15rem;
|
||||
}
|
||||
|
||||
.quick-access-content strong {
|
||||
color: var(--text-background);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.2;
|
||||
.section-heading h2 {
|
||||
margin: .25rem 0 0;
|
||||
color: var(--ph-text);
|
||||
font-size: clamp(1.25rem, 2vw, 1.55rem);
|
||||
font-weight: 730;
|
||||
}
|
||||
|
||||
.quick-access-content small {
|
||||
color: var(--text-background);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
opacity: 0.66;
|
||||
.section-heading > p {
|
||||
max-width: 38rem;
|
||||
margin: 0 0 .12rem;
|
||||
font-size: .9rem;
|
||||
line-height: 1.45;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.quick-access-arrow {
|
||||
color: var(--text-background);
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
.home-content-grid {
|
||||
.featured-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: minmax(0, 1.3fr) minmax(280px, 0.7fr);
|
||||
}
|
||||
|
||||
.home-content-grid section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.kpi-grid {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.kpi-card,
|
||||
.activity-list {
|
||||
background: var(--background-highlight);
|
||||
border: 1px solid var(--background-highlight-light);
|
||||
border-radius: 8px;
|
||||
.featured-card,
|
||||
.management-card {
|
||||
position: relative;
|
||||
color: var(--ph-text);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
align-items: center;
|
||||
.featured-card {
|
||||
display: flex;
|
||||
gap: 0.9rem;
|
||||
min-height: 92px;
|
||||
padding: 1rem;
|
||||
min-height: 18rem;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 1.55rem;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(145deg, var(--ph-surface-raised), var(--ph-surface));
|
||||
border: 1px solid var(--feature-border);
|
||||
border-radius: var(--ph-radius-lg);
|
||||
box-shadow: var(--ph-shadow-sm);
|
||||
transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
font-size: 1rem;
|
||||
height: 2.65rem;
|
||||
width: 2.65rem;
|
||||
.featured-card::after {
|
||||
position: absolute;
|
||||
right: -5rem;
|
||||
bottom: -7rem;
|
||||
width: 14rem;
|
||||
height: 14rem;
|
||||
background: radial-gradient(circle, var(--feature-glow), transparent 68%);
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.kpi-card strong {
|
||||
color: var(--text-background);
|
||||
display: block;
|
||||
font-size: 1.75rem;
|
||||
.featured-card:hover,
|
||||
.featured-card:focus-visible {
|
||||
border-color: var(--feature-color);
|
||||
box-shadow: var(--ph-shadow-md);
|
||||
outline: none;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tone-blue {
|
||||
--feature-color: #719bea;
|
||||
--feature-border: color-mix(in srgb, #719bea 52%, var(--ph-border));
|
||||
--feature-glow: color-mix(in srgb, #719bea 17%, transparent);
|
||||
}
|
||||
|
||||
.tone-green {
|
||||
--feature-color: #55b7a4;
|
||||
--feature-border: color-mix(in srgb, #55b7a4 52%, var(--ph-border));
|
||||
--feature-glow: color-mix(in srgb, #55b7a4 17%, transparent);
|
||||
}
|
||||
|
||||
.featured-card-topline {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.feature-icon,
|
||||
.management-icon,
|
||||
.metric-icon,
|
||||
.activity-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 3.3rem;
|
||||
height: 3.3rem;
|
||||
color: var(--feature-color);
|
||||
background: color-mix(in srgb, var(--feature-color) 9%, var(--ph-surface));
|
||||
border: 1px solid color-mix(in srgb, var(--feature-color) 35%, var(--ph-border));
|
||||
border-radius: .75rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
padding: .38rem .65rem;
|
||||
color: var(--feature-color);
|
||||
background: color-mix(in srgb, var(--feature-color) 9%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--feature-color) 28%, transparent);
|
||||
border-radius: 999px;
|
||||
font-size: .65rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: .055em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.featured-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .45rem;
|
||||
max-width: 36rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.featured-copy strong {
|
||||
color: var(--ph-text);
|
||||
font-size: 1.45rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.featured-copy small {
|
||||
color: var(--ph-text-muted);
|
||||
font-size: .9rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.card-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
z-index: 1;
|
||||
color: var(--feature-color);
|
||||
font-size: .84rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card-action i,
|
||||
.management-arrow {
|
||||
transition: transform .18s ease;
|
||||
}
|
||||
|
||||
.featured-card:hover .card-action i,
|
||||
.management-card:hover .management-arrow {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.management-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: .85rem;
|
||||
}
|
||||
|
||||
.management-card {
|
||||
display: grid;
|
||||
min-height: 10rem;
|
||||
align-content: start;
|
||||
grid-template-columns: 2.55rem minmax(0, 1fr) auto;
|
||||
gap: .9rem;
|
||||
padding: 1.15rem;
|
||||
background: var(--ph-surface);
|
||||
border: 1px solid var(--ph-border);
|
||||
border-radius: var(--ph-radius);
|
||||
box-shadow: var(--ph-shadow-sm);
|
||||
transition: transform .18s ease, border-color .18s ease, background-color .18s ease;
|
||||
}
|
||||
|
||||
.management-card:hover,
|
||||
.management-card:focus-visible {
|
||||
background: var(--ph-surface-raised);
|
||||
border-color: var(--ph-border-strong);
|
||||
outline: none;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.management-icon {
|
||||
width: 2.55rem;
|
||||
height: 2.55rem;
|
||||
color: var(--accent-color);
|
||||
background: color-mix(in srgb, var(--accent-color) 9%, var(--ph-surface-subtle));
|
||||
border: 1px solid color-mix(in srgb, var(--accent-color) 25%, var(--ph-border));
|
||||
border-radius: .65rem;
|
||||
}
|
||||
|
||||
.management-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: .32rem;
|
||||
align-self: end;
|
||||
grid-column: 1 / 4;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.management-copy strong {
|
||||
color: var(--ph-text);
|
||||
font-size: .95rem;
|
||||
}
|
||||
|
||||
.management-copy small {
|
||||
color: var(--ph-text-muted);
|
||||
font-size: .75rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.management-arrow {
|
||||
align-self: center;
|
||||
color: var(--ph-text-muted);
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.section-heading-compact {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sample-label {
|
||||
color: var(--ph-text-muted);
|
||||
font-size: .72rem;
|
||||
}
|
||||
|
||||
.overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(20rem, .65fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.metrics-panel {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
overflow: hidden;
|
||||
background: var(--ph-surface);
|
||||
border: 1px solid var(--ph-border);
|
||||
border-radius: var(--ph-radius-lg);
|
||||
box-shadow: var(--ph-shadow-sm);
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
display: flex;
|
||||
min-height: 7.5rem;
|
||||
align-items: center;
|
||||
gap: .9rem;
|
||||
padding: 1.2rem;
|
||||
}
|
||||
|
||||
.metric-item:nth-child(even) {
|
||||
border-left: 1px solid var(--ph-border);
|
||||
}
|
||||
|
||||
.metric-item:nth-child(n+3) {
|
||||
border-top: 1px solid var(--ph-border);
|
||||
}
|
||||
|
||||
.metric-icon,
|
||||
.activity-icon {
|
||||
color: var(--accent-color);
|
||||
background: color-mix(in srgb, var(--accent-color) 10%, var(--ph-surface-subtle));
|
||||
border: 1px solid color-mix(in srgb, var(--accent-color) 24%, var(--ph-border));
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
border-radius: .7rem;
|
||||
}
|
||||
|
||||
.metric-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: .28rem;
|
||||
}
|
||||
|
||||
.metric-copy strong {
|
||||
color: var(--ph-text);
|
||||
font-size: 1.65rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.kpi-card span {
|
||||
color: var(--text-background);
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.35rem;
|
||||
opacity: 0.68;
|
||||
.metric-copy small {
|
||||
color: var(--ph-text-muted);
|
||||
font-size: .78rem;
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.activity-panel {
|
||||
overflow: hidden;
|
||||
background: var(--ph-surface);
|
||||
border: 1px solid var(--ph-border);
|
||||
border-radius: var(--ph-radius-lg);
|
||||
box-shadow: var(--ph-shadow-sm);
|
||||
}
|
||||
|
||||
.activity-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1.1rem 1.2rem;
|
||||
background: var(--ph-surface-subtle);
|
||||
border-bottom: 1px solid var(--ph-border);
|
||||
}
|
||||
|
||||
.activity-header h3 {
|
||||
margin: 0;
|
||||
color: var(--ph-text);
|
||||
font-size: .95rem;
|
||||
}
|
||||
|
||||
.activity-header p {
|
||||
margin: .2rem 0 0;
|
||||
font-size: .72rem;
|
||||
}
|
||||
|
||||
.activity-header > i {
|
||||
color: var(--ph-text-muted);
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.85rem;
|
||||
min-height: 72px;
|
||||
padding: 0.95rem 1rem;
|
||||
min-height: 4.2rem;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
padding: .75rem 1.2rem;
|
||||
}
|
||||
|
||||
.activity-item + .activity-item {
|
||||
border-top: 1px solid var(--background-highlight-light);
|
||||
border-top: 1px solid var(--ph-border);
|
||||
}
|
||||
|
||||
.activity-icon {
|
||||
font-size: 0.9rem;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
width: 2.1rem;
|
||||
height: 2.1rem;
|
||||
border-radius: .55rem;
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.activity-item strong,
|
||||
.activity-item small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.activity-item strong {
|
||||
color: var(--text-background);
|
||||
display: block;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.25;
|
||||
color: var(--ph-text);
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.activity-item small {
|
||||
color: var(--text-background);
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
margin-top: 0.18rem;
|
||||
opacity: 0.62;
|
||||
margin-top: .15rem;
|
||||
color: var(--ph-text-muted);
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.accent-blue {
|
||||
background: #2473a6;
|
||||
}
|
||||
|
||||
.accent-green {
|
||||
background: #138f77;
|
||||
}
|
||||
|
||||
.accent-indigo {
|
||||
background: #34495e;
|
||||
}
|
||||
|
||||
.accent-orange {
|
||||
background: #da8c10;
|
||||
}
|
||||
|
||||
.accent-teal {
|
||||
background: #2c8f9e;
|
||||
}
|
||||
|
||||
.accent-violet {
|
||||
background: #7f3d9b;
|
||||
}
|
||||
|
||||
.accent-red {
|
||||
background: #cf4435;
|
||||
}
|
||||
.accent-blue { --accent-color: #5288c7; }
|
||||
.accent-green { --accent-color: #49a38b; }
|
||||
.accent-indigo { --accent-color: #7186ab; }
|
||||
.accent-orange { --accent-color: #d59a34; }
|
||||
.accent-teal { --accent-color: #55a0b0; }
|
||||
.accent-violet { --accent-color: #8d62b5; }
|
||||
.accent-red { --accent-color: #cc604e; }
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.quick-access-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.management-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.home-content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.quick-access-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.overview-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.home-hero {
|
||||
flex-direction: column;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.home-hero h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.home-hero-status {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.kpi-grid,
|
||||
.quick-access-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.home-dashboard { gap: 2.5rem; }
|
||||
.home-intro,
|
||||
.section-heading { align-items: flex-start; flex-direction: column; gap: .8rem; }
|
||||
.section-heading > p { max-width: none; text-align: left; }
|
||||
.featured-grid,
|
||||
.management-grid { grid-template-columns: 1fr; }
|
||||
.featured-card { min-height: 15rem; }
|
||||
.management-card { min-height: 8.5rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 479.98px) {
|
||||
.platform-status { align-self: flex-start; }
|
||||
.metrics-panel { grid-template-columns: 1fr; }
|
||||
.metric-item:nth-child(even) { border-left: 0; }
|
||||
.metric-item:nth-child(n+2) { border-top: 1px solid var(--ph-border); }
|
||||
.feature-tag { max-width: 9.5rem; text-align: center; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.featured-card,
|
||||
.management-card,
|
||||
.card-action i,
|
||||
.management-arrow { transition: none; }
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
@using System.Security.Claims
|
||||
<AuthorizeView>
|
||||
<Authorized Context="authContext">
|
||||
<div class="d-flex justify-content-end align-items-center gap-3 px-3 py-2">
|
||||
<span class="text-dark">
|
||||
Bienvenido, @GetUserDisplayName(authContext.User)
|
||||
<div class="login-state">
|
||||
<span class="user-avatar" aria-hidden="true">@GetInitial(authContext.User)</span>
|
||||
<span class="user-copy">
|
||||
<small>Sesión activa</small>
|
||||
<strong>@GetUserDisplayName(authContext.User)</strong>
|
||||
</span>
|
||||
<a href="Logout" class="btn btn-sm btn-danger rounded-3 d-flex align-items-center gap-1 text-decoration-none">
|
||||
<a href="Logout" class="logout-button" title="Cerrar sesión" aria-label="Cerrar sesión">
|
||||
<i class="oi oi-account-logout"></i>
|
||||
<span>Cerrar sesión</span>
|
||||
</a>
|
||||
</div>
|
||||
</Authorized>
|
||||
@ -19,4 +20,9 @@
|
||||
?? user.Identity?.Name
|
||||
?? "Usuario";
|
||||
}
|
||||
private string GetInitial(ClaimsPrincipal user)
|
||||
{
|
||||
var displayName = GetUserDisplayName(user).Trim();
|
||||
return displayName.Length > 0 ? displayName[..1].ToUpperInvariant() : "U";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,8 @@
|
||||
/* .btn-logout {
|
||||
text-decoration: none !important;
|
||||
margin-left: 20px;
|
||||
padding: 8px 8px 8px 8px;
|
||||
background-image: linear-gradient(90deg, rgb(169, 15, 144) 0%, rgb(23, 25, 85) 90%);
|
||||
border-radius: 4px 12px 12px 12px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
} */
|
||||
.btn-logout {
|
||||
text-decoration: none !important;
|
||||
background-color: rgb(39, 54, 97); /* Azul oscuro (igual al fondo) */
|
||||
color: white;
|
||||
padding: 8px 8px 8px 8px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2); /* Borde sutil */
|
||||
border-radius: 0.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.login-state { display: flex; min-width: 0; align-items: center; gap: .65rem; }
|
||||
.user-avatar { display: inline-flex; width: 2.2rem; height: 2.2rem; flex: 0 0 2.2rem; align-items: center; justify-content: center; color: #fff; background: #3157a4; border-radius: 50%; font-size: .78rem; font-weight: 750; }
|
||||
.user-copy { display: flex; min-width: 0; flex-direction: column; line-height: 1.15; text-align: left; }
|
||||
.user-copy small { color: var(--ph-text-muted); font-size: .66rem; }
|
||||
.user-copy strong { max-width: 13rem; overflow: hidden; color: var(--ph-text); font-size: .82rem; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.logout-button { display: inline-flex; width: 2.2rem; height: 2.2rem; align-items: center; justify-content: center; color: var(--ph-text-muted); border: 1px solid var(--ph-border); border-radius: .55rem; text-decoration: none; transition: color .15s ease, background-color .15s ease, border-color .15s ease; }
|
||||
.logout-button:hover { color: var(--ph-danger); background: color-mix(in srgb, var(--ph-danger) 8%, transparent); border-color: color-mix(in srgb, var(--ph-danger) 35%, var(--ph-border)); }
|
||||
@media (max-width: 575.98px) { .user-copy { display: none; } }
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
@if( Data is not null && Data.Any())
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="ph-table-toolbar">
|
||||
<div class="ph-search-area">
|
||||
<!-- TITULO TABLA-->
|
||||
<div class="col-md-10">
|
||||
@if (ShowQuickSearch)
|
||||
{
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<InputText type="text" class="form-control" placeholder="Búsqueda rápida..." @bind-Value="SearchTerm"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (ShowQuickSearch)
|
||||
{
|
||||
<label for="ph-quick-search" class="visually-hidden">Búsqueda rápida</label>
|
||||
<div class="ph-search-input">
|
||||
<i class="fas fa-magnifying-glass" aria-hidden="true"></i>
|
||||
<InputText id="ph-quick-search" type="search" class="form-control" placeholder="Buscar en la tabla…" @bind-Value="SearchTerm"/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- CONTROLES DE PAGINACION-->
|
||||
|
||||
<div class="col-md-2">
|
||||
<div class="ph-pagination-area">
|
||||
@if (ShowPageButtons)
|
||||
{
|
||||
<nav aria-label="Page Navigation">
|
||||
<nav aria-label="Paginación de resultados">
|
||||
<ul class="pagination justify-content-end">
|
||||
<li class="page-item @(CurrentPage == 1 ? "disabled" : "")">
|
||||
<a class="page-link" href="#" tabindex="-1" @onclick:preventDefault @onclick="GoToPreviousPage">Anterior</a>
|
||||
@ -71,10 +70,10 @@
|
||||
</nav>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- RENDERIZACION DE TABLA-->
|
||||
<table class="table table-sm table-hover">
|
||||
<div class="ph-table-scroll">
|
||||
<table class="table table-hover">
|
||||
<!-- RENDERIZAR ENCABEZADOS-->
|
||||
<thead>
|
||||
<tr>
|
||||
@ -90,7 +89,7 @@
|
||||
{
|
||||
if (!column.Contains("Hide:"))
|
||||
{
|
||||
<th @onclick="()=>SortbyColumn(column)" style="cursor:pointer;user-select:none;" class="ph-header">
|
||||
<th @onclick="()=>SortbyColumn(column)" class="ph-header @(SorteableCols.Contains(column) ? "sortable" : string.Empty)">
|
||||
@column
|
||||
@if (column==SortedColumn)
|
||||
{
|
||||
@ -102,7 +101,7 @@
|
||||
<!-- RENDERIZAR ENCABEZADO DE COLUMNA TIPO BUTTON-->
|
||||
@if (RenderButtons)
|
||||
{
|
||||
<th style="cursor:default; user-select:none;" class="ph-header">Acciones</th>
|
||||
<th class="ph-header actions-header">Acciones</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
@ -115,7 +114,7 @@
|
||||
<!-- RENDERIZAR COLUMNA DE SELECCION-->
|
||||
@if (RenderSelect)
|
||||
{
|
||||
<td>
|
||||
<td class="ph-actions">
|
||||
@{
|
||||
string rowId = string.Empty;
|
||||
if (item.TryGetValue(SelectionField, out var idObj) && idObj is not null)
|
||||
@ -199,6 +198,15 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="empty-state ph-empty-state">
|
||||
<i class="fas fa-inbox" aria-hidden="true"></i>
|
||||
<strong>Sin resultados</strong>
|
||||
<span>No hay información disponible para mostrar.</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -404,4 +412,4 @@
|
||||
StateHasChanged(); // Forzar la actualización de la interfaz de usuario
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
ph-header {
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
/*.ph-container {
|
||||
overflow: auto;
|
||||
height: 460px;
|
||||
}
|
||||
.ph-container thead th {
|
||||
position:sticky;
|
||||
top:0;
|
||||
z-index:1;
|
||||
}*/
|
||||
td, th {
|
||||
padding: 5px
|
||||
}
|
||||
.ph-table-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .85rem; }
|
||||
.ph-search-area { width: min(100%, 27rem); }
|
||||
.ph-search-input { position: relative; }
|
||||
.ph-search-input > i { position: absolute; top: 50%; left: .9rem; z-index: 1; color: var(--ph-text-muted); font-size: .8rem; transform: translateY(-50%); pointer-events: none; }
|
||||
.ph-search-input ::deep input { padding-left: 2.45rem; }
|
||||
.ph-pagination-area { margin-left: auto; }
|
||||
.ph-header.sortable { cursor: pointer; user-select: none; }
|
||||
.ph-header.sortable:hover { color: var(--ph-primary); background: var(--ph-primary-soft); }
|
||||
.actions-header { cursor: default; user-select: none; }
|
||||
.ph-actions { display: flex; flex-wrap: wrap; gap: .4rem; min-width: 7rem; }
|
||||
.ph-actions ::deep .btn { margin: 0 !important; }
|
||||
.ph-empty-state { display: flex; flex-direction: column; align-items: center; gap: .35rem; }
|
||||
.ph-empty-state i { margin-bottom: .25rem; color: var(--ph-primary); font-size: 1.5rem; }
|
||||
.ph-empty-state strong { color: var(--ph-text); }
|
||||
.ph-empty-state span { font-size: .84rem; }
|
||||
@media (max-width: 767.98px) { .ph-table-toolbar { align-items: stretch; flex-direction: column; } .ph-search-area { width: 100%; } .ph-pagination-area { max-width: 100%; margin-left: 0; overflow-x: auto; } }
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
|
||||
<div class="toggle-container">
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="switch" @bind=ToggleState />
|
||||
<label class="switch" title="Cambiar tema visual">
|
||||
<input type="checkbox" class="switch" aria-label="Activar tema oscuro" @bind=ToggleState />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
@if (!string.IsNullOrEmpty(Caption))
|
||||
{
|
||||
<span class="toggle-label">@Caption</span>
|
||||
}
|
||||
<label style="margin:10px;">
|
||||
<span class="toggle-icon">
|
||||
@ChildContent
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
@code {
|
||||
[Parameter]
|
||||
|
||||
@ -1,74 +1,11 @@
|
||||
/* Begin Toggle Switch*/
|
||||
.toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.toggle-label {
|
||||
margin-left: 15px; /* Ajusta el espaciado según sea necesario */
|
||||
font-size: 130%;
|
||||
}
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 70px;
|
||||
height: 34px;
|
||||
}
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #6610f2;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #6610f2;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(36px);
|
||||
-ms-transform: translateX(36px);
|
||||
transform: translateX(36px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
margin-left: 3px; /* Ajusta el espaciado según sea necesario */
|
||||
}
|
||||
/*End Toggle Switch*/
|
||||
.toggle-container { display: flex; align-items: center; gap: .5rem; color: var(--ph-text-muted); }
|
||||
.switch { position: relative; display: inline-block; width: 2.45rem; height: 1.4rem; margin: 0; }
|
||||
.switch input { width: 0; height: 0; opacity: 0; }
|
||||
.slider { position: absolute; inset: 0; cursor: pointer; background: var(--ph-border-strong); border-radius: 999px; transition: background-color .2s ease; }
|
||||
.slider::before { position: absolute; bottom: .2rem; left: .2rem; width: 1rem; height: 1rem; content: ""; background: #fff; border-radius: 50%; box-shadow: 0 1px 3px rgba(0, 0, 0, .25); transition: transform .2s ease; }
|
||||
input:checked + .slider { background: #3157a4; }
|
||||
input:focus-visible + .slider { box-shadow: 0 0 0 .22rem var(--ph-focus); }
|
||||
input:checked + .slider::before { transform: translateX(1.05rem); }
|
||||
.toggle-label { color: var(--ph-text-muted); font-size: .76rem; font-weight: 600; white-space: nowrap; }
|
||||
.toggle-icon { display: inline-flex; width: 1rem; justify-content: center; font-size: .85rem; }
|
||||
@media (max-width: 575.98px) { .toggle-label { display: none; } }
|
||||
|
||||
@ -1,181 +1,128 @@
|
||||
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
|
||||
|
||||
:root {
|
||||
--background: white;
|
||||
--text-background: black;
|
||||
--background-highlight: #f7f7f7;
|
||||
--background-highlight-light: #e2e3e5;
|
||||
--link-color: #0366d6;
|
||||
color-scheme: light;
|
||||
--ph-primary: #3157a4; --ph-primary-hover: #274887; --ph-primary-soft: #e9effb;
|
||||
--ph-accent: #159b8c; --ph-danger: #c33d4a; --ph-warning: #b86f0b; --ph-success: #18785d; --ph-info: #2476a8;
|
||||
--ph-canvas: #f4f6fa; --ph-surface: #fff; --ph-surface-subtle: #f8fafc; --ph-surface-raised: #fff;
|
||||
--ph-border: #dfe5ee; --ph-border-strong: #c8d1df; --ph-text: #1d2939; --ph-text-muted: #667085;
|
||||
--ph-sidebar: #202f55; --ph-sidebar-deep: #182442; --ph-sidebar-text: #dce4f3; --ph-sidebar-muted: #9facc4;
|
||||
--ph-focus: rgba(49, 87, 164, .22); --ph-shadow-sm: 0 1px 2px rgba(16, 24, 40, .05); --ph-shadow-md: 0 10px 30px rgba(16, 24, 40, .08);
|
||||
--ph-radius-sm: .5rem; --ph-radius: .75rem; --ph-radius-lg: 1rem;
|
||||
--background: var(--ph-surface); --text-background: var(--ph-text); --background-highlight: var(--ph-surface-subtle);
|
||||
--background-highlight-light: var(--ph-border); --link-color: var(--ph-primary);
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
--background: #1C1B1F;
|
||||
--text-background: white;
|
||||
--background-highlight: #202124;
|
||||
--background-highlight-light: #3C4043;
|
||||
--link-color: #3ca4ff;
|
||||
}
|
||||
.nav-flex-column {
|
||||
list-style-type: square;
|
||||
padding-left: 20px; /* Alineación adecuada para submenús */
|
||||
color-scheme: dark;
|
||||
--ph-primary: #8daef2; --ph-primary-hover: #abc2f3; --ph-primary-soft: #263a63;
|
||||
--ph-accent: #52c7b8; --ph-danger: #f27a84; --ph-warning: #f1b85c; --ph-success: #63c5a5; --ph-info: #74b9e4;
|
||||
--ph-canvas: #101725; --ph-surface: #182131; --ph-surface-subtle: #1d283a; --ph-surface-raised: #202c40;
|
||||
--ph-border: #303d52; --ph-border-strong: #435068; --ph-text: #edf2f8; --ph-text-muted: #aab6c8;
|
||||
--ph-sidebar: #121b2e; --ph-sidebar-deep: #0e1626; --ph-sidebar-text: #e4eaf4; --ph-sidebar-muted: #91a0ba;
|
||||
--ph-focus: rgba(141, 174, 242, .28); --ph-shadow-sm: 0 1px 2px rgba(0, 0, 0, .25); --ph-shadow-md: 0 12px 32px rgba(0, 0, 0, .28);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body { min-height: 100%; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--ph-canvas); color: var(--ph-text); -webkit-font-smoothing: antialiased; }
|
||||
body { margin: 0; }
|
||||
a, .btn-link { color: var(--link-color); }
|
||||
a { text-underline-offset: .18em; }
|
||||
h1, h2, h3, h4, h5, h6 { color: var(--ph-text); letter-spacing: -.018em; }
|
||||
h1:focus { outline: none; }
|
||||
p { color: var(--ph-text-muted); }
|
||||
main { min-width: 0; background: var(--ph-canvas); color: var(--ph-text); }
|
||||
::selection { background: var(--ph-primary-soft); color: var(--ph-text); }
|
||||
.content { padding-top: 1.5rem; padding-bottom: 2.5rem; }
|
||||
.content > * { max-width: 1600px; margin-inline: auto; }
|
||||
|
||||
.nav-link label {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.card { overflow: hidden; color: var(--ph-text); background: var(--ph-surface); border: 1px solid var(--ph-border); border-radius: var(--ph-radius-lg); box-shadow: var(--ph-shadow-sm); }
|
||||
.card-header, .card-footer { min-height: 4rem; padding: 1rem 1.25rem; color: var(--ph-text); background: var(--ph-surface); border-color: var(--ph-border); }
|
||||
.card-header { border-bottom: 1px solid var(--ph-border); }
|
||||
.card-footer { border-top: 1px solid var(--ph-border); }
|
||||
.card-body { padding: 1.25rem; color: var(--ph-text); background: var(--ph-surface); }
|
||||
.card-title { font-size: 1.15rem; font-weight: 700; }
|
||||
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.btn { min-height: 2.5rem; padding: .48rem .9rem; border-radius: var(--ph-radius-sm); font-weight: 600; box-shadow: none; transition: transform .15s ease, box-shadow .15s ease, background-color .15s ease, border-color .15s ease; }
|
||||
.btn:hover:not(:disabled) { transform: translateY(-1px); }
|
||||
.btn:active:not(:disabled) { transform: translateY(0); }
|
||||
.btn-sm { min-height: 2.1rem; padding: .35rem .7rem; }
|
||||
.btn-primary { color: #fff; background: #3157a4; border-color: #3157a4; }
|
||||
.btn-primary:hover, .btn-primary:focus { color: #fff; background: #274887; border-color: #274887; }
|
||||
.btn-success { color: #fff; background: #18785d; border-color: #18785d; }
|
||||
.btn-danger { color: #fff; background: #b93845; border-color: #b93845; }
|
||||
.btn-warning { color: #2b1a02; background: #f0b24d; border-color: #f0b24d; }
|
||||
.btn-secondary { color: var(--ph-text); background: var(--ph-surface-subtle); border-color: var(--ph-border-strong); }
|
||||
.btn-secondary:hover { color: var(--ph-text); background: var(--ph-border); border-color: var(--ph-border-strong); }
|
||||
.btn-outline-primary { color: var(--ph-primary); border-color: var(--ph-primary); }
|
||||
.btn-outline-primary:hover { color: #fff; background: #3157a4; border-color: #3157a4; }
|
||||
.btn.rounded-pill { border-radius: var(--ph-radius-sm) !important; }
|
||||
.btn:disabled { opacity: .58; cursor: not-allowed; }
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
.form-label, label { margin-bottom: .42rem; color: var(--ph-text); font-size: .875rem; font-weight: 600; }
|
||||
.form-control, .form-select, select, textarea, input:not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]) { min-height: 2.65rem; color: var(--ph-text); background-color: var(--ph-surface); border: 1px solid var(--ph-border-strong); border-radius: var(--ph-radius-sm); box-shadow: none; }
|
||||
.form-control::placeholder, textarea::placeholder, input::placeholder { color: var(--ph-text-muted); opacity: .78; }
|
||||
.form-control:focus, .form-select:focus, select:focus, textarea:focus, input:focus { color: var(--ph-text); background-color: var(--ph-surface); border-color: var(--ph-primary); outline: 0; box-shadow: 0 0 0 .22rem var(--ph-focus); }
|
||||
.form-control:disabled, .form-select:disabled, input:disabled, select:disabled, textarea:disabled { color: var(--ph-text-muted); background: var(--ph-surface-subtle); }
|
||||
.form-check-input { background-color: var(--ph-surface); border-color: var(--ph-border-strong); }
|
||||
.form-check-input:checked { background-color: #3157a4; border-color: #3157a4; }
|
||||
.row + .row { margin-top: 1rem; }
|
||||
.row > [class*="col-"] > label:first-child { display: inline-block; }
|
||||
hr { margin: 1.5rem 0; border-color: var(--ph-border); opacity: 1; }
|
||||
.valid.modified:not([type=checkbox]) { border-color: var(--ph-success); outline: none; }
|
||||
.invalid { border-color: var(--ph-danger) !important; outline: none; }
|
||||
.validation-message { display: block; margin-top: .3rem; color: var(--ph-danger); font-size: .82rem; }
|
||||
.validation-errors { margin: 0 0 1rem; padding: .85rem 1rem .85rem 2rem; color: var(--ph-danger); background: color-mix(in srgb, var(--ph-danger) 9%, transparent); border: 1px solid color-mix(in srgb, var(--ph-danger) 28%, transparent); border-radius: var(--ph-radius-sm); }
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.table-responsive, .ph-table-scroll { overflow-x: auto; border: 1px solid var(--ph-border); border-radius: var(--ph-radius); }
|
||||
.table { margin-bottom: 0; color: var(--ph-text); background: var(--ph-surface); border-color: var(--ph-border); vertical-align: middle; }
|
||||
.table > :not(caption) > * > * { padding: .78rem .85rem; color: inherit; background: transparent; border-bottom-color: var(--ph-border); box-shadow: none; }
|
||||
.table thead th { color: var(--ph-text-muted); background: var(--ph-surface-subtle); font-size: .75rem; font-weight: 700; letter-spacing: .035em; text-transform: uppercase; white-space: nowrap; }
|
||||
.table tbody tr { transition: background-color .12s ease; }
|
||||
.table-hover > tbody > tr:hover > * { --bs-table-accent-bg: transparent; color: var(--ph-text); background: var(--ph-primary-soft); }
|
||||
.table-bordered > :not(caption) > * { border-color: var(--ph-border); }
|
||||
.pagination { flex-wrap: wrap; gap: .25rem; margin-bottom: 0; }
|
||||
.page-link { min-width: 2.2rem; color: var(--ph-text-muted); background: var(--ph-surface); border: 1px solid var(--ph-border); border-radius: .45rem !important; text-align: center; }
|
||||
.page-link:hover { color: var(--ph-primary); background: var(--ph-primary-soft); border-color: var(--ph-primary); }
|
||||
.page-item.active .page-link { color: #fff; background: #3157a4; border-color: #3157a4; }
|
||||
.page-item.disabled .page-link { color: var(--ph-text-muted); background: var(--ph-surface-subtle); border-color: var(--ph-border); opacity: .55; }
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
a, .btn-link {
|
||||
/*color: #0071c1;*/
|
||||
color: var(--link-color);
|
||||
}
|
||||
.alert-secondary {
|
||||
background-color: var(--background-highlight-light);
|
||||
color: var(--text-background);
|
||||
}
|
||||
.table {
|
||||
color: var(--text-background);
|
||||
background-color: var(--background);
|
||||
}
|
||||
main {
|
||||
background-color: var(--background);
|
||||
color: var(--text-background);
|
||||
}
|
||||
.card-header {
|
||||
background-color: var(--background-highlight-light);
|
||||
color: var(--text-background);
|
||||
}
|
||||
.badge { padding: .42em .65em; border-radius: 999px; font-weight: 650; }
|
||||
.alert { color: var(--ph-text); background: var(--ph-surface-subtle); border-color: var(--ph-border); border-radius: var(--ph-radius); }
|
||||
.alert-secondary { color: var(--ph-text); background: var(--ph-surface-subtle); }
|
||||
.modal-content, .blazored-modal { color: var(--ph-text); background: var(--ph-surface-raised) !important; border: 1px solid var(--ph-border) !important; border-radius: var(--ph-radius-lg) !important; box-shadow: var(--ph-shadow-md) !important; }
|
||||
.modal-header, .modal-footer { border-color: var(--ph-border); }
|
||||
.modal-body { padding: 1.35rem; }
|
||||
.modal-footer { gap: .5rem; padding: 1rem 1.35rem; background: var(--ph-surface-subtle); }
|
||||
.blazored-modal-overlay { background-color: rgba(8, 15, 30, .62) !important; backdrop-filter: blur(2px); }
|
||||
.blazored-toast { color: var(--ph-text) !important; background: var(--ph-surface-raised) !important; border: 1px solid var(--ph-border) !important; border-radius: var(--ph-radius) !important; box-shadow: var(--ph-shadow-md) !important; }
|
||||
.blazored-toast-heading { color: var(--ph-text) !important; }
|
||||
.blazored-toast-message { color: var(--ph-text-muted) !important; }
|
||||
.empty-state { padding: 2.5rem 1rem; color: var(--ph-text-muted); text-align: center; background: var(--ph-surface-subtle); border: 1px dashed var(--ph-border-strong); border-radius: var(--ph-radius); }
|
||||
.spinner-border { color: var(--ph-primary); }
|
||||
|
||||
.card-body {
|
||||
background-color: var(--background-highlight);
|
||||
color: var(--text-background);
|
||||
#blazor-error-ui { position: fixed; right: 1rem; bottom: 1rem; left: 1rem; z-index: 2000; display: none; max-width: 50rem; margin-inline: auto; padding: 1rem 3rem 1rem 1rem; color: #fff; background: #8f2833; border-radius: var(--ph-radius); box-shadow: var(--ph-shadow-md); }
|
||||
#blazor-error-ui a { color: #fff; }
|
||||
#blazor-error-ui .dismiss { position: absolute; top: .8rem; right: 1rem; cursor: pointer; }
|
||||
.blazor-error-boundary { padding: 1rem; color: #fff; background: #8f2833; border-radius: var(--ph-radius); }
|
||||
.blazor-error-boundary::after { content: "Ocurrió un error inesperado."; }
|
||||
.loading-progress { position: relative; display: block; width: 5.5rem; height: 5.5rem; margin: 28vh auto 1rem; }
|
||||
.loading-progress circle { fill: none; stroke: var(--ph-border); stroke-width: .48rem; transform: rotate(-90deg); transform-origin: 50% 50%; }
|
||||
.loading-progress circle:last-child { stroke: #3157a4; stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * .8), 500%; transition: stroke-dasharray .05s ease-in-out; }
|
||||
.loading-progress-text { position: absolute; inset: calc(28vh + 2.15rem) 0 auto; color: var(--ph-text-muted); font-size: .8rem; font-weight: 700; text-align: center; }
|
||||
.loading-progress-text::after { content: var(--blazor-load-percentage-text, "Cargando"); }
|
||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--ph-text-muted) 35%, transparent); border: 2px solid transparent; border-radius: 999px; background-clip: content-box; }
|
||||
::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--ph-text-muted) 60%, transparent); background-clip: content-box; }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.content { padding: 1rem .85rem 2rem !important; }
|
||||
.card-header, .card-body, .card-footer { padding: 1rem; }
|
||||
.card-header { min-height: auto; }
|
||||
.card-header.d-flex, .card-footer .d-flex, .card-body > .d-flex { flex-wrap: wrap; gap: .65rem; }
|
||||
.card-body .row { row-gap: 1rem; }
|
||||
.table { min-width: 680px; }
|
||||
.btn { max-width: 100%; }
|
||||
}
|
||||
.card-footer {
|
||||
background-color: var(--background-highlight-light);
|
||||
color: var(--text-background);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin: 20vh auto 1rem auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
/* Cambiar el color del scrollbar con transparencia */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: rgba(241, 241, 241, 0); /* Fondo semi transparente */
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(136, 136, 136, 0.2); /* Pulgar semi transparente */
|
||||
border-radius: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(100, 100, 100, 0.5); /* Cambio al pasar el mouse semi transparente */
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; } }
|
||||
|
||||
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
@ -1,17 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>phronCare.UIBlazor</title>
|
||||
<title>PhronCare · SaludLAB</title>
|
||||
<base href="/" />
|
||||
|
||||
<!-- Estilos propios -->
|
||||
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="css/fontawesome-free-6.4.2-web/css/all.min.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="icon" type="image/png" href="images/SaludLAB.png" />
|
||||
<link rel="apple-touch-icon" href="images/SaludLAB.png" />
|
||||
<link href="phronCare.UIBlazor.styles.css" rel="stylesheet" />
|
||||
<!-- Leaflet CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
|
||||
@ -41,14 +42,13 @@
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
Ocurrió un error inesperado.
|
||||
<a href="" class="reload">Recargar</a>
|
||||
<a class="dismiss" aria-label="Cerrar">×</a>
|
||||
</div>
|
||||
|
||||
<!-- Blazor y JS propios -->
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="css/fontawesome-free-6.4.2-web/js/all.min.js"></script>
|
||||
<script src="js/services.js"></script>
|
||||
|
||||
|
||||
@ -57,4 +57,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user