feat: add investment success modal and redesign investor bike cards with plan-based styling
This commit is contained in:
717
src/app/admin/investors/[id]/investments/[investmentId]/page.tsx
Normal file
717
src/app/admin/investors/[id]/investments/[investmentId]/page.tsx
Normal file
@@ -0,0 +1,717 @@
|
||||
'use client';
|
||||
|
||||
import { useState, use } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import {
|
||||
ArrowLeft, TrendingUp, Plus, X, Edit, Trash2, Bike, DollarSign, Calendar,
|
||||
CreditCard, FileText, Users, TrendingDown, ArrowRight, Download, Check,
|
||||
Printer, BarChart3, Wallet, Clock, Shield, Percent, Activity, AlertTriangle
|
||||
} from 'lucide-react';
|
||||
import { investors as initialInvestors, bikes as initialBikes, transactions as initialTransactions } from '@/data/mockData';
|
||||
|
||||
export default function InvestmentDetailPage({ params }: { params: Promise<{ id: string; investmentId: string }> }) {
|
||||
const resolvedParams = use(params);
|
||||
const { id: investorId, investmentId } = resolvedParams;
|
||||
const router = useRouter();
|
||||
|
||||
const investor = initialInvestors.find(i => i.id === investorId);
|
||||
const investment = investor?.investments?.find((inv: any) => inv.id === investmentId);
|
||||
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
const [showAddBikeModal, setShowAddBikeModal] = useState(false);
|
||||
const [showDeleteBikeModal, setShowDeleteBikeModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [bikeToDelete, setBikeToDelete] = useState<any>(null);
|
||||
const [selectedBikeId, setSelectedBikeId] = useState('');
|
||||
const [editForm, setEditForm] = useState<any>({});
|
||||
|
||||
if (!investor || !investment) {
|
||||
return (
|
||||
<div className="p-4 lg:p-6">
|
||||
<div className="text-center py-12">
|
||||
<h2 className="text-xl font-bold text-slate-800">Investment Not Found</h2>
|
||||
<p className="text-slate-500 mt-2">The investment you're looking for doesn't exist.</p>
|
||||
<Link href={`/admin/investors/${investorId}`} className="mt-4 inline-flex items-center gap-2 text-investor hover:underline">
|
||||
<ArrowLeft className="w-4 h-4" /> Back to Investor
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const planConfig: Record<string, { bg: string; border: string; text: string }> = {
|
||||
silver: { bg: 'bg-slate-100', border: 'border-slate-300', text: 'text-slate-600' },
|
||||
gold: { bg: 'bg-amber-100', border: 'border-amber-300', text: 'text-amber-700' },
|
||||
platinum: { bg: 'bg-purple-100', border: 'border-purple-300', text: 'text-purple-700' },
|
||||
diamond: { bg: 'bg-blue-100', border: 'border-blue-300', text: 'text-blue-700' },
|
||||
};
|
||||
const style = planConfig[investment.planType] || planConfig.gold;
|
||||
|
||||
const assignedBikes = initialBikes.filter((b: any) => b.investorId === investorId);
|
||||
const investmentTransactions = initialTransactions.filter((t: any) => t.investorId === investorId && t.type === 'investment_return');
|
||||
|
||||
return (
|
||||
<div className="p-4 lg:p-6 bg-slate-50 min-h-screen">
|
||||
<div className="max-w-8xl mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<button onClick={() => router.push(`/admin/investors/${investorId}`)} className="p-2 hover:bg-slate-200 rounded-lg transition-colors">
|
||||
<ArrowLeft className="w-5 h-5 text-slate-600" />
|
||||
</button>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-bold text-slate-800">{investment.planName}</h1>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${style.bg} ${style.text}`}>
|
||||
{investment.planType} Plan
|
||||
</span>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${investment.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600'}`}>
|
||||
{investment.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-slate-500 text-sm mt-1">
|
||||
ID: #{investment.id?.slice(-8)} • Investor: {investor.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-white flex items-center gap-2">
|
||||
<Download className="w-4 h-4" /> Statement
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
setEditForm({
|
||||
planName: investment.planName,
|
||||
planType: investment.planType,
|
||||
totalInvestment: investment.totalInvestment,
|
||||
status: investment.status,
|
||||
});
|
||||
setShowEditModal(true);
|
||||
}} className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-2">
|
||||
<Edit className="w-4 h-4" /> Edit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div className="bg-white rounded-xl p-5 border border-slate-200">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||
<Wallet className="w-5 h-5 text-purple-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Investment</p>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-800">৳{investment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl p-5 border border-slate-200">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<TrendingUp className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Total Return</p>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-green-600">৳{investment.actualEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl p-5 border border-slate-200">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 bg-amber-100 rounded-lg flex items-center justify-center">
|
||||
<Clock className="w-5 h-5 text-amber-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Pending</p>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-amber-600">৳{Math.round(investment.totalInvestment * 0.24 - investment.actualEarnings).toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl p-5 border border-slate-200">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<Percent className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Expected ROI</p>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-blue-600">24%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="flex gap-2 border-b border-slate-200 p-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('overview')}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'overview' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
||||
>
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('transactions')}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'transactions' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
||||
>
|
||||
Transactions
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('pnl')}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'pnl' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
||||
>
|
||||
P&L Statement
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('journals')}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${activeTab === 'journals' ? 'bg-investor text-white' : 'text-slate-600 hover:bg-slate-100'}`}
|
||||
>
|
||||
Journals
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5">
|
||||
{activeTab === 'overview' && (
|
||||
<div className="space-y-6">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="bg-slate-50 rounded-xl p-4">
|
||||
<h4 className="font-semibold text-slate-800 mb-3 flex items-center gap-2">
|
||||
<FileText className="w-4 h-4 text-slate-500" />
|
||||
Investment Details
|
||||
</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Plan Name</span>
|
||||
<span className="font-medium">{investment.planName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Plan Type</span>
|
||||
<span className="font-medium capitalize">{investment.planType}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Period</span>
|
||||
<span className="font-medium">{investment.startDate} - {investment.endDate || 'Ongoing'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Payment</span>
|
||||
<span className="font-medium capitalize">{investment.paymentMethod}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Transaction ID</span>
|
||||
<span className="font-medium">#{investment.id?.slice(-8)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-50 rounded-xl p-4">
|
||||
<h4 className="font-semibold text-slate-800 mb-3 flex items-center gap-2">
|
||||
<Shield className="w-4 h-4 text-slate-500" />
|
||||
Plan Configuration
|
||||
</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">EV Base Price</span>
|
||||
<span className="font-medium">৳200,000</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Min Quantity</span>
|
||||
<span className="font-medium">1 bike</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Duration</span>
|
||||
<span className="font-medium">12 months</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Lock-in</span>
|
||||
<span className="font-medium">3 months</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Exit Penalty</span>
|
||||
<span className="font-medium text-red-500">10%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
|
||||
<h4 className="font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
||||
<Percent className="w-4 h-4" />
|
||||
FICO Share - Jaiben's Profit per Ride
|
||||
</h4>
|
||||
<p className="text-xs text-amber-600 mb-3">Profit sharing when bikes are rented to end customers</p>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="bg-white rounded-lg p-3 text-center border border-amber-200">
|
||||
<p className="text-xs text-slate-500 mb-1">Single Rent</p>
|
||||
<p className="text-lg font-bold text-slate-800">45%</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-3 text-center border border-amber-200">
|
||||
<p className="text-xs text-slate-500 mb-1">Rent to Own</p>
|
||||
<p className="text-lg font-bold text-slate-800">55%</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-3 text-center border border-amber-200">
|
||||
<p className="text-xs text-slate-500 mb-1">Share EV</p>
|
||||
<p className="text-lg font-bold text-slate-800">60%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'transactions' && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h4 className="font-semibold text-slate-800">Transaction History</h4>
|
||||
<button className="px-3 py-1.5 text-xs bg-investor text-white rounded-lg">Export</button>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-slate-50">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500">Date</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500">Description</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500">Type</th>
|
||||
<th className="px-4 py-3 text-right text-xs font-medium text-slate-500">Amount</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
<tr>
|
||||
<td className="px-4 py-3 text-sm text-slate-500">2024-01-15</td>
|
||||
<td className="px-4 py-3 text-sm">Investment Funded</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Credit</span></td>
|
||||
<td className="px-4 py-3 text-sm text-right font-medium text-green-600">+৳85,000</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-4 py-3 text-sm text-slate-500">2024-02-15</td>
|
||||
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
||||
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳1,700</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-4 py-3 text-sm text-slate-500">2024-03-15</td>
|
||||
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
||||
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳1,700</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-4 py-3 text-sm text-slate-500">2024-04-15</td>
|
||||
<td className="px-4 py-3 text-sm">Monthly Return</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">Return</span></td>
|
||||
<td className="px-4 py-3 text-sm text-right font-medium text-blue-600">+৳1,700</td>
|
||||
<td className="px-4 py-3 text-sm"><span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Completed</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'pnl' && (
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800 mb-4">Profit & Loss Statement</h4>
|
||||
<div className="bg-slate-50 rounded-xl p-5 space-y-4">
|
||||
<div className="flex justify-between items-center pb-3 border-b border-slate-200">
|
||||
<span className="text-slate-500">Total Revenue (Ride Rentals)</span>
|
||||
<span className="text-lg font-bold text-green-600">৳1,50,000</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-500">Jaiben's Share (45%)</span>
|
||||
<span className="text-red-500">-৳67,500</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-500">Operational Costs</span>
|
||||
<span className="text-red-500">-৳0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pt-3 border-t border-slate-200">
|
||||
<span className="font-semibold text-slate-800">Net Profit to Investor</span>
|
||||
<span className="text-xl font-bold text-green-600">৳82,500</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'journals' && (
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800 mb-4">Accounting Journal Entries</h4>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-xs font-medium text-green-700">Entry #JE-001 • {investment.startDate}</span>
|
||||
<span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Posted</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-green-200">
|
||||
<div>
|
||||
<p className="text-xs text-green-600 font-medium">Debit (Dr)</p>
|
||||
<p className="text-sm font-medium">Bank - City Bank</p>
|
||||
<p className="text-xs text-slate-400">1200</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{investment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-6 h-6 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-2 bg-white rounded border border-blue-200">
|
||||
<div>
|
||||
<p className="text-xs text-blue-600 font-medium">Credit (Cr)</p>
|
||||
<p className="text-sm font-medium">Investor Liabilities</p>
|
||||
<p className="text-xs text-slate-400">2200</p>
|
||||
</div>
|
||||
<p className="font-bold text-blue-700">৳{investment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-5">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h4 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||
<Bike className="w-4 h-4 text-slate-500" />
|
||||
Assigned Bikes ({assignedBikes.length})
|
||||
</h4>
|
||||
<button onClick={() => setShowAddBikeModal(true)} className="px-3 py-1.5 text-xs bg-investor text-white rounded-lg flex items-center gap-1 hover:bg-investor-dark transition-colors">
|
||||
<Plus className="w-3 h-3" /> Add Bike
|
||||
</button>
|
||||
</div>
|
||||
{assignedBikes.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{assignedBikes.map((bike: any) => {
|
||||
const bikeEarnings = initialTransactions.filter(
|
||||
(t: any) => t.investorId === investorId && t.bikeId === bike.id
|
||||
).reduce((sum: number, t: any) => sum + t.amount, 0);
|
||||
const planColors: Record<string, string> = {
|
||||
silver: 'from-slate-400 to-slate-500',
|
||||
gold: 'from-amber-400 to-amber-600',
|
||||
platinum: 'from-purple-400 to-purple-600',
|
||||
diamond: 'from-blue-400 to-blue-600',
|
||||
};
|
||||
const planBg: Record<string, string> = {
|
||||
silver: 'bg-slate-50 border-slate-200',
|
||||
gold: 'bg-amber-50 border-amber-200',
|
||||
platinum: 'bg-purple-50 border-purple-200',
|
||||
diamond: 'bg-blue-50 border-blue-200',
|
||||
};
|
||||
const planText: Record<string, string> = {
|
||||
silver: 'text-slate-600',
|
||||
gold: 'text-amber-700',
|
||||
platinum: 'text-purple-700',
|
||||
diamond: 'text-blue-700',
|
||||
};
|
||||
const planBadge: Record<string, string> = {
|
||||
silver: 'bg-slate-100 text-slate-600',
|
||||
gold: 'bg-amber-100 text-amber-700',
|
||||
platinum: 'bg-purple-100 text-purple-700',
|
||||
diamond: 'bg-blue-100 text-blue-700',
|
||||
};
|
||||
const rentalTypes: Record<string, { label: string; color: string; bg: string }> = {
|
||||
single_rent: { label: 'Single Rent', color: 'text-green-600', bg: 'bg-green-100' },
|
||||
rent_to_own: { label: 'Rent to Own', color: 'text-blue-600', bg: 'bg-blue-100' },
|
||||
share_ev: { label: 'Share EV', color: 'text-purple-600', bg: 'bg-purple-100' },
|
||||
};
|
||||
const rentalInfo = rentalTypes[bike.rentalType || 'single_rent'] || rentalTypes.single_rent;
|
||||
return (
|
||||
<div
|
||||
key={bike.id}
|
||||
className={`block p-4 rounded-xl border ${planBg[investment.planType]} hover:shadow-md transition-all group`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-14 h-14 bg-gradient-to-br ${planColors[investment.planType]} rounded-xl flex items-center justify-center shadow-sm`}>
|
||||
<Bike className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h5 className="font-semibold text-slate-800 truncate">{bike.model}</h5>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${planBadge[investment.planType]} capitalize`}>
|
||||
{investment.planType}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">{bike.plateNumber} • {bike.brand}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center gap-1 justify-end mb-1">
|
||||
<TrendingUp className="w-3 h-3 text-green-600" />
|
||||
<span className="text-sm font-bold text-green-600">৳{bikeEarnings.toLocaleString()}</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400">Total Earnings</p>
|
||||
</div>
|
||||
<Link href={`/bikes?bike=${bike.id}`} className="p-2 hover:bg-white/50 rounded-lg">
|
||||
<ArrowRight className="w-4 h-4 text-slate-400 group-hover:text-investor transition-colors" />
|
||||
</Link>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setBikeToDelete(bike);
|
||||
setShowDeleteBikeModal(true);
|
||||
}}
|
||||
className="p-2 hover:bg-red-50 rounded-lg text-slate-400 hover:text-red-600 transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-3 border-t border-slate-200/50 grid grid-cols-2 md:grid-cols-5 gap-3">
|
||||
<div>
|
||||
<p className="text-xs text-slate-400">Status</p>
|
||||
<p className={`text-sm font-medium capitalize ${bike.status === 'rented' ? 'text-green-600' :
|
||||
bike.status === 'available' ? 'text-blue-600' :
|
||||
bike.status === 'maintenance' ? 'text-amber-600' : 'text-slate-600'
|
||||
}`}>
|
||||
{bike.status}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400">Daily Rent</p>
|
||||
<p className="text-sm font-medium text-slate-700">৳{bike.currentRent}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400">Battery</p>
|
||||
<p className="text-sm font-medium text-slate-700">{bike.batteryLevel}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400">Location</p>
|
||||
<p className="text-sm font-medium text-slate-700 truncate">{bike.location}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400">Rental Type</p>
|
||||
<span className={`inline-block px-2 py-0.5 rounded text-xs font-medium ${rentalInfo.bg} ${rentalInfo.color}`}>
|
||||
{rentalInfo.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-10 border-2 border-dashed border-slate-200 rounded-xl">
|
||||
<Bike className="w-10 h-10 mx-auto mb-3 text-slate-300" />
|
||||
<p className="text-sm text-slate-500 mb-3">No bikes assigned to this investment yet</p>
|
||||
<button onClick={() => setShowAddBikeModal(true)} className="px-4 py-2 text-sm bg-investor text-white rounded-lg hover:bg-investor-dark transition-colors">
|
||||
Assign First Bike
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-5">
|
||||
<h4 className="font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-slate-500" />
|
||||
Investor Profile
|
||||
</h4>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-blue-500 flex items-center justify-center">
|
||||
<span className="text-lg font-bold text-white">{investor.name.charAt(0)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">{investor.name}</p>
|
||||
<p className="text-sm text-slate-500">{investor.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Phone</span>
|
||||
<span className="font-medium">{investor.phone}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">ID</span>
|
||||
<span className="font-medium">{investor.id}</span>
|
||||
</div>
|
||||
<Link href={`/admin/investors/${investorId}`} className="flex items-center justify-between text-sm text-investor hover:underline">
|
||||
<span>View Full Profile</span>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-5">
|
||||
<h4 className="font-semibold text-slate-800 mb-4">Quick Actions</h4>
|
||||
<div className="space-y-2">
|
||||
<button className="w-full px-4 py-2 text-left text-sm text-slate-600 hover:bg-slate-50 rounded-lg flex items-center gap-2">
|
||||
<Download className="w-4 h-4" /> Download Statement
|
||||
</button>
|
||||
<button className="w-full px-4 py-2 text-left text-sm text-slate-600 hover:bg-slate-50 rounded-lg flex items-center gap-2">
|
||||
<Printer className="w-4 h-4" /> Print Report
|
||||
</button>
|
||||
<button className="w-full px-4 py-2 text-left text-sm text-slate-600 hover:bg-slate-50 rounded-lg flex items-center gap-2">
|
||||
<BarChart3 className="w-4 h-4" /> Export to Excel
|
||||
</button>
|
||||
<button className="w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50 rounded-lg flex items-center gap-2">
|
||||
<X className="w-4 h-4" /> Close Investment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showAddBikeModal && (
|
||||
<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-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||
<Bike className="w-5 h-5" /> Assign Bike to Investment
|
||||
</h3>
|
||||
<button onClick={() => setShowAddBikeModal(false)} className="p-1 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-500" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Select Bike</label>
|
||||
<select
|
||||
value={selectedBikeId}
|
||||
onChange={(e) => setSelectedBikeId(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor"
|
||||
>
|
||||
<option value="">Choose a bike...</option>
|
||||
{initialBikes.filter((b: any) => !b.investorId).map((bike: any) => (
|
||||
<option key={bike.id} value={bike.id}>{bike.model} - {bike.plateNumber}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowAddBikeModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
|
||||
Cancel
|
||||
</button>
|
||||
<button className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark">
|
||||
Assign Bike
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showEditModal && (
|
||||
<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-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||
<Edit className="w-5 h-5" /> Edit Investment
|
||||
</h3>
|
||||
<button onClick={() => setShowEditModal(false)} className="p-1 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-500" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Plan Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editForm.planName || ''}
|
||||
onChange={(e) => setEditForm({ ...editForm, planName: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Plan Type</label>
|
||||
<select
|
||||
value={editForm.planType || 'gold'}
|
||||
onChange={(e) => setEditForm({ ...editForm, planType: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor"
|
||||
>
|
||||
<option value="silver">Silver</option>
|
||||
<option value="gold">Gold</option>
|
||||
<option value="platinum">Platinum</option>
|
||||
<option value="diamond">Diamond</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* <div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Investment Amount</label>
|
||||
<input
|
||||
type="number"
|
||||
value={editForm.totalInvestment || 0}
|
||||
onChange={(e) => setEditForm({ ...editForm, totalInvestment: Number(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor"
|
||||
/>
|
||||
</div> */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Status</label>
|
||||
<select
|
||||
value={editForm.status || 'active'}
|
||||
onChange={(e) => setEditForm({ ...editForm, status: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor"
|
||||
>
|
||||
<option value="active">Active</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowEditModal(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={() => {
|
||||
console.log('Saving investment:', editForm);
|
||||
setShowEditModal(false);
|
||||
}}
|
||||
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showDeleteBikeModal && bikeToDelete && (
|
||||
<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-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||
<Trash2 className="w-5 h-5 text-red-500" /> Remove Bike
|
||||
</h3>
|
||||
<button onClick={() => setShowDeleteBikeModal(false)} className="p-1 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-500" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<p className="text-slate-600 mb-4">Are you sure you want to remove this bike from this investment?</p>
|
||||
<div className="bg-slate-50 rounded-lg p-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Bike</span>
|
||||
<span className="font-medium">{bikeToDelete.model}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Plate</span>
|
||||
<span className="font-medium">{bikeToDelete.plateNumber}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Investment</span>
|
||||
<span className="font-medium">{investment.planName}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-amber-600 mt-3 flex items-center gap-2">
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
This action will unassign the bike from the investor.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowDeleteBikeModal(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={() => {
|
||||
console.log('Removing bike:', bikeToDelete.id);
|
||||
setShowDeleteBikeModal(false);
|
||||
}}
|
||||
className="px-4 py-2 bg-red-500 text-white rounded-lg text-sm font-medium hover:bg-red-600"
|
||||
>
|
||||
Remove Bike
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -86,6 +86,8 @@ export default function InvestorDetailPage() {
|
||||
const [showAssignBikeModal, setShowAssignBikeModal] = useState(false);
|
||||
const [selectedBikeId, setSelectedBikeId] = useState('');
|
||||
const [showCreateInvestmentModal, setShowCreateInvestmentModal] = useState(false);
|
||||
const [showInvestmentSuccessModal, setShowInvestmentSuccessModal] = useState(false);
|
||||
const [lastCreatedInvestment, setLastCreatedInvestment] = useState<any>(null);
|
||||
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
|
||||
const [showJournalModal, setShowJournalModal] = useState(false);
|
||||
const [selectedInvoice, setSelectedInvoice] = useState<any>(null);
|
||||
@@ -172,35 +174,22 @@ export default function InvestorDetailPage() {
|
||||
paymentMethod: newInvestment.paymentMethod
|
||||
};
|
||||
|
||||
setInvestorJournals([journalEntry, ...investorJournals]);
|
||||
setInvestorJournals([journalEntry, ...investorJournals]);
|
||||
|
||||
console.log('Creating Investment:', {
|
||||
setLastCreatedInvestment({
|
||||
id: invId,
|
||||
investorId: investor.id,
|
||||
...newInvestment,
|
||||
actualEarnings: 0,
|
||||
status: 'active' as const,
|
||||
status: 'active',
|
||||
transactionId: transactionRef,
|
||||
createdAt: new Date().toISOString()
|
||||
createdAt: new Date().toISOString(),
|
||||
debitAccount,
|
||||
journalEntry
|
||||
});
|
||||
|
||||
alert(`Investment created successfully!
|
||||
|
||||
Investor: ${investor.name}
|
||||
Investment ID: ${invId}
|
||||
Transaction Ref: ${transactionRef}
|
||||
Amount: ৳${newInvestment.totalInvestment.toLocaleString()}
|
||||
|
||||
Accounting Entry Created (Auto-Journal):
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Date: ${newInvestment.startDate}
|
||||
Ref: ${transactionRef}
|
||||
Description: ${investor.name} - ${newInvestment.planName}
|
||||
|
||||
Debit (Dr): ${debitAccount.name} ৳${newInvestment.totalInvestment.toLocaleString()}
|
||||
Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleString()}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
||||
setShowCreateInvestmentModal(false);
|
||||
setShowInvestmentSuccessModal(true);
|
||||
setNewInvestment({
|
||||
planName: '',
|
||||
planType: 'gold',
|
||||
@@ -908,59 +897,114 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
{activeTab === 'bikes' && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-slate-800">Assigned Bikes</h3>
|
||||
<button
|
||||
onClick={() => setShowAssignBikeModal(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" /> Assign Bike
|
||||
</button>
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-800">Assigned Bikes</h3>
|
||||
<p className="text-sm text-slate-500">{assignedBikes.length} bikes across {investor.investments?.length || 0} investment plans</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{assignedBikes.map(bike => (
|
||||
<div key={bike.id} className="bg-slate-50 rounded-xl p-4">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-xl bg-white flex items-center justify-center shadow-sm">
|
||||
<Bike className="w-6 h-6 text-blue-600" />
|
||||
{assignedBikes.map(bike => {
|
||||
const investment = investor.investments?.find((inv: any) => inv.id === bike.investmentId);
|
||||
const planColors: Record<string, string> = {
|
||||
silver: 'from-slate-400 to-slate-600',
|
||||
gold: 'from-amber-400 to-amber-600',
|
||||
platinum: 'from-purple-400 to-purple-600',
|
||||
diamond: 'from-blue-400 to-blue-600',
|
||||
};
|
||||
const planBadges: Record<string, string> = {
|
||||
silver: 'bg-slate-100 text-slate-600',
|
||||
gold: 'bg-amber-100 text-amber-700',
|
||||
platinum: 'bg-purple-100 text-purple-700',
|
||||
diamond: 'bg-blue-100 text-blue-700',
|
||||
};
|
||||
const planBg: Record<string, string> = {
|
||||
silver: 'bg-slate-50 border-slate-200',
|
||||
gold: 'bg-amber-50 border-amber-200',
|
||||
platinum: 'bg-purple-50 border-purple-200',
|
||||
diamond: 'bg-blue-50 border-blue-200',
|
||||
};
|
||||
const rentalTypes: Record<string, { label: string; color: string }> = {
|
||||
single_rent: { label: 'Single Rent', color: 'text-green-600' },
|
||||
rent_to_own: { label: 'Rent to Own', color: 'text-blue-600' },
|
||||
share_ev: { label: 'Share EV', color: 'text-purple-600' },
|
||||
};
|
||||
const statusConfig: Record<string, { bg: string; color: string }> = {
|
||||
rented: { bg: 'bg-green-100', color: 'text-green-700' },
|
||||
available: { bg: 'bg-blue-100', color: 'text-blue-700' },
|
||||
maintenance: { bg: 'bg-amber-100', color: 'text-amber-700' },
|
||||
retired: { bg: 'bg-slate-100', color: 'text-slate-600' },
|
||||
};
|
||||
const rentalInfo = rentalTypes[bike.rentalType || 'single_rent'];
|
||||
const planType = investment?.planType || 'gold';
|
||||
const status = statusConfig[bike.status] || statusConfig.available;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={bike.id}
|
||||
href={`/bikes?bike=${bike.id}`}
|
||||
className={`block bg-white rounded-xl border ${planBg[planType]} overflow-hidden hover:shadow-lg transition-all hover:scale-[1.02] cursor-pointer group`}
|
||||
>
|
||||
<div className={`h-2 bg-gradient-to-r ${planColors[planType]}`} />
|
||||
<div className="p-4">
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${planColors[planType]} flex items-center justify-center shadow-sm`}>
|
||||
<Bike className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-semibold text-slate-800 truncate">{bike.model}</h4>
|
||||
<p className="text-sm text-slate-500">{bike.brand}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${status.bg} ${status.color} capitalize`}>
|
||||
{bike.status}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-700">{bike.model}</p>
|
||||
<p className="text-xs text-slate-400">{bike.brand}</p>
|
||||
|
||||
<div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${planBadges[planType]} capitalize mb-3`}>
|
||||
<span className={`w-2 h-2 rounded-full ${
|
||||
planType === 'silver' ? 'bg-slate-500' :
|
||||
planType === 'gold' ? 'bg-amber-500' :
|
||||
planType === 'platinum' ? 'bg-purple-500' : 'bg-blue-500'
|
||||
}`} />
|
||||
{planType} Plan • {investment?.planName || 'Investment'}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="bg-white/80 rounded-lg p-2">
|
||||
<p className="text-xs text-slate-400">Plate</p>
|
||||
<p className="text-sm font-medium text-slate-700 truncate">{bike.plateNumber.split('-').pop()}</p>
|
||||
</div>
|
||||
<div className="bg-white/80 rounded-lg p-2">
|
||||
<p className="text-xs text-slate-400">Location</p>
|
||||
<p className="text-sm font-medium text-slate-700 truncate">{bike.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/80 rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Rental Type</span>
|
||||
<span className={`text-xs font-semibold ${rentalInfo.color}`}>{rentalInfo.label}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Total Earnings</span>
|
||||
<span className="text-sm font-bold text-green-600">৳{bike.totalEarnings?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Battery</span>
|
||||
<span className={`text-xs font-medium ${
|
||||
bike.batteryLevel > 50 ? 'text-green-600' :
|
||||
bike.batteryLevel > 20 ? 'text-amber-600' : 'text-red-600'
|
||||
}`}>{bike.batteryLevel}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${bikeStatusColors[bike.status]}`}>
|
||||
{bike.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Plate</span>
|
||||
<span className="font-medium">{bike.plateNumber}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Location</span>
|
||||
<span className="font-medium">{bike.location}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Battery</span>
|
||||
<span className={`font-medium ${bike.batteryLevel > 50 ? 'text-green-600' : bike.batteryLevel > 20 ? 'text-amber-600' : 'text-red-600'}`}>{bike.batteryLevel}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Purchase Price</span>
|
||||
<span className="font-medium text-purple-600">৳{bike.purchasePrice?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Total Earnings</span>
|
||||
<span className="font-medium text-green-600">৳{bike.totalEarnings?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{assignedBikes.length === 0 && (
|
||||
<div className="col-span-full text-center py-8 text-slate-400">
|
||||
<Bike className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p>No bikes assigned yet</p>
|
||||
<div className="col-span-full text-center py-12 border-2 border-dashed border-slate-200 rounded-xl">
|
||||
<Bike className="w-12 h-12 mx-auto mb-3 text-slate-300" />
|
||||
<p className="text-slate-500">No bikes assigned to this investor</p>
|
||||
<p className="text-sm text-slate-400 mt-1">Bikes will appear here once assigned to investments</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -970,57 +1014,92 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
{activeTab === 'investments' && (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-slate-800">Investment Plans</h3>
|
||||
<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
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-800">Investment Plans</h3>
|
||||
<p className="text-sm text-slate-500">Manage investment portfolios for this investor</p>
|
||||
</div>
|
||||
<button onClick={() => setShowCreateInvestmentModal(true)} className="py-2 px-4 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> Create Investment
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{investor.investments?.map((inv) => (
|
||||
<div key={inv.id} className="bg-slate-50 rounded-xl p-4">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<p className="font-semibold text-slate-700">{inv.planName}</p>
|
||||
<p className="text-xs text-slate-400">{inv.planType} Plan</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{investor.investments?.map((inv) => {
|
||||
const planConfig: Record<string, { bg: string; border: string; icon: string }> = {
|
||||
silver: { bg: 'bg-slate-100', border: 'border-slate-300', icon: 'text-slate-500' },
|
||||
gold: { bg: 'bg-amber-100', border: 'border-amber-300', icon: 'text-amber-500' },
|
||||
platinum: { bg: 'bg-purple-100', border: 'border-purple-300', icon: 'text-purple-500' },
|
||||
diamond: { bg: 'bg-blue-100', border: 'border-blue-300', icon: 'text-blue-500' },
|
||||
};
|
||||
const style = planConfig[inv.planType] || planConfig.gold;
|
||||
|
||||
return (
|
||||
<div key={inv.id} className={`bg-white rounded-xl border ${style.border} overflow-hidden`}>
|
||||
<div className={`${style.bg} p-4 flex items-center justify-between`}>
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800">{inv.planName}</h4>
|
||||
<p className="text-sm text-slate-500 capitalize">{inv.planType} Plan</p>
|
||||
</div>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${inv.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-200 text-slate-600'}`}>
|
||||
{inv.status}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${planColors[inv.planType]}`}>
|
||||
{inv.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs mb-3">
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Investment</p>
|
||||
<p className="font-medium text-purple-600">৳{inv.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Monthly Return</p>
|
||||
<p className="font-medium text-green-600">৳{inv.monthlyReturn.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Expected ROI</p>
|
||||
<p className="font-medium text-slate-700">{inv.expectedRoi}%</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-2">
|
||||
<p className="text-slate-400">Actual Earned</p>
|
||||
<p className="font-medium text-green-600">৳{inv.actualEarnings.toLocaleString()}</p>
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<p className="text-xs text-slate-500">Investment</p>
|
||||
<p className="font-bold text-slate-800">৳{inv.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="bg-purple-50 rounded-lg p-3">
|
||||
<p className="text-xs text-purple-600">Total Return</p>
|
||||
<p className="font-bold text-purple-700">৳{inv.actualEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-50 rounded-lg p-3 space-y-1.5">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-slate-400">Duration</span>
|
||||
<span className="font-medium">12 months</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-slate-400">Lock-in Period</span>
|
||||
<span className="font-medium">3 months</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-slate-400">Early Exit Penalty</span>
|
||||
<span className="font-medium text-red-500">10%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-slate-400">{inv.startDate} - {inv.endDate || 'Ongoing'}</span>
|
||||
<span className="capitalize text-slate-500">{inv.paymentMethod}</span>
|
||||
</div>
|
||||
<div className="pt-3 border-t border-slate-100">
|
||||
<p className="text-xs text-slate-400 mb-2">ID: #{inv.id?.slice(-6) || 'N/A'}</p>
|
||||
<div className="flex gap-2">
|
||||
<Link href={`/admin/investors/${investor.id}/investments/${inv.id}`} className="flex-1 py-2 text-sm font-medium text-slate-600 hover:bg-slate-50 rounded-lg border border-slate-200 text-center">
|
||||
View
|
||||
</Link>
|
||||
<button className="flex-1 py-2 text-sm font-medium text-investor hover:bg-investor/10 rounded-lg border border-investor/30">Statement</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-400">{inv.startDate} to {inv.endDate || 'Ongoing'}</span>
|
||||
<span className="capitalize">{inv.paymentMethod}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(!investor.investments || investor.investments.length === 0) && (
|
||||
<div className="col-span-full text-center py-8 text-slate-400">
|
||||
<TrendingUp className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p>No investments yet</p>
|
||||
<button className="mt-2 px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">
|
||||
Create First Investment
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{(!investor.investments || investor.investments.length === 0) && (
|
||||
<div className="text-center py-16 bg-slate-50 rounded-xl border-2 border-dashed border-slate-200">
|
||||
<TrendingUp className="w-12 h-12 mx-auto mb-3 text-slate-300" />
|
||||
<h4 className="font-semibold text-slate-600 mb-2">No Investments Yet</h4>
|
||||
<p className="text-slate-400 mb-4">Create your first investment plan for this investor</p>
|
||||
<button onClick={() => setShowCreateInvestmentModal(true)} className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark">
|
||||
<Plus className="w-4 h-4 inline mr-1" /> Create Investment
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1455,15 +1534,46 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
|
||||
{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="bg-white rounded-xl shadow-xl w-full max-w-3xl 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>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-slate-800">Create New Investment</h2>
|
||||
<p className="text-sm text-slate-500">Set up investment for {investor.name}</p>
|
||||
</div>
|
||||
<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="p-5 overflow-y-auto flex-1 space-y-5">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-2 block">Select Plan Template</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{[
|
||||
{ id: 'inv_demo_1', name: '1 Bike Plan', tier: 'Economy', evBasePrice: 200000, minQuantity: 1, duration: 12, maxInvestment: 1000000 },
|
||||
{ id: 'inv_demo_2', name: '5 Bike Plan', tier: 'Standard', evBasePrice: 180000, minQuantity: 5, duration: 24, maxInvestment: 5000000 },
|
||||
].map(plan => (
|
||||
<button
|
||||
key={plan.id}
|
||||
onClick={() => {
|
||||
setNewInvestment({
|
||||
...newInvestment,
|
||||
planName: plan.name,
|
||||
planType: plan.tier.toLowerCase() as any,
|
||||
totalInvestment: plan.evBasePrice * plan.minQuantity,
|
||||
monthlyReturn: 0
|
||||
});
|
||||
}}
|
||||
className={`p-4 rounded-lg border-2 text-left transition-all ${newInvestment.planName === plan.name ? 'border-investor bg-investor/5' : 'border-slate-200 hover:border-investor/50'}`}
|
||||
>
|
||||
<p className="font-semibold text-slate-800">{plan.name}</p>
|
||||
<p className="text-xs text-slate-500">{plan.evBasePrice.toLocaleString()} × {plan.minQuantity} bikes</p>
|
||||
<p className="text-sm text-slate-600 mt-1">Duration: {plan.duration} months</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Name *</label>
|
||||
@@ -1472,77 +1582,90 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
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"
|
||||
placeholder="Plan name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Plan Type *</label>
|
||||
<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 })}
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, planType: e.target.value as any })}
|
||||
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>
|
||||
<option value="silver">Silver</option>
|
||||
<option value="gold">Gold</option>
|
||||
<option value="platinum">Platinum</option>
|
||||
<option value="diamond">Diamond</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">EV Base Price (৳)</label>
|
||||
<input type="number" value={200000} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Minimum Quantity (Bikes)</label>
|
||||
<input type="number" value={1} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Min Investment (৳)</label>
|
||||
<input type="number" value={200000} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
<p className="text-xs text-slate-400 mt-1">= Qty × Base Price</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 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"
|
||||
onChange={(e) => setNewInvestment({ ...newInvestment, totalInvestment: Number(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-yellow-50"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Max Investment (৳)</label>
|
||||
<input type="number" value={1000000} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Duration (Months)</label>
|
||||
<input type="number" value={12} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</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"
|
||||
/>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Lock-in Period (Months)</label>
|
||||
<input type="number" value={3} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Early Exit Penalty (%)</label>
|
||||
<input type="number" value={10} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-100 cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
|
||||
<h4 className="text-sm font-semibold text-amber-800 mb-3 flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
FICO Share - Jaiben's Profit per Ride
|
||||
</h4>
|
||||
<p className="text-xs text-amber-600 mb-3">Profit sharing when bikes are rented to end customers</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Single Rent (%)</label>
|
||||
<input type="number" value={45} disabled className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm bg-white cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Rent to Own (%)</label>
|
||||
<input type="number" value={55} disabled className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm bg-white cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Share an EV (%)</label>
|
||||
<input type="number" value={60} disabled className="w-full px-3 py-2 border border-amber-200 rounded-lg text-sm bg-white cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1569,7 +1692,7 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Payment Method *</label>
|
||||
<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 })}
|
||||
@@ -1594,72 +1717,66 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Notes</label>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Description</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>
|
||||
|
||||
{(() => {
|
||||
const getDebitAccountDisplay = (method: string) => {
|
||||
switch (method) {
|
||||
case 'bank': return 'Bank - City Bank (1200)';
|
||||
case 'cash': return 'Cash in Hand (1100)';
|
||||
case 'mobile': return 'bKash Business (1300)';
|
||||
case 'cheque': return 'Cheque Receivable (1410)';
|
||||
default: return 'Bank - City Bank (1200)';
|
||||
}
|
||||
};
|
||||
const debitDisplay = getDebitAccountDisplay(newInvestment.paymentMethod);
|
||||
|
||||
return (
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-5 h-5 text-green-600" />
|
||||
<h4 className="font-semibold text-green-800">Auto-Journal Entry</h4>
|
||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
|
||||
<h4 className="font-semibold text-green-800 mb-3 flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
Auto-Journal Entry
|
||||
</h4>
|
||||
<div className="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div className="flex items-center justify-between text-sm mb-2">
|
||||
<span className="text-slate-500">Date:</span>
|
||||
<span className="font-medium">{newInvestment.startDate || 'Not set'}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm mb-3">
|
||||
<span className="text-slate-500">Reference:</span>
|
||||
<span className="font-medium">{newInvestment.transactionReference || `INV/${new Date().getFullYear()}/auto`}</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg border border-green-200">
|
||||
<div>
|
||||
<p className="text-xs text-green-600 font-medium uppercase">Debit (Dr)</p>
|
||||
<p className="font-medium text-slate-800">
|
||||
{newInvestment.paymentMethod === 'bank' ? 'Bank - City Bank' : newInvestment.paymentMethod === 'cash' ? 'Cash in Hand' : 'bKash Business'} ({newInvestment.paymentMethod === 'bank' ? '1200' : newInvestment.paymentMethod === 'cash' ? '1100' : '1300'})
|
||||
</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="text-sm text-green-700 space-y-2">
|
||||
<p className="text-xs text-green-600 uppercase font-medium mb-2">Double EntryAccounting</p>
|
||||
<div className="flex items-center justify-between bg-white rounded p-2 border border-green-100">
|
||||
<div>
|
||||
<p className="font-medium">Debit (Dr)</p>
|
||||
<p className="text-xs text-green-600">{debitDisplay}</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-8 h-8 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600">▼</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-1">
|
||||
<div className="w-6 h-6 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between bg-white rounded p-2 border border-green-100">
|
||||
<div>
|
||||
<p className="font-medium">Credit (Cr)</p>
|
||||
<p className="text-xs text-green-600">Investor Liabilities (2200)</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="mt-3 pt-2 border-t border-green-200">
|
||||
<p className="text-xs text-green-600">Transaction Ref: Auto Generate</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<div>
|
||||
<p className="text-xs text-blue-600 font-medium uppercase">Credit (Cr)</p>
|
||||
<p className="font-medium text-slate-800">Investor Liabilities (2200)</p>
|
||||
</div>
|
||||
<p className="font-bold text-blue-700">৳{newInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</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={() => 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"
|
||||
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" /> Create Investment
|
||||
<Plus className="w-4 h-4" /> Create Investment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2015,6 +2132,90 @@ Credit (Cr): Investor Liability ৳${newInvestment.totalInvestment.toLocaleStri
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showInvestmentSuccessModal && lastCreatedInvestment && (
|
||||
<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-5 border-b border-slate-100 flex items-center justify-between bg-green-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<Check className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-green-800">Investment Created!</h2>
|
||||
<p className="text-sm text-green-600">Auto-journal entry generated</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => setShowInvestmentSuccessModal(false)} className="p-2 hover:bg-green-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-green-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-4">
|
||||
<div className="bg-slate-50 rounded-lg p-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Investment ID</span>
|
||||
<span className="text-sm font-medium">#{lastCreatedInvestment.id.slice(-8)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Transaction Ref</span>
|
||||
<span className="text-sm font-medium">{lastCreatedInvestment.transactionId}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Amount</span>
|
||||
<span className="text-sm font-bold text-green-600">৳{lastCreatedInvestment.totalInvestment.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Plan</span>
|
||||
<span className="text-sm font-medium">{lastCreatedInvestment.planName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-slate-500">Date</span>
|
||||
<span className="text-sm font-medium">{lastCreatedInvestment.startDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
|
||||
<h4 className="text-sm font-semibold text-green-800 mb-3 flex items-center gap-2">
|
||||
<FileText className="w-4 h-4" />
|
||||
Auto-Journal Entry
|
||||
</h4>
|
||||
<div className="bg-white rounded-lg p-3 border border-green-100">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between p-2 bg-green-50 rounded border border-green-200">
|
||||
<div>
|
||||
<p className="text-xs text-green-600 font-medium uppercase">Debit (Dr)</p>
|
||||
<p className="text-sm font-medium text-slate-800">{lastCreatedInvestment.debitAccount?.name}</p>
|
||||
<p className="text-xs text-slate-400">{lastCreatedInvestment.debitAccount?.code}</p>
|
||||
</div>
|
||||
<p className="font-bold text-green-700">৳{lastCreatedInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-6 h-6 rounded-full bg-green-200 flex items-center justify-center">
|
||||
<span className="text-green-600 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-2 bg-blue-50 rounded border border-blue-200">
|
||||
<div>
|
||||
<p className="text-xs text-blue-600 font-medium uppercase">Credit (Cr)</p>
|
||||
<p className="text-sm font-medium text-slate-800">Investor Liabilities</p>
|
||||
<p className="text-xs text-slate-400">2200</p>
|
||||
</div>
|
||||
<p className="font-bold text-blue-700">৳{lastCreatedInvestment.totalInvestment.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowInvestmentSuccessModal(false)} className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 flex items-center gap-2">
|
||||
<Check className="w-4 h-4" /> Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,8 @@ export interface Bike {
|
||||
location: string;
|
||||
assignedTo?: string;
|
||||
investorId?: string;
|
||||
investmentId?: string;
|
||||
rentalType?: 'single_rent' | 'rent_to_own' | 'share_ev';
|
||||
purchasePrice?: number;
|
||||
purchaseDate?: string;
|
||||
currentRent?: number;
|
||||
@@ -171,14 +173,14 @@ export const users: User[] = [
|
||||
|
||||
export const bikes: Bike[] = [
|
||||
{
|
||||
id: 'EV001', model: 'Etron ET50', brand: 'Etron', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-1234', status: 'rented', batteryLevel: 78, location: 'Gulshan 1', assignedTo: 'u1', investorId: 'inv1', purchasePrice: 85000, purchaseDate: '2024-01-15', currentRent: 350, totalEarnings: 14250,
|
||||
id: 'EV001', model: 'Etron ET50', brand: 'Etron', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-1234', status: 'rented', batteryLevel: 78, location: 'Gulshan 1', assignedTo: 'u1', investorId: 'inv1', investmentId: 'ip1', rentalType: 'single_rent', purchasePrice: 85000, purchaseDate: '2024-01-15', currentRent: 350, totalEarnings: 14250,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash1', bikeId: 'EV001', bikerId: 'u3', bikerName: 'Rahim Khan', assignedAt: '2024-01-20 10:30:00', assignedBy: 'admin1', unassignedAt: '2024-02-15 14:20:00', unassignedBy: 'admin1', reason: 'Bike transfer to another biker', status: 'completed', notes: 'Initial assignment' },
|
||||
{ id: 'ash2', bikeId: 'EV001', bikerId: 'u1', bikerName: 'Karim Ahmed', assignedAt: '2024-02-15 15:00:00', assignedBy: 'admin1', status: 'active', notes: 'Reassigned after maintenance' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'EV002', model: 'Yadea DT3', brand: 'Yadea', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-5678', status: 'available', batteryLevel: 95, location: 'Banani', investorId: 'inv1', purchasePrice: 65000, purchaseDate: '2024-01-20', currentRent: 0, totalEarnings: 12750,
|
||||
id: 'EV002', model: 'Yadea DT3', brand: 'Yadea', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-5678', status: 'available', batteryLevel: 95, location: 'Banani', investorId: 'inv1', investmentId: 'ip2', rentalType: 'rent_to_own', purchasePrice: 65000, purchaseDate: '2024-01-20', currentRent: 0, totalEarnings: 12750,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash3', bikeId: 'EV002', bikerId: 'u1', bikerName: 'Karim Ahmed', assignedAt: '2024-01-25 09:00:00', assignedBy: 'admin1', unassignedAt: '2024-03-01 11:30:00', unassignedBy: 'admin1', reason: 'Rental completed', status: 'completed', notes: 'First rental period' }
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user