feat: add battery management functionality with assignment, registration, and investment tracking to investor dashboard

This commit is contained in:
sazzadulalambd
2026-05-19 17:25:32 +06:00
parent 623500d845
commit be137d65df
3 changed files with 1064 additions and 106 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Settings, Package, Palette, Link2, Mail, Monitor, FileCheck, DollarSign, Zap, Users, Plus, X, Save, Pencil, Trash2, FileText, Battery } from 'lucide-react'; import { Settings, Package, Palette, Link2, Mail, Monitor, FileCheck, DollarSign, Zap, Users, Plus, X, Save, Pencil, Trash2, FileText, Battery } from 'lucide-react';
import RichTextEditor from '@/components/RichTextEditor'; import RichTextEditor from '@/components/RichTextEditor';
import GeneralSettings from './components/GeneralSettings'; import GeneralSettings from './components/GeneralSettings';
@@ -871,6 +871,25 @@ const initialSettings: CompanySettings = {
export default function CompanySettingsPage() { export default function CompanySettingsPage() {
const [settings, setSettings] = useState<CompanySettings>(initialSettings); const [settings, setSettings] = useState<CompanySettings>(initialSettings);
useEffect(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('companySettings');
if (stored) {
try {
setSettings(JSON.parse(stored));
} catch (e) {
console.error(e);
}
}
}
}, []);
useEffect(() => {
if (typeof window !== 'undefined' && settings !== initialSettings) {
localStorage.setItem('companySettings', JSON.stringify(settings));
}
}, [settings]);
const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'companyPolicy' | 'plans' | 'investment' | 'batteryinvestment' | 'swapstation' | 'riderrequest' | 'templates'>('general'); const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'companyPolicy' | 'plans' | 'investment' | 'batteryinvestment' | 'swapstation' | 'riderrequest' | 'templates'>('general');
const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rentalType'>('investor'); const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rentalType'>('investor');
const [activeRentalTypeTab, setActiveRentalTypeTab] = useState<'single' | 'shared' | 'renttoown'>('single'); const [activeRentalTypeTab, setActiveRentalTypeTab] = useState<'single' | 'shared' | 'renttoown'>('single');

View File

@@ -2,7 +2,7 @@
import { useState } from 'react'; import { useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { Target, Plus, Zap, ChevronRight, ArrowRight, Edit, Trash2, Eye, TrendingUp, X, CreditCard } from 'lucide-react'; import { Target, Plus, Zap, ChevronRight, ArrowRight, Edit, Trash2, Eye, TrendingUp, X, CreditCard, Battery } from 'lucide-react';
import { investors } from '@/data/mockData'; import { investors } from '@/data/mockData';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import InvestorNotification from '@/components/InvestorNotification'; import InvestorNotification from '@/components/InvestorNotification';
@@ -12,14 +12,20 @@ export default function MyInvestmentsPage() {
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const [selectedTemplate, setSelectedTemplate] = useState<any>(null); const [selectedTemplate, setSelectedTemplate] = useState<any>(null);
const [newInvestment, setNewInvestment] = useState({ const [newInvestment, setNewInvestment] = useState({
planName: '', planType: 'gold', totalInvestment: 0, initialPayment: 0, paymentType: 'full', planName: '', planType: 'gold', assetType: 'bike', totalInvestment: 0, initialPayment: 0, paymentType: 'full',
startDate: '', endDate: '', paymentMethod: 'bank', transactionReference: '', notes: '' startDate: '', endDate: '', paymentMethod: 'bank', transactionReference: '', notes: ''
}); });
const PLAN_TEMPLATES = [ const PLAN_TEMPLATES = [
{ id: '1bike', name: '1 Bike Plan', tier: 'Standard', evBasePrice: 200000, minQuantity: 1, duration: 12, maxInvestment: 1000000 }, { id: '1bike', name: '1 Bike Plan', tier: 'Standard', evBasePrice: 200000, minQuantity: 1, duration: 12, maxInvestment: 1000000, type: 'bike' },
{ id: '5bike', name: '5 Bike Plan', tier: 'Premium', evBasePrice: 180000, minQuantity: 5, duration: 24, maxInvestment: 5000000 }, { id: '5bike', name: '5 Bike Plan', tier: 'Premium', evBasePrice: 180000, minQuantity: 5, duration: 24, maxInvestment: 5000000, type: 'bike' },
{ id: '10bike', name: '10 Bike Plan', tier: 'Enterprise', evBasePrice: 170000, minQuantity: 10, duration: 36, maxInvestment: 10000000 }, { id: '10bike', name: '10 Bike Plan', tier: 'Enterprise', evBasePrice: 170000, minQuantity: 10, duration: 36, maxInvestment: 10000000, type: 'bike' },
];
const BATTERY_TEMPLATES = [
{ id: '1battery', name: '1 Battery Pack Plan', tier: 'Standard', evBasePrice: 45000, minQuantity: 1, duration: 12, maxInvestment: 500000, type: 'battery' },
{ id: '5battery', name: '5 Battery Pack Plan', tier: 'Premium', evBasePrice: 42000, minQuantity: 5, duration: 18, maxInvestment: 2000000, type: 'battery' },
{ id: '10battery', name: '10 Battery Pack Fleet', tier: 'Enterprise', evBasePrice: 40000, minQuantity: 10, duration: 24, maxInvestment: 5000000, type: 'battery' },
]; ];
const planConfig: Record<string, { bg: string; border: string; icon: string }> = { const planConfig: Record<string, { bg: string; border: string; icon: string }> = {
@@ -44,7 +50,7 @@ export default function MyInvestmentsPage() {
); );
setShowCreateModal(false); setShowCreateModal(false);
setSelectedTemplate(null); setSelectedTemplate(null);
setNewInvestment({ planName: '', planType: 'gold', totalInvestment: 0, initialPayment: 0, paymentType: 'full', startDate: '', endDate: '', paymentMethod: 'bank', transactionReference: '', notes: '' }); setNewInvestment({ planName: '', planType: 'gold', assetType: 'bike', totalInvestment: 0, initialPayment: 0, paymentType: 'full', startDate: '', endDate: '', paymentMethod: 'bank', transactionReference: '', notes: '' });
}; };
return ( return (
@@ -85,7 +91,14 @@ export default function MyInvestmentsPage() {
<div key={inv.id} className={`bg-white rounded-xl border ${style.border} overflow-hidden`}> <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 className={`${style.bg} p-4 flex items-center justify-between`}>
<div> <div>
<h4 className="font-semibold text-slate-800">{inv.planName}</h4> <h4 className="font-bold text-slate-800 flex items-center gap-1.5">
{inv.planName.toLowerCase().includes('battery') ? (
<Battery className="w-4 h-4 text-emerald-600 animate-pulse flex-shrink-0" />
) : (
<Zap className="w-4 h-4 text-investor flex-shrink-0" />
)}
{inv.planName}
</h4>
<p className="text-sm text-slate-500 capitalize">{inv.planType} Plan</p> <p className="text-sm text-slate-500 capitalize">{inv.planType} Plan</p>
</div> </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'}`}> <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'}`}>
@@ -166,10 +179,41 @@ export default function MyInvestmentsPage() {
<div className="p-5 overflow-y-auto flex-1 space-y-5"> <div className="p-5 overflow-y-auto flex-1 space-y-5">
{!selectedTemplate ? ( {!selectedTemplate ? (
<> <>
{/* Category Selector */}
<div>
<label className="text-sm font-semibold text-slate-700 mb-2 block">Choose Investment Asset Type *</label>
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => {
setNewInvestment({ ...newInvestment, assetType: 'bike' });
}}
className={`py-3 px-4 rounded-xl border-2 flex items-center justify-center gap-2 font-bold text-sm transition-all ${
newInvestment.assetType === 'bike'
? 'bg-investor/10 border-investor text-investor shadow-sm'
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
<Zap className="w-4 h-4 text-investor animate-pulse" /> Bike Investment Plans
</button>
<button
onClick={() => {
setNewInvestment({ ...newInvestment, assetType: 'battery' });
}}
className={`py-3 px-4 rounded-xl border-2 flex items-center justify-center gap-2 font-bold text-sm transition-all ${
newInvestment.assetType === 'battery'
? 'bg-emerald-100 border-emerald-500 text-emerald-800 shadow-sm'
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
<Battery className="w-4 h-4 text-emerald-600 animate-pulse" /> Battery Investment Plans
</button>
</div>
</div>
<div> <div>
<label className="text-sm font-medium text-slate-600 mb-2 block">Select Plan Template</label> <label className="text-sm font-medium text-slate-600 mb-2 block">Select Plan Template</label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{PLAN_TEMPLATES.map(plan => ( {(newInvestment.assetType === 'battery' ? BATTERY_TEMPLATES : PLAN_TEMPLATES).map(plan => (
<button <button
key={plan.id} key={plan.id}
onClick={() => { onClick={() => {
@@ -182,10 +226,10 @@ export default function MyInvestmentsPage() {
startDate: new Date().toISOString().split('T')[0], startDate: new Date().toISOString().split('T')[0],
}); });
}} }}
className="p-4 rounded-lg border-2 border-slate-200 hover:border-investor/50 text-left transition-all hover:bg-slate-50" className={`p-4 rounded-lg border-2 text-left transition-all hover:bg-slate-50 ${newInvestment.assetType === 'battery' ? 'border-slate-200 hover:border-emerald-500' : 'border-slate-200 hover:border-investor/50'}`}
> >
<p className="font-semibold text-slate-800">{plan.name}</p> <p className="font-semibold text-slate-800">{plan.name}</p>
<p className="text-xs text-slate-500 mt-1">{plan.evBasePrice.toLocaleString()} × {plan.minQuantity} bikes</p> <p className="text-xs text-slate-500 mt-1">{plan.evBasePrice.toLocaleString()} × {plan.minQuantity} {newInvestment.assetType}(s)</p>
<p className="text-sm text-slate-600 mt-1">Duration: {plan.duration} months</p> <p className="text-sm text-slate-600 mt-1">Duration: {plan.duration} months</p>
</button> </button>
))} ))}
@@ -228,11 +272,11 @@ export default function MyInvestmentsPage() {
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div> <div>
<label className="text-sm font-medium text-slate-600 mb-1 block">EV Base Price ()</label> <label className="text-sm font-medium text-slate-600 mb-1 block">{selectedTemplate.type === 'battery' ? 'Battery Pack Cost (৳)' : 'EV Base Price (৳)'}</label>
<input type="number" value={selectedTemplate.evBasePrice} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50 cursor-not-allowed" /> <input type="number" value={selectedTemplate.evBasePrice} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50 cursor-not-allowed" />
</div> </div>
<div> <div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Minimum Quantity</label> <label className="text-sm font-medium text-slate-600 mb-1 block">Minimum Quantity ({selectedTemplate.type === 'battery' ? 'Packs' : 'Bikes'})</label>
<input type="number" value={selectedTemplate.minQuantity} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50 cursor-not-allowed" /> <input type="number" value={selectedTemplate.minQuantity} disabled className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-slate-50 cursor-not-allowed" />
</div> </div>
<div> <div>