feat: make MH Paid column inline-editable

Add PATCH /api/payments/:id/mh-paid-amount for direct value updates.
Clicking the MH Paid cell opens an input; Enter/blur saves and refreshes,
Escape cancels. Dash placeholder is also clickable to enter a value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-06 17:50:52 -04:00
parent 1196e2afee
commit 6f33e416c1
2 changed files with 83 additions and 5 deletions

View File

@@ -427,6 +427,34 @@ router.patch(
}
);
// PATCH /api/payments/:id/mh-paid-amount
router.patch(
"/:id/mh-paid-amount",
async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const paymentId = parseIntOrError(req.params.id, "Payment ID");
const raw = req.body.mhPaidAmount;
const mhPaidAmount = parseFloat(raw);
if (isNaN(mhPaidAmount) || mhPaidAmount < 0) {
return res.status(400).json({ message: "Invalid mhPaidAmount value" });
}
const updated = await prisma.payment.update({
where: { id: paymentId },
data: { mhPaidAmount, updatedById: userId },
});
return res.json({ ...updated, mhPaidAmount: Number(updated.mhPaidAmount) });
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Failed to update MH paid amount";
return res.status(500).json({ message });
}
}
);
// PATCH /api/payments/:id/mh-payment-check
router.patch(
"/:id/mh-payment-check",

View File

@@ -99,6 +99,8 @@ export default function PaymentsRecentTable({
const [selectedPaymentId, setSelectedPaymentId] = useState<number | null>(null);
const [checkedPaymentIds, setCheckedPaymentIds] = useState<Set<number>>(new Set());
const [isMhChecking, setIsMhChecking] = useState(false);
const [editingMhPaidId, setEditingMhPaidId] = useState<number | null>(null);
const [editingMhPaidValue, setEditingMhPaidValue] = useState<string>("");
const [isRevertOpen, setIsRevertOpen] = useState(false);
const [revertPaymentId, setRevertPaymentId] = useState<number | null>(null);
@@ -741,12 +743,60 @@ export default function PaymentsRecentTable({
</TableCell>
<TableCell>
{payment.mhPaidAmount != null ? (
<span className="text-sm font-medium text-green-700">
${Number(payment.mhPaidAmount).toFixed(2)}
</span>
{editingMhPaidId === payment.id ? (
<input
type="number"
min="0"
step="0.01"
autoFocus
className="w-24 border border-blue-400 rounded px-1 py-0.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-300"
value={editingMhPaidValue}
onChange={(e) => setEditingMhPaidValue(e.target.value)}
onKeyDown={async (e) => {
if (e.key === "Enter") {
e.currentTarget.blur();
} else if (e.key === "Escape") {
setEditingMhPaidId(null);
}
}}
onBlur={async () => {
const val = parseFloat(editingMhPaidValue);
if (!isNaN(val) && val >= 0) {
try {
const res = await apiRequest(
"PATCH",
`/api/payments/${payment.id}/mh-paid-amount`,
{ mhPaidAmount: val }
);
if (res.ok) {
await queryClient.invalidateQueries({ queryKey: QK_PAYMENTS_RECENT_BASE });
} else {
toast({ title: "Error", description: "Failed to save MH paid amount.", variant: "destructive" });
}
} catch {
toast({ title: "Error", description: "Failed to save MH paid amount.", variant: "destructive" });
}
}
setEditingMhPaidId(null);
}}
/>
) : (
<span className="text-xs text-gray-400"></span>
<span
className="text-sm font-medium text-green-700 cursor-pointer hover:underline hover:text-green-900"
title="Click to edit"
onClick={() => {
setEditingMhPaidId(payment.id);
setEditingMhPaidValue(
payment.mhPaidAmount != null
? Number(payment.mhPaidAmount).toFixed(2)
: "0.00"
);
}}
>
{payment.mhPaidAmount != null
? `$${Number(payment.mhPaidAmount).toFixed(2)}`
: <span className="text-gray-400 font-normal"></span>}
</span>
)}
</TableCell>