diff --git a/src/app/admin/bikers/[id]/page.tsx b/src/app/admin/bikers/[id]/page.tsx index e79fb3d..5ac2901 100644 --- a/src/app/admin/bikers/[id]/page.tsx +++ b/src/app/admin/bikers/[id]/page.tsx @@ -140,6 +140,13 @@ const statusColors: Record = { blocked: 'bg-red-100 text-red-700', }; +const statusLabels: Record = { + active: 'Active', + pending: 'Pending', + inactive: 'Inactive', + blocked: 'Blocked', +}; + const kycColors: Record = { verified: 'bg-green-100 text-green-700', pending: 'bg-amber-100 text-amber-700', @@ -228,7 +235,7 @@ export default function BikerDetailPage({ params }: PageProps) { {biker.status === 'active' && } {biker.status === 'pending' && } {biker.status === 'blocked' && } - {biker.status} + {statusLabels[biker.status] || biker.status}

ID: {biker.id} • {biker.location}

diff --git a/src/app/admin/bikers/page.tsx b/src/app/admin/bikers/page.tsx index cfe87a4..b78970a 100644 --- a/src/app/admin/bikers/page.tsx +++ b/src/app/admin/bikers/page.tsx @@ -385,6 +385,13 @@ const statusColors: Record = { blocked: 'bg-red-100 text-red-700', }; +const statusLabels: Record = { + active: 'Active', + pending: 'Pending', + inactive: 'Inactive', + blocked: 'Blocked', +}; + const kycColors: Record = { verified: 'bg-green-100 text-green-700', pending: 'bg-amber-100 text-amber-700', @@ -749,7 +756,7 @@ export default function BikersPage() {
- {biker.status} + {statusLabels[biker.status] || biker.status} {biker.kycStatus} @@ -1160,7 +1167,7 @@ export default function BikersPage() {

{selectedBiker.name}

ID: {selectedBiker.id}

- {selectedBiker.status} + {statusLabels[selectedBiker.status] || selectedBiker.status}
diff --git a/src/app/admin/merchants/[id]/page.tsx b/src/app/admin/merchants/[id]/page.tsx new file mode 100644 index 0000000..769886c --- /dev/null +++ b/src/app/admin/merchants/[id]/page.tsx @@ -0,0 +1,315 @@ +'use client'; + +import { useState, use } from 'react'; +import { Store, ArrowLeft, Phone, Mail, MapPin, User, Bike, DollarSign, Clock, FileText, CheckCircle, XCircle, AlertCircle, Edit, Trash2, Plus, X, Upload } from 'lucide-react'; +import Link from 'next/link'; + +type MerchantStatus = 'pending' | 'verified' | 'suspended'; +type MerchantTier = 'basic' | 'silver' | 'gold' | 'platinum'; + +interface Merchant { + id: string; + name: string; + shopName: string; + email: string; + phone: string; + address: string; + city: string; + status: MerchantStatus; + tier: MerchantTier; + kycStatus: 'pending' | 'approved' | 'rejected'; + bikersAssigned: number; + bikesRequested: number; + totalEarnings: number; + pendingPayment: number; + joinedAt: string; +} + +interface Rental { + id: string; + bikeName: string; + biker: string; + startDate: string; + endDate: string; + amount: number; + status: 'active' | 'pending' | 'completed' | 'cancelled'; + type: 'daily' | 'monthly'; +} + +const mockMerchant: Merchant = { + id: 'MCH-001', + name: 'Rahim Ahmed', + shopName: 'Rahim Delivery Service', + email: 'rahim@delivery.com', + phone: '+8801712345678', + address: 'House 12, Road 5, Gulshan 1', + city: 'Dhaka', + status: 'verified', + tier: 'gold', + kycStatus: 'approved', + bikersAssigned: 5, + bikesRequested: 5, + totalEarnings: 250000, + pendingPayment: 15000, + joinedAt: '2024-01-15' +}; + +const mockRentals: Rental[] = [ + { id: 'RNT-001', bikeName: 'Yamaha NMAX', biker: 'Karim Khan', startDate: '2024-01-20', endDate: '2024-02-20', amount: 5000, status: 'completed', type: 'monthly' }, + { id: 'RNT-002', bikeName: 'Honda PCX', biker: 'Jamal Mia', startDate: '2024-02-01', endDate: '2024-03-01', amount: 5500, status: 'active', type: 'monthly' }, + { id: 'RNT-003', bikeName: 'TVS Ntorq', biker: 'Rashid Ali', startDate: '2024-02-15', endDate: '2024-03-15', amount: 4500, status: 'active', type: 'monthly' }, + { id: 'RNT-004', bikeName: 'Suzuki Burgman', biker: 'New Request', startDate: '-', endDate: '-', amount: 5000, status: 'pending', type: 'monthly' }, +]; + +const tierColors: Record = { + basic: 'bg-slate-100 text-slate-700 border-slate-300', + silver: 'bg-gray-200 text-gray-700 border-gray-400', + gold: 'bg-yellow-100 text-yellow-700 border-yellow-400', + platinum: 'bg-purple-100 text-purple-700 border-purple-400' +}; + +const statusColors: Record = { + pending: 'bg-amber-100 text-amber-700 border-amber-300', + verified: 'bg-green-100 text-green-700 border-green-300', + suspended: 'bg-red-100 text-red-700 border-red-300' +}; + +const kycColors: Record = { + pending: 'bg-amber-100 text-amber-700', + approved: 'bg-green-100 text-green-700', + rejected: 'bg-red-100 text-red-700' +}; + +const rentalStatusColors: Record = { + active: 'bg-green-100 text-green-700', + pending: 'bg-amber-100 text-amber-700', + completed: 'bg-blue-100 text-blue-700', + cancelled: 'bg-red-100 text-red-700' +}; + +export default function MerchantDetailPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = use(params); + const [merchant] = useState(mockMerchant); + const [activeTab, setActiveTab] = useState<'overview' | 'rentals' | 'payments'>('overview'); + + const stats = [ + { label: 'Bikers Assigned', value: merchant.bikersAssigned, icon: Bike, color: 'text-blue-600' }, + { label: 'Bikes Requested', value: merchant.bikesRequested, icon: Plus, color: 'text-purple-600' }, + { label: 'Total Earnings', value: `৳${merchant.totalEarnings.toLocaleString()}`, icon: DollarSign, color: 'text-green-600' }, + { label: 'Pending Payment', value: `৳${merchant.pendingPayment.toLocaleString()}`, icon: Clock, color: 'text-amber-600' }, + ]; + + return ( +
+
+ + + +
+

{merchant.shopName}

+

{merchant.id} • Joined {merchant.joinedAt}

+
+
+ + +
+
+ +
+ {stats.map((stat, i) => ( +
+
+
+ +
+
+

{stat.value}

+

{stat.label}

+
+
+
+ ))} +
+ +
+
+ {(['overview', 'rentals', 'payments'] as const).map(tab => ( + + ))} +
+ + {activeTab === 'overview' && ( +
+
+
+

Business Information

+
+
+ + {merchant.shopName} +
+
+ + {merchant.name} + + {merchant.tier.toUpperCase()} + +
+
+ Status + + {merchant.status.toUpperCase()} + +
+
+
+ +
+

Contact Information

+
+
+ + {merchant.phone} +
+
+ + {merchant.email} +
+
+ + {merchant.address}, {merchant.city} +
+
+
+
+ +
+
+

KYC Status

+
+ {merchant.kycStatus === 'approved' ? ( + + ) : merchant.kycStatus === 'rejected' ? ( + + ) : ( + + )} + + {merchant.kycStatus.toUpperCase()} + +
+
+ +
+

Documents

+
+ + + +
+
+
+
+ )} + + {activeTab === 'rentals' && ( +
+
+

Active Rentals

+ +
+
+ + + + + + + + + + + + + + {mockRentals.map(rental => ( + + + + + + + + + + ))} + +
Rental IDBikeBikerPeriodAmountStatusAction
{rental.id}{rental.bikeName}{rental.biker} + {rental.startDate === '-' ? '-' : `${rental.startDate} to ${rental.endDate}`} + ৳{rental.amount.toLocaleString()} + + {rental.status.charAt(0).toUpperCase() + rental.status.slice(1)} + + + {rental.status === 'pending' ? ( + + ) : ( + View + )} +
+
+
+ )} + + {activeTab === 'payments' && ( +
+
+

Payment History

+ +
+
+ +

No payment records found

+
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/admin/merchants/page.tsx b/src/app/admin/merchants/page.tsx new file mode 100644 index 0000000..f593c42 --- /dev/null +++ b/src/app/admin/merchants/page.tsx @@ -0,0 +1,470 @@ +'use client'; + +import { useState } from 'react'; +import { Store, Plus, Search, Eye, Edit, Trash2, X, MapPin, Phone, Mail, User, DollarSign, Bike, FileText, Clock, CheckCircle, XCircle } from 'lucide-react'; +import Link from 'next/link'; + +type MerchantStatus = 'pending' | 'verified' | 'suspended'; +type MerchantTier = 'basic' | 'silver' | 'gold' | 'platinum'; + +interface Merchant { + id: string; + name: string; + shopName: string; + email: string; + phone: string; + address: string; + city: string; + status: MerchantStatus; + tier: MerchantTier; + kycStatus: 'pending' | 'approved' | 'rejected'; + bikersAssigned: number; + bikesRequested: number; + totalEarnings: number; + pendingPayment: number; + joinedAt: string; +} + +const mockMerchants: Merchant[] = [ + { + id: 'MCH-001', + name: 'Rahim Ahmed', + shopName: 'Rahim Delivery Service', + email: 'rahim@delivery.com', + phone: '+8801712345678', + address: 'House 12, Road 5, Gulshan 1', + city: 'Dhaka', + status: 'verified', + tier: 'gold', + kycStatus: 'approved', + bikersAssigned: 5, + bikesRequested: 5, + totalEarnings: 250000, + pendingPayment: 15000, + joinedAt: '2024-01-15' + }, + { + id: 'MCH-002', + name: 'Karim Hasan', + shopName: 'Karim Express', + email: 'karim@express.com', + phone: '+8801812345678', + address: 'House 8, Road 11, Banani', + city: 'Dhaka', + status: 'pending', + tier: 'basic', + kycStatus: 'pending', + bikersAssigned: 0, + bikesRequested: 3, + totalEarnings: 0, + pendingPayment: 0, + joinedAt: '2024-02-01' + }, + { + id: 'MCH-003', + name: 'Jamal Uddin', + shopName: 'Jamal Logistics', + email: 'jamal@logistics.com', + phone: '+8801912345678', + address: 'Sector 10, Uttara', + city: 'Dhaka', + status: 'verified', + tier: 'platinum', + kycStatus: 'approved', + bikersAssigned: 12, + bikesRequested: 10, + totalEarnings: 580000, + pendingPayment: 45000, + joinedAt: '2023-11-20' + }, + { + id: 'MCH-004', + name: 'Rashid Mia', + shopName: 'Rashid Delivery', + email: 'rashid@delivery.com', + phone: '+8801512345678', + address: 'Section 10, Mirpur', + city: 'Dhaka', + status: 'suspended', + tier: 'silver', + kycStatus: 'rejected', + bikersAssigned: 2, + bikesRequested: 2, + totalEarnings: 45000, + pendingPayment: 0, + joinedAt: '2024-01-05' + } +]; + +const tierColors: Record = { + basic: 'bg-slate-100 text-slate-700', + silver: 'bg-gray-200 text-gray-700', + gold: 'bg-yellow-100 text-yellow-700', + platinum: 'bg-purple-100 text-purple-700' +}; + +const statusColors: Record = { + pending: 'bg-amber-100 text-amber-700', + verified: 'bg-green-100 text-green-700', + suspended: 'bg-red-100 text-red-700' +}; + +const kycColors: Record = { + pending: 'bg-amber-100 text-amber-700', + approved: 'bg-green-100 text-green-700', + rejected: 'bg-red-100 text-red-700' +}; + +const statusLabels: Record = { + pending: 'Pending', + verified: 'Verified', + suspended: 'Suspended' +}; + +const tierLabels: Record = { + basic: 'Basic', + silver: 'Silver', + gold: 'Gold', + platinum: 'Platinum' +}; + +const kycLabels: Record = { + pending: 'Pending', + approved: 'Approved', + rejected: 'Rejected' +}; + +export default function MerchantsPage() { + const [merchants, setMerchants] = useState(mockMerchants); + const [search, setSearch] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [showCreateModal, setShowCreateModal] = useState(false); + const [editingMerchant, setEditingMerchant] = useState(null); + + const [formData, setFormData] = useState({ + name: '', + shopName: '', + email: '', + phone: '', + address: '', + city: 'Dhaka', + tier: 'basic' as MerchantTier, + }); + + const filteredMerchants = merchants.filter(m => { + const matchesSearch = m.name.toLowerCase().includes(search.toLowerCase()) || + m.shopName.toLowerCase().includes(search.toLowerCase()) || + m.phone.includes(search) || + m.id.toLowerCase().includes(search.toLowerCase()); + const matchesStatus = statusFilter === 'all' || m.status === statusFilter; + return matchesSearch && matchesStatus; + }); + + const stats = { + total: merchants.length, + verified: merchants.filter(m => m.status === 'verified').length, + pending: merchants.filter(m => m.status === 'pending').length, + totalBikers: merchants.reduce((a, m) => a + m.bikersAssigned, 0), + }; + + const handleSave = () => { + if (!formData.name || !formData.shopName || !formData.phone) return; + + if (editingMerchant) { + setMerchants(merchants.map(m => m.id === editingMerchant.id ? { ...m, ...formData } : m)); + } else { + const newMerchant: Merchant = { + id: `MCH-${String(merchants.length + 1).padStart(3, '0')}`, + ...formData, + status: 'pending', + kycStatus: 'pending', + bikersAssigned: 0, + bikesRequested: 0, + totalEarnings: 0, + pendingPayment: 0, + joinedAt: new Date().toISOString().split('T')[0], + }; + setMerchants([...merchants, newMerchant]); + } + + setShowCreateModal(false); + setEditingMerchant(null); + setFormData({ name: '', shopName: '', email: '', phone: '', address: '', city: 'Dhaka', tier: 'basic' }); + }; + + const handleDelete = (id: string) => { + if (confirm('Are you sure you want to delete this merchant?')) { + setMerchants(merchants.filter(m => m.id !== id)); + } + }; + + const openEdit = (merchant: Merchant) => { + setEditingMerchant(merchant); + setFormData({ + name: merchant.name, + shopName: merchant.shopName, + email: merchant.email, + phone: merchant.phone, + address: merchant.address, + city: merchant.city, + tier: merchant.tier, + }); + setShowCreateModal(true); + }; + + return ( +
+
+
+

Merchants (P2)

+

Manage merchant partners and fleet requests

+
+ +
+ +
+
+

{stats.total}

+

Total Merchants

+
+
+

{stats.verified}

+

Verified

+
+
+

{stats.pending}

+

Pending

+
+
+

{stats.totalBikers}

+

Bikers Assigned

+
+
+ +
+
+
+
+ + setSearch(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" + /> +
+ +
+
+ +
+ + + + + + + + + + + + + + + {filteredMerchants.map(merchant => ( + + + + + + + + + + + ))} + +
MerchantShopContactBikersEarningsKYCStatusActions
+ +
+ {merchant.name} +

{merchant.id}

+
+ +
+
+ + {merchant.shopName} +
+ + {tierLabels[merchant.tier]} + +
+

{merchant.phone}

+

{merchant.city}

+
+
+ + {merchant.bikersAssigned}/{merchant.bikesRequested} +
+
+

৳{merchant.totalEarnings.toLocaleString()}

+ {merchant.pendingPayment > 0 && ( +

Pending: ৳{merchant.pendingPayment.toLocaleString()}

+ )} +
+ + {kycLabels[merchant.kycStatus]} + + + + {statusLabels[merchant.status]} + + +
+ + + + + +
+
+
+
+ + {showCreateModal && ( +
+
+
+

{editingMerchant ? 'Edit Merchant' : 'Add New Merchant'}

+ +
+
+
+ + setFormData({ ...formData, name: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" + placeholder="Full name" + /> +
+
+ + setFormData({ ...formData, shopName: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" + placeholder="Business name" + /> +
+
+
+ + setFormData({ ...formData, phone: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" + placeholder="+8801xxxxxxxxx" + /> +
+
+ + setFormData({ ...formData, email: e.target.value })} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" + placeholder="email@example.com" + /> +
+
+
+ +