From 92554c177ce06fa2702d62fe729e5d7e17d61fc4 Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Wed, 13 May 2026 15:05:58 +0600 Subject: [PATCH] refactor: simplify investor management UI and remove complex CRUD logic for table view --- src/app/admin/investors/page.tsx | 868 +++++++------------------------ 1 file changed, 194 insertions(+), 674 deletions(-) diff --git a/src/app/admin/investors/page.tsx b/src/app/admin/investors/page.tsx index 1714aec..feff245 100644 --- a/src/app/admin/investors/page.tsx +++ b/src/app/admin/investors/page.tsx @@ -2,9 +2,12 @@ import { useState } from 'react'; import Link from 'next/link'; -import { investors as initialInvestors, bikes as initialBikes, transactions } from '@/data/mockData'; +import { investors as initialInvestors, bikes as initialBikes } from '@/data/mockData'; import type { Investor } from '@/data/mockData'; -import { Plus, Search, Filter, Download, Upload, Eye, Edit, Trash2, X, LayoutGrid, List, Phone, Mail, MapPin, Calendar, Banknote, TrendingUp, Wallet, AlertTriangle, User, FileText, CreditCard, Smartphone, Shield, Star, ExternalLink } from 'lucide-react'; + +type InvestorWithImage = Investor & { profileImage: string }; +import { Search, Download, Upload, Edit, Trash2, X, LayoutGrid, List, Phone, Mail, Wallet, TrendingUp, Banknote, Bike, Eye } from 'lucide-react'; +import toast from 'react-hot-toast'; const statusColors: Record = { active: 'bg-green-100 text-green-700', @@ -27,164 +30,47 @@ const kycColors: Record = { not_submitted: 'bg-slate-100 text-slate-500', }; +const investorsWithImages = initialInvestors.map((inv, idx) => ({ + ...inv, + profileImage: `https://picsum.photos/200/200?random=${idx + 1}`, +})); + export default function InvestorsPage() { - const [investors, setInvestors] = useState(initialInvestors); - const [bikes, setBikes] = useState(initialBikes); + const [investors, setInvestors] = useState(investorsWithImages as InvestorWithImage[]); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); - const [planFilter, setPlanFilter] = useState('all'); - const [selectedInvestor, setSelectedInvestor] = useState(null); - const [showModal, setShowModal] = useState(false); - const [showDetailsModal, setShowDetailsModal] = useState(false); - const [editingInvestor, setEditingInvestor] = useState(null); - const [activeTab, setActiveTab] = useState('personal'); - const [sortField, setSortField] = useState('name'); - const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [kycFilter, setKycFilter] = useState('all'); const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); - const [showAssignBikeModal, setShowAssignBikeModal] = useState(false); - const [selectedBikeId, setSelectedBikeId] = useState(''); + const [deleteModal, setDeleteModal] = useState<{ show: boolean; investor: InvestorWithImage | null }>({ show: false, investor: null }); const filteredInvestors = investors.filter(inv => { const matchesSearch = inv.name.toLowerCase().includes(searchQuery.toLowerCase()) || inv.email.toLowerCase().includes(searchQuery.toLowerCase()) || - inv.phone.includes(searchQuery) || - inv.id.toLowerCase().includes(searchQuery.toLowerCase()); + inv.phone.includes(searchQuery); const matchesStatus = statusFilter === 'all' || inv.status === statusFilter; - const matchesPlan = planFilter === 'all' || (inv.investments?.some(inv => inv.planType === planFilter) ?? false); - return matchesSearch && matchesStatus && matchesPlan; + const matchesKyc = kycFilter === 'all' || inv.kycStatus === kycFilter; + return matchesSearch && matchesStatus && matchesKyc; }); - const sortedInvestors = [...filteredInvestors].sort((a, b) => { - const aVal = String(a[sortField as keyof Investor] || ''); - const bVal = String(b[sortField as keyof Investor] || ''); - if (sortOrder === 'asc') return aVal.localeCompare(bVal); - return bVal.localeCompare(aVal); - }); - - const handleSort = (field: string) => { - if (sortField === field) { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); - } else { - setSortField(field); - setSortOrder('asc'); + const handleDeleteInvestor = () => { + if (deleteModal.investor) { + setInvestors(investors.filter(i => i.id !== deleteModal.investor!.id)); + setDeleteModal({ show: false, investor: null }); + toast.success('Investor deleted successfully'); } }; - const handleAddInvestor = () => { - setEditingInvestor({ - id: `INV${String(investors.length + 1).padStart(3, '0')}`, - userId: `u${100 + investors.length + 1}`, - name: '', - email: '', - phone: '', - address: '', - totalInvested: 0, - totalEarnings: 0, - activeBikes: 0, - withdrawalPending: 0, - totalWithdrawn: 0, - pendingEarnings: 0, - roi: 0, - status: 'pending', - createdAt: new Date().toISOString().split('T')[0], - kycStatus: 'not_submitted', - riskLevel: 'low', - totalReferrals: 0, - referralEarnings: 0, - investments: [], - }); - setShowModal(true); + const getInitials = (name: string) => { + return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); }; - const handleEditInvestor = (inv: Investor) => { - setEditingInvestor(JSON.parse(JSON.stringify(inv))); - setShowModal(true); - }; - - const handleDeleteInvestor = (id: string) => { - if (confirm('Are you sure you want to delete this investor?')) { - setInvestors(investors.filter(i => i.id !== id)); - } - }; - - const handleSaveInvestor = () => { - if (editingInvestor) { - const existingIndex = investors.findIndex(i => i.id === editingInvestor.id); - if (existingIndex >= 0) { - const newInvestors = [...investors]; - newInvestors[existingIndex] = editingInvestor; - setInvestors(newInvestors); - } else { - setInvestors([...investors, editingInvestor]); - } - } - setShowModal(false); - setEditingInvestor(null); - }; - - const handleAssignBike = () => { - if (selectedInvestor && selectedBikeId) { - const bikeIndex = bikes.findIndex(b => b.id === selectedBikeId); - if (bikeIndex >= 0) { - const updatedBikes = [...bikes]; - updatedBikes[bikeIndex] = { ...updatedBikes[bikeIndex], investorId: selectedInvestor.id }; - setBikes(updatedBikes); - - const investorIndex = investors.findIndex(i => i.id === selectedInvestor.id); - if (investorIndex >= 0) { - const updatedInvestors = [...investors]; - updatedInvestors[investorIndex] = { - ...updatedInvestors[investorIndex], - activeBikes: updatedInvestors[investorIndex].activeBikes + 1, - totalInvested: updatedInvestors[investorIndex].totalInvested + (bikes[bikeIndex].purchasePrice || 0), - }; - setInvestors(updatedInvestors); - } - } - setShowAssignBikeModal(false); - setSelectedBikeId(''); - } - }; - - const handleUnassignBike = (bikeId: string) => { - if (selectedInvestor && confirm('Are you sure you want to unassign this bike from the investor?')) { - const bikeIndex = bikes.findIndex(b => b.id === bikeId); - if (bikeIndex >= 0) { - const bike = bikes[bikeIndex]; - const updatedBikes = [...bikes]; - updatedBikes[bikeIndex] = { ...updatedBikes[bikeIndex], investorId: undefined }; - setBikes(updatedBikes); - - const investorIndex = investors.findIndex(i => i.id === selectedInvestor.id); - if (investorIndex >= 0) { - const updatedInvestors = [...investors]; - updatedInvestors[investorIndex] = { - ...updatedInvestors[investorIndex], - activeBikes: Math.max(0, updatedInvestors[investorIndex].activeBikes - 1), - totalInvested: updatedInvestors[investorIndex].totalInvested - (bike.purchasePrice || 0), - }; - setInvestors(updatedInvestors); - } - } - } - }; - - const availableBikesForAssignment = bikes.filter(b => !b.investorId && b.status === 'available'); - - const assignedBikes = bikes.filter(b => b.investorId === selectedInvestor?.id); + const isMobile = typeof window !== 'undefined' && window.innerWidth < 1024; return (
-
-
-

Investor Management

-

Manage investor accounts and investments

-
-
- -
+
+

Investors

+

Manage investor accounts and investments

@@ -213,7 +99,7 @@ export default function InvestorsPage() {
- +

{investors.reduce((sum, i) => sum + i.activeBikes, 0)}

@@ -224,7 +110,7 @@ export default function InvestorsPage() {
- +

{investors.length}

@@ -238,19 +124,19 @@ export default function InvestorsPage() {
- setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent" + className="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent" />
- - setKycFilter(e.target.value)} + className="py-2.5 px-3 border border-slate-200 rounded-lg text-sm font-medium text-slate-600 bg-white" > - - - - - + + + - - -
- {viewMode === 'table' ? ( + {(viewMode === 'cards' || isMobile) ? ( +
+ {filteredInvestors.map(inv => ( +
+
+
+ {inv.profileImage ? ( + {inv.name} + ) : ( +
+ {getInitials(inv.name)} +
+ )} +
+

{inv.name}

+

{inv.id}

+
+
+
+ +
+ + {inv.kycStatus} + + + {inv.status} + +
+ +
+
+ + {inv.phone} +
+
+ + {inv.email} +
+
+ +
+
+

Investment

+

৳{inv.totalInvested.toLocaleString()}

+
+
+

Earnings

+

৳{inv.totalEarnings.toLocaleString()}

+
+
+

ROI

+

{inv.roi}%

+
+
+

Bikes

+

{inv.activeBikes}

+
+
+

Plans

+

{inv.investments?.length || 0}

+
+
+

Plan Type

+ + {inv.investments?.[0]?.planType || 'silver'} + +
+
+ +
+ + View Details + + + + + +
+
+ ))} +
+ ) : (
+ {/* */} @@ -302,13 +285,21 @@ export default function InvestorsPage() { - {sortedInvestors.map(inv => ( + {filteredInvestors.map(inv => (
AvatarInvestor Contact Investment
-
- {inv.name.charAt(0)} -
+ {inv.profileImage ? ( + {inv.name} + ) : ( +
+ {getInitials(inv.name)} +
+ )}

{inv.name}

{inv.id}

@@ -331,28 +322,32 @@ export default function InvestorsPage() {
- {inv.investments?.length || 0} Plan{inv.investments?.length !== 1 ? 's' : ''} + {inv.investments?.[0]?.planType || 'silver'} - + {inv.kycStatus} - + {inv.status}
- - -
@@ -362,510 +357,35 @@ export default function InvestorsPage() {
- ) : ( -
- {sortedInvestors.map(inv => ( - -
-
-
- {inv.name.charAt(0)} -
-
-

{inv.name}

-

{inv.id}

-
-
- -
-
- - {inv.status} - - - {inv.investments?.length || 0} Plans - - - {inv.kycStatus} - -
-
-
-

Invested

-

৳{inv.totalInvested.toLocaleString()}

-
-
-

Earnings

-

৳{inv.totalEarnings.toLocaleString()}

-
-
-

Bikes

-

{inv.activeBikes}

-
-
-

ROI

-

{inv.roi}%

-
-
- - ))} -
)}
- {showModal && editingInvestor && ( -
-
-
-

- {investors.find(i => i.id === editingInvestor.id) ? 'Edit Investor' : 'Add New Investor'} -

- -
- -
- - - - - -
- -
- {activeTab === 'personal' && ( -
-
-
- - setEditingInvestor({ ...editingInvestor, name: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Enter full name" /> -
-
- - setEditingInvestor({ ...editingInvestor, phone: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="01XXXXXXXXX" /> -
-
-
-
- - setEditingInvestor({ ...editingInvestor, phoneAlt: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Optional" /> -
-
- - setEditingInvestor({ ...editingInvestor, email: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="email@example.com" /> -
-
-
-
- - setEditingInvestor({ ...editingInvestor, dateOfBirth: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> -
-
- - setEditingInvestor({ ...editingInvestor, nidNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="NID Number" /> -
-
-
- -