feat: implement persistent local storage state for batteries, bikes, and investors with dynamic patching logic
This commit is contained in:
@@ -75,9 +75,49 @@ export default function InvestorDetailPage() {
|
||||
const router = useRouter();
|
||||
const investorId = params.id as string;
|
||||
|
||||
const [investors, setInvestors] = useState<Investor[]>(initialInvestors);
|
||||
const [investors, setInvestors] = useState<Investor[]>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem('jaiben_investors');
|
||||
if (stored) {
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return initialInvestors;
|
||||
});
|
||||
const investor = investors.find(i => i.id === investorId);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('jaiben_investors', JSON.stringify(investors));
|
||||
}
|
||||
}, [investors]);
|
||||
|
||||
const [bikes, setBikes] = useState<any[]>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem('jaiben_bikes');
|
||||
if (stored) {
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return initialBikes;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('jaiben_bikes', JSON.stringify(bikes));
|
||||
}
|
||||
}, [bikes]);
|
||||
|
||||
const assignedBikes = bikes.filter(b => b.investorId === investorId);
|
||||
|
||||
const [settings, setSettings] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -93,13 +133,23 @@ export default function InvestorDetailPage() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const assignedBikes = initialBikes.filter(b => b.investorId === investorId);
|
||||
// Investor transactions are filtered below
|
||||
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showAssignBikeModal, setShowAssignBikeModal] = useState(false);
|
||||
const [selectedBikeId, setSelectedBikeId] = useState('');
|
||||
const [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false);
|
||||
const [registerBikeForm, setRegisterBikeForm] = useState({
|
||||
plateNumber: '',
|
||||
brand: 'Etron',
|
||||
model: 'ET50',
|
||||
currentRent: 150,
|
||||
location: 'Banani',
|
||||
purchasePrice: 200000,
|
||||
rentalType: 'single_rent',
|
||||
investmentId: ''
|
||||
});
|
||||
const [showCreateInvestmentModal, setShowCreateInvestmentModal] = useState(false);
|
||||
const [showInvestmentSuccessModal, setShowInvestmentSuccessModal] = useState(false);
|
||||
const [lastCreatedInvestment, setLastCreatedInvestment] = useState<any>(null);
|
||||
@@ -122,57 +172,232 @@ export default function InvestorDetailPage() {
|
||||
const [newDoc, setNewDoc] = useState({ type: 'nid', number: '', url: '' });
|
||||
const [editingMobileIndex, setEditingMobileIndex] = useState<number | null>(null);
|
||||
|
||||
const [batteries, setBatteries] = useState<any[]>([
|
||||
{
|
||||
id: 'BAT-001',
|
||||
serialNumber: 'SN-2024-00001',
|
||||
brand: 'EVE Energy',
|
||||
model: 'Li-Ion 60V50Ah',
|
||||
type: 'lithium-ion',
|
||||
capacity: 50,
|
||||
voltage: 60,
|
||||
purchaseDate: '2024-01-15',
|
||||
purchasePrice: 45000,
|
||||
deposit: 5000,
|
||||
rentPrice: 150,
|
||||
investorId: investorId,
|
||||
investorName: investor?.name || 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 60,
|
||||
investedAmount: 45000,
|
||||
investmentId: 'ip1',
|
||||
status: 'in-use',
|
||||
currentSoc: 78,
|
||||
health: 95,
|
||||
cycleCount: 156
|
||||
},
|
||||
{
|
||||
id: 'BAT-002',
|
||||
serialNumber: 'SN-2024-00002',
|
||||
brand: 'CATL',
|
||||
model: 'LiFePO4 48V40Ah',
|
||||
type: 'lifepo4',
|
||||
capacity: 40,
|
||||
voltage: 48,
|
||||
purchaseDate: '2024-02-10',
|
||||
purchasePrice: 38000,
|
||||
deposit: 4000,
|
||||
rentPrice: 120,
|
||||
investorId: investorId,
|
||||
investorName: investor?.name || 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 38000,
|
||||
investmentId: 'ip2',
|
||||
status: 'available',
|
||||
currentSoc: 92,
|
||||
health: 98,
|
||||
cycleCount: 45
|
||||
const [batteries, setBatteries] = useState<any[]>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem('jaiben_batteries');
|
||||
if (stored) {
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
return [
|
||||
{
|
||||
id: 'BAT-001',
|
||||
serialNumber: 'SN-2024-00001',
|
||||
brand: 'EVE Energy',
|
||||
model: 'Li-Ion 60V50Ah',
|
||||
type: 'lithium-ion',
|
||||
capacity: 50,
|
||||
voltage: 60,
|
||||
purchaseDate: '2024-01-15',
|
||||
purchasePrice: 45000,
|
||||
deposit: 5000,
|
||||
rentPrice: 150,
|
||||
investorId: investorId,
|
||||
investorName: 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 45000,
|
||||
investmentId: 'ip3',
|
||||
status: 'in-use',
|
||||
currentSoc: 78,
|
||||
health: 95,
|
||||
cycleCount: 156
|
||||
},
|
||||
{
|
||||
id: 'BAT-002',
|
||||
serialNumber: 'SN-2024-00002',
|
||||
brand: 'CATL',
|
||||
model: 'LiFePO4 48V40Ah',
|
||||
type: 'lifepo4',
|
||||
capacity: 40,
|
||||
voltage: 48,
|
||||
purchaseDate: '2024-02-10',
|
||||
purchasePrice: 38000,
|
||||
deposit: 4000,
|
||||
rentPrice: 120,
|
||||
investorId: investorId,
|
||||
investorName: 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 38000,
|
||||
investmentId: 'ip3',
|
||||
status: 'available',
|
||||
currentSoc: 92,
|
||||
health: 98,
|
||||
cycleCount: 45
|
||||
},
|
||||
{
|
||||
id: 'BAT-005',
|
||||
serialNumber: 'SN-2024-00005',
|
||||
brand: 'BYD',
|
||||
model: 'Li-Ion 60V50Ah',
|
||||
type: 'lithium-ion',
|
||||
capacity: 50,
|
||||
voltage: 60,
|
||||
purchaseDate: '2024-02-15',
|
||||
purchasePrice: 45000,
|
||||
deposit: 5000,
|
||||
rentPrice: 150,
|
||||
investorId: investorId,
|
||||
investorName: 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 45000,
|
||||
investmentId: 'ip3',
|
||||
status: 'in-use',
|
||||
currentSoc: 82,
|
||||
health: 97,
|
||||
cycleCount: 18
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const [unassignedBatteries, setUnassignedBatteries] = useState<any[]>([
|
||||
{ id: 'BAT-003', serialNumber: 'SN-2024-00003', brand: 'BYD', model: 'Li-Ion 72V60Ah', type: 'lithium-ion', capacity: 60, voltage: 72, purchasePrice: 52000, deposit: 6000, rentPrice: 180, status: 'available', currentSoc: 85, health: 97, cycleCount: 12 },
|
||||
{ id: 'BAT-004', serialNumber: 'SN-2024-00004', brand: 'Panasonic', model: 'Li-Ion 60V40Ah', type: 'lithium-ion', capacity: 40, voltage: 60, purchasePrice: 41000, deposit: 4500, rentPrice: 130, status: 'available', currentSoc: 90, health: 99, cycleCount: 8 }
|
||||
]);
|
||||
const [unassignedBatteries, setUnassignedBatteries] = useState<any[]>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem('jaiben_unassigned_batteries');
|
||||
if (stored) {
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{ id: 'BAT-003', serialNumber: 'SN-2024-00003', brand: 'BYD', model: 'Li-Ion 72V60Ah', type: 'lithium-ion', capacity: 60, voltage: 72, purchasePrice: 52000, deposit: 6000, rentPrice: 180, status: 'available', currentSoc: 85, health: 97, cycleCount: 12 },
|
||||
{ id: 'BAT-004', serialNumber: 'SN-2024-00004', brand: 'Panasonic', model: 'Li-Ion 60V40Ah', type: 'lithium-ion', capacity: 40, voltage: 60, purchasePrice: 41000, deposit: 4500, rentPrice: 130, status: 'available', currentSoc: 90, health: 99, cycleCount: 8 }
|
||||
];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('jaiben_batteries', JSON.stringify(batteries));
|
||||
}
|
||||
}, [batteries]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('jaiben_unassigned_batteries', JSON.stringify(unassignedBatteries));
|
||||
}
|
||||
}, [unassignedBatteries]);
|
||||
|
||||
// Patch local storage state to make sure inv1 has the Standard Battery Plan (ip3) and correct assigned batteries
|
||||
useEffect(() => {
|
||||
if (!investor) return;
|
||||
const hasIp3 = investor.investments?.some(inv => inv.id === 'ip3');
|
||||
if (!hasIp3) {
|
||||
const updated = investors.map(i => {
|
||||
if (i.id === investorId) {
|
||||
const investments = i.investments || [];
|
||||
return {
|
||||
...i,
|
||||
totalInvested: 300000,
|
||||
investments: [
|
||||
...investments.filter(inv => inv.id !== 'ip3'),
|
||||
{
|
||||
id: 'ip3',
|
||||
investorId: investorId,
|
||||
planName: 'Standard Battery Plan',
|
||||
planType: 'silver' as const,
|
||||
assetType: 'battery' as const,
|
||||
batteryIds: ['BAT-001', 'BAT-002', 'BAT-005'],
|
||||
totalInvestment: 150000,
|
||||
monthlyReturn: 4500,
|
||||
expectedRoi: 16,
|
||||
actualEarnings: 9000,
|
||||
startDate: '2024-02-01',
|
||||
endDate: '2025-02-01',
|
||||
status: 'active' as const,
|
||||
paymentMethod: 'bank' as const,
|
||||
transactionId: 'invt3',
|
||||
createdAt: '2024-02-01'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return i;
|
||||
});
|
||||
setInvestors(updated);
|
||||
}
|
||||
}, [investors, investorId, investor]);
|
||||
|
||||
useEffect(() => {
|
||||
const hasBat5 = batteries.some(b => b.id === 'BAT-005');
|
||||
const hasIp3Assignment = batteries.some(b => b.id === 'BAT-001' && b.investmentId === 'ip3');
|
||||
if (!hasBat5 || !hasIp3Assignment) {
|
||||
const updated = [
|
||||
{
|
||||
id: 'BAT-001',
|
||||
serialNumber: 'SN-2024-00001',
|
||||
brand: 'EVE Energy',
|
||||
model: 'Li-Ion 60V50Ah',
|
||||
type: 'lithium-ion',
|
||||
capacity: 50,
|
||||
voltage: 60,
|
||||
purchaseDate: '2024-01-15',
|
||||
purchasePrice: 45000,
|
||||
deposit: 5000,
|
||||
rentPrice: 150,
|
||||
investorId: investorId,
|
||||
investorName: 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 45000,
|
||||
investmentId: 'ip3',
|
||||
status: 'in-use',
|
||||
currentSoc: 78,
|
||||
health: 95,
|
||||
cycleCount: 156
|
||||
},
|
||||
{
|
||||
id: 'BAT-002',
|
||||
serialNumber: 'SN-2024-00002',
|
||||
brand: 'CATL',
|
||||
model: 'LiFePO4 48V40Ah',
|
||||
type: 'lifepo4',
|
||||
capacity: 40,
|
||||
voltage: 48,
|
||||
purchaseDate: '2024-02-10',
|
||||
purchasePrice: 38000,
|
||||
deposit: 4000,
|
||||
rentPrice: 120,
|
||||
investorId: investorId,
|
||||
investorName: 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 38000,
|
||||
investmentId: 'ip3',
|
||||
status: 'available',
|
||||
currentSoc: 92,
|
||||
health: 98,
|
||||
cycleCount: 45
|
||||
},
|
||||
{
|
||||
id: 'BAT-005',
|
||||
serialNumber: 'SN-2024-00005',
|
||||
brand: 'BYD',
|
||||
model: 'Li-Ion 60V50Ah',
|
||||
type: 'lithium-ion',
|
||||
capacity: 50,
|
||||
voltage: 60,
|
||||
purchaseDate: '2024-02-15',
|
||||
purchasePrice: 45000,
|
||||
deposit: 5000,
|
||||
rentPrice: 150,
|
||||
investorId: investorId,
|
||||
investorName: 'Md. Hasan Mahmud',
|
||||
investorSharePercentage: 100,
|
||||
investedAmount: 45000,
|
||||
investmentId: 'ip3',
|
||||
status: 'in-use',
|
||||
currentSoc: 82,
|
||||
health: 97,
|
||||
cycleCount: 18
|
||||
},
|
||||
...batteries.filter(b => b.id !== 'BAT-001' && b.id !== 'BAT-002' && b.id !== 'BAT-005')
|
||||
];
|
||||
setBatteries(updated);
|
||||
}
|
||||
}, [batteries, investorId]);
|
||||
|
||||
const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false);
|
||||
const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false);
|
||||
@@ -269,14 +494,84 @@ export default function InvestorDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const availableBikesForAssignment = initialBikes.filter(b => !b.investorId && b.status === 'available');
|
||||
const availableBikesForAssignment = bikes.filter(b => !b.investorId && b.status === 'available');
|
||||
|
||||
const handleAssignBike = () => {
|
||||
alert('Bike assignment functionality - would update bike investorId here');
|
||||
if (!selectedBikeId) {
|
||||
toast.error('Please select a bike');
|
||||
return;
|
||||
}
|
||||
const bikeToAssign = bikes.find(b => b.id === selectedBikeId);
|
||||
if (!bikeToAssign) return;
|
||||
|
||||
// Find first active EV Investment Plan of the investor to attach to
|
||||
const evInv = investor.investments?.find((inv: any) => inv.assetType === 'bike' || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')) || investor.investments?.[0];
|
||||
|
||||
setBikes(prev => prev.map(b => {
|
||||
if (b.id === selectedBikeId) {
|
||||
return {
|
||||
...b,
|
||||
investorId: investorId,
|
||||
investorName: investor.name,
|
||||
investmentId: evInv?.id || 'ip1',
|
||||
status: 'rented',
|
||||
totalEarnings: b.totalEarnings || 0
|
||||
};
|
||||
}
|
||||
return b;
|
||||
}));
|
||||
|
||||
toast.success(`Bike ${bikeToAssign.model} (${bikeToAssign.plateNumber}) successfully assigned!`);
|
||||
setShowAssignBikeModal(false);
|
||||
setSelectedBikeId('');
|
||||
};
|
||||
|
||||
const handleRegisterAndAssignBike = () => {
|
||||
if (!registerBikeForm.plateNumber) {
|
||||
toast.error('Please enter a plate number');
|
||||
return;
|
||||
}
|
||||
|
||||
const evInv = investor.investments?.find((inv: any) => inv.assetType === 'bike' || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')) || investor.investments?.[0];
|
||||
|
||||
const newBike = {
|
||||
id: `BIKE-${Date.now()}`,
|
||||
plateNumber: registerBikeForm.plateNumber,
|
||||
brand: registerBikeForm.brand,
|
||||
model: registerBikeForm.model,
|
||||
currentRent: Number(registerBikeForm.currentRent),
|
||||
location: registerBikeForm.location,
|
||||
purchasePrice: Number(registerBikeForm.purchasePrice),
|
||||
rentalType: registerBikeForm.rentalType,
|
||||
investorId: investorId,
|
||||
investorName: investor.name,
|
||||
investmentId: registerBikeForm.investmentId || evInv?.id || 'ip1',
|
||||
status: 'rented',
|
||||
batteryLevel: 100,
|
||||
totalEarnings: 0
|
||||
};
|
||||
|
||||
setBikes(prev => [...prev, newBike]);
|
||||
setShowRegisterBikeModal(false);
|
||||
toast.success(`New bike ${newBike.model} registered and assigned!`);
|
||||
};
|
||||
|
||||
const handleUnassignBike = (bikeId: string) => {
|
||||
setBikes(prev => prev.map(b => {
|
||||
if (b.id === bikeId) {
|
||||
return {
|
||||
...b,
|
||||
investorId: null,
|
||||
investorName: null,
|
||||
investmentId: null,
|
||||
status: 'available'
|
||||
};
|
||||
}
|
||||
return b;
|
||||
}));
|
||||
toast.success('Bike successfully unassigned from investor!');
|
||||
};
|
||||
|
||||
const handleCreateInvestment = () => {
|
||||
const invId = `INV-${Date.now()}`;
|
||||
const year = new Date().getFullYear();
|
||||
@@ -1211,10 +1506,45 @@ export default function InvestorDetailPage() {
|
||||
|
||||
{activeTab === 'bikes' && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<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} EV Investment Plans</p>
|
||||
<h3 className="font-bold text-lg text-slate-800 flex items-center gap-2">
|
||||
<Bike className="w-5 h-5 text-investor" />
|
||||
Assigned Bikes
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
{assignedBikes.length} bikes across {investor.investments?.filter((inv: any) => inv.assetType === 'bike' || !inv.assetType).length || 0} EV Investment Plans
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedBikeId('');
|
||||
setShowAssignBikeModal(true);
|
||||
}}
|
||||
className="px-4 py-2 bg-blue-50 text-blue-700 border border-blue-200 rounded-lg text-sm font-semibold hover:bg-blue-100 flex items-center gap-2 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" /> Assign Existing
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const evInv = investor.investments?.find((inv: any) => inv.assetType === 'bike' || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')) || investor.investments?.[0];
|
||||
setRegisterBikeForm({
|
||||
plateNumber: `DHAKA-METRO-HA-${Math.floor(1000 + Math.random() * 9000)}`,
|
||||
brand: 'Etron',
|
||||
model: 'ET50',
|
||||
currentRent: 150,
|
||||
location: 'Banani',
|
||||
purchasePrice: 200000,
|
||||
rentalType: 'single_rent',
|
||||
investmentId: evInv?.id || ''
|
||||
});
|
||||
setShowRegisterBikeModal(true);
|
||||
}}
|
||||
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-semibold hover:bg-investor-dark flex items-center gap-2 shadow-sm transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" /> Register & Assign New
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@@ -1249,15 +1579,14 @@ export default function InvestorDetailPage() {
|
||||
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 rentalInfo = rentalTypes[bike.rentalType || 'single_rent'] || rentalTypes.single_rent;
|
||||
const planType = investment?.planType || 'gold';
|
||||
const status = statusConfig[bike.status] || statusConfig.available;
|
||||
|
||||
return (
|
||||
<Link
|
||||
<div
|
||||
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`}
|
||||
className={`block bg-white rounded-xl border ${planBg[planType]} overflow-hidden hover:shadow-lg transition-all group relative`}
|
||||
>
|
||||
<div className={`h-2 bg-gradient-to-r ${planColors[planType]}`} />
|
||||
<div className="p-4">
|
||||
@@ -1277,7 +1606,7 @@ export default function InvestorDetailPage() {
|
||||
<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 === 'platinum' ? 'bg-purple-500' : 'bg-blue-500'
|
||||
}`} />
|
||||
{planType} Plan • {investment?.planName || 'Investment'}
|
||||
</div>
|
||||
@@ -1309,8 +1638,24 @@ export default function InvestorDetailPage() {
|
||||
}`}>{bike.batteryLevel}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-3 border-t border-slate-100 flex justify-end gap-2">
|
||||
<Link href={`/bikes?bike=${bike.id}`} className="text-xs text-slate-500 hover:text-slate-800 font-semibold transition-colors px-2 py-1 bg-slate-100 rounded">
|
||||
View Details
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm(`Are you sure you want to unassign bike ${bike.model} (${bike.plateNumber})?`)) {
|
||||
handleUnassignBike(bike.id);
|
||||
}
|
||||
}}
|
||||
className="text-xs text-red-600 hover:text-red-800 font-semibold transition-colors px-2 py-1 bg-red-50 rounded"
|
||||
>
|
||||
Unassign
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{assignedBikes.length === 0 && (
|
||||
@@ -3071,6 +3416,116 @@ export default function InvestorDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showRegisterBikeModal && (
|
||||
<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">
|
||||
<h2 className="text-lg font-bold text-slate-800">Register & Assign New Bike</h2>
|
||||
<button onClick={() => setShowRegisterBikeModal(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 space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Plate Number</label>
|
||||
<input
|
||||
type="text"
|
||||
value={registerBikeForm.plateNumber}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, plateNumber: e.target.value })}
|
||||
placeholder="DHAKA-METRO-HA-1234"
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Brand</label>
|
||||
<input
|
||||
type="text"
|
||||
value={registerBikeForm.brand}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, brand: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Model</label>
|
||||
<input
|
||||
type="text"
|
||||
value={registerBikeForm.model}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, model: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Purchase Price (৳)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={registerBikeForm.purchasePrice}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, purchasePrice: Number(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Daily Rent (৳)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={registerBikeForm.currentRent}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, currentRent: Number(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Location</label>
|
||||
<input
|
||||
type="text"
|
||||
value={registerBikeForm.location}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, location: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Rental Type</label>
|
||||
<select
|
||||
value={registerBikeForm.rentalType}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, rentalType: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="single_rent">Single Rent</option>
|
||||
<option value="rent_to_own">Rent to Own</option>
|
||||
<option value="share_ev">Share EV</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Attach to Investment Plan</label>
|
||||
<select
|
||||
value={registerBikeForm.investmentId}
|
||||
onChange={(e) => setRegisterBikeForm({ ...registerBikeForm, investmentId: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="">Select plan</option>
|
||||
{investor.investments?.filter((inv: any) => inv.assetType === 'bike' || !inv.assetType).map((inv: any) => (
|
||||
<option key={inv.id} value={inv.id}>{inv.planName} ({inv.id})</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowRegisterBikeModal(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={handleRegisterAndAssignBike} className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">Register & Assign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showCreateInvestmentModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
|
||||
Reference in New Issue
Block a user