feat: add withdrawal management tab and request processing functionality to accounting page
This commit is contained in:
@@ -6,7 +6,8 @@ import {
|
|||||||
DollarSign, Calendar, FileText, ArrowDownLeft, ArrowUpRight, Building,
|
DollarSign, Calendar, FileText, ArrowDownLeft, ArrowUpRight, Building,
|
||||||
ChevronLeft, ChevronRight, Wallet, Receipt, BookOpen, PieChart, List,
|
ChevronLeft, ChevronRight, Wallet, Receipt, BookOpen, PieChart, List,
|
||||||
Banknote, Smartphone, Users, Home, Wrench, Printer, FileSpreadsheet,
|
Banknote, Smartphone, Users, Home, Wrench, Printer, FileSpreadsheet,
|
||||||
Filter, ShoppingCart, Tag, Move, Calculator, Save, CreditCard, Bike
|
Filter, ShoppingCart, Tag, Move, Calculator, Save, CreditCard, Bike,
|
||||||
|
Clock, Check, CheckCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
export type AccountType = 'asset' | 'liability' | 'equity' | 'income' | 'expense';
|
export type AccountType = 'asset' | 'liability' | 'equity' | 'income' | 'expense';
|
||||||
@@ -71,6 +72,20 @@ export interface AccountingTransaction {
|
|||||||
createdBy: string;
|
createdBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WithdrawRequest {
|
||||||
|
id: string;
|
||||||
|
investorId: string;
|
||||||
|
investorName: string;
|
||||||
|
phone: string;
|
||||||
|
amount: number;
|
||||||
|
requestDate: string;
|
||||||
|
status: 'pending' | 'approved' | 'completed' | 'rejected';
|
||||||
|
bankName: string;
|
||||||
|
accountNo: string;
|
||||||
|
processedDate?: string;
|
||||||
|
paymentMethod?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultAccounts: ChartOfAccount[] = [
|
const defaultAccounts: ChartOfAccount[] = [
|
||||||
{ id: 'ASSET-001', code: '1000', name: 'Assets', type: 'asset', isActive: true, balance: 0 },
|
{ id: 'ASSET-001', code: '1000', name: 'Assets', type: 'asset', isActive: true, balance: 0 },
|
||||||
{ id: 'ASSET-101', code: '1100', name: 'Cash in Hand', type: 'asset', parentId: 'ASSET-001', isActive: true, balance: 85000 },
|
{ id: 'ASSET-101', code: '1100', name: 'Cash in Hand', type: 'asset', parentId: 'ASSET-001', isActive: true, balance: 85000 },
|
||||||
@@ -223,7 +238,7 @@ function generateAutoJournalEntries(type: TransactionType, amount: number, descr
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AccountingPage() {
|
export default function AccountingPage() {
|
||||||
const [activeTab, setActiveTab] = useState<'dashboard' | 'transactions' | 'journal' | 'ledger' | 'accounts'>('dashboard');
|
const [activeTab, setActiveTab] = useState<'dashboard' | 'transactions' | 'journal' | 'ledger' | 'accounts' | 'withdraw'>('dashboard');
|
||||||
const [transactions, setTransactions] = useState(mockTransactions);
|
const [transactions, setTransactions] = useState(mockTransactions);
|
||||||
const [accounts] = useState(defaultAccounts);
|
const [accounts] = useState(defaultAccounts);
|
||||||
const [journalEntries, setJournalEntries] = useState(mockJournalEntries);
|
const [journalEntries, setJournalEntries] = useState(mockJournalEntries);
|
||||||
@@ -234,6 +249,14 @@ export default function AccountingPage() {
|
|||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [editingTransaction, setEditingTransaction] = useState<AccountingTransaction | null>(null);
|
const [editingTransaction, setEditingTransaction] = useState<AccountingTransaction | null>(null);
|
||||||
const [viewingTransaction, setViewingTransaction] = useState<AccountingTransaction | null>(null);
|
const [viewingTransaction, setViewingTransaction] = useState<AccountingTransaction | null>(null);
|
||||||
|
const [showWithdrawModal, setShowWithdrawModal] = useState(false);
|
||||||
|
const [payNowModal, setPayNowModal] = useState<WithdrawRequest | null>(null);
|
||||||
|
const [paymentForm, setPaymentForm] = useState({ method: 'bank', reference: '', notes: '', date: new Date().toISOString().split('T')[0] });
|
||||||
|
const [withdrawRequests, setWithdrawRequests] = useState<WithdrawRequest[]>([
|
||||||
|
{ id: 'WDR-001', investorId: 'INV-001', investorName: 'Mohammad Islam', phone: '01987654321', amount: 15000, requestDate: '2024-03-20', status: 'pending', bankName: 'City Bank', accountNo: '1234567890' },
|
||||||
|
{ id: 'WDR-002', investorId: 'INV-002', investorName: 'Rahima Begum', phone: '01876543210', amount: 25000, requestDate: '2024-03-18', status: 'approved', bankName: 'DBBL', accountNo: '9876543210', processedDate: '2024-03-19', paymentMethod: 'bank' },
|
||||||
|
{ id: 'WDR-003', investorId: 'INV-003', investorName: 'Ahmed Hassan', phone: '01765432109', amount: 8000, requestDate: '2024-03-15', status: 'completed', bankName: 'bKash', accountNo: '01765432109', processedDate: '2024-03-16', paymentMethod: 'mobile' },
|
||||||
|
]);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const itemsPerPage = 10;
|
const itemsPerPage = 10;
|
||||||
|
|
||||||
@@ -417,7 +440,8 @@ export default function AccountingPage() {
|
|||||||
{ id: 'transactions', label: 'Transactions', icon: Receipt },
|
{ id: 'transactions', label: 'Transactions', icon: Receipt },
|
||||||
{ id: 'journal', label: 'Journal', icon: BookOpen },
|
{ id: 'journal', label: 'Journal', icon: BookOpen },
|
||||||
{ id: 'ledger', label: 'Ledger', icon: List },
|
{ id: 'ledger', label: 'Ledger', icon: List },
|
||||||
{ id: 'accounts', label: 'Chart of Accounts', icon: Calculator },
|
{ id: 'accounts', label: 'Accounts', icon: Calculator },
|
||||||
|
{ id: 'withdraw', label: 'Withdraw', icon: ArrowDownLeft },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -453,6 +477,11 @@ export default function AccountingPage() {
|
|||||||
>
|
>
|
||||||
<Icon className="w-4 h-4" />
|
<Icon className="w-4 h-4" />
|
||||||
{tab.label}
|
{tab.label}
|
||||||
|
{tab.id === 'withdraw' && withdrawRequests.filter(w => w.status === 'pending').length > 0 && (
|
||||||
|
<span className="ml-1 px-1.5 py-0.5 text-xs font-medium bg-orange-100 text-orange-700 rounded-full">
|
||||||
|
{withdrawRequests.filter(w => w.status === 'pending').length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -547,6 +576,289 @@ export default function AccountingPage() {
|
|||||||
{activeTab === 'ledger' && <LedgerView accounts={accounts} journalEntries={journalEntries} dateFrom={dateFrom} dateTo={dateTo} />}
|
{activeTab === 'ledger' && <LedgerView accounts={accounts} journalEntries={journalEntries} dateFrom={dateFrom} dateTo={dateTo} />}
|
||||||
{activeTab === 'accounts' && <AccountsView accounts={accounts} />}
|
{activeTab === 'accounts' && <AccountsView accounts={accounts} />}
|
||||||
|
|
||||||
|
{activeTab === 'withdraw' && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-slate-800">Withdraw Management</h2>
|
||||||
|
<p className="text-sm text-slate-500">Process investor withdrawal requests</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowWithdrawModal(true)}
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2.5 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
<Plus className="w-5 h-5" />
|
||||||
|
<span>New Withdraw Request</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||||
|
<div className="bg-orange-50 rounded-xl p-4 border border-orange-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center">
|
||||||
|
<Clock className="w-5 h-5 text-orange-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xl font-bold text-slate-800">{withdrawRequests.filter(w => w.status === 'pending').length}</p>
|
||||||
|
<p className="text-sm text-slate-500">Pending</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-blue-50 rounded-xl p-4 border border-blue-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
|
||||||
|
<Check className="w-5 h-5 text-blue-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xl font-bold text-slate-800">{withdrawRequests.filter(w => w.status === 'approved').length}</p>
|
||||||
|
<p className="text-sm text-slate-500">Approved</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-green-50 rounded-xl p-4 border border-green-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
|
||||||
|
<DollarSign className="w-5 h-5 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xl font-bold text-slate-800">{withdrawRequests.filter(w => w.status === 'completed').length}</p>
|
||||||
|
<p className="text-sm text-slate-500">Completed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-purple-50 rounded-xl p-4 border border-purple-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||||
|
<TrendingDown className="w-5 h-5 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xl font-bold text-slate-800">৳{withdrawRequests.reduce((sum, w) => sum + w.amount, 0).toLocaleString()}</p>
|
||||||
|
<p className="text-sm text-slate-500">Total Amount</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead className="bg-slate-50 border-b border-slate-100">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-slate-600">ID</th>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-slate-600">Investor</th>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-slate-600">Phone</th>
|
||||||
|
<th className="px-4 py-3 text-right font-semibold text-slate-600">Amount</th>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-slate-600">Bank/Method</th>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-slate-600">Request Date</th>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-slate-600">Status</th>
|
||||||
|
<th className="px-4 py-3 text-center font-semibold text-slate-600">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{withdrawRequests.map((req) => (
|
||||||
|
<tr key={req.id} className="border-b border-slate-50 hover:bg-slate-50">
|
||||||
|
<td className="px-4 py-3 font-medium text-slate-800">{req.id}</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-slate-800">{req.investorName}</p>
|
||||||
|
<p className="text-xs text-slate-500">{req.investorId}</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-slate-600">{req.phone}</td>
|
||||||
|
<td className="px-4 py-3 text-right font-bold text-slate-800">৳{req.amount.toLocaleString()}</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<p className="text-slate-600">{req.bankName}</p>
|
||||||
|
<p className="text-xs text-slate-400">{req.accountNo}</p>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-slate-600">{req.requestDate}</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full ${
|
||||||
|
req.status === 'pending' ? 'bg-orange-100 text-orange-700' :
|
||||||
|
req.status === 'approved' ? 'bg-blue-100 text-blue-700' :
|
||||||
|
req.status === 'completed' ? 'bg-green-100 text-green-700' :
|
||||||
|
'bg-red-100 text-red-700'
|
||||||
|
}`}>
|
||||||
|
{req.status === 'pending' && <Clock className="w-3 h-3" />}
|
||||||
|
{req.status === 'approved' && <Check className="w-3 h-3" />}
|
||||||
|
{req.status === 'completed' && <CheckCircle className="w-3 h-3" />}
|
||||||
|
{req.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
{req.status === 'pending' && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setWithdrawRequests(prev => prev.map(w => w.id === req.id ? { ...w, status: 'approved', processedDate: new Date().toISOString().split('T')[0] } : w))}
|
||||||
|
className="p-1.5 bg-blue-100 text-blue-600 rounded-lg hover:bg-blue-200"
|
||||||
|
title="Approve"
|
||||||
|
>
|
||||||
|
<Check className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setWithdrawRequests(prev => prev.map(w => w.id === req.id ? { ...w, status: 'rejected' } : w))}
|
||||||
|
className="p-1.5 bg-red-100 text-red-600 rounded-lg hover:bg-red-200"
|
||||||
|
title="Reject"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{req.status === 'approved' && (
|
||||||
|
<button
|
||||||
|
onClick={() => { setPayNowModal(req); setPaymentForm({ method: 'bank', reference: `PAY-${req.id}`, notes: '', date: new Date().toISOString().split('T')[0] }); }}
|
||||||
|
className="px-3 py-1.5 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<DollarSign className="w-3 h-3" /> Pay Now
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{req.status === 'completed' && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-xs text-green-600 font-medium">Paid</span>
|
||||||
|
<button onClick={() => window.print()} className="p-1 text-slate-400 hover:text-blue-600" title="Print Invoice">
|
||||||
|
<Printer className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{req.status === 'rejected' && (
|
||||||
|
<span className="text-xs text-red-600 font-medium">Rejected</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showWithdrawModal && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg">
|
||||||
|
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||||||
|
<h3 className="font-semibold text-slate-800">New Withdraw Request</h3>
|
||||||
|
<button onClick={() => setShowWithdrawModal(false)} className="text-slate-400 hover:text-slate-600">
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Investor ID</label>
|
||||||
|
<input type="text" placeholder="INV-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Phone</label>
|
||||||
|
<input type="tel" placeholder="01XXXXXXXXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Investor Name</label>
|
||||||
|
<input type="text" placeholder="Enter name" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Amount (৳)</label>
|
||||||
|
<input type="number" placeholder="0" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Method</label>
|
||||||
|
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
|
||||||
|
<option value="bank">Bank Transfer</option>
|
||||||
|
<option value="mobile">Mobile Banking</option>
|
||||||
|
<option value="cash">Cash</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Bank Name</label>
|
||||||
|
<input type="text" placeholder="Bank name" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Account Number</label>
|
||||||
|
<input type="text" placeholder="Account number" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||||
|
<button onClick={() => setShowWithdrawModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||||
|
<button onClick={() => setShowWithdrawModal(false)} className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">Submit Request</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{payNowModal && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||||||
|
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||||||
|
<h3 className="font-semibold text-slate-800">Process Payment</h3>
|
||||||
|
<button onClick={() => setPayNowModal(null)} className="text-slate-400 hover:text-slate-600">
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
<div className="bg-slate-50 p-3 rounded-lg">
|
||||||
|
<p className="text-sm text-slate-500">Paying to</p>
|
||||||
|
<p className="font-medium text-slate-800">{payNowModal.investorName}</p>
|
||||||
|
<p className="text-sm text-slate-600">{payNowModal.bankName} - {payNowModal.accountNo}</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-blue-50 p-3 rounded-lg">
|
||||||
|
<p className="text-sm text-slate-500">Amount</p>
|
||||||
|
<p className="text-xl font-bold text-blue-600">৳{payNowModal.amount.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Date</label>
|
||||||
|
<input type="date" value={paymentForm.date} onChange={(e) => setPaymentForm(p => ({ ...p, date: e.target.value }))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Method</label>
|
||||||
|
<select value={paymentForm.method} onChange={(e) => setPaymentForm(p => ({ ...p, method: e.target.value }))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
|
||||||
|
<option value="bank">Bank Transfer</option>
|
||||||
|
<option value="mobile">Mobile Banking</option>
|
||||||
|
<option value="cash">Cash</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Reference No.</label>
|
||||||
|
<input type="text" value={paymentForm.reference} onChange={(e) => setPaymentForm(p => ({ ...p, reference: e.target.value }))} placeholder="e.g. TRX-123456" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-600 mb-1 block">Notes (Optional)</label>
|
||||||
|
<textarea value={paymentForm.notes} onChange={(e) => setPaymentForm(p => ({ ...p, notes: e.target.value }))} placeholder="Add any notes..." className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" rows={2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||||
|
<button onClick={() => setPayNowModal(null)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||||
|
<button onClick={() => {
|
||||||
|
const newTransaction: AccountingTransaction = {
|
||||||
|
id: `TXN-${Date.now()}`,
|
||||||
|
date: paymentForm.date,
|
||||||
|
type: 'investor_withdraw',
|
||||||
|
amount: payNowModal.amount,
|
||||||
|
description: `Investor Withdrawal - ${payNowModal.investorName}`,
|
||||||
|
beneficiary: payNowModal.investorName,
|
||||||
|
beneficiaryPhone: payNowModal.phone,
|
||||||
|
paymentMethod: paymentForm.method as 'cash' | 'bank' | 'mobile',
|
||||||
|
reference: paymentForm.reference,
|
||||||
|
notes: paymentForm.notes,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
createdBy: 'Admin'
|
||||||
|
};
|
||||||
|
setTransactions(prev => [newTransaction, ...prev]);
|
||||||
|
setWithdrawRequests(prev => prev.map(w => w.id === payNowModal.id ? { ...w, status: 'completed' as const, processedDate: paymentForm.date, paymentMethod: paymentForm.method } : w));
|
||||||
|
setPayNowModal(null);
|
||||||
|
}} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2">
|
||||||
|
<Banknote className="w-4 h-4" /> Complete Payment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<TransactionModal
|
<TransactionModal
|
||||||
isOpen={showModal}
|
isOpen={showModal}
|
||||||
onClose={() => { setShowModal(false); setEditingTransaction(null); }}
|
onClose={() => { setShowModal(false); setEditingTransaction(null); }}
|
||||||
|
|||||||
Reference in New Issue
Block a user