import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Search, ChevronLeft, ChevronRight, Filter, Plus, Trash2, Loader2, Building2, ListOrdered, } from "lucide-react"; import { paymentService } from "@/services"; import { cn } from "@/lib/utils"; import { useAdminRole } from "@/hooks/use-admin-role"; import { toast } from "sonner"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { ScrollArea } from "@/components/ui/scroll-area"; export default function PaymentRequestsPage() { const { canCreateBusinessData } = useAdminRole(); const queryClient = useQueryClient(); const [page, setPage] = useState(1); const [search, setSearch] = useState(""); // Create Modal State const [isModalOpen, setIsModalOpen] = useState(false); const [formData, setFormData] = useState({ paymentRequestNumber: `PAYREQ-${new Date().getFullYear()}-${Math.floor(100 + Math.random() * 900)}`, customerName: "", customerEmail: "", customerPhone: "", amount: 0, currency: "USD", issueDate: new Date().toISOString(), dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), description: "", notes: "", taxAmount: 0, discountAmount: 0, status: "DRAFT", paymentId: "", customerId: "", accounts: [ { bankName: "Yaltopia Bank", accountName: "Yaltopia Tech PLC", accountNumber: "", currency: "ETB", }, ], items: [{ description: "", quantity: 1, unitPrice: 0, total: 0 }], }); const { data: requestsData, isLoading: requestsLoading } = useQuery({ queryKey: ["admin", "payment-requests", page, search], queryFn: () => paymentService.getPaymentRequests({ page, limit: 10, search: search || undefined, }), }); const createMutation = useMutation({ mutationFn: (data: any) => paymentService.createPaymentRequest(data), onSuccess: () => { toast.success("Payment request created successfully"); setIsModalOpen(false); queryClient.invalidateQueries({ queryKey: ["admin", "payment-requests"], }); }, onError: () => { toast.error("Failed to create payment request"); }, }); const handleCreate = (e: React.FormEvent) => { e.preventDefault(); createMutation.mutate(formData); }; const addItem = () => { setFormData({ ...formData, items: [ ...formData.items, { description: "", quantity: 1, unitPrice: 0, total: 0 }, ], }); }; const removeItem = (idx: number) => { setFormData({ ...formData, items: formData.items.filter((_: any, i: number) => i !== idx), }); }; const handleItemChange = (idx: number, field: string, value: any) => { const newItems = [...formData.items]; newItems[idx] = { ...newItems[idx], [field]: value }; // Auto-calculate total if (field === "quantity" || field === "unitPrice") { newItems[idx].total = newItems[idx].quantity * newItems[idx].unitPrice; } const newAmount = newItems.reduce((sum, item) => sum + item.total, 0); setFormData({ ...formData, items: newItems, amount: newAmount }); }; const addAccount = () => { setFormData({ ...formData, accounts: [ ...formData.accounts, { bankName: "", accountName: "", accountNumber: "", currency: "ETB" }, ], }); }; const removeAccount = (idx: number) => { setFormData({ ...formData, accounts: formData.accounts.filter((_: any, i: number) => i !== idx), }); }; const handleAccountChange = (idx: number, field: string, value: any) => { const newAccounts = [...formData.accounts]; newAccounts[idx] = { ...newAccounts[idx], [field]: value }; setFormData({ ...formData, accounts: newAccounts }); }; const formatCurrency = (amount: number | any) => { const val = typeof amount === "number" ? amount : 0; return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(val); }; const getStatusColor = (status: string) => { switch (status) { case "PAID": return "text-emerald-600 bg-emerald-50"; case "SENT": return "text-blue-600 bg-blue-50"; case "OPENED": return "text-amber-600 bg-amber-50"; case "EXPIRED": case "CANCELLED": return "text-red-600 bg-red-50"; default: return "text-gray-600 bg-gray-50"; } }; return (

Payment Requests

Manage outbound customer requests.

{canCreateBusinessData && (
)}
Request Queue
setSearch(e.target.value)} />
{requestsLoading ? ( ) : requestsData?.data && requestsData.data.length > 0 ? ( requestsData.data.map((request) => ( )) ) : ( )}
Request # Customer Amount Due Date Status
Loading requests...
{request.paymentRequestNumber}
{request.customerName} {request.customerEmail}
{formatCurrency(request.amount)} {new Date(request.dueDate).toLocaleDateString()} {request.status}
No records found.
{requestsData?.meta && (

Page {requestsData.meta.page} of {requestsData.meta.totalPages}

)}
{/* Create Modal */}
Draft Issue Payment Request
Draft a formal financial request for outbound settlement.
{/* Header Info */}
setFormData({ ...formData, paymentRequestNumber: e.target.value, }) } className="rounded-none border-slate-200 h-10 font-mono text-xs font-bold" />
setFormData({ ...formData, issueDate: new Date(e.target.value).toISOString(), }) } className="rounded-none border-slate-200 h-10 text-xs" />
setFormData({ ...formData, dueDate: new Date(e.target.value).toISOString(), }) } className="rounded-none border-slate-200 h-10 text-xs text-rose-600 font-bold" />
{/* Customer Details */}

Recipient Details

setFormData({ ...formData, customerName: e.target.value, }) } className="rounded-none border-slate-200 h-10 text-xs" required />
setFormData({ ...formData, customerEmail: e.target.value, }) } className="rounded-none border-slate-200 h-10 text-xs text-slate-500" />
setFormData({ ...formData, customerPhone: e.target.value, }) } className="rounded-none border-slate-200 h-10 text-xs" />
setFormData({ ...formData, customerId: e.target.value, }) } className="rounded-none border-slate-200 h-10 text-xs" />
{/* Financials & Logic */}

Financial Basis

setFormData({ ...formData, description: e.target.value, }) } className="rounded-none border-slate-200 h-10 text-xs" />
setFormData({ ...formData, taxAmount: parseFloat(e.target.value) || 0, }) } className="rounded-none border-slate-200 h-10 text-xs" />
setFormData({ ...formData, discountAmount: parseFloat(e.target.value) || 0, }) } className="rounded-none border-slate-200 h-10 text-xs" />
Estimated Total {formatCurrency( formData.amount + formData.taxAmount - formData.discountAmount, )}
{/* Line Items */}

Line Items

{formData.items.map((item: any, idx: number) => (
handleItemChange( idx, "description", e.target.value, ) } className="rounded-none border-slate-200 h-8 text-[11px]" />
handleItemChange( idx, "quantity", parseInt(e.target.value) || 0, ) } className="rounded-none border-slate-200 h-8 text-xs text-center" />
handleItemChange( idx, "unitPrice", parseFloat(e.target.value) || 0, ) } className="rounded-none border-slate-200 h-8 text-xs" />
{formatCurrency(item.total)}
))}
{/* Settlement Accounts */}

Settlement Accounts

{formData.accounts.map((acc: any, idx: number) => (
handleAccountChange( idx, "bankName", e.target.value, ) } className="rounded-none border-slate-200 h-8 text-[11px] font-bold" />
handleAccountChange( idx, "accountName", e.target.value, ) } className="rounded-none border-slate-200 h-8 text-[11px]" />
handleAccountChange( idx, "accountNumber", e.target.value, ) } className="rounded-none border-slate-200 h-8 text-[11px] font-mono" />
))}