| Server IP : 173.209.174.21 / Your IP : 216.73.216.89 Web Server : Apache/2.4.58 (Ubuntu) System : Linux wcfs-server 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64 User : nodor ( 1000) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/wcfs/NAOPOS/src/components/ |
Upload File : |
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { CreditCard, Banknote, Delete } from "lucide-react";
import { PaymentMethod } from "@/types/sales";
import { CartItem } from "@/components/Cart";
interface PaymentModalProps {
open: boolean;
onClose: () => void;
onComplete: (method: PaymentMethod, amountTendered?: number) => void;
items: CartItem[];
}
const PaymentModal = ({ open, onClose, onComplete, items }: PaymentModalProps) => {
const [step, setStep] = useState<"method" | "cash">("method");
const [cashDisplay, setCashDisplay] = useState("0.00");
const formatCurrencyInput = (digits: string) => {
const cents = parseInt(digits, 10) || 0;
return (cents / 100).toFixed(2);
};
const subtotal = items.reduce((s, i) => s + i.price * i.quantity, 0);
const tax = items.reduce((s, i) => {
if (i.taxRate) return s + i.price * i.quantity * (i.taxRate / 100);
return s;
}, 0);
const total = subtotal + tax;
const handleDigit = (d: string) => {
setCashDisplay((prev) => {
const raw = prev.replace(".", "").replace(/^0+/, "");
return formatCurrencyInput(`${raw}${d}`);
});
};
const handleClear = () => setCashDisplay("0.00");
const handleBackspace = () => {
setCashDisplay((prev) => {
const raw = prev.replace(".", "").replace(/^0+/, "");
const next = raw.length <= 1 ? "0" : raw.slice(0, -1);
return formatCurrencyInput(next);
});
};
const cashAmount = parseFloat(cashDisplay);
const changeDue = cashAmount - total;
const handleCashComplete = () => {
if (cashAmount >= total) {
onComplete("cash", cashAmount);
resetState();
}
};
const handleCreditComplete = () => {
onComplete("credit");
resetState();
};
const resetState = () => {
setStep("method");
setCashDisplay("0.00");
};
const handleClose = () => {
resetState();
onClose();
};
const quickAmounts = [1, 5, 10, 20, 50, 100];
const handleQuickAmount = (amt: number) => {
setCashDisplay(formatCurrencyInput(Math.round(amt * 100).toString()));
};
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-md bg-card border-border">
<DialogHeader>
<DialogTitle className="text-foreground text-lg font-extrabold">
{step === "method" ? "Payment Method" : "Cash Payment"}
</DialogTitle>
</DialogHeader>
{/* Total display */}
<div className="rounded-xl bg-background border border-border px-4 py-3 text-center">
<p className="text-xs text-muted-foreground font-semibold">Total Due</p>
<p className="text-3xl font-extrabold text-foreground font-mono">${total.toFixed(2)}</p>
</div>
{step === "method" ? (
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => setStep("cash")}
className="flex flex-col items-center gap-2 rounded-xl bg-success/15 border border-success/30 p-6 hover:bg-success hover:text-success-foreground transition-all active:scale-95"
>
<Banknote className="h-10 w-10" />
<span className="text-sm font-bold">Cash</span>
</button>
<button
onClick={handleCreditComplete}
className="flex flex-col items-center gap-2 rounded-xl bg-primary/15 border border-primary/30 p-6 hover:bg-primary hover:text-primary-foreground transition-all active:scale-95"
>
<CreditCard className="h-10 w-10" />
<span className="text-sm font-bold">Credit Card</span>
</button>
</div>
) : (
<div className="space-y-3">
{/* Cash tendered display */}
<div className="rounded-xl bg-background border border-border px-4 py-3 text-right">
<p className="text-xs text-muted-foreground font-semibold">Cash Tendered</p>
<p className="text-3xl font-extrabold text-foreground font-mono">${cashDisplay}</p>
</div>
{/* Change due */}
<div className={`rounded-xl border px-4 py-2 text-center ${
cashAmount >= total
? "bg-success/15 border-success/30"
: "bg-destructive/10 border-destructive/20"
}`}>
<p className="text-xs font-semibold text-muted-foreground">Change Due</p>
<p className={`text-2xl font-extrabold font-mono ${
cashAmount >= total ? "text-success" : "text-destructive"
}`}>
${changeDue >= 0 ? changeDue.toFixed(2) : "—"}
</p>
</div>
{/* Quick amounts */}
<div className="grid grid-cols-3 gap-2">
{quickAmounts.map((amt) => (
<button
key={amt}
onClick={() => handleQuickAmount(amt)}
className="rounded-lg bg-secondary border border-border py-2 text-sm font-bold text-foreground hover:bg-primary hover:text-primary-foreground active:scale-95 transition-all"
>
${amt}
</button>
))}
</div>
{/* Numpad */}
<div className="grid grid-cols-3 gap-2">
{["7","8","9","4","5","6","1","2","3"].map((d) => (
<button
key={d}
onClick={() => handleDigit(d)}
className="rounded-lg bg-background border border-border py-2.5 text-lg font-bold text-foreground hover:bg-secondary active:scale-95 transition-all"
>
{d}
</button>
))}
<button onClick={handleClear} className="rounded-lg bg-destructive/10 border border-destructive/20 py-2.5 text-sm font-bold text-destructive active:scale-95 transition-all">CLR</button>
<button onClick={() => handleDigit("0")} className="rounded-lg bg-background border border-border py-2.5 text-lg font-bold text-foreground hover:bg-secondary active:scale-95 transition-all">0</button>
<button onClick={handleBackspace} className="rounded-lg bg-background border border-border py-2.5 text-lg font-bold text-foreground hover:bg-secondary active:scale-95 transition-all flex items-center justify-center">
<Delete className="h-5 w-5" />
</button>
</div>
{/* Actions */}
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setStep("method")}
className="rounded-xl bg-secondary py-3 text-sm font-bold text-secondary-foreground hover:bg-border transition-all"
>
Back
</button>
<button
onClick={handleCashComplete}
disabled={cashAmount < total}
className="rounded-xl bg-success py-3 text-sm font-bold text-success-foreground shadow-md hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed active:scale-[0.98] transition-all"
>
Complete Sale
</button>
</div>
</div>
)}
</DialogContent>
</Dialog>
);
};
export default PaymentModal;