Add investment create form with accounting module integration and update SPEC.md

This commit is contained in:
sazzadulalambd
2026-04-22 01:09:51 +06:00
parent dab0c11b15
commit faae06fbe3

View File

@@ -53,6 +53,20 @@ export default function InvestorDetailPage() {
const [showEditModal, setShowEditModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false);
const [showAssignBikeModal, setShowAssignBikeModal] = useState(false); const [showAssignBikeModal, setShowAssignBikeModal] = useState(false);
const [selectedBikeId, setSelectedBikeId] = useState(''); const [selectedBikeId, setSelectedBikeId] = useState('');
const [showCreateInvestmentModal, setShowCreateInvestmentModal] = useState(false);
const [newInvestment, setNewInvestment] = useState({
planName: '',
planType: 'gold' as 'silver' | 'gold' | 'platinum' | 'diamond',
selectedBikeIds: [] as string[],
totalInvestment: 0,
monthlyReturn: 0,
expectedRoi: 15,
startDate: new Date().toISOString().split('T')[0],
endDate: '',
paymentMethod: 'bank' as 'bank' | 'mobile' | 'cash' | 'cheque',
transactionReference: '',
notes: ''
});
if (!investor) { if (!investor) {
return ( return (
@@ -76,6 +90,49 @@ export default function InvestorDetailPage() {
setSelectedBikeId(''); setSelectedBikeId('');
}; };
const handleCreateInvestment = () => {
const invId = `ip${Date.now()}`;
const transactionId = `INV/${new Date().getFullYear()}/${String(Math.floor(Math.random() * 1000)).padStart(3, '0')}`;
console.log('Creating Investment:', {
id: invId,
investorId: investor.id,
...newInvestment,
actualEarnings: 0,
status: 'active' as const,
transactionId: transactionId,
createdAt: new Date().toISOString()
});
console.log('Accounting Entry:', {
entryId: `AC-${Date.now()}`,
type: 'investment_created',
investorId: investor.id,
investmentId: invId,
amount: newInvestment.totalInvestment,
debitAccount: 'Investment Asset - Investor',
creditAccount: newInvestment.paymentMethod === 'bank' ? 'Bank Account' : 'Cash Account',
transactionRef: transactionId,
createdAt: new Date().toISOString()
});
alert(`Investment created successfully!\n\nInvestment ID: ${invId}\nTransaction Ref: ${transactionId}\nAmount: ৳${newInvestment.totalInvestment.toLocaleString()}\n\nAccounting entries have been logged.`);
setShowCreateInvestmentModal(false);
setNewInvestment({
planName: '',
planType: 'gold',
selectedBikeIds: [],
totalInvestment: 0,
monthlyReturn: 0,
expectedRoi: 15,
startDate: new Date().toISOString().split('T')[0],
endDate: '',
paymentMethod: 'bank',
transactionReference: '',
notes: ''
});
};
return ( return (
<div className="p-4 lg:p-6"> <div className="p-4 lg:p-6">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
@@ -380,7 +437,7 @@ export default function InvestorDetailPage() {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="font-semibold text-slate-800">Investment Plans</h3> <h3 className="font-semibold text-slate-800">Investment Plans</h3>
<button className="py-2 px-3 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-1"> <button onClick={() => setShowCreateInvestmentModal(true)} className="py-2 px-3 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-1">
<Plus className="w-4 h-4" /> Add Investment <Plus className="w-4 h-4" /> Add Investment
</button> </button>
</div> </div>
@@ -664,6 +721,181 @@ export default function InvestorDetailPage() {
</div> </div>
</div> </div>
)} )}
{showCreateInvestmentModal && (
<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-2xl max-h-[90vh] overflow-hidden flex flex-col">
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
<h2 className="text-lg font-bold text-slate-800">Create New Investment</h2>
<button onClick={() => setShowCreateInvestmentModal(false)} className="p-2 hover:bg-slate-100 rounded-lg">
<X className="w-5 h-5 text-slate-400" />
</button>
</div>
<div className="p-5 overflow-y-auto flex-1 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Name *</label>
<input
type="text"
value={newInvestment.planName}
onChange={(e) => setNewInvestment({ ...newInvestment, planName: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
placeholder="e.g., Gold EV Fleet 2024"
/>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Type *</label>
<select
value={newInvestment.planType}
onChange={(e) => setNewInvestment({ ...newInvestment, planType: e.target.value as any, expectedRoi: e.target.value === 'silver' ? 12 : e.target.value === 'gold' ? 15 : e.target.value === 'platinum' ? 18 : 20 })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="silver">Silver (12% ROI)</option>
<option value="gold">Gold (15% ROI)</option>
<option value="platinum">Platinum (18% ROI)</option>
<option value="diamond">Diamond (20% ROI)</option>
</select>
</div>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Select Bikes</label>
<div className="grid grid-cols-2 gap-2 max-h-32 overflow-y-auto border border-slate-200 rounded-lg p-2">
{availableBikesForAssignment.map(bike => (
<label key={bike.id} className="flex items-center gap-2 p-2 hover:bg-slate-50 rounded cursor-pointer">
<input
type="checkbox"
checked={newInvestment.selectedBikeIds.includes(bike.id)}
onChange={(e) => {
if (e.target.checked) {
setNewInvestment({ ...newInvestment, selectedBikeIds: [...newInvestment.selectedBikeIds, bike.id] });
} else {
setNewInvestment({ ...newInvestment, selectedBikeIds: newInvestment.selectedBikeIds.filter(id => id !== bike.id) });
}
}}
className="rounded text-investor"
/>
<span className="text-sm">{bike.model} ({bike.plateNumber})</span>
</label>
))}
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Investment Amount () *</label>
<input
type="number"
value={newInvestment.totalInvestment}
onChange={(e) => setNewInvestment({ ...newInvestment, totalInvestment: Number(e.target.value), monthlyReturn: Math.round(Number(e.target.value) * newInvestment.expectedRoi / 100 / 12) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
placeholder="0"
/>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Monthly Return ()</label>
<input
type="number"
value={newInvestment.monthlyReturn}
onChange={(e) => setNewInvestment({ ...newInvestment, monthlyReturn: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
placeholder="0"
/>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Expected ROI (%)</label>
<input
type="number"
value={newInvestment.expectedRoi}
onChange={(e) => setNewInvestment({ ...newInvestment, expectedRoi: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
placeholder="15"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Start Date *</label>
<input
type="date"
value={newInvestment.startDate}
onChange={(e) => setNewInvestment({ ...newInvestment, startDate: 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">End Date</label>
<input
type="date"
value={newInvestment.endDate}
onChange={(e) => setNewInvestment({ ...newInvestment, endDate: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Method *</label>
<select
value={newInvestment.paymentMethod}
onChange={(e) => setNewInvestment({ ...newInvestment, paymentMethod: e.target.value as any })}
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>
<option value="cheque">Cheque</option>
</select>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Transaction Reference</label>
<input
type="text"
value={newInvestment.transactionReference}
onChange={(e) => setNewInvestment({ ...newInvestment, transactionReference: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
placeholder="Auto-generated if empty"
/>
</div>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Notes</label>
<textarea
value={newInvestment.notes}
onChange={(e) => setNewInvestment({ ...newInvestment, notes: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
rows={2}
placeholder="Additional notes..."
/>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 className="font-semibold text-blue-800 mb-2">Accounting Entries to be Created:</h4>
<div className="text-sm text-blue-700 space-y-1">
<p> Debit: Investment Asset - Investor ({newInvestment.totalInvestment ? `${newInvestment.totalInvestment.toLocaleString()}` : '৳0'})</p>
<p> Credit: {newInvestment.paymentMethod === 'bank' ? 'Bank Account' : newInvestment.paymentMethod === 'mobile' ? 'Mobile Wallet' : newInvestment.paymentMethod === 'cash' ? 'Cash Account' : 'Cheque Receivable'} ({newInvestment.totalInvestment ? `${newInvestment.totalInvestment.toLocaleString()}` : '৳0'})</p>
<p> Transaction ID will be auto-generated</p>
</div>
</div>
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
<button onClick={() => setShowCreateInvestmentModal(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={handleCreateInvestment}
disabled={!newInvestment.planName || !newInvestment.totalInvestment}
className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark disabled:opacity-50 flex items-center gap-2"
>
<TrendingUp className="w-4 h-4" /> Create Investment
</button>
</div>
</div>
</div>
)}
</div> </div>
); );
} }