import { useState, useEffect } from "react"; import api from "../../../api/client"; import { FLOW_LABELS, PAYMENT_TYPE_LABELS, CATEGORY_LABELS, ORDER_STATUS_LABELS, fmtDate, } from "./shared"; const labelStyle = { fontSize: 11, fontWeight: 600, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 4 }; // Greek number format: 12500800.70 → "12.500.800,70" function fmtEuro(amount, currency = "EUR") { const n = Number(amount || 0); const [intPart, decPart] = n.toFixed(2).split("."); const intFormatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, "."); const suffix = currency && currency !== "EUR" ? ` ${currency}` : ""; return `€${intFormatted},${decPart}${suffix}`; } const inputStyle = { backgroundColor: "var(--bg-input)", borderColor: "var(--border-primary)", color: "var(--text-primary)", border: "1px solid var(--border-primary)", borderRadius: 6, padding: "6px 10px", fontSize: 13, width: "100%" }; const TERMINAL = new Set(["declined", "complete"]); const emptyTxn = () => ({ date: new Date().toISOString().slice(0, 16), flow: "payment", payment_type: "cash", category: "full_payment", amount: "", currency: "EUR", invoice_ref: "", order_ref: "", recorded_by: "", note: "", }); function TransactionModal({ initialData, orders, customerId, onClose, onSaved, user, editIndex, outstandingBalance }) { const [form, setForm] = useState(() => ({ ...emptyTxn(), recorded_by: user?.name || "", ...initialData, })); const [saving, setSaving] = useState(false); const set = (k, v) => setForm((f) => ({ ...f, [k]: v })); const isInvoice = form.flow === "invoice"; // Auto-fill amount when flow=payment + category=full_payment // - No order_ref: fill total outstanding across all orders // - With order_ref: fill balance due for that specific order // - Any other flow/category: clear amount const isFullPayment = form.flow === "payment" && form.category === "full_payment"; useEffect(() => { if (initialData) return; // don't touch edits if (!isFullPayment) { setForm((f) => ({ ...f, amount: "" })); return; } if (form.order_ref) { // Find balance due for this specific order const order = (orders || []).find((o) => o.id === form.order_ref); const due = order?.payment_status?.balance_due ?? 0; if (due > 0) setForm((f) => ({ ...f, amount: Number(due).toFixed(2) })); } else { // Total outstanding across all orders if (outstandingBalance != null && outstandingBalance > 0) { setForm((f) => ({ ...f, amount: outstandingBalance.toFixed(2) })); } } }, [isFullPayment, form.order_ref]); const handleSubmit = async () => { if (!form.amount || !form.flow) { alert("Amount and Flow are required."); return; } // Category is required only for non-invoice flows if (!isInvoice && !form.category) { alert("Category is required."); return; } setSaving(true); try { const payload = { ...form, amount: parseFloat(form.amount) || 0, date: form.date ? new Date(form.date).toISOString() : new Date().toISOString(), invoice_ref: form.invoice_ref || null, order_ref: form.order_ref || null, payment_type: isInvoice ? null : (form.payment_type || null), // For invoices, category defaults to "full_payment" as a neutral placeholder category: isInvoice ? "full_payment" : (form.category || "full_payment"), }; let updated; if (editIndex !== undefined && editIndex !== null) { updated = await api.patch(`/crm/customers/${customerId}/transactions/${editIndex}`, payload); } else { updated = await api.post(`/crm/customers/${customerId}/transactions`, payload); } onSaved(updated); onClose(); } catch (err) { alert(err.message); } finally { setSaving(false); } }; return (

{editIndex !== null && editIndex !== undefined ? "Edit Transaction" : "Record Transaction"}

Date
set("date", e.target.value)} style={inputStyle} />
Flow
{/* Payment type — hidden for invoices */} {!isInvoice && (
Payment Type
)} {/* Category — hidden for invoices */} {!isInvoice && (
Category
)}
Amount
set("amount", e.target.value)} style={inputStyle} placeholder="0.00" />
Currency
Invoice Ref
set("invoice_ref", e.target.value)} style={inputStyle} placeholder="e.g. INV-2026-001" />
Order Ref
Recorded By
set("recorded_by", e.target.value)} style={inputStyle} />
Note