From 2b4678c26db1c9c314db070ffad8895b0c5d61a8 Mon Sep 17 00:00:00 2001 From: Pedro Date: Thu, 30 Jul 2026 12:35:50 +0200 Subject: [PATCH] =?UTF-8?q?-=20Sustituidas=20las=20confirmaciones=20gen?= =?UTF-8?q?=C3=A9ricas=20del=20navegador=20por=20una=20ventana=20propia,?= =?UTF-8?q?=20responsive=20y=20accesible.=20-=20Adaptados=20los=20avisos?= =?UTF-8?q?=20al=20importar,=20tramitar,=20rechazar=20y=20cambiar=20la=20a?= =?UTF-8?q?signaci=C3=B3n=20de=20denuncias=20de=20otros=20usuarios=20o=20g?= =?UTF-8?q?rupos.=20-=20A=C3=B1adidos=20textos=20y=20botones=20espec=C3=AD?= =?UTF-8?q?ficos=20para=20cada=20operaci=C3=B3n.=20-=20Actualizadas=20las?= =?UTF-8?q?=20instrucciones,=20sustituyendo=20=E2=80=9Cmodal=E2=80=9D=20po?= =?UTF-8?q?r=20=E2=80=9Cventana=20de=20configuraci=C3=B3n=20de=20la=20subi?= =?UTF-8?q?da=E2=80=9D.=20-=20Aclarado=20el=20funcionamiento=20del=20asunt?= =?UTF-8?q?o,=20grupo=20de=20destino=20y=20tercero.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/AppConfirmationDialog.razor | 110 ++++++++++++ .../AppConfirmationDialog.razor.css | 163 ++++++++++++++++++ .../Components/Layout/MainLayout.razor | 1 + .../Components/Pages/Actualizaciones.razor | 18 +- .../Components/Pages/Entrada.razor | 8 +- .../Components/Pages/Instrucciones.razor | 10 +- .../Components/Pages/Pendientes.razor | 22 ++- Antifraude.Net/GestionaDenunciasAN/Program.cs | 1 + .../Services/UiDialogService.cs | 64 +++++++ 9 files changed, 373 insertions(+), 24 deletions(-) create mode 100644 Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor create mode 100644 Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor.css create mode 100644 Antifraude.Net/GestionaDenunciasAN/Services/UiDialogService.cs diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor b/Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor new file mode 100644 index 0000000..0acdb0f --- /dev/null +++ b/Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor @@ -0,0 +1,110 @@ +@implements IDisposable +@inject UiDialogService Dialog + +@if (Dialog.IsVisible) +{ + +} + +@code { + private ElementReference _cancelButton; + private bool _focusPending; + + private string DialogCss => + $"app-confirmation app-confirmation--{ToneName}"; + + private string IconCss => + $"app-confirmation__icon app-confirmation__icon--{ToneName}"; + + private string ConfirmButtonCss => + Dialog.Tone == UiDialogTone.Danger + ? "btn app-confirmation__button app-confirmation__button--danger" + : "btn app-confirmation__button app-confirmation__button--primary"; + + private string ToneName => Dialog.Tone switch + { + UiDialogTone.Danger => "danger", + UiDialogTone.Information => "information", + _ => "warning" + }; + + private string Eyebrow => Dialog.Tone switch + { + UiDialogTone.Danger => "Acción irreversible", + UiDialogTone.Information => "Confirmación", + _ => "Revisa antes de continuar" + }; + + protected override void OnInitialized() + { + Dialog.Changed += HandleDialogChanged; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!_focusPending || !Dialog.IsVisible) + { + return; + } + + _focusPending = false; + await _cancelButton.FocusAsync(); + } + + public void Dispose() + { + Dialog.Changed -= HandleDialogChanged; + } + + private void HandleDialogChanged() + { + _focusPending = Dialog.IsVisible; + _ = InvokeAsync(StateHasChanged); + } + + private void HandleKeyDown(KeyboardEventArgs args) + { + if (string.Equals(args.Key, "Escape", StringComparison.Ordinal)) + { + Dialog.Cancel(); + } + } +} diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor.css b/Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor.css new file mode 100644 index 0000000..67f6b8d --- /dev/null +++ b/Antifraude.Net/GestionaDenunciasAN/Components/AppConfirmationDialog.razor.css @@ -0,0 +1,163 @@ +.app-confirmation-backdrop { + position: fixed; + inset: 0; + z-index: 5100; + display: grid; + place-items: center; + padding: 1.25rem; + background: rgba(6, 22, 41, 0.64); + backdrop-filter: blur(5px); + overscroll-behavior: contain; +} + +.app-confirmation { + position: relative; + overflow: hidden; + width: min(560px, 100%); + border: 1px solid rgba(255, 255, 255, 0.65); + border-radius: 8px; + background: #fff; + box-shadow: 0 28px 80px rgba(5, 27, 54, 0.34); + color: #12395f; +} + +.app-confirmation__accent { + height: 0.35rem; + background: #d49620; +} + +.app-confirmation--danger .app-confirmation__accent { + background: #c93f4f; +} + +.app-confirmation--information .app-confirmation__accent { + background: #2a5caa; +} + +.app-confirmation__content { + display: grid; + grid-template-columns: 3.25rem minmax(0, 1fr); + gap: 1rem; + padding: 1.5rem 1.5rem 1.1rem; +} + +.app-confirmation__icon { + display: grid; + place-items: center; + width: 3.25rem; + height: 3.25rem; + border: 1px solid #efd8a8; + border-radius: 50%; + background: #fff6df; + color: #8d5e08; + font-size: 1.45rem; + font-weight: 800; +} + +.app-confirmation__icon--danger { + border-color: #efc2c8; + background: #fff0f2; + color: #a82d3b; +} + +.app-confirmation__icon--information { + border-color: #c4d6ef; + background: #eef5ff; + color: #214f91; +} + +.app-confirmation__copy { + min-width: 0; +} + +.app-confirmation__eyebrow { + margin-bottom: 0.3rem; + color: #6a7786; + font-size: 0.74rem; + font-weight: 800; + letter-spacing: 0; + text-transform: uppercase; +} + +.app-confirmation__title { + margin: 0; + color: #0a315c; + font-size: 1.35rem; + font-weight: 800; + line-height: 1.25; +} + +.app-confirmation__message { + margin: 0.65rem 0 0; + color: #405f7d; + line-height: 1.55; + white-space: pre-line; +} + +.app-confirmation__actions { + display: flex; + justify-content: flex-end; + gap: 0.65rem; + padding: 1rem 1.5rem 1.35rem; + border-top: 1px solid #e4ebf2; + background: #f8fafc; +} + +.app-confirmation__button { + min-width: 8.5rem; + min-height: 2.7rem; + border-radius: 6px; +} + +.app-confirmation__button--primary { + border-color: #24539a; + background: #24539a; + color: #fff; +} + +.app-confirmation__button--primary:hover, +.app-confirmation__button--primary:focus-visible { + border-color: #193f79; + background: #193f79; + color: #fff; +} + +.app-confirmation__button--danger { + border-color: #b93343; + background: #b93343; + color: #fff; +} + +.app-confirmation__button--danger:hover, +.app-confirmation__button--danger:focus-visible { + border-color: #922936; + background: #922936; + color: #fff; +} + +@media (max-width: 575.98px) { + .app-confirmation-backdrop { + align-items: end; + padding: 0.75rem; + } + + .app-confirmation__content { + grid-template-columns: 2.75rem minmax(0, 1fr); + gap: 0.8rem; + padding: 1.2rem 1rem 1rem; + } + + .app-confirmation__icon { + width: 2.75rem; + height: 2.75rem; + } + + .app-confirmation__actions { + flex-direction: column-reverse; + padding: 0.9rem 1rem 1rem; + } + + .app-confirmation__button { + width: 100%; + } +} diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Layout/MainLayout.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Layout/MainLayout.razor index 1738cf9..b2a751b 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Layout/MainLayout.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Layout/MainLayout.razor @@ -52,6 +52,7 @@ +
An unhandled error has occurred. diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor index df36bd8..d802cc4 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor @@ -16,7 +16,7 @@ @inject IDenunciaStore DenunciaStore @inject ApiDenunciasClient ApiDenuncias @inject UiBusyService Busy -@inject IJSRuntime JSRuntime +@inject UiDialogService Dialogs Actualizaciones @@ -720,10 +720,11 @@ else { if (d.RequiresOwnerConfirmation) { - var confirmed = await JSRuntime.InvokeAsync( - "confirm", - $"La denuncia #{d.Id_Denuncia} es propiedad de {d.OwnerUsername}. " + - "Puedes actualizarla porque pertenece a un usuario de tu grupo. Deseas continuar?"); + var confirmed = await Dialogs.ConfirmAsync( + "Denuncia asignada a otro usuario", + $"La denuncia #{d.Id_Denuncia} está asignada a {d.OwnerUsername}. " + + "Puedes actualizarla porque pertenece a un usuario de tu grupo. La acción quedará registrada con tu usuario.", + "Configurar actualización"); if (!confirmed) { return; @@ -1401,10 +1402,11 @@ else var configuredGroups = currentGroups.GroupCodes.Count == 0 ? "ningún grupo" : string.Join(", ", currentGroups.GroupCodes); - return await JSRuntime.InvokeAsync( - "confirm", + return await Dialogs.ConfirmAsync( + "Cambio de asignación en Gestiona", $"El grupo de destino {selectedGroup} no pertenece a tus grupos configurados ({configuredGroups}). " + - "Si continúas, la denuncia cambiará su asignación en Gestiona a un grupo distinto de los tuyos. ¿Deseas continuar?"); + "Si continúas, la denuncia cambiará su asignación en Gestiona a un grupo distinto de los tuyos.", + "Cambiar asignación"); } catch (Exception ex) { diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor index 8cd7cd0..11be562 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Entrada.razor @@ -9,6 +9,7 @@ @inject ApiDenunciasClient ApiDenuncias @inject IJSRuntime JSRuntime @inject UiBusyService Busy +@inject UiDialogService Dialogs Entrada de denuncias @@ -732,9 +733,10 @@ Environment.NewLine, reportsFromOtherOwners.Select(report => $"Denuncia #{report.Progressive ?? 0}: propiedad de {report.OwnerUsername}.")); - var confirmed = await JSRuntime.InvokeAsync( - "confirm", - $"Vas a importar denuncias propiedad de otros usuarios de tu grupo:{Environment.NewLine}{Environment.NewLine}{ownerSummary}{Environment.NewLine}{Environment.NewLine}¿Deseas continuar?"); + var confirmed = await Dialogs.ConfirmAsync( + "Denuncias asignadas a otros usuarios", + $"Vas a importar denuncias asignadas a otros usuarios de tu grupo:{Environment.NewLine}{Environment.NewLine}{ownerSummary}{Environment.NewLine}{Environment.NewLine}La importación quedará registrada con tu usuario.", + "Importar denuncias"); if (!confirmed) { return; diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Instrucciones.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Instrucciones.razor index 639d346..0a737a6 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Instrucciones.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Instrucciones.razor @@ -67,15 +67,15 @@
-

Modal de subida a Gestiona

+

Ventana de configuración de la subida

- Antes de confirmar la subida, revisa estos puntos: + Al configurar un expediente nuevo o una actualización, revisa estos puntos antes de confirmar:

    -
  • Asunto: texto que identificara el expediente/documentos en Gestiona.
  • -
  • Grupo destino: unidad a la que se asignara el expediente.
  • +
  • Asunto: texto que identifica el expediente en Gestiona. En las actualizaciones se muestra en modo de solo lectura.
  • +
  • Grupo destino: grupo al que quedará asignado el expediente en Gestiona.
  • Modo de subida: puedes unir adjuntos en un PDF o subirlos de forma independiente.
  • -
  • Tercero: la app lo rellena desde la denuncia. Si es anonima, se usa el tercero anonimo configurado.
  • +
  • Tercero: la aplicación lo completa con los datos de la denuncia. Si es anónima, se utiliza el tercero anónimo configurado.
  • Expedientes del tercero: puedes consultarlos antes de confirmar si necesitas contexto.
diff --git a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Pendientes.razor b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Pendientes.razor index 85263ff..cdd4156 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Pendientes.razor +++ b/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Pendientes.razor @@ -19,7 +19,7 @@ @inject IDenunciaStore DenunciaStore @inject ApiDenunciasClient ApiDenuncias @inject UiBusyService Busy -@inject IJSRuntime JSRuntime +@inject UiDialogService Dialogs Denuncias Pendientes @@ -1336,10 +1336,15 @@ else return true; } - return await JSRuntime.InvokeAsync( - "confirm", - $"La denuncia #{denuncia.Id_Denuncia} es propiedad de {denuncia.OwnerUsername}. " + - $"Puedes {action}la porque pertenece a un usuario de tu grupo. Deseas continuar?"); + var confirmText = string.Equals(action, "rechazar", StringComparison.OrdinalIgnoreCase) + ? "Continuar con el rechazo" + : "Continuar con la apertura"; + + return await Dialogs.ConfirmAsync( + "Denuncia asignada a otro usuario", + $"La denuncia #{denuncia.Id_Denuncia} está asignada a {denuncia.OwnerUsername}. " + + $"Puedes {action}la porque pertenece a un usuario de tu grupo. La acción quedará registrada con tu usuario.", + confirmText); } private async Task ConfirmSelectedGroupAsync() @@ -1355,10 +1360,11 @@ else var configuredGroups = currentGroups.GroupCodes.Count == 0 ? "ningún grupo" : string.Join(", ", currentGroups.GroupCodes); - return await JSRuntime.InvokeAsync( - "confirm", + return await Dialogs.ConfirmAsync( + "Cambio de asignación en Gestiona", $"El grupo de destino {selectedGroup} no pertenece a tus grupos configurados ({configuredGroups}). " + - "Si continúas, la denuncia quedará asignada en Gestiona a un grupo distinto de los tuyos. ¿Deseas continuar?"); + "Si continúas, la denuncia quedará asignada en Gestiona a un grupo distinto de los tuyos.", + "Cambiar asignación"); } catch (Exception ex) { diff --git a/Antifraude.Net/GestionaDenunciasAN/Program.cs b/Antifraude.Net/GestionaDenunciasAN/Program.cs index f2731f7..9854de9 100644 --- a/Antifraude.Net/GestionaDenunciasAN/Program.cs +++ b/Antifraude.Net/GestionaDenunciasAN/Program.cs @@ -52,6 +52,7 @@ builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Antifraude.Net/GestionaDenunciasAN/Services/UiDialogService.cs b/Antifraude.Net/GestionaDenunciasAN/Services/UiDialogService.cs new file mode 100644 index 0000000..b7e2a67 --- /dev/null +++ b/Antifraude.Net/GestionaDenunciasAN/Services/UiDialogService.cs @@ -0,0 +1,64 @@ +namespace GestionaDenunciasAN.Services; + +public enum UiDialogTone +{ + Information, + Warning, + Danger +} + +public sealed class UiDialogService +{ + private TaskCompletionSource? _pendingConfirmation; + + public event Action? Changed; + + public bool IsVisible { get; private set; } + public string Title { get; private set; } = string.Empty; + public string Message { get; private set; } = string.Empty; + public string ConfirmText { get; private set; } = "Continuar"; + public string CancelText { get; private set; } = "Cancelar"; + public UiDialogTone Tone { get; private set; } = UiDialogTone.Warning; + + public Task ConfirmAsync( + string title, + string message, + string confirmText = "Continuar", + string cancelText = "Cancelar", + UiDialogTone tone = UiDialogTone.Warning) + { + _pendingConfirmation?.TrySetResult(false); + + Title = title.Trim(); + Message = message.Trim(); + ConfirmText = confirmText.Trim(); + CancelText = cancelText.Trim(); + Tone = tone; + IsVisible = true; + + _pendingConfirmation = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + + Changed?.Invoke(); + return _pendingConfirmation.Task; + } + + public void Confirm() + { + Complete(true); + } + + public void Cancel() + { + Complete(false); + } + + private void Complete(bool confirmed) + { + var pendingConfirmation = _pendingConfirmation; + _pendingConfirmation = null; + IsVisible = false; + Changed?.Invoke(); + pendingConfirmation?.TrySetResult(confirmed); + } +}