Registro de horas: accion explicita para regresar una partida pagada a "Por pagar"
El badge de estado deja de ser un toggle (facil de disparar por error) y pasa a ser un indicador. En su lugar, la columna de acciones muestra un boton claro segun el estado: "Marcar como pagada" (pendientes) o "Regresar a Por pagar" (pagadas, con confirmacion). Asi se corrigen los casos donde una partida se marca como pagada por error. El backend ya limpiaba la fechaPago al regresar a por_pagar; solo cambia la UX del panel. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3824dd00be
commit
63b14fc8b8
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { Clock, Plus, Trash2, Pencil, Download, Eye, X, CheckCircle2, CircleDashed } from "lucide-react";
|
||||
import { Clock, Plus, Trash2, Pencil, Download, Eye, X, CheckCircle2, CircleDashed, RotateCcw } from "lucide-react";
|
||||
import {
|
||||
formatCurrency,
|
||||
formatFechaRegistro,
|
||||
@@ -230,9 +230,10 @@ export function RegistroHorasPanel({
|
||||
}
|
||||
};
|
||||
|
||||
// Alterna el estado de cobro de una partida (por_pagar <-> pagada). Optimista.
|
||||
const handleToggleEstado = async (r: Registro) => {
|
||||
const nuevo = esPagada(r.estadoPago) ? "por_pagar" : "pagada";
|
||||
// Cambia el estado de cobro de una partida a un valor explícito. Optimista.
|
||||
// Al regresar a "por_pagar" el backend limpia la fechaPago (deja de contar como cobrada).
|
||||
const cambiarEstado = async (r: Registro, nuevo: "por_pagar" | "pagada") => {
|
||||
if (esPagada(r.estadoPago) === (nuevo === "pagada")) return; // ya está en ese estado
|
||||
const prev = registros;
|
||||
setTogglingId(r.id);
|
||||
setRegistros((rs) => rs.map((x) => (x.id === r.id ? { ...x, estadoPago: nuevo } : x)));
|
||||
@@ -252,6 +253,13 @@ export function RegistroHorasPanel({
|
||||
}
|
||||
};
|
||||
|
||||
// Regresa una partida pagada por error a "Por pagar". Pide confirmación porque se
|
||||
// pierde la marca/fecha de pago y la partida vuelve a entrar en la nota por cobrar.
|
||||
const regresarPorPagar = (r: Registro) => {
|
||||
if (!confirm('¿Regresar esta partida a "Por pagar"? Se quitará la marca de pagada.')) return;
|
||||
cambiarEstado(r, "por_pagar");
|
||||
};
|
||||
|
||||
// Congela el HTML de la nota (con branding) y abre el modal de previsualización.
|
||||
const abrirPreview = () => setPreviewHtml(construirHTMLNota());
|
||||
|
||||
@@ -658,17 +666,27 @@ export function RegistroHorasPanel({
|
||||
<td className="py-2 pr-3 text-right">{formatCurrency(r.tarifaHora)}</td>
|
||||
<td className="py-2 pr-3 text-right">{formatCurrency(r.horas * r.tarifaHora)}</td>
|
||||
<td className="py-2 pr-3">{r.descripcion}</td>
|
||||
<td className="py-2 pr-3 whitespace-nowrap">
|
||||
<button
|
||||
onClick={() => handleToggleEstado(r)}
|
||||
disabled={togglingId === r.id}
|
||||
title={esPagada(r.estadoPago) ? "Marcar como por pagar" : "Marcar como pagada"}
|
||||
className="disabled:opacity-50"
|
||||
>
|
||||
{badgeEstado(r.estadoPago)}
|
||||
</button>
|
||||
</td>
|
||||
<td className="py-2 pr-3 whitespace-nowrap">{badgeEstado(r.estadoPago)}</td>
|
||||
<td className="py-2 text-right whitespace-nowrap">
|
||||
{esPagada(r.estadoPago) ? (
|
||||
<button
|
||||
onClick={() => regresarPorPagar(r)}
|
||||
disabled={togglingId === r.id}
|
||||
className="p-1 text-muted hover:text-amber-600 disabled:opacity-50"
|
||||
title="Regresar a Por pagar"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => cambiarEstado(r, "pagada")}
|
||||
disabled={togglingId === r.id}
|
||||
className="p-1 text-muted hover:text-green-600 disabled:opacity-50"
|
||||
title="Marcar como pagada"
|
||||
>
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleEdit(r)}
|
||||
className="p-1 text-muted hover:text-primary"
|
||||
|
||||
Reference in New Issue
Block a user